Flutter 05 组件状态、生命周期、数据传递(共享)、Key

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

二、Widget组件生命周期详解

1、Widget组件生命周期

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

createState:

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

initState:

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

didChangeDependencies:

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

build:

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

setState:

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

didUpdateWidget:

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

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

deactivate:

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

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

dispose:

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

2、生命周期调用

A Page:
import 'package:flutter/material.dart';
import 'package:flutter_app/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({super.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 key"),
      ),
      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() {
    // TODO: implement initState
    super.initState();
    print("------main--------initState");
  }

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

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

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

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

import 'package:flutter/material.dart';


class DemoPage extends StatelessWidget {
  const DemoPage({super.key}) ;

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

class DemoWidget extends StatefulWidget {
  const DemoWidget({super.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() {
    // TODO: implement initState
    super.initState();
    print("------_DemoWidgetState--------initState");
  }

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

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

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

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

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

公共页面:

import 'package:flutter/material.dart';
import 'transfer_constructor_page.dart';
import 'transfer_data_entity.dart';
import 'transfer_data_inherited.dart';
import 'transfer_data_singleton.dart';
import 'transfer_router_page.dart';
import 'transfer_singleton_page.dart';
import 'transfer_stream_singleton.dart';
import 'transfer_stream_singleton_page.dart';

import 'inherited_data_provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  var params = TransferDataEntity(name:"王五", id:"009", age:16);

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

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  var data = TransferDataEntity(name:"张三丰", id:"001", age:18);
  List<String> itemNames = [
    "构造器传递数据",
    "返回上个页面时携带参数",
    "InheritedWidget方式",
    "Singleton方式",
    "Singleton结合Stream",
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        children: <Widget>[
          Container(
              alignment: Alignment.center,
              height: 60.0,
              color: Colors.black12,
              child: Text("${data.id},${data.name},${data.age}")),
          Expanded(
            child: ListView.separated(
              separatorBuilder: (BuildContext contex, int index) {
                return const Divider(
                  color: Colors.black12,
                  height: 0.5,
                );
              },
              itemBuilder: (BuildContext context, int index) {
                return InkWell(
                  onTap: () => _onClick(index, data, context),
                  child: Container(
                    alignment: Alignment.center,
                    height: 48.0,
                    width: double.infinity,
                    child:  Text(
                      itemNames[index],
                      style: const TextStyle(
                        color: Colors.black,
                        fontWeight: FontWeight.bold,
                        fontSize: 14.0,
                      ),
                    ),
                  ),
                );
              },
              itemCount: itemNames.length,
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  _onClick(int index, TransferDataEntity data, BuildContext context) {
    switch (index) {
      case 0:
        _transferDataByConstructor(context, data);
        break;
      case 1:
        _toTransferForResult(context, data);
        break;

      case 2:
        _inheritedToPage(context, data);
        break;
      case 3:
        _singletonDataTransfer(context);
        break;
      case 4:
        _streamDataTransfer(context);
        break;

    }
  }

  //通过构造器方法传递数据
  _transferDataByConstructor(BuildContext context, TransferDataEntity data) {
    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => DataTransferByConstructorPage(data: data)));
  }

  _toTransferForResult(BuildContext context, TransferDataEntity data) async {
    final dataFromOtherPage = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => TransferRouterPage(data: data)),
    ) as TransferDataEntity;

   setState(() {
     data.name = dataFromOtherPage.name;
     data.id = dataFromOtherPage.id;
     data.age = dataFromOtherPage.age;
   });
  }

  _inheritedToPage(BuildContext context, TransferDataEntity data) {
    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => IDataProvider(
                  data: data,
                  child: IDataWidget(),
                )));
  }


  _singletonDataTransfer(BuildContext context) {
    var transferData = TransferDataEntity(name:"三喵2", id:"002", age:20);
    transSingletonData.transData = transferData;
    Navigator.push(context,
        MaterialPageRoute(builder: (context) => TransferSingletonPage()));
  }


  _streamDataTransfer(BuildContext context) {
    var transferData = TransferDataEntity(name:"三喵", id:"005", age:20);
    streamSingletonData.setTransferData(transferData);
    Navigator.push(context,
        MaterialPageRoute(builder: (context) => TransferStreamPage()));
  }
}

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

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

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

2)定义一个跳转到DataTransferByConstructorPage页面的方法:

3)在DataTransferByConstructorPage页面接收到数据并展示出来:

import 'package:flutter/material.dart';
import 'transfer_data_entity.dart';

///通过构造器的方式传递参数
class DataTransferByConstructorPage extends StatelessWidget {
  final TransferDataEntity data;

