Flutter: una descripción general del sistema de diseño

Guía de Lao Meng: este artículo explica el principio de funcionamiento del sistema de diseño Flutter con gran detalle .

Traducido de: https://itnext.io/flutter-layout-system-overview-c70bbe9ba909?source=bookmarks---------17---------------- -

Recientemente, decidí centrarme en los conceptos básicos de Flutter. Esta vez, traté de comprender mejor "cómo funciona el sistema de diseño" y responder a las siguientes preguntas:

  • El tamaño de mi widget no se ve bien, ¿qué pasa?
  • Solo quiero colocar el widget en una ubicación específica, pero no hay ninguna propiedad para controlarlo. ¿Por qué?
  • Sigo viendo términos como BoxConstraints, RenderBox y Size. Cual es la relacion entre ellos?
  • ¿Tiene un conocimiento general de cómo funciona el sistema de diseño?

Este artículo no pretende proporcionar una descripción detallada y en profundidad de todo lo anterior. Sin embargo, daremos una buena visión general del contenido más importante e intentaremos visualizarlo todo.

Sistema de diseño de "dos etapas" y limitaciones

En primer lugar, los widgets son los componentes básicos del Flutter SDK, pero no son responsables de dibujarse en la pantalla. Cada widget está asociado con el objeto RenderBox responsable de esta operación. Estos cuadros son un sistema de coordenadas rectangulares 2D y su tamaño se expresa como un desplazamiento desde el origen. Cada RenderBox también se asociará con un objeto BoxConstraints, que contiene cuatro valores: máximo | ancho mínimo y máximo | alto mínimo. El RenderBox puede elegir tener cualquier tamaño requerido, pero debe cumplir con estos valores / restricciones. El tamaño / posición de los widgets depende completamente de las propiedades de estos RenderBox.

原文 : De la misma manera que los widgets crean un widget tres, los RenderBoxes hacen un render tres.

Creo que tres pueden estar mal, debería ser árbol, traducción: de la misma manera que los widgets generan árboles de componentes, los RenderBoxes generan árboles de renderizado.

Podemos pensar en el sistema de diseño de Flutter como un sistema de dos etapas. En la primera etapa, el marco pasa recursivamente BoxConstraints a los componentes secundarios a lo largo del árbol de renderizado. Proporciona una forma para que los componentes principales ajusten / mejoren el tamaño de los componentes secundarios y actualicen estos límites según sea necesario. En otras palabras, esta es la etapa encargada de difundir la información de restricción, dejando que todos conozcan su valor máximo / mínimo.

Una vez finalizado, comienza la segunda fase. Esta vez, cada RenderBox devuelve el tamaño seleccionado a su objeto principal. El padre recopila los tamaños de todos los hijos y luego usa esta información geométrica para colocar correctamente a cada hijo en su propio sistema cartesiano. Esta etapa es responsable de determinar el tamaño y la ubicación. En esta etapa, el componente principal conoce el tamaño de cada componente secundario y su ubicación.

¿Entonces, qué significa esto?

Esto significa que el componente padre es responsable de definir / limitar / restringir el tamaño del componente hijo y colocarlo en relación con su sistema de coordenadas. En otras palabras, el widget puede elegir su tamaño, pero siempre debe obedecer las restricciones recibidas de su padre. Además, el widget no conoce su posición en la pantalla, pero su padre sí.

Si tiene preguntas sobre el tamaño o la ubicación de un widget, intente ver (actualizar) su componente principal.

Ejemplo

De acuerdo, visualicemos todo e intentemos comprender lo que está sucediendo a través de ejemplos. Pero antes de eso, aquí hay algunos términos que pueden ser útiles al depurar restricciones,

Los siguientes términos no están traducidos porque ellos mismos se entienden mejor que la traducción:

  • Si max (w | h) = min (w | h) \ , eso está muy restringido.
  • Si min (w | h) = 0 \ , tenemos una restricción \ flexible .
  • Si max (w | h)! = Infinito \ , la restricción está acotada. \
  • Si max (w | h) = infinito \ , la restricción es ilimitada. \
  • Si min (w | h) = infinito \ , simplemente se dice que es infinito \

