【Flutter入门到进阶】Flutter基础篇---组件生命周期与状态

news2024/11/19 15:23:16

1 Android界面渲染流程UI树与FlutterUI树的设计思路对比

1.1 android渲染流程中的树的组织

1.1.1 XML加载与解析

1.1.2 ViewRootImpl组织树结构

1.1.3 编舞者掌控调用时机

1.1.4 View负责UI渲染

1.1.5 底层surfacefiling负责沟通硬件

1.2 flutter组件设计思路,无状态与有状态

1.2.1 声明式UI没有XML数据加载解析流程

1.2.2 区别

        1.自己组织树结构

        2.数据状态的变动

2 Widget组件生命周期详解

2.1 说明

2.1.1 在Flutter中自定义组件其实就是一个类,这个类需要继承StatelessWidget/StatefulWidget。

2.1.2 StatelessWidget是无状态组件,状态不可变的widget

2.1.3 StatefulWidget是有状态组件,持有的状态可能在widget生命周期改变。

2.1.4 如果需要在生命周期中改变页面中的数据,需要用到StatefulWidget

2.2 Widget组件生命周期

2.2.1 说明

        和其他的视图框架比如android的Activity一样,flutter中的视图Widget也存在生命周期,生命周期的回调函数体现在了State上面。组件State的生命周期整理如下图所示:

2.2.2 图例

2.3 生命周期函数说明

2.3.1 framework中对于生命周期函数

2.3.2 createState

        当一个StatefulWidget插入到渲染树结构、或者从渲染树结构移除时,都会调用StatefulWidget.createState方法,从而达到更新UI的效果

2.3.3 initState

        initState是StatefulWidget创建后调用的第一个方法,而且只执行一次。在执行initState时,View没有渲染,但是StatefulWidget 已经被加载到渲染树里了

2.3.4 didChangeDependencies

        didChangeDependencies会在initState后立即调用,当StatefulWidget依赖的InheritedWidget发生变化之后,didChangeDependencies会调用,所以didChangeDependencies可以调用多次

2.3.5 build

        build方法会在didChangeDeoendencies之后立即调用,在之后setState()刷新时,会重新调用build绘制页面,所以build方法可以调用多次。但一般不再build中创建除创建Widget的方法,否则会影响渲染效率。

2.3.6 setState

        [State] 对象可以通过调用它们的 [setState] 方法自发地请求重建其子树,这表明它们的某些内部状态已经改变,可能会影响该子树中的用户界面。setState方法会被多次调用

2.3.7 didUpdateWidget

        1、当调用setState更新UI的时候,都会调用didUpdateWidget。

        2、框架在调用 [didUpdateWidget] 之后总是调用 [build],在 [didUpdateWidget] 中对 [setState] 的任何调用都是多余的

2.3.8 deactivate

        1、当框架从树中移除此 State 对象时将会调用此方法,

        2、在某些情况下,框架将重新插入 State 对象到树的其他位置(例如,如果包含该树的子树 State 对象从树中的一个位置移植到另一位置),框架将会调用 build 方法来提供 State 对象适应其在树中的新位置。

2.3.9 dispose

        当框架从树中永久移除此 State 对象时将会调用此方法,与 deactivate的区别是,deactivate 还可以重新插入到树中,而 dispose 表示此 State 对象永远不会在 build。调用完 dispose后,mounted 属性被设置为 false,也代表组件生命周期的结束,此时再调用 setState 方法将会抛出异常。子类重写此方法,释放相关资源,比如动画等

2.4 生命周期调用

2.4.1 说明

        假设我们从A页面跳转到B页面, 那么A,B页面的生命周期会是怎样的呢?

        B页面进入初始化状态,依次执行4个函数:构造函数 > initState > didChangeDependencies > Widget build , 此时页面加载完成,进入运行态。

        此时A页面依次执行deactivate > build函数。注意 此时A页面并未卸载。

        然后我们假设B页面只有一个按钮,点击B页面中的按钮,改变按钮的文字,会执行widget的build方法 ,(理论上也应该执行didUpdateWidget,但我这里没有)。

        这时,我们点击返回键从B页面返回到A页面。

        A页面重新显示,B页面开始卸载。

        那么A先执行deactivate > build , 然后B页面依次执行:deactivate > dispose 。

        此时A页面进入运行态,B页面移除

2.4.2 代码

import 'package:flutter/material.dart';
import 'package:flutter_chapter_04/src/pages/DemoPages.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key}); // This widget is the root of your application.   
  @override Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue,),
      home: const Demo(),);
  }
}

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

  @override State<Demo> createState() => _DemoState();
}