  const DataTransferByConstructorPage({super.key, required 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}"),
          )
        ],
      ),
    );
  }
}

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

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

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

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

import 'package:flutter/material.dart';
import 'transfer_data_entity.dart';

class TransferRouterPage extends StatelessWidget {
  final TransferDataEntity data;

  const TransferRouterPage({super.key, required this.data});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("返回上个页面传递参数"),
        leading: Builder(builder: (BuildContext context) {
          return IconButton(
              icon: const Icon(Icons.arrow_back_ios),
              onPressed: () {
                _backToData(context);
              });
        }),
      ),
      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}"),
          ),
          ElevatedButton(
              onPressed: () {
                _backToData(context);
              },
              child: const Text("点我返回上一个页面并把数据传回去"))
        ],
      ),
    );
  }

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

3、InheritedWidget方式 

使用lnheritedWidget方式如下几步:

1)继承lnheritedWidget提供一个数据源:

import 'package:flutter/material.dart';
import 'transfer_data_entity.dart';

//所有的子组件共享数据
class IDataProvider extends InheritedWidget {

  final TransferDataEntity data;

  const IDataProvider({super.key, required super.child, required this.data});


  @override
  bool updateShouldNotify(IDataProvider oldWidget) {
    return data != oldWidget.data;
  }

  //版本问题
  //InheritedWidget
  static IDataProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<IDataProvider>();
  }

// static _InheritedProviders of(BuildContext context) {
//   final widget = context.inheritFromWidgetOfExactType(_InheritedProviders);
//   return widget is _InheritedProviders ? widget : null;
// }

}

2)定义页面跳转时候携带数据的方法:

3)跳转的到的页面并展示数据代码如下:

import 'package:flutter/material.dart';

import 'inherited_data_provider.dart';

class IDataWidget extends StatelessWidget {
  const IDataWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final data = IDataProvider.of(context)!.data;

    return Scaffold(
      appBar: AppBar(
        title: const Text("Inherited方式传递数据"),
      ),
      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}"),
          ),
          const IDataChildWidget()
        ],
      ),
    );
  }
}

class IDataChildWidget extends StatelessWidget {
  const IDataChildWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final data = IDataProvider.of(context)!.data;
    return Text(data.name);
  }
}

4、全局的提供数据的方式

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

1)定义顶部数据:

2)接收数据的方式基本和 lnheritedWidget相同:

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

3)使用代码:

5、通过全局单例模式来使用 

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

1)创建单例对象:

import 'transfer_data_entity.dart';

class TransferDataSingleton {
  static final TransferDataSingleton _instanceTransfer =
      TransferDataSingleton.__internal();

  TransferDataEntity? transData;

  factory TransferDataSingleton() {
    return _instanceTransfer;
  }

  TransferDataSingleton.__internal();
}

final transSingletonData = TransferDataSingleton();

 2)给单例对象存放数据:

3)接收并使用传递的值:

import 'package:flutter/material.dart';
import 'transfer_data_singleton.dart';

class TransferSingletonPage extends StatefulWidget {
  const TransferSingletonPage({super.key});

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

class _TransferSingletonPageState extends State<TransferSingletonPage> {
  @override
  Widget build(BuildContext context) {
    var data = transSingletonData.transData;
    return Scaffold(
      appBar: AppBar(
        title: const 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}"),
          ),
        ],
      ),
    );
  }
}

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

1)单例模式:

import 'dart:async';

import 'transfer_data_entity.dart';

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();

2)传递要携带的数据:

3)接收要传递的值:

import 'dart:async';

import 'package:flutter/material.dart';
import 'transfer_data_entity.dart';
import 'transfer_stream_singleton.dart';

class TransferStreamPage extends StatefulWidget {
  const TransferStreamPage({super.key});

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("全局单例结合Stream"),
        ),
        body: StreamBuilder(
            stream: _streamController!.stream,
            initialData: TransferDataEntity(),
            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();
  }
}

四、Flutter Key

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

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

1、没有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没法通过Box里面传入的参数是代码和以前是一样的,所以Flutter不会重构List Widget里面的内容,也就是来识别Box是否改变。如果要让FLutter能识别到List Widget子元素的改变,就需要给每个Box指定一个key。 

实现代码:

import 'package:flutter/material.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 MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.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({super.key, required 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"),
        ),
      ),
    );
  }
}

2、LocalKey与GlobalKey

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

 

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

