Пользовательский макет Flutter, выбор размера виджета в зависимости от его дочернего элемента - PullRequest
6 голосов
/ 10 июля 2020

Статья понимание ограничений очень полезна для понимания того, как работает макет. Однако у меня возникли проблемы с созданием пользовательского макета, который работает так, как описано в нем.

В статье говорится, что размер увеличивается, сначала мы получаем размеры дочерних элементов, а затем мы можем решить сами размер основан на размерах детей.

Кажется, CustomSingleChildLayout - это способ создать собственный макет с одним дочерним элементом. В нем, основываясь на статье, которую я упомянул выше, я хотел бы получить размер моего ребенка, а затем на основе этого выбрать свой собственный размер. Однако это кажется невозможным. Метод getSize() вызывается с ограничениями, поэтому я должен выбрать размер своих виджетов на основе ограничений, которые я получаю сверху. Я могу получить размер ребенка в getPositionForChild(), но это вызвано после того, как я укажу свой размер в getSize(). В этот момент имело смысл запускать ретрансляцию, но, похоже, мне не разрешено делать это из функции, вызываемой системой макета.

Итак, вопрос в том, как лучше всего реализовать настраиваемый макет, с поведением макета, ограничивающим движение вниз и размером вверх, как описано в понимание ограничений ?

Изменить: Вот код, объясняющий, что я действительно хочу.

Это то, что я ищу. Здесь я вручную задаю ширину и высоту синего квадрата. Я не хочу этого делать. Я хочу, чтобы размер ребенка определял размер квадрата. Я хочу, чтобы размер вытек из текста, и синий квадрат должен определять длину края max(width, height).

https://codepen.io/gazialankus/pen/abdKyVR

import 'package:flutter/material.dart';

class AppRoot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', // Appears in app switcher
      theme: ThemeData(
        primaryColor: Colors.grey.shade300,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyPage(),
    );
  }
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.yellow,
          child: SizedBox(
            width: 180,
            height: 180,
            child: Container(
              color: Colors.blue,
              child: AspectRatio(
                aspectRatio: 1,
                child: Text(
                  "I want to be in a tight square.",
                  style: TextStyle(
                    backgroundColor: Colors.red
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(AppRoot());
}

Это было предложено обернуть его Align. Вот попробовал, но Align подрастает. Align s widthFactor и heightFactor используют размер ребенка, но не могут сделать его квадратным таким образом. Удаление синего Container не имеет никакого эффекта:

https://codepen.io/gazialankus/pen/MWKXvpO

import 'package:flutter/material.dart';

class AppRoot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', // Appears in app switcher
      theme: ThemeData(
        primaryColor: Colors.grey.shade300,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyPage(),
    );
  }
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.yellow,
          child: Align(
            alignment: Alignment.center,
            child: Container(
              color: Colors.blue,
              child: Text(
                "I want to be in a square.",
                style: TextStyle(
                  backgroundColor: Colors.red
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(AppRoot());
}

Здесь я пытаюсь использовать AspectRatio, чтобы сделать квадрат, но он не заботится о размерах ребенка и просто растет:

https://codepen.io/gazialankus/pen/KKVevXv

import 'package:flutter/material.dart';

class AppRoot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', // Appears in app switcher
      theme: ThemeData(
        primaryColor: Colors.grey.shade300,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyPage(),
    );
  }
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.yellow,
          child: Align(
            alignment: Alignment.center,
            child: Container(
              color: Colors.blue,
              child: AspectRatio(
                aspectRatio: 1,
                child: Text(
                  "I want to be in a tight square.",
                  style: TextStyle(
                    backgroundColor: Colors.red
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(AppRoot());
}

И вот я пытаюсь сделать нестандартный макет, но я не нужно использовать размер ребенка, чтобы выбрать свой размер. Я должен определиться со своим размером до того, как получить ребенка. Попытка принудительно выполнить ретрансляцию, как только я узнаю размер моего ребенка, вызывает исключение, которое находится в конце кода. Codepen выходит из строя тихо.

https://codepen.io/gazialankus/pen/LYGrjxM

import 'package:flutter/material.dart';

class AppRoot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', // Appears in app switcher
      theme: ThemeData(
        primaryColor: Colors.grey.shade300,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyPage(),
    );
  }
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.yellow,
          child: CustomSingleChildLayout(
            delegate: MyDelegate(relayout: ValueNotifier<int>(0)),
            child: Text(
              "I want to be in a square.",
              style: TextStyle(
                backgroundColor: Colors.red
              ),
            ),
          ),
        ),
      ),
    );
  }
}


class MyDelegate extends SingleChildLayoutDelegate {
  ValueNotifier<int> relayout;
  MyDelegate({ this.relayout }) : super(relayout: relayout);

  @override
  bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
    return desiredWidth == 100;
  }

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    print('getConstraintsForChild');
    return super.getConstraintsForChild(constraints);
  }

  double desiredWidth = 300;

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    print('getPositionForChild');
    print(size);
    print(childSize);
    if (size.width > childSize.width) {
      desiredWidth = childSize.width;
      relayout.value++;
      // ^trying to force a relayout
      // throws exception, output is given at the bottom in comments
      print("RELAYOUT");
    }
    return super.getPositionForChild(size, childSize);
  }

  @override
  Size getSize(BoxConstraints constraints) {
    print('getSize');
    return Size(desiredWidth, 100);
  }
}