class _DemoState extends State<Demo> {
  @override Widget build(BuildContext context) {
    print("------main--------build");
    return Scaffold(appBar: AppBar(title: const Text("StatefulWidget demo"),),
      body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[ ElevatedButton(onPressed: () {
          Navigator.of(context).push(MaterialPageRoute(builder: (setting) {
            return const DemoPage();
          }));
        }, child: const Text("页面跳转"),),
        ],),),
      floatingActionButton: FloatingActionButton(onPressed: () {
        setState(() {
          print("调用....");
        });
      },
        tooltip: 'Increment',
        child: const Icon(Icons
            .add),), // This trailing comma makes auto-formatting nicer for build methods.     
    );
  }

  @override void initState() {
    //  implement initState     
    super.initState();
    print("------main--------initState");
  }

  @override void didChangeDependencies() {
    //  implement didChangeDependencies     
    super.didChangeDependencies();
    print("-------main-------didChangeDependencies");
  }

  @override void setState(VoidCallback fn) {
    //  implement setState     
    super.setState(fn);
    print("------main--------setState");
  }

  @override void deactivate() {
    //  implement deactivate     
    super.deactivate();
    print("------main--------deactivate");
  }

  @override void dispose() {
    //  implement dispose     
    super.dispose();
    print("------main--------dispose");
  }
}

import 'package:flutter / material.dart';

class DemoPage extends StatelessWidget {
  const DemoPage({Key? key }) : super(key: key);

  @override Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Demo页面"),), body: const DemoWidget(),);
  }
}

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

  @override State<DemoWidget> createState() => _DemoWidgetState();
}

class _DemoWidgetState extends State<DemoWidget> {
  String btnText = "test";

  @override Widget build(BuildContext context) {
    print("------_DemoWidgetState--------build");
    return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[ ElevatedButton(onPressed: () {
        setState(() {
          if (btnText == "test") {
            btnText = "测试";
          } else {
            btnText = "test";
          }
        });
      }, child: Text(btnText),), ElevatedButton(onPressed: () {
        print("返回......");
        Navigator.of(context).pop();
      }, child: const Text("返回"),),
      ],),);
  }

  @override void initState() {
    //  implement initState     
    super.initState();
    print("------_DemoWidgetState--------initState");
  }

  @override void didChangeDependencies() {
    //  implement didChangeDependencies     
    super.didChangeDependencies();
    print("-------_DemoWidgetState-------didChangeDependencies");
  }

  @override void setState(VoidCallback fn) {
    //  implement setState     
    super.setState(fn);
    print("------_DemoWidgetState--------setState");
  }

  @override void deactivate() {
    //  implement deactivate     
    super.deactivate();
    print("------_DemoWidgetState--------deactivate");
  }

  @override void dispose() {
    //  implement dispose     
    super.dispose();
    print("-----_DemoWidgetState---------dispose");
  }
}

3 Flutter 页面间数据传递(共享)的几种常用方式

3.1 通过构造器(constructor)传递数据

3.1.1 说明

        通过构造器传递数据是一种最简单的方式,也是最常用的方式,在第一个页面,我们模拟创建一个我们需要传递数据的对象。当点击跳转的时候,我们把数据传递给DataTransferByConstructorPage页面,并把携带过来的数据展示到页面上

3.1.2 示例

//1.创建一个传递数据对象
final data = TransferDataEntity("001", "张三丰", 18);

//2.定义一个跳转到DataTransferByConstructorPage页面的方法
_transferDataByConstructor(BuildContext context, TransferDataEntity data) {
  Navigator.push(
      context,
      MaterialPageRoute(
          builder: (context) => DataTransferByConstructorPage(data: data)));
}


//3.在DataTransferByConstructorPage页面接收到数据并展示出来
//通过构造器的方式传递参数
class DataTransferByConstructorPage extends StatelessWidget {
  final TransferDataEntity data;

  DataTransferByConstructorPage({this.data});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("构造器方式"),
      ),
      body: Column(
        children: <Widget>[
          Container(
            width: double.infinity,
            height: 40.0,
            alignment: Alignment.center,
            child: Text(data.id),
          ),
          Container(
            width: double.infinity,
            height: 40.0,
            alignment: Alignment.center,
            child: Text(data.name),
          ),
          Container(
            width: double.infinity,
            height: 40.0,
            alignment: Alignment.center,
            child: Text("${data.age}"),
          )
        ],
      ),
    );
  }
}

1.提供一个final变量 final TransferDataEntity data

2.提供一个构造器接收参数 DataTransferByConstructorPage({this.data});

3.2 当一个页面关闭时携带数据到上一个页面(Navigator.pop)