import 'package:flutter/material.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 MyHomePage(),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Center(
      key: UniqueKey(),
      child: Box(
        color: Colors.blue,
      ),
    ),
    // Box(
    //   // key: const ValueKey("1"),//指定值
    //   color: Colors.blue,
    // ),
    Box(
      key: UniqueKey(), //唯一值, 自动生成
      color: Colors.red,
    ),
    Box(
      key: const ObjectKey("2"), //类似于ValueKey
      // key: const ObjectKey(2),//类似于ValueKey
      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({super.key, required this.color});

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

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

  _BoxState() {
    print("--------_BoxState----------构造");
  }

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

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

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

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

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

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

3、GlobalKey的使用

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

下面使用了LocalKey,当屏幕状态改变的时候把Colum换成了Row,Box的状态就会丢失。一个Widget状态的保存主要是通过判断组件的类型或者key值是否一致。 LocalKey只在当前的组件树有效,所以把Colum换成了Row的时候Widget的状态就丢失了。

import 'package:flutter/material.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 MyHomePage(),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  List<Widget> list = [
    Box(
      key: const ValueKey(1), //int double string
      color: Colors.blue,
    ),
    Box(
      key: ObjectKey(Object()),
      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: 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({super.key, required 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"),
        ),
      ),
    );
  }
}

为了解决这个问题我们就可以使用GlobalKey。

import 'package:flutter/material.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 MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.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({super.key, required 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"),
        ),
      ),
    );
  }
}

4、GlobalKey 获取子组件

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

import 'package:flutter/material.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 HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.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({super.key, required this.color});

  @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.mfbz.cn/a/112866.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QT实现在线流媒体播放平台

文章目录 QT实现在线流媒体播放平台简介开发视频ffmpeg下载SimpleVideoPlayer.hSimpleVideoPlayer.cpp 开发音频添加功能打开文件夹播放暂停播放上下一首选择倍速 效果展示项目下载 QT实现在线流媒体播放平台 简介 Qt是一种流行的C开发框架&#xff0c;它提供了用于构建图形用…

FPGA_Signal TapII 逻辑分析仪 在线信号波形抓取

FPGA_Signal TapII 逻辑分析仪 在线信号波形抓取 由于一些工程的仿真文件不易产生&#xff0c;所以我们可以利用 quartus 软件自带的 SignalTap 工具对波形进行抓取 对各个信号进行分析处理&#xff0c;让电子器件与FPGA进行正常通讯工作&#xff0c;也验证所绘制的波形图是否一…

Scala库用HTTP爬虫IP代码示例

根据提供的引用内容&#xff0c;sttp.client3和sttp.model库是用于HTTP请求和响应处理的Scala库&#xff0c;可以与各种Scala堆栈集成&#xff0c;提供同步和异步&#xff0c;过程和功能接口。这些库可以用于爬虫程序中&#xff0c;用于发送HTTP请求和处理响应。需要注意的是&a…

JavaScript从入门到精通系列第二十七篇:详解JavaScript中的包装类

大神引荐&#xff1a;作者有幸结识技术大神孙哥为好友获益匪浅&#xff0c;现在把孙哥视频分享给大家 孙哥链接&#xff1a;孙哥个人主页 作者简介&#xff1a;一个颜值99分&#xff0c;只比孙哥差一点的程序员 本专栏简介&#xff1a;话不多说&#xff0c;让我们一起干翻JavaS…

vue自定义组件:中线分割拖动盘

在GitHub上可以找到类似的组件&#xff0c;比如4年前发布的vue2版本的 Vue Split Pane&#xff0c; 但是我还是自己写了一个类似的&#xff1a; 组件效果&#xff1a; 特点&#xff1a; 不是照抄别人的。同时支持vue2、vue3&#xff08;组件内部使用选项式API风格&#xff09…

不一样的网络协议-------KCP协议

1、kcp 的协议特点 1.1、RTO 不翻倍 RTO(Retransmission TimeOut)&#xff0c;重传超时时间。tcp x 2&#xff0c;kcp x 1.5&#xff0c;提高传输速度 1.2、选择重传 TCP丢包时会全部重传从该包开始以后的数据&#xff0c;而KCP选择性重传&#xff0c;只重传真正丢失的数据包…

同为科技(TOWE)自动断电倒计时定时桌面PDU插排

在每个家庭中&#xff0c;插排插座都是必不可少的电源设备。随着各种电器的普及应用和生活节奏的加快&#xff0c;人们对插排也有着多样化的需求&#xff0c;比如在插排中加入定时开关、自动断电、断电记忆、倒计时等等功能&#xff0c;让原本不支持智能家居的用电器秒变智能。…

DL4J无法下载MNIST数据集解决 Server returned HTTP response code: 403 for URL解决方法

报错情况 报错如下&#xff1a; 16:45:41.463 [main] INFO org.nd4j.nativeblas.Nd4jBlas - Number of threads used for OpenMP BLAS: 6 16:45:41.497 [main] INFO org.nd4j.linalg.api.ops.executioner.DefaultOpExecutioner - Backend used: [CPU]; OS: [Windows 10] 16:4…

