Entrega de eventos y control de datos de los componentes de Flutter | Charla para desarrolladores · DTalk

Autor de este artículo original:  Ma Jialun, el original lanzado en segmento por defecto

https://segmentfault.com/a/1190000023338363

Este artículo usa la forma nativa de Flutter para diseñar el código, solo habla de las cosas más básicas y no usa ninguna otra biblioteca de terceros (Proveedor, etc.)

Después de escribir Flutter durante casi dos años, descubrí que la transmisión de datos y eventos es una pregunta que los principiantes a menudo hacen cuando aprenden: muchos principiantes introducen por error el proveedor, BLOC y otros modos para administrar datos en una etapa muy temprana, y el uso excesivo de marcos externos , lo que resulta en El caos del proyecto dificulta la organización del código. La razón principal es que se ignora el método de transferencia de datos básico y más simple .

Es difícil imaginar que alguien coloque todos los datos en un proveedor de nivel superior y luego no escriba StatefulWidget. De todos modos, yo no mantengo este tipo de proyectos, a quien le guste verlo.

Este artículo enumerará los métodos básicos de entrega de eventos y métodos e ilustrará cómo utilizar los métodos básicos para lograr estas funciones. Todos los ejemplos de este artículo se han modificado en función de la demostración de adición predeterminada de flutter. Puedes ejecutar los ejemplos de código de este proyecto en el tablero de dardos o en un nuevo proyecto de flutter.

Pasar datos y eventos localmente

Veamos primero algunas situaciones básicas de aplicación. Siempre que se logren estas situaciones, los datos y eventos se pueden transmitir localmente sin problemas:

Preste atención al pensamiento: ¿Cuáles de los siguientes widgets son StatefulWidgets?

Descripción: después de que un widget recibe un evento, cambia el valor mostrado por el niño

Función de realización: haga clic en el signo más para hacer que el número sea +1

Dificultad: ⭐

Descripción: un widget cambia su valor cuando el niño recibe un evento

Función de realización: haga clic para cambiar el color de la página

Dificultad: ⭐

Descripción: un widget activa su propio estado cuando el niño recibe un evento

Función de realización: haga clic para iniciar una solicitud de red y actualizar la página actual

Dificultad: ⭐

Descripción: un widget cambia su propio valor

Función de realización: cuenta atrás, carga de datos de la red

Dificultad: ⭐⭐⭐

Descripción: un método para activar el estado cuando cambian los datos propios de un widget

Función de realización: un componente que reproduce la animación de transición cuando cambian los datos

Dificultad: ⭐⭐⭐⭐

Descripción: después de que un widget recibe un evento, activa el método del estado del niño

Función de realización: haga clic en el botón para iniciar una cuenta regresiva para niños o enviar una solicitud

Dificultad: ⭐⭐⭐⭐⭐

Por lo general, redactamos proyectos basados ​​en los requisitos anteriores. Siempre que aprendamos a implementar estos eventos y la transferencia de datos, podemos escribir fácilmente cualquier proyecto.

Utilice devoluciones de llamada para pasar eventos

Puede usar devoluciones de llamada simples para lograr estos requisitos, que también es la base de todo el aleteo: cómo cambiar los datos en un estado y cómo cambiar los datos de un widget.

Descripción: después de que un widget recibe un evento, cambia el valor mostrado por el niño 

Función de realización: haga clic en el signo más para hacer que el número sea +1 

Descripción: un widget cambia su valor cuando el niño recibe un evento 

Función de realización: haga clic para cambiar el color de la página 

Descripción: un widget activa su propio estado cuando el niño recibe un evento 

Función de realización: haga clic para iniciar una solicitud de red y actualizar la página actual

Todos estos son sin dificultad, solo miramos el mismo código

Código:

/// 这段代码是使用官方的代码修改的,通常情况下,只需要使用回调就能获取点击事件
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);


  final String title;


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


class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;


  void _incrementCounter() {
    // 在按钮的回调中,您可以设置数据与调用方法
    // 在这里,让计数器+1后刷新页面
    setState(å() {
      _counter++;
    });
  }


  // setState后就会使用新的数据重新进行build
  // flutter的build性能非常强,甚至支持每秒60次rebuild
  // 所以不必过于担心触发build,但是要偶尔注意超大范围的build
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Text(
          '$_counter',
          style: Theme.of(context).textTheme.headline4,
        ),
      ),
      floatingActionButton: _AddButton(
        onAdd: _incrementCounter,
      ),
    );
  }
}