3.2.1 说明

        在Android开发中我们需要将数据传递给上一个页面通常使用的传统方式是startActivityForResult()方法。但是在flutter就不用这么麻烦了。只需要使用Navigator.pop方法即可将数据结果带回去。但是我们跳转的时候需要注意两点:

3.2.2 示例

1.我们需要定义一个异步方法用于接收返回来的结果

///跳转的时候我们需要使用异步等待回调结果 dataFromOtherPage 就是返回的结果 _toTransferForResult(BuildContext context, TransferDataEntity data) async {
  final dataFromOtherPage = await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => TransferRouterPage(data: data)),
  ) as TransferDataEntity;
}



2.在我们要关闭的页面使用Navigator.pop 返回第一个页面

//返回并携带数据
_backToData(BuildContext context) {
  var transferData = TransferDataEntity("嘻嘻哈哈", "007", 20);
  Navigator.pop(context, transferData);
}

3.3 InheritedWidget方式

3.3.1 说明

        官网给出的解释:InheritedWidget是Flutter中非常重要的一个功能型Widget,它可以高效的将数据在Widget树中向下传递、共享,这在一些需要在Widget树中共享数据的场景中非常方便,如Flutter中,正是通过InheritedWidget来共享应用主题(Theme)和Locale(当前语言环境)信息的。

        InheritedWidget和React中的context功能类似,和逐级传递数据相比,它们能实现组件跨级传递数据。InheritedWidget的在Widget树中数据传递方向是从上到下的,这和Notification的传递方向正好相反。

        优点:可以控制每个Widget单独去取数据并使用

        如果一个页面只存在同一层级的Weiget这时候使用构造器的方式当然是最简单的,也是最方便的,但是如果一个页面存在多个层级的Weiget这时候构造器的方法就有了局限性,这时候我们使用InheritedWidget 是一个比较好的选择。

3.3.2 示例

使用InheritedWidget方式如下几步

3.4 全局的提供数据的方式

3.4.1 说明

        这种方式我们还是使用InheritedWidget,区别就是我们不是跳转的时候去创建IGenericDataProvider。而是把他放在最顶层 注意:这种方式一定要把数据放在顶层

3.4.2 示例

(1)定义顶部数据

class MyApp extends StatelessWidget {
  //  This  widget  is  the  root  of  your  application.
  // 传递值的数据
  var params = InheritedParams();

  @override
  Widget build(BuildContext context) {
    return IGenericDataProvider(
      data: params,
      child: MaterialApp(
        title: 'Data  Transfer  Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Data  Transfer  Demo'),
      ),
    );
  }
}

(2)接收数据的方式基本和 InheritedWidget相同

final data = IGenericDataProvider.of<TransferDataEntity>(context),获取数据

(3)使用

class InheritedParamsPage extends StatefulWidget {
  @override
  _InheritedParamsPageState createState() => _InheritedParamsPageState();
}

class _InheritedParamsPageState extends State<InheritedParamsPage> {
  @override
  Widget build(BuildContext context) {
    final data = IGenericDataProvider.of<TransferDataEntity>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text("通过全局数据方式"),
      ),
      body: Column(
        children: <Widget>[
          Container(
            alignment: Alignment.center,
            height: 40.0,
            child: Text(data.name),
          ),
          Container(
            alignment: Alignment.center,
            height: 40.0,
            child: Text(data.id),
          ),
          Container(
            alignment: Alignment.center,
            height: 40.0,
            child: Text("${data.age}"),
          ),
        ],
      ),
    );
  }
}

3.5 通过全局单例模式来使用

3.5.1 说明

        这种方式就是创建一个全局单例对象,任何地方都可以操控这个对象,存储和取值都可以通过这个对象

3.5.2 示例

//创建单例对象
class TransferDataSingleton {
  static final TransferDataSingleton _instanceTransfer =
      TransferDataSingleton.__internal();
  TransferDataEntity transData;

  factory TransferDataSingleton() {
    return _instanceTransfer;
  }

  TransferDataSingleton.__internal();
}

final transSingletonData = TransferDataSingleton();

//给单例对象存放数据
_singletonDataTransfer(BuildContext context) {
  var transferData = TransferDataEntity("二汪", "002", 25);
  transSingletonData.transData = transferData;
  Navigator.push(context,
      MaterialPageRoute(builder: (context) => TransferSingletonPage()));
}

//接收并使用传递的值
class TransferSingletonPage extends StatefulWidget {
  @override
  _TransferSingletonPageState createState() => _TransferSingletonPageState();
}