void main() {
  runApp(AppRoot());
}

/*
Performing hot reload...
Syncing files to device LG H990...
I/flutter (19850): getSize
I/flutter (19850): getConstraintsForChild
I/flutter (19850): getPositionForChild
I/flutter (19850): Size(300.0, 100.0)
I/flutter (19850): Size(148.0, 16.0)

════════ Exception caught by foundation library ════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for ValueNotifier<int>:
'package:flutter/src/rendering/object.dart': Failed assertion: line 1527 pos 12: '_debugCanPerformMutations': is not true.


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=BUG.md

When the exception was thrown, this was the stack: 
#2      RenderObject.markNeedsLayout (package:flutter/src/rendering/object.dart:1527:12)
#3      RenderBox.markNeedsLayout (package:flutter/src/rendering/box.dart:2053:11)
#4      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:207:21)
#5      ValueNotifier.value= (package:flutter/src/foundation/change_notifier.dart:274:5)
#6      MyDelegate.getPositionForChild (package:m_health/ui/app_root.dart:64:16)
...
The ValueNotifier<int> sending notification was: ValueNotifier<int>#ac2fb(1)
════════════════════════════════════════════════════════════════════════════════════════════════════
Reloaded 2 of 522 libraries in 1,390ms.
I/flutter (19850): RELAYOUT

 */

1 Ответ

8 голосов
/ 14 июля 2020

Это должно решить вашу проблему. Мы удаляем переданные ограничения, используя UnconstrainedBox. Нам нужно только удалить вертикаль, чтобы AspectRatio использовал ограничение ширины. Затем мы просим размер поддерева соответствовать его внутреннему размеру c. Его, конечно, теперь легко переполнить, потому что он никогда не будет переноситься и всегда будет использовать свою внутреннюю ширину c. Однако вы можете обернуть UnconstrainedBox в FittedBox, и он никогда не переполнится, а вместо этого масштабируется, чтобы соответствовать родительскому элементу, который может быть меньше, чем его внутреннее измерение c. Вы также можете попробовать установить параметр оси на UnconstrainedBox по горизонтали. Тогда IntrinsicWidth будет ограничен родительской шириной.

import 'package:flutter/material.dart';

class AppRoot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo', // Appears in app switcher
      theme: ThemeData(
        primaryColor: Colors.grey.shade300,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyPage(),
    );
  }
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.yellow,
          child: UnconstrainedBox(
            child: IntrinsicWidth(
              child: Container(
                color: Colors.blue,
                child: AspectRatio(
                  aspectRatio: 1,
                  child: Text(
                    "I want to be in a tight square.",
                    style: TextStyle(backgroundColor: Colors.red),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(AppRoot());
}

...