Usaremos una versión modificada de la plantilla de aplicación inicial. Generalmente, puede verificar el widget RenderBox y sus atributos de dos maneras simples:

  1. Mediante la ejecución de código: podemos usar LayoutBuilder para interceptar la propagación de BoxConstraints en la primera etapa del sistema de diseño y verificar las restricciones. Luego, una vez completada la segunda etapa, usamos la clave para obtener el RenderBox del widget y poder verificar el Tamaño, Posición.

  2. O use el inspector de widgets de DevTools

import 'package:flutter/material.dart';

GlobalKey _keyMyApp = GlobalKey();
GlobalKey _keyMaterialApp = GlobalKey();
GlobalKey _keyHomePage = GlobalKey();
GlobalKey _keyScaffold = GlobalKey();
GlobalKey _keyAppbar = GlobalKey();
GlobalKey _keyCenter = GlobalKey();
GlobalKey _keyFAB = GlobalKey();
GlobalKey _keyText = GlobalKey();

void printConstraint(String name, BoxConstraints c) {
  print(
    'CONSTRAINT of $name: min(w=${c.minWidth.toInt()},h=${c.minHeight.toInt()}) max(w=${c.maxWidth.toInt()},h=${c.maxHeight.toInt()})',
  );
}

void printSizes() {
  printSize('MyApp', _keyMyApp);
  printSize('MaterialApp', _keyMaterialApp);
  printSize('HomePage', _keyHomePage);
  printSize('Scaffold', _keyScaffold);
  printSize('Appbar', _keyAppbar);
  printSize('Center', _keyCenter);
  printSize('Text', _keyText);
  printSize('FAB', _keyFAB);
}

void printSize(String name, GlobalKey key) {
  final RenderBox renderBox = key.currentContext.findRenderObject();
  final size = renderBox.size;
  print("SIZE of $name: w=${size.width.toInt()},h=${size.height.toInt()}");
}

void printPositions() {
  printPosition('MyApp', _keyMyApp);
  printPosition('MaterialApp', _keyMaterialApp);
  printPosition('HomePage', _keyHomePage);
  printPosition('Scaffold', _keyScaffold);
  printPosition('Appbar', _keyAppbar);
  printPosition('Center', _keyCenter);
  printPosition('Text', _keyText);
  printPosition('FAB', _keyFAB);
}

void printPosition(String name, GlobalKey key) {
  final RenderBox renderBox = key.currentContext.findRenderObject();
  final position = renderBox.localToGlobal(Offset.zero);
  print("POSITION of $name: $position ");
}

void main() {
  runApp(LayoutBuilder(
    builder: (context, constraints) {
      printConstraint('MyApp', constraints);
      return MyApp();
    },
  ));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      key: _keyMyApp,
      builder: (context, constraints) {
        printConstraint('MaterialApp', constraints);
        return MaterialApp(
          key: _keyMaterialApp,
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: LayoutBuilder(
            builder: (context, constraints) {
              printConstraint('HomePage', constraints);
              return HomePage(
                key: _keyHomePage,
                title: 'Flutter Demo Home Page',
              );
            },
          ),
        );
      },
    );
  }
}

class HomePage extends StatefulWidget {
  HomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    super.initState();
  }

  void _afterLayout(_) {
    printSizes();
    printPositions();
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      printConstraint('Scaffold', constraints);
      return Scaffold(
        backgroundColor: Colors.purple,
        key: _keyScaffold,
        appBar: AppBar(
          key: _keyAppbar,
          title: Text(widget.title),
        ),
        body: LayoutBuilder(
          builder: (context, constraints) {
            printConstraint('Center', constraints);
            return Center(
              key: _keyCenter,
              child: LayoutBuilder(builder: (context, constraints) {
                printConstraint('Text', constraints);
                return Text(
                  'You have pushed the button this many times:',
                  key: _keyText,
                  style: TextStyle(color: Colors.white),
                );
              }),
            );
          },
        ),
        floatingActionButton: LayoutBuilder(
          builder: (context, constraints) {
            printConstraint('FAB', constraints);
            return FloatingActionButton(
              key: _keyFAB,
              onPressed: printSizes,
              tooltip: 'Increment',
              child: Icon(Icons.add),
            );
          },
        ),
      );
    });
  }
}

Echemos un vistazo paso a paso a lo que sucedió (aquí ignoraremos LayoutBuilders).