class _TransferSingletonPageState extends State<TransferSingletonPage> {
  @override
  Widget build(BuildContext context) {
    //直接引入单例对象使用
    var data = transSingletonData.transData;
    return Scaffold(
      appBar: AppBar(
        title: Text("全局单例传递数据"),
      ),
      body: Column(
        children: <Widget>[
          Container(
            alignment: Alignment.center,
            height: 40.0,
            child: Text(data.name),
          ),
          Container(
            alignment: Alignment.center,
            height: 40.0,
            child: Text(data.id),
          ),
          Container(
            alignment: Alignment.center,
            height: 40.0,
            child: Text("${data.age}"),
          ),
        ],
      ),
    );
  }
}

3.6 全局单例结合Stream的方式传递数据

3.6.1 说明

        创建一个接受全局的单例对象,并把传递值转成Stream方式

        在接收数据可以使用StreamBuilder直接接收并处理

3.6.2 示例

//单例模式
class TransferStreamSingleton {
  static final TransferStreamSingleton _instanceTransfer =
      TransferStreamSingleton.__internal();
  StreamController streamController;

  void setTransferData(TransferDataEntity transData) {
    streamController = StreamController<TransferDataEntity>();
    streamController.sink.add(transData);
  }

  factory TransferStreamSingleton() {
    return _instanceTransfer;
  }

  TransferStreamSingleton.__internal();
}

final streamSingletonData = TransferStreamSingleton();

// 传递要携带的数据
_streamDataTransfer(BuildContext context) {
  var transferData = TransferDataEntity("三喵", "005", 20);
  streamSingletonData.setTransferData(transferData);
  Navigator.push(
      context, MaterialPageRoute(builder: (context) => TransferStreamPage()));
}

// 接收要传递的值
class TransferStreamPage extends StatefulWidget {
  @override
  _TransferStreamPageState createState() => _TransferStreamPageState();
}

class _TransferStreamPageState extends State<TransferStreamPage> {
  StreamController _streamController = streamSingletonData.streamController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("全局单例结合Stream"),
        ),
        body: StreamBuilder(
            stream: _streamController.stream,
            initialData: TransferDataEntity("", "", 0),
            builder: (context, snapshot) {
              return Column(
                children: <Widget>[
                  Container(
                    alignment: Alignment.center,
                    height: 40.0,
                    child: Text(snapshot.data.name),
                  ),
                  Container(
                    alignment: Alignment.center,
                    height: 40.0,
                    child: Text(snapshot.data.id),
                  ),
                  Container(
                    alignment: Alignment.center,
                    height: 40.0,
                    child: Text("${snapshot.data.age}"),
                  ),
                ],
              );
            }));
  }

  @override
  void dispose() {
    _streamController.close();
    super.dispose();
  }
}

4 跨组件状态共享

4.1 Steam

4.1.1 说明

        steam主要目的就是类似于自己建立一个android体系中的Boudle,利用一个FD资源处理

4.1.2 示例

import 'package:flutter/material.dart';
import 'package:flutter_chapter_04/src/pages/DemoPages.dart';
import 'package:flutter_chapter_04/src/pages/FirstPage.dart';
import 'package:flutter_chapter_04/src/pages/HeartProvide.dart';
import 'package:provide/provide.dart';
import '../data_share/transfer_data_entity.dart';
import '../data_share/transfer_stream_singleton.dart';

void main() {
  runApp(const MyApp());
}

var data = ProvideData();

class MyApp extends StatelessWidget {
  const MyApp({Key key}) : super(
      key: key); //  This  widget  is  the  root  of  your  application.     
  @override Widget build(BuildContext context) {
    var transferData = TransferDataEntity();
    streamSingletonData.setTransferData(transferData);
    return MaterialApp(title: 'Flutter  Demo',
      theme: ThemeData(primarySwatch: Colors.blue,),
      home: ShareDataWidget(data: data, child: const FirstPage(),),);
  }
}



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

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

class FirstPageState extends State<FirstPage> {
  final StreamController _streamController = streamSingletonData
      .streamController;

  @override Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: const Text("第一个页面"),),
      body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('阅读人数:$num', style: const TextStyle(fontSize: 30.0),),
          StreamBuilder(stream: _streamController.stream,
            initialData: TransferDataEntity(),
            builder: (context, data) {
              return IconButton(icon: data.data.isCheck
                  ? const Icon(Icons.favorite, size: 50)
                  : const Icon(Icons.favorite_border, size: 50), onPressed: () {
                setState(() {
                  data.data.isCheck = data.data.isCheck ? false : true;
                  print("data-first:${data.data}");
                  streamSingletonData.setTransferData(data.data);
                });
              },);
            },),
        ],),),
      floatingActionButton: FloatingActionButton(onPressed: () async {
        await Navigator.push(context,
          MaterialPageRoute(builder: (context) => const SecondPage()),);
        setState(() {
          print("更新UI......");
        });
      }, child: const Icon(Icons.arrow_right),),);
  }
}

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

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