/// 一般会使用GestureDetector来获取点击事件
/// 因为官方的FloatingActionButton会自带样式,一般我们会自己写按钮样式
class _AddButton extends StatelessWidget {
  final Function onAdd;


  const _AddButton({Key key, this.onAdd}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: onAdd,
      child: Icon(Icons.add),
    );
  }
}

Este método es muy simple. Solo necesitas cambiar los datos en la devolución de llamada, y luego setState activará el método de compilación para reconstruir el widget actual basado en los datos actuales. Este es también el método de actualización más básico para flutter.

Cambiar datos en el estado

En Flutter, solo StatefulWidget tiene estado, y el estado tiene un ciclo de vida tradicional (no página). A través de estos ciclos, puede comenzar a cargar datos desde el servidor tan pronto como ingrese a la página, o dejar que un widget se reproduzca automáticamente Animación

Veamos primero este requisito:

Descripción: un widget cambia su propio valor 

Función de realización: cuenta atrás, carga de datos de la red

Este también es un requisito común, pero muchas escrituras nuevas no se escribirán aquí. Puede ser incorrecto usar FutureBuilder para realizar solicitudes de red, lo que provocará solicitudes repetidas cada vez. De hecho, el estado de StatefulWidget debe usarse para almacenar solicitudes. Devolver información.

En proyectos generales, las solicitudes de animación, cuenta regresiva y asincrónicas necesitan usar estado, y la mayoría de las otras funciones no necesitan estado.

Por ejemplo, este widget mostrará un número:

class _CounterText extends StatelessWidget {
  final int count;


  const _CounterText({Key key, this.count}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$count'),
    );
  }
}

Puede intentar hacer que el widget cargue este número desde el servidor:

class _CounterText extends StatefulWidget {
  const _CounterText({Key key}) : super(key: key);


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


class __CounterTextState extends State<_CounterText> {
  @override
  void initState() {
    // 在initState中发出请求
    _fetchData();
    super.initState();
  }


  // 在数据加载之前,显示0
  int count = 0;


  // 加载数据,模拟一个异步,请求后刷新
  Future<void> _fetchData() async {
    await Future.delayed(Duration(seconds: 1));
    setState(() {
      count = 10;
    });
  }


  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$count'),
    );
  }
}

O queremos que este número disminuya en 1 cada segundo, hasta 0. Luego, conviértalo en estado e inicialice un temporizador en initState para reducir el número:

class _CounterText extends StatefulWidget {
  final int initCount;


  const _CounterText({Key key, this.initCount:10}) : super(key: key);


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


class __CounterTextState extends State<_CounterText> {
  Timer _timer;


  int count = 0;


  @override
  void initState() {
    count = widget.initCount;
    _timer = Timer.periodic(
      Duration(seconds: 1),
      (timer) {
        if (count > 0) {
          setState(() {
            count--;
          });
        }
      },
    );
    super.initState();
  }


  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('${widget.initCount}'),
    );
  }
}

Entonces podemos ver que el widget disminuye en 1 por segundo desde el número ingresado.

Se puede ver que los widgets pueden cambiar datos en el estado, por lo que cuando usamos StatefulWidget, solo necesitamos dar los datos iniciales, y el widget cargará o cambiará los datos de acuerdo con su ciclo de vida.

Aquí, el uso que sugiero es cargar datos en Scaffold. Cada página consta de un Stateful Scaffold y varios StatelessWidgets. State of Scaffold administra todos los datos y luego los actualiza.

Tenga en cuenta que incluso si el cuerpo de esta página es ListView, no se recomienda que ListView administre su propio estado, solo mantenga una lista de datos en el estado actual. El uso de ListView.builder para crear la lista puede evitar actualizar todos los elementos de la lista en la página al actualizar la matriz, manteniendo la actualización de alto rendimiento.

Supervisar cambios de widget en el estado

Descripción: un método para activar el estado cuando cambian los datos propios de un widget 

Función de realización: un componente que reproduce la animación de transición cuando cambian los datos

Antes de hacer esto, veamos un requisito simple: un widget de una línea que acepta un número.Cuando el número es par, está a 24px de la izquierda y 60px de la izquierda cuando el número es impar.

Esto debe ser muy simple, lo escribimos directamente StatelessWidget;

class _Row extends StatelessWidget {
  final int number;


  const _Row({
    Key key,
    this.number,
  }) : super(key: key);


  double get leftPadding => number % 2 == 1 ? 60.0 : 24.0;


  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      width: double.infinity,
      alignment: Alignment.centerLeft,
      padding: EdgeInsets.only(
        left: leftPadding,
      ),
      child: Text('$number'),
    );
  }
}