Lo primero que sucede en nuestro ejemplo es ejecutar runApp (..). Esta función verifica el tamaño actual de la pantalla (392: 759 en nuestro ejemplo) y luego crea un objeto BoxConstraints que contiene las restricciones que se enviarán a nuestro primer widget (MyApp). Tenga en cuenta que el ancho y el alto de max | min son iguales; por lo tanto, runApp usa restricciones estrictas; al hacerlo, MyApp no ​​tiene otra opción para elegir su tamaño, excepto por el espacio disponible en la pantalla.

Luego propague las restricciones al árbol de widgets. MyApp, MaterialApp, HomePage y Scaffold tienen las mismas restricciones estrictas. Por lo tanto, todos se verán obligados a ocupar toda la pantalla. Cada widget tiene la oportunidad de notificar a sus hijos de diferentes BoxConstraints (aún respetando los hijos recibidos). Sin embargo, en este caso, optaron por no hacerlo.

Ahora las cosas se vuelven cada vez más interesantes. Scaffold le dice a AppBar sobre las BoxConstraints que se deben usar, pero, esta vez, usa restricciones sueltas (min h = 0). Le da a AppBar la oportunidad de elegir cualquier altura requerida, pero aún debe usarse width = 390.

AppBar es un widget especial llamado PreferredSizeWidget. Este tipo de widget no impone restricciones a sus hijos. Si intenta utilizar LayoutBuilder para obtener las restricciones de Título, se producirá un error. En cambio, AppBar responde a Scaffold con el tamaño preferido / predeterminado: alto = 80, ancho = 392 (sujeto a las restricciones recibidas)

Después de obtener el tamaño de la AppBar, Scaffold continúa con el siguiente subelemento: Centro

Bien, pasaron muchas cosas aquí. Tratemos de entender:

  1. Scaffold le dice a Center sus restricciones y le permite elegir entre 0 <ancho <392 y 0 <alto <697. Tenga en cuenta que la altura máxima es 759 (la altura máxima de la pantalla) menos 80 (la altura seleccionada por la AppBar).
  2. El centro va a su subcomponente "Texto" y envía las mismas restricciones.
  3. Texto selecciona un tamaño lo suficientemente grande para mostrar sus datos (279: 16) y luego regresa al Centro.
  4. Con la ayuda de la información geométrica (tamaño) disponible, Center puede colocar correctamente el texto en su sistema cartesiano. Como padre, Center tiene derecho a elegir la ubicación de sus componentes secundarios, en cuyo caso decide centrarlo.

El proceso continúa:

  1. Luego, Center eligió un tamaño para sí mismo, en lugar de simplemente elegir un tamaño "suficiente" (como "Texto"), decidió ser lo más grande posible, por lo que era limitado.
  2. Scaffold recibe el tamaño requerido por Center, y el proceso continúa hasta su último hijo: FAB
  3. FAB recibe la restricción y devuelve su tamaño preferido a Scaffold (56:56)
  4. Finalmente, Scaffold también tiene toda la información geométrica necesaria para ubicar a cada niño en su sistema cartesiano.

Finalmente, repita el proceso para todos los widgets arriba de Scaffold:

  1. La información de tamaño continúa propagándose a lo largo del árbol de renderizado.
  2. Cada widget usa esta información para colocar a cada niño en el sistema cartesiano.
  3. Scaffold responde a HomePage, HomePage responde a MaterialApp y MaterialApp responde a MyApp. Hasta que finalmente llegue a Main nuevamente.
  4. Main obtiene este widget "final" y finalmente lo vincula a la pantalla.

El árbol de RenderBox finalmente está vinculado a la pantalla. Tenemos una aplicación en ejecución.

Cosas interesantes para recordar

  • El widget no conoce su posición en la pantalla, solo su componente padre lo sabe.
  • El widget puede elegir el tamaño que desee, pero debe estar limitado por su padre.
  • Las restricciones se propagan hacia abajo, mientras que los tamaños se propagan hacia arriba.
  • Trate de comprender las limitaciones, pueden resultar útiles más adelante.

Espero que todo esto pueda ayudarlo a comprender mejor cómo funciona el sistema de diseño Flutter.

comunicarse con

Dirección del blog de Laomeng Flutter (uso de control 330): http://laomengit.com

Bienvenido a unirse al grupo de intercambio Flutter (WeChat: laomengit) y seguir la cuenta pública [Lao Meng Flutter]:

Supongo que te gusta

Origin blog.csdn.net/mengks1987/article/details/108370075
Recomendado
Clasificación