class SecondPageState extends State<SecondPage> {
  SecondPageState();

  final StreamController _streamController = streamSingletonData
      .streamController;

  @override Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: const Text("第二个页面"),),
      body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          const Text(
            '这是第二个页面', style: TextStyle(fontSize: 30.0, color: Colors.red),),
          StreamBuilder(stream: _streamController.stream,
            initialData: TransferDataEntity(),
            builder: (context, data) {
              return IconButton(icon: data.data.isCheck
                  ? const Icon(Icons.favorite, size: 50)
                  : const Icon(Icons.favorite_border, size: 50), onPressed: () {
                setState(() {
                  data.data.isCheck = data.data.isCheck ? false : true;
                  print("********data:${data.data}");
                  streamSingletonData.setTransferData(data.data);
                });
              },);
            },)
        ],),),
      floatingActionButton: FloatingActionButton(onPressed: () {
        setState(() {});
      }, child: const Icon(Icons.add),),);
  }

  @override void dispose() {
    _streamController.close();
    super.dispose();
  }
}

4.2 Flutter Provides

4.2.1 说明

        第三方组件应用

4.2.2 示例

import 'package:flutter/material.dart';
import 'package:flutter_chapter_04/src/pages/FirstPage.dart';
import 'package:provide/provide.dart';
import '../pages/HeartProvide.dart';

void main() {
  //初始化     
  var myProvide = MyProvide();
  var providers = Providers(); //将myProvide加到providers中     
  providers.provide(Provider.function((context) => myProvide));
  runApp(ProviderNode(providers: providers, child: MyApp(),));
}

class MyApp extends StatelessWidget {
  //  This  widget  is  the  root  of  your  application.     
  @override Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter  Provide',
      theme: ThemeData(primarySwatch: Colors.blue,),
      home: const FirstProvidePage(),);
  }
}

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

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

class FirstProvidePageState extends State<FirstProvidePage> {
  @override Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: const Text("第一个页面"),),
      body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Provide<MyProvide>(builder: (context, child, provide) {
            return Text(
              '阅读人数:' + '${provide.getNum}', style: TextStyle(fontSize: 30.0),);
          },),
          Provide<MyProvide>(builder: (context, child, provide) {
            return IconButton(icon: Provide
                .value<MyProvide>(context)
                .getIsFavorite
                ? const Icon(Icons.favorite, size: 50)
                : const Icon(Icons.favorite_border, size: 50), onPressed: () {
              Provide.value<MyProvide>(context).changeFavorite(!Provide
                  .value<MyProvide>(context)
                  .getIsFavorite);
            },);
          },),
        ],),),
      floatingActionButton: FloatingActionButton(onPressed: () {
        Navigator.push(context,
          MaterialPageRoute(builder: (context) => SecondProvidePage()),);
      }, child: const Icon(Icons.arrow_right),),);
  }
}

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

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

class _SecondProvideState extends State<SecondProvidePage> {
  @override Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: const Text("第二个页面"),),
      body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          const Text(
            '这是第二个页面', style: TextStyle(fontSize: 30.0, color: Colors.red),),
          Provide<MyProvide>(builder: (context, child, provide) {
            return Text('阅读人数:' + '${provide.getNum}',
              style: const TextStyle(fontSize: 30.0),);
          },),
          Provide<MyProvide>(builder: (context, child, provide) {
            return IconButton(icon: Provide
                .value<MyProvide>(context)
                .getIsFavorite
                ? const Icon(Icons.favorite, size: 50)
                : const Icon(Icons.favorite_border, size: 50), onPressed: () {
              Provide.value<MyProvide>(context).changeFavorite(!Provide
                  .value<MyProvide>(context)
                  .getIsFavorite);
            },);
          },),
        ],),),
      floatingActionButton: FloatingActionButton(onPressed: () {
        Provide.value<MyProvide>(context).addNum();
      }, child: const Icon(Icons.add),),);
  }
}

5 Flutter Key

5.1 说明

        我们平时一定接触过很多的 Widget,比如 Container、  Row、Column 等,它们在我们绘制界面的过程 中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同  的参数,它们通常在参数列表的第一个,那就是 Key。

        在Flutter中,  Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保 存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够 用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候, 此时类型已经无法作为区分的条件了,我们就需要使用到key。

5.2 没有 Key 会发生什么现象