De esta manera, este efecto simplemente se logra, pero en la operación real, se encuentra que los números saltan de un lado a otro, lo cual es muy desagradable. Parece que es necesario optimizar este widget para que reproduzca la animación cuando se mueva hacia la izquierda y hacia la derecha, y se mueva en lugar de saltar.

Una solución relativamente simple es pasar un AnimationController para un control preciso, pero esto es demasiado complicado. En este escenario, cuando lo usamos, generalmente solo queremos actualizar el número, y luego setState, esperamos que reproduzca la animación internamente (generalmente una animación de transición), por lo que no necesitamos operar el complejo AnimationController.

De hecho, en este momento, podemos usar el ciclo de vida didUpdateWidget. Cuando se actualice el widget adjunto al estado, se activará esta devolución de llamada. Aquí puede responder a la actualización de los datos transmitidos por la capa superior y reproducir la animación internamente.

Código:

class _Row extends StatefulWidget {
  final int number;


  const _Row({
    Key key,
    this.number,
  }) : super(key: key);


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


class __RowState extends State<_Row> with TickerProviderStateMixin {
  AnimationController animationController;


  double get leftPadding => widget.number % 2 == 1 ? 60.0 : 24.0;


  @override
  void initState() {
    animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 500),
      lowerBound: 24,
      upperBound: 60,
    );
    animationController.addListener(() {
      setState(() {});
    });
    super.initState();
  }


  // widget更新,就会触发这个方法
  @override
  void didUpdateWidget(_Row oldWidget) {
    // 播放动画去当前位置
    animationController.animateTo(leftPadding);
    super.didUpdateWidget(oldWidget);
  }


  @override
  void dispose() {
    animationController.dispose();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      width: double.infinity,
      alignment: Alignment.centerLeft,
      padding: EdgeInsets.only(
        left: animationController.value,
      ),
      child: Text('${widget.number}'),
    );
  }
}

De esta manera, se completa un cambio de animación muy suave entre los estados, y ya no saltos horizontales.

Pass ValueNotifier / controlador personalizado

Aquí todavía miramos la demanda primero

Descripción: después de que un widget recibe un evento, activa el método del estado del niño 

Función de realización: haga clic en el botón para permitir que un niño comience la cuenta regresiva o envíe una solicitud (llame al método estatal) 

Dificultad: ⭐⭐⭐⭐⭐

En primer lugar, debe quedar claro que si aparece en la lógica empresarial, obviamente no es razonable y debe evitarse. Cuando StatefulWidget está anidado, debes evitar llamar a los métodos de los demás . En este caso, es mejor extraer los métodos y datos en el estado del niño hacia arriba y ponerlos en el estado de capa actual.

Aquí hay un análisis simple:

  1. Cuando hay
    cambios en los datos, es más razonable utilizar el ciclo de vida didUpdateWidget de State. Aquí también apenas podemos lograrlo En el marco de flutter, recomiendo usar ValueNotifier para la entrega, y el niño puede escuchar ValueNotifier.

  2. Sin cambios de datos

    Es más problemático si no hay cambio de datos Necesitamos un controlador para ingresar, y luego el niño registra una devolución de llamada en el controlador, para que pueda ser controlado por el controlador.

También puede usar proveedores, eventbus y otras bibliotecas aquí, o usar métodos relacionados con key y globalKey. Sin embargo, debe enfatizarse una vez más: no importa qué método se use, este anidamiento no es razonable.Cuando los métodos del estado deben llamarse entre sí en el proyecto, deben combinarse y escribirse en un estado. En principio, este tipo de anidamiento debe evitarse, no importa cómo se implemente, no debe ser una práctica común en el proyecto.

Aunque no se recomienda escribir esto en código comercial, es posible escribir esta estructura en el código del marco (porque la interfaz debe estar expuesta). En este caso, puede consultar ScrollController, puede controlar el estado deslizante a través de este controlador.

Vale la pena mencionar que: ScrollController hereda de ValueNotifier. Por lo tanto, todavía se recomienda usar ValueNotifier.

De hecho, el modo controlador también es un modo común en el código fuente de flutter, que generalmente se usa para exponer métodos encapsulados. El controlador es más complicado que otros métodos, pero afortunadamente no lo usamos a menudo.

Como ejemplo, implementemos una clase CountController para ayudarnos a llamar a métodos dentro del componente.

Código:

class CountController extends ValueNotifier<int> {
  CountController(int value) : super(value);


