Flutter: понимание того, как виджет без состояния / состояния выполняет макет своего дочернего элемента?

#android #flutter #dart #layout #flutter-layout

Вопрос:

TL; DR

Как я могу узнать правила компоновки виджета (какой размер он будет запрашивать у своего родителя и какие ограничения он будет передавать своим дочерним элементам), если нет документации об этом?

Подробности проблемы

У меня есть это очень простое приложение

 void main() {
  runApp(
    Container(
        color: Colors.red,
        width: 20,
        height: 20,
      ),
  );
}
 

Я ожидал Container , что ширина и высота будут равны 20, но я получил Container , который заполнил весь экран.

Читая эту статью на flutter.dev о понимании ограничений, в ее последней части, называемой «Изучение правил компоновки для конкретных виджетов», упоминается, как это сделать, найдя createRenderObject метод, а затем найдя performLayout метод.

Однако этот createRenderObject метод доступен только для подклассов RenderObjectWidget . Например, просматривая код Transform виджета, я нахожу createRenderObject , что возвращает a RenderTransform , который расширяется RenderProxyBox , который в конечном итоге реализуется performLayout как :

   @override
  void performLayout() {
    if (child != null) {
      child!.layout(constraints, parentUsesSize: true);
      size = child!.size;
    } else {
      size = computeSizeForNoChild(constraints);
    }
  }
 

Я могу сделать вывод, что Transform виджет, наконец, примет размер своего дочернего элемента из-за этой строки size = child!.size; .

Но в случае Container , указанном выше, непосредственно расширяется StatelessWidget . Я не мог найти, просматривая его код, методы performLayout и createRenderObject , я мог только найти createElement , но я ищу RenderObject в дереве рендеринга, связанном с контейнером, а не с элементом.

Вопрос

Итак, вопрос в том, как найти этот объект рендеринга, связанный с виджетом без состояния / виджетом с сохранением состояния, чтобы узнать правила компоновки, которые этот виджет передаст своим дочерним элементам, и сам будет следовать им в этом случае?

Ответ №1:

Вы правы. Я бы сказал, что моя статья неточна в этом отношении.

Виджету не нужно создавать RenderObject . Вместо этого он может использовать композицию других виджетов, которые создают RenderObjects сами себя.

Если виджет состоит из других виджетов, то вместо того, чтобы смотреть на performLayout , вы можете просто посмотреть на build метод этого виджета, чтобы увидеть, что он делает. В случае a Container это его build метод:

 Widget build(BuildContext context) {
    Widget? current = child;

    if (child == null amp;amp; (constraints == null || !constraints!.isTight)) {
      current = LimitedBox(
        maxWidth: 0.0,
        maxHeight: 0.0,
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
      );
    }

    if (alignment != null)
      current = Align(alignment: alignment!, child: current);

    final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
    if (effectivePadding != null)
      current = Padding(padding: effectivePadding, child: current);

    if (color != null)
      current = ColoredBox(color: color!, child: current);

    if (clipBehavior != Clip.none) {
      assert(decoration != null);
      current = ClipPath(
        clipper: _DecorationClipper(
          textDirection: Directionality.maybeOf(context),
          decoration: decoration!,
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

    if (decoration != null)
      current = DecoratedBox(decoration: decoration!, child: current);

    if (foregroundDecoration != null) {
      current = DecoratedBox(
        decoration: foregroundDecoration!,
        position: DecorationPosition.foreground,
        child: current,
      );
    }

    if (constraints != null)
      current = ConstrainedBox(constraints: constraints!, child: current);

    if (margin != null)
      current = Padding(padding: margin!, child: current);

    if (transform != null)
      current = Transform(transform: transform!, alignment: transformAlignment, child: current);

    return current!;
  }
 

Как вы можете видеть, он определяется в терминах других виджетов. И эти виджеты также могут быть определены в терминах других виджетов и так далее. Но в какой-то момент вы дойдете до виджетов, которые создают RenderObject s.


Что касается причины Container , по которой размер не равен 20×20, это потому, что, как объясняется в статье, размеры устанавливаются родителями. Таким Container образом, размер s устанавливается Container родительским элементом, который в данном случае является экраном. И экран всегда заставляет своего дочернего элемента занимать все доступное пространство, в данном случае игнорируя Container желание быть 20×20. Исправление здесь заключается в предоставлении Container другого родителя. Тот, который позволяет Container выбирать свой собственный размер. Например, оба Center и Align позволят этому произойти, и именно поэтому вы можете решить проблему, выполнив:

 void main() {
  runApp(
    Center( 
      child: Container(
        color: Colors.red,
        width: 20,
        height: 20,
      ),),);
}
 

Что касается того, почему экран заставляет своего дочернего элемента занимать все доступное пространство: именно так решили создатели Flutter. Если вы покопаетесь в коде Flutter, вы найдете его там. Но, вероятно, лучше всего просто запомнить этот факт.

Надеюсь, это поможет!

Комментарии:

1. Спасибо, что нашли время, чтобы ответить на этот вопрос, я только изучаю этот макет и рендеринг, и его сложность заставила меня пропустить метод сборки в первую очередь 🙂 Спасибо!

Ответ №2:

Meh, пока не слишком усложняйте. вот самая простая вещь, которую я могу вам объяснить, позволяет разделить приложение на 4 разных уровня.

  1. Приложение -> Вы можете использовать MaterialApp для создания приложения material
  2. Страницы виджетов -> Вы можете выбирать между использованием Stateless или StatefulWidget , это зависит от ваших потребностей. Если вам нужен динамический, с множеством изменяющихся состояний, которые вы можете использовать StatefulWidget , или вы можете создать статическую страницу StatelessWidget .
  3. Scaffold -> Страницы виджетов должны возвращать каркас, чтобы сформировать страницы материалов, здесь вы можете использовать Scaffold , вы можете добавить AppBar, fab, body, BottomNavigationBar и т. Д.
  4. Виджеты -> Здесь виджеты могут быть любыми, это может быть панель приложений для вашей панели приложений scaffold, или ListView, GridView или пользовательский виджет.

Итак, ваш код должен выглядеть так, вроде

 /// This block is the first point of your application, this will run your application
void main() {
  runApp(myApp());
}

/// Then you need an material app, this part should return your app
class MyApp extends StatelessWidget{

  /// This is very important, both StatelessWidget / StatefullWidget 
  /// always having a build method. It's should returning a Widget. But in this case we will return an material application
  @override
  Widget build(BuildContext context){
    return MaterialApp(
      home: MyHome(),
    ),
  }
}

class MyHome extends StatelessWidget{

  /// This is home page, it's have Scaffold so the page will using, material guidelines. 
  @override
  Widget build(BuildContext context){
    return Scaffold(
       body:Container(
         color: Colors.red,
         width: 20,
         height: 20,
       ),
    );
  }
}
 

Комментарии:

1. Спасибо, но мой вопрос о том, как узнать правила компоновки любого виджета, но это не отвечает на мой вопрос.

2. Чувак, ты упускаешь из виду, что здесь Scaffold тот, который заставляет ваш виджет отображаться в соответствии с вашими ожиданиями, без Scaffold ваш виджет не будет отображаться корректно

3. это всего лишь демонстрация вопроса, я знаю о каркасе, вы читали TL; DR?? пожалуйста, прочтите это, чтобы понять мой вопрос

4. хорошо, тогда я понятия не имею, чего вы пытаетесь достичь. удачи