案例

        如下面例:定义了一个StatefulWidget的Box,点击Box的时候可以改变Box里面的数字,当我们重新 对Box排序的时候Flutter就无法识别到Box的变化了,   这是什么原因呢?

图例

说明

        运行后我们发现改变list Widget顺序后,Widget颜色会变化,但是每个Widget里面的文本内容并没有 变化,为什么会这样呢?当我们List重新排序后Flutter检测到了Widget的顺序变化,所以重新绘制List Widget,但是Flutter 发现List Widget 里面的元素没有变化,所以就没有改变Widget里面的内容。

        把List 里面的Box的颜色改成一样,这个时候您重新对list进行排序,就很容易理解了。重新排序后虽然 执行了setState,但是代码和以前是一样的,所以Flutter不会重构List Widget里面的内容, 也就是 Flutter没法通过Box里面传入的参数来识别Box是否改变。如果要让FLutter能识别到List Widget子元素 的改变,就需要给每个Box指定一个key。

代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key key})
      : super(key: key); //  This  widget  is  the  root  of  your  application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter  Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Center(
      child: Box(
        color: Colors.blue,
      ),
    ),
    Box(
      color: Colors.blue,
    ),
    Box(
      color: Colors.red,
    ),
    Box(
      color: Colors.orange,
    )
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            list.shuffle(); //打乱list的顺序
          });
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  Color color;

  Box({this.color});

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

5.3 LocalKey与GlobalKey

5.3.1 说明

        在Flutter中,  Key是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保 存主要是通过判断组件的类型或者key值是否一致。因此,当各组件的类型不同的时候,类型已经足够 用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候, 此时类型已经无法作为区分的条件了,我们就需要使用到key。

5.3.2 Flutter key子类

        LocalKey

                局部键(LocalKey):  ValueKey、ObjectKey、  UniqueKey

        GlobalKey 

                全局键(GlobalKey):   GlobalKey、GlobalObjectKey

图例

        ValueKey (值key)把一个值作为key ,  UniqueKey  (唯一key)程序生成唯一的Key,当我们不知道 如何指定ValueKey的时候就可以使用UniqueKey ,ObjectKey  (对象key)把一个对象实例作为key。

        GlobalKey  (全局key),  GlobalObjectKey  (全局Objec key,和ObjectKey有点类似)

5.3.3 运用key变更上面案例业务