Android 驱动学习调试

1 Android 驱动代码编译 参考https://www.sharetechnote.com/html/Linux_DeviceDriver_Programing.html#Device_Driver_HelloWorld编译ko文件调试驱动代码&#xff0c;将ko文件push到手机上验证 相关C文件testdriver.c #include <linux/init.h> #include <linux/mod…

云安全—kubelet攻击面

0x00 前言 虽然说总结的是kubelet的攻击面&#xff0c;但是在总结攻击面之前还是需要去了解kubelet的基本原理&#xff0c;虽然说我们浅尝即止&#xff0c;但是还是要以能给别人讲出来为基本原则。 其他文章: 云安全—K8s APi Server 6443 攻击面云安全—K8S API Server 未授…

08-Docker-网络管理

Docker 在网络管理这块提供了多种的网络选择方式&#xff0c;他们分别是桥接网络、主机网络、覆盖网络、MACLAN 网络、无桥接网络、自定义网络。 1-无桥接网络&#xff08;None Network&#xff09; 当使用无桥接网络时&#xff0c;容器不会分配 IP 地址&#xff0c;也不会连…

SpringCloud 微服务全栈体系(十)

第十章 RabbitMQ 一、初识 MQ 1. 同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得…

Sci Immunol丨Tim-3 适配器蛋白 Bat3 是耐受性树突状细胞

今天和大家分享一篇发表于2022年3月的文章&#xff0c;题目为“Tim-3 adapter protein Bat3 acts as an endogenous regulator of tolerogenic dendritic cell function”&#xff0c;发表在《Sci Immunol》杂志上。文章主要研究了Tim-3和其适配蛋白Bat3在调节免疫应答中的作用…

以八数码问题为例实现A*算法的求解(未完结)

八数码&#xff1a; 在一个 33 的网格中&#xff0c;1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 33 的网格中。 例如&#xff1a; 1 2 3 x 4 6 7 5 8在游戏过程中&#xff0c;可以把 x 与其上、下、左、右四个方向之一的数字交换&#xff08;如果存在&#xff09;。…

ssh远程登录服务

目录 1.1版本协商阶段 1.2密钥和算法协商阶段 1.3认证阶段(两种认证方法): 2.1.安装ssh 2.2.配置文件分析: 3.1配置ssh监听端口号 3.2拒绝以root身份登录服务器 3.3虚拟机之间实现免密登录 3.4xshell免密登录 SSH (Secure Shell Protocol,安全壳程序协议)由IETF的网络…

厦门万宾科技智能井盖监测仪器的作用如何?

越来越多的人们希望改善生活&#xff0c;走出农村走出大山&#xff0c;前往城市之中居住。由此城市的人口和车辆在不断增加&#xff0c;与之而来的是城市的交通压力越来越大&#xff0c;时常会出现道路安全隐患&#xff0c;这给城市未来发展和智慧城市建设都带来一定的难题&…

电脑开机显示器不亮?正确操作分享(4个方法)!

“我的电脑最近不知道为什么&#xff0c;每次开机后显示器都不亮&#xff0c;重启也没反应&#xff0c;有什么方法可以解决该问题吗&#xff1f;快帮帮我&#xff01;” 随着电脑在我们日常生活和工作中的广泛应用&#xff0c;出现问题时需要及时解决。在众多的电脑问题中&…

家政保洁团队服务预约小程序的效果如何

家政保洁可以是大公司也可以是小团队&#xff0c;但无论什么规格&#xff0c;其市场需求都是稳中有增&#xff0c;随着人们生活品质提升&#xff0c;其对居住环境/办公环境等都有一定要求&#xff0c;这意味着家政团队可以拓展同城乃至外地多个领域的生意。 但线下信息单一&am…

【Linux】第九站:make和makefile

文章目录 一、 Linux项目自动化构建工具make/Makefile1.make/makefile工作现象2.依赖关系与依赖方法3.如何清理4.为什么这里我们需要带上clean5.连续的make6.特殊符号 二、Linux下实现一个简单的进度条1.回车换行2.缓冲区3.倒计时的实现 一、 Linux项目自动化构建工具make/Make…

Mybatis—XML配置文件、动态SQL

学习完Mybatis的基本操作之后&#xff0c;继续学习Mybatis—XML配置文件、动态SQL。 目录 Mybatis的XML配置文件XML配置文件规范XML配置文件实现MybatisX的使用 Mybatis动态SQL动态SQL-if条件查询 \<if\>与\<where\>更新员工 \<set\>小结 动态SQL-\<forea…
最新文章