  // 逐个增加到目标数字
  Future<void> countTo(int target) async {
    int delta = target - value;
    for (var i = 0; i < delta.abs(); i++) {
      await Future.delayed(Duration(milliseconds: 1000 ~/ delta.abs()));
      this.value += delta ~/ delta.abs();
    }
  }


  // 实在想不出什么例子了,总之是可以这样调用方法
  void customFunction() {
    _onCustomFunctionCall?.call();
  }


  // 目标state注册这个方法
  Function _onCustomFunctionCall;
}


class _Row extends StatefulWidget {
  final CountController controller;
  const _Row({
    Key key,
    @required this.controller,
  }) : super(key: key);


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


class __RowState extends State<_Row> with TickerProviderStateMixin {
  @override
  void initState() {
    widget.controller.addListener(() {
      setState(() {});
    });
    widget.controller._onCustomFunctionCall = () {
      print('响应方法调用');
    };
    super.initState();
  }


  // 这里controller应该是在外面dispose
  // @override
  // void dispose() {
  //   widget.controller.dispose();
  //   super.dispose();
  // }


  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      width: double.infinity,
      alignment: Alignment.centerLeft,
      padding: EdgeInsets.only(
        left: 24,
      ),
      child: Text('${widget.controller.value}'),
    );
  }
}

Utilice el controlador para controlar completamente los datos y las llamadas a métodos de la siguiente capa de estado, que es más flexible. Sin embargo, la cantidad de código es grande y este modo debe evitarse en el negocio, y solo el controlador está integrado en lugares complejos para controlar los datos. Si escribe muchos controladores personalizados, debe reflexionar sobre si hay un problema con la estructura de su proyecto. No importa cómo se implemente, este método de entrega no debería ser una práctica común en el proyecto.

Gestión única de datos y eventos globales

Los datos globales pueden ser administrados por proveedores de alto nivel o singletons. Mi costumbre es usar singletons para que los datos se puedan obtener sin depender del contexto.

Escritura simple de singleton, extiende cualquier atributo a singleton.

class Manager {
  // 工厂模式
  factory Manager() =>_getInstance();
  static Manager get instance => _getInstance();
  static Manager _instance;
  Manager._internal() {
    // 初始化
  }
  static Manager _getInstance() {
    if (_instance == null) {
      _instance = new Manager._internal();
    }
    return _instance;
  }
}

para resumir

La razón por la que escribo este artículo es que cuando muchas personas están aprendiendo flutter, no están muy familiarizadas con la transmisión de datos y eventos. También aprendieron marcos de terceros, como proveedores, muy temprano, y tienen un poco de comprensión de las cosas básicas. El código está desordenado y el proyecto es desordenado. No sé cómo transferir datos y cómo actualizar la interfaz. Así que escribir este artículo resume los métodos más básicos de transmisión de varios eventos y datos.

Para resumir brevemente, el más básico de flutter para cambiar datos son los siguientes modos:

  • Cambiar sus propios datos de estado, setState pasa nuevos datos al niño

  • Recibir devolución de llamada de evento secundario

  • Actualice los datos de destino al niño, el niño monitorea los cambios en los datos y cambia su estado con más detalle

  • Pase el controlador al niño para controlar completamente el estado del niño

En el proyecto, solo se necesitan estos modos para escribirlos todos fácilmente. Usando otras bibliotecas como proveedor, no habrá una mejora significativa o progreso en el código. Todavía espero que cuando aprendas flutter, puedas descubrir la escritura básica primero y luego aprender más profundamente.


Mantenga presionado el código QR a la derecha

Ver más intercambio maravilloso por parte de los desarrolladores

"Los desarrolladores dicen que DTalk" recopila contenido técnico / de productos relacionados con las aplicaciones móviles de Google (aplicaciones y juegos) para desarrolladores chinos  . Todos son bienvenidos a compartir sus conocimientos de la industria o sus conocimientos sobre aplicaciones móviles, experiencia o nuevos descubrimientos en el proceso de desarrollo móvil, resúmenes de la experiencia práctica en la aplicación al mar y comentarios sobre el uso de productos relacionados. Esperamos sinceramente poder brindar a estos destacados desarrolladores chinos una plataforma para expresarse mejor y aprovechar al máximo sus fortalezas. Nos centraremos en seleccionar casos destacados basados ​​en su contenido técnico para recomendar a los expertos en tecnología de desarrollo de Google (GDE).

 

 Haga clic en la pantalla al final  |  leer leer el artículo original  | inscrito inmediatamente en  "los desarrolladores dicen · DTalk" 

 


Supongo que te gusta

Origin blog.csdn.net/jILRvRTrc/article/details/109088489
Recomendado
Clasificación