代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key key})
      : super(key: key); //  This  widget  is  the  root  of  your  application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter  Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Box(
      key: const ValueKey(1),
      color: Colors.blue,
    ),
    Box(
      key: ObjectKey(Box(color: Colors.red)),
      color: Colors.red,
    ),
    Box(
      key: UniqueKey(), //程序自动生成一个key
      color: Colors.orange,
    )
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            list.shuffle(); //打乱list的顺序
          });
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  Color color;

  Box({Key key, this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

5.3.4 GlobalKey的使用

说明

        如果把LocalKey比作局部变量,   GlobalKey就类似于全局变量

        下面使用了LocalKey,当屏幕状态改变的时候把 Colum换成了Row ,  Box的状态就会丢失。

        一个Widget状态的保存主要是通过判断组件的类型或者key值是否一致。  LocalKey只在当前的组件树有效,所以把Colum换成了Row的时候Widget的状态就丢失了。为了解决这个问题我们 就可以使用GlobalKey。

案例

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key key})
      : super(key: key); //  This  widget  is  the  root  of  your  application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter  Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Box(
      key: const ValueKey(1),
      color: Colors.blue,
    ),
    Box(
      key: ObjectKey(Box(color: Colors.red)),
      color: Colors.red,
    ),
    Box(
      key: UniqueKey(), //程序自动生成一个key
      color: Colors.orange,
    )
  ];
  List<Widget> list = [
    Box(
      key: GlobalKey(),
      color: Colors.blue,
    ),
    Box(
      key: GlobalKey(),
      color: Colors.red,
    ),
    Box(
      key: GlobalKey(), //程序自动生成一个key
      color: Colors.orange,
    )
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            list.shuffle(); //打乱list的顺序
          });
        },
        child: const Icon(Icons.refresh),
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: MediaQuery.of(context).orientation == Orientation.portrait
            ? Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              )
            : Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  Color color;

  Box({Key key, this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

5.3.5 GlobalKey 获取子组件

说明

        globalKey.currentState 可以获取子组件的状态,执行子组件的方法,globalKey.currentWidget可以获 取子组件的属性,  _globalKey.currentContext!.findRenderObject()可以获取渲染的属性。

案例

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key key})
      : super(key: key); //  This  widget  is  the  root  of  your  application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter  Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final GlobalKey _globalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          //1、获取子组件的状态  调用子组件的属性
          var state = (_globalKey.currentState as _BoxState);
          setState(() {
            state._count++;
          }); //2、获取子组件的属性
          var box = (_globalKey.currentWidget as Box);
          print(box.color); //3、获取子组件渲染的属性
          var renderBox =
              (_globalKey.currentContext.findRenderObject() as RenderBox);
          print(renderBox.size);
        },
      ),
      appBar: AppBar(
        title: const Text('Title'),
      ),
      body: Center(
        child: Box(
          key: _globalKey,
          color: Colors.red,
        ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;

  const Box({Key key, this.color}) : super(key: key);

  @override
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;

  run() {
    print("run");
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Center(
          child: Text("$_count"),
        ),
      ),
    );
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/388465.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Canal实时监控案例

Canal实时监控案例 文章目录Canal实时监控案例0. 写在前面1. TCP 模式测试1.1 IDEA创建项目canal-module1.2 通用监视类——CanalClient1.2.1 Canal 封装的数据结构1.2.2 在 canal-module 模块下创建 cn.canal 包&#xff0c;并在该包下创建 CanalClient.java文件2. Kafka 模式…

前后端身份验证

1、web 开发模式 【】基于服务端渲染的传统 Web 开发模式 【】基于前后端分离的新型 Web 开发模式&#xff1a;依赖于 Ajax 技术的广泛应用。后端只负责提供 API 接口&#xff0c;前端使用 Ajax 调用接口的开发模式 2、身份认证 【】服务端渲染推荐使用 Session 认证机制 【】…

《堆的应用》TOP-K问题

TOP-K问题:即求数据中前k个最大的元素或者最小的元素&#xff0c;一般情况下&#xff0c;这些数据量是非常大的。 比如:专业前10名、世界500强、世界富豪榜、游戏中前100名等这些排名都是TOP-K问题。 来源于《财富》世界500强排行榜。 对于TOP-k问题&#xff0c;能想到的最简…

【XXL-JOB】XXL-JOB定时处理视频转码

【XXL-JOB】XXL-JOB定时处理视频转码 文章目录【XXL-JOB】XXL-JOB定时处理视频转码1. 准备工作1.1 高级配置1.2 分片广播2. 需求分析2.1 作业分片方案2.2 保证任务不重复执行2.2.1 保证幂等性3. 视频处理业务流程3.1 添加待处理任务3.2 查询待处理任务3.3 更新任务状态3.4 工具…

考研还是工作?两战失败老道有话说

老道入职第一周自我介绍谈谈考研谈谈工作新的启程自我介绍 大家好&#xff01;在下是一枚考研失败两次的自认为聪明能干的有点小帅的实则超级垃圾的三非名校毕业的自动化渣男。大一下就加入实验室&#xff0c;在实验室焊板子、画板子、培训、打比赛外加摸鱼&#xff1b;参加过…

Swagger扩展 - 同一个接口生成多份Swagger API文档

为同一个ApiOperation生成多份不同Swagger API文档。 0. 目录1. 背景2. 效果展示3. 实现3.1 关键逻辑 - 让接口自解释3.2 关键逻辑 - 如何生成相应的ApiDescription3.3 关键逻辑 - 如何为生成的ApiDescription 赋值3.4 关键逻辑 - 如何动态生成Docket4. 继续优化5. 参考1. 背景…

【Spark分布式内存计算框架——Structured Streaming】3. Structured Streaming —— 入门案例:WordCount

1.3 入门案例&#xff1a;WordCount 入门案例与SparkStreaming的入门案例基本一致&#xff1a;实时从TCP Socket读取数据&#xff08;采用nc&#xff09;实时进行词频统计WordCount&#xff0c;并将结果输出到控制台Console。 文档&#xff1a;http://spark.apache.org/docs/2…

一个Bug让人类科技倒退几十年?

大家好&#xff0c;我是良许。 前几天在直播的时候&#xff0c;问了直播间的小伙伴有没人知道「千年虫」这种神奇的「生物」的&#xff0c;居然没有一人能够答得上来的。 所以&#xff0c;今天就跟大家科普一下这个人类历史上最大的 Bug 。 1. 全世界的恐慌 一个Bug会让人类…

Java中的自动类型提升与强制类型转换

一、自动类型提升 自动类型提升是指在程序运行时因为某种情况需要&#xff0c;JVM将较小的数据类型自动转换为较大的数据类型&#xff0c;以保证精度和正确性。在Java中&#xff0c;需要进行类型提升的情况有以下几种&#xff1a; 1. byte、short和char提升为int类型 当运算…

spark sql(五)sparksql支持查询哪些数据源,查询hive与查询mysql的区别

1、数据源介绍 sparksql默认查询的数据源是hive数据库&#xff0c;除此之外&#xff0c;它还支持其它类型的数据源查询&#xff0c;具体的到源码中看一下&#xff1a; 可以看到sparksql支持查询的数据源有CSV、parquet、json、orc、txt、jdbc。这些数据源中前面五个我还能理解&…

【Python】RPA批量生成word文件/重命名及批量删除

批量生成word文件 场景&#xff1a;需要新建多个类似文件名 比如&#xff1a;今天的事例是新建12个文件名为&#xff1a; ​ 保安员考试试卷1及答案.docx ​ 保安员考试试卷2及答案.docx ​ … ​ 保安员考试试卷12及答案.docx 痛点&#xff1a; ​ 手动操作重复性高&a…

目标检测中回归损失函数(L1Loss,L2Loss,Smooth L1Loss,IOU,GIOU,DIOU,CIOU,EIOU,αIOU ,SIOU)

文章目录L-norm Loss 系列L1 LossL2 LossSmooth L1 LossIOU系列IOU &#xff08;2016&#xff09;GIOU &#xff08;2019&#xff09;DIOU &#xff08;2020&#xff09;CIOU &#xff08;2020&#xff09;EIOU &#xff08;2022&#xff09;αIOU (2021)SIOU &#xff08;2022…

【SpringCloud】SpringCloud详解之Eureka实战

目录前言SpringCloud Eureka 注册中心一.服务提供者和服务消费者二.需求三.搭建Eureka-Server四.搭建Eureka-Client(在服务提供者配置:用户订单)前言 微服务中多个服务&#xff0c;想要调用&#xff0c;怎么找到对应的服务呢&#xff1f; 这里有组件的讲解 → SpringCloud组件…

深圳大学《计算机论题》作业:大数据与人工智能技术对人类生活的影响

说明 本作业为小组作业&#xff0c;要求基于一场报告完成&#xff08;即观后感&#xff09;。共分4个小题&#xff0c;讨论人工智能时代的伦理思考。由于版权原因&#xff0c;不提供报告的具体内容&#xff0c;只展示答题内容。 第一题 &#xff08;1&#xff09; 你如何看待…

winform控件PropertyGrid的应用(使运行中的程序能像vistual studio那样设置控件属性)

上周在看别人写的上位机demo代码时&#xff0c;发现创建的项目模板是"Windows 窗体控件库"(如下图) 生成的项目结构像自定义控件库&#xff0c;没有程序入口方法Main&#xff0c;但却很神奇能调试&#xff0c;最后发现原来Vistual Studio启动了一个外挂程序UserContr…

LSM(日志结构合并树)_笔记

WAL&#xff1a;Write Ahead Log 写前日志&#xff0c;顺序日志文件 1 LSM tree的定义 LSM tree&#xff1a; Log-Structured-Merge-Tree&#xff0c;日志结构合并树。 Log-Structured Merge-tree (LSM-tree) is a disk-based data structure designed to provide low-cost …

Linux操作系统学习(了解文件系统动静态库)

文章目录浅谈文件系统了解EXT系列文件系统目录与inode的关系软硬链接动静态库浅谈文件系统 当我们创建一个文件时由两部分组成&#xff1a;文件内容文件属性&#xff0c;即使是空文件也有文件属性 一个文件没有被打开是存储在磁盘中的&#xff0c;而磁盘是计算机中的一个机械…

你想赚的钱不一定属于你

昨天一个同行跟我说&#xff0c;最近有个五十多万的订单&#xff0c;客户是拿着别人家的设计来找的他&#xff0c;跟了也有大半个月了&#xff0c;自己明明报的价格比原设计的公司要低&#xff0c;客户一直说会尽快下的&#xff0c;他原本想着能够从这个订单里赚到几万块&#…

王道计算机组成原理课代表 - 考研计算机 第六章 总线 究极精华总结笔记

本篇博客是考研期间学习王道课程 传送门 的笔记&#xff0c;以及一整年里对 计算机组成 知识点的理解的总结。希望对新一届的计算机考研人提供帮助&#xff01;&#xff01;&#xff01; 关于对 “总线” 章节知识点总结的十分全面&#xff0c;涵括了《计算机组成原理》课程里的…

软件测试用例(3)

按照测试对象划分: 一)界面测试: 1)软件只是一种工具&#xff0c;软件和人的信息交流是通过界面来进行的&#xff0c;界面是软件和用户交流的最直接的一层&#xff0c;界面的设计决定了用户对于我们设计软件的第一映像&#xff0c;界面如同人的面孔&#xff0c;具有最吸引用户的…