跳至主要內容

Flutter PageView

xiaoye大约 13 分钟Flutter PageViewFlutterFlutter PageView

官网地址:选择你的开发平台,开始使用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutteropen in new window

文档地址:第二版序 | 《Flutter实战·第二版》 (flutterchina.club)open in new window

Flutter PageView

Flutter中的轮动图以及抖音上下滑页切换视频功能等等,这些都可以通过 PageView 轻松实现

PageView常见属性:

属性描述
scrollDirectionAxis.horizonta水平方向 Axis.vertical锤直方向
children配置子元素
allowImplicitScrolling缓存当前页面的前后两页
onPageChangedpage改变的时候触发

1、PageView

import 'package:flutter/material.dart';

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

  
  State<pageViewPage> createState() => _pageViewPageState();
}

class _pageViewPageState extends State<pageViewPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('pageView')),
      //PageView中children下的每一个组件都是一个页面,默认水平防线
      body: PageView(
          scrollDirection: Axis.vertical, //设置为垂直方向滑动
          children: [
            Center(
              child: Text(
                '第一屏',
                style: Theme.of(context).textTheme.headlineLarge,
              ),
            ),
            Center(
              child: Text(
                '第二屏',
                style: Theme.of(context).textTheme.headlineLarge,
              ),
            ),
            Center(
              child: Text(
                '第三屏',
                style: Theme.of(context).textTheme.headlineLarge,
              ),
            )
          ]),
    );
  }
}

2、pageViewBuilder

import 'package:flutter/material.dart';

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

  
  State<pageViewBuilder> createState() => _pageViewBuilderState();
}

class _pageViewBuilderState extends State<pageViewBuilder> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('pageViewBuilder')),
      body: PageView.builder(
          scrollDirection: Axis.vertical, //设置方向
          itemCount: 10, //总数10页
          onPageChanged: (value) {
            print(value);
          },
          itemBuilder: (context, index) {
            return Center(
              child: Text(
                '第${index + 1}屏',
                style: Theme.of(context).textTheme.headlineLarge,
              ),
            );
          }),
    );
  }
}

3、PageView上拉无限加载的实现思路

import 'package:flutter/material.dart';

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

  
  State<pageViewInfiniteLoad> createState() => _pageViewInfiniteLoadState();
}

class _pageViewInfiniteLoadState extends State<pageViewInfiniteLoad> {
  // 初始化数据
  List<Widget> list = [];
  
  void initState() {
    // TODO: implement initState
    super.initState();
    //循环添加10条
    for (var i = 0; i < 10; i++) {
      list.add(Center(
        child: Text(
          "第${i + 1}屏",
          style: const TextStyle(fontSize: 60),
        ),
      ));
    }
  }

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('pageView无限加载')),
      body: PageView(
        scrollDirection: Axis.vertical,
        onPageChanged: (index) {
          //监听index是索引,如果到最后一个了  立马再给list.add一个数据
          if (index == list.length - 1) {
            list.add(Center(
              child: Text(
                "第${list.length + 1}屏",
                style: const TextStyle(fontSize: 60),
              ),
            ));
          }
        },
        children: list,
      ),
    );
  }
}

4、PageView实现轮播图功能

import 'package:flutter/material.dart';

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

  
  State<pageViewSwiper> createState() => _pageViewSwiperState();
}

class _pageViewSwiperState extends State<pageViewSwiper> {
  List<Widget> list = [];
  int _currentInbdex = 0;
  
  void initState() {
    // TODO: implement initState
    super.initState();
    // 添加三个轮播
    for (var i = 0; i < 3; i++) {
      list.add(ImagePage(
        src: 'https://picsum.photos/500/200?id=$i',
      ));
    }
  }

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('pageView实现轮播图')),
      body: Stack(
        children: [
          SizedBox(
            height: 200,
            child: PageView.builder(
                onPageChanged: ((index) {
                  setState(() {
                    //当前索引_currentInbdex=index % list.length;
                    _currentInbdex = index % list.length;
                  });
                }),
                itemCount: 10000,
                itemBuilder: ((context, index) {
                  //返回list的第index % list.length个元素
                  return list[index % list.length];
                })),
          ),
          // 轮播图的点
          Positioned(
              left: 0, //设置left 0  和right0 就会占满整行
              right: 0,
              bottom: 2,
              child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: List.generate(list.length, (index) {
                    return Container(
                      margin: const EdgeInsets.all(5),
                      width: 10,
                      height: 10,
                      decoration: BoxDecoration(
                        color:
                            _currentInbdex == index ? Colors.blue : Colors.grey,
                        shape: BoxShape.circle, //圆
                        // borderRadius: BorderRadius.circular(5)
                      ),
                    );
                  }).toList()))
        ],
      ),
    );
  }
}

// image图片组件
class ImagePage extends StatefulWidget {
  final double width;
  final double height;
  final String src;
  const ImagePage(
      {super.key,
      this.width = double.infinity,
      this.height = 200,
      required this.src});

  
  State<ImagePage> createState() => _ImagePageState();
}

class _ImagePageState extends State<ImagePage> {
  
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.width,
      height: widget.height,
      child: Image.network(
        widget.src,
        fit: BoxFit.cover,
      ),
    );
  }
}

5、轮播图代码抽离+定时器控制轮播自动切换

5.1、widget/swiper.dart

import 'dart:async';

import 'package:flutter/material.dart';

class Swiper extends StatefulWidget {
  final double width;
  final double height;
  final List<String> list; //图片列表
  const Swiper(
      {super.key,
      this.height = 200,
      this.width = double.infinity,
      required this.list});

  
  State<Swiper> createState() => _SwiperState();
}

class _SwiperState extends State<Swiper> {
  int _currentInbdex = 0;
  List<Widget> pageList = [];
  late PageController _pageController;
  late Timer timer;
  
  void initState() {
    // TODO: implement initState
    super.initState();
    // 添加轮播图片数据
    for (var i = 0; i < widget.list.length; i++) {
      pageList.add(ImagePage(
        src: widget.list[i],
        width: widget.width,
        height: widget.height,
      ));
    }
    // PageController当前激活的轮播
    _pageController = PageController(initialPage: 0);
    //每隔3秒
    timer = Timer.periodic(Duration(seconds: 3), (t) {
      _pageController.animateToPage((_currentInbdex + 1) % pageList.length,
          duration: Duration(milliseconds: 200), curve: Curves.linear);
    });
  }

  // 页面销毁时
  
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    timer.cancel(); //销毁定时器
    _pageController.dispose(); //销毁控制器
  }

  
  Widget build(BuildContext context) {
    return Stack(
      children: [
        SizedBox(
          height: 200,
          child: PageView.builder(
              controller: _pageController, //控制第几个
              onPageChanged: ((index) {
                setState(() {
                  //当前索引_currentInbdex=index % list.length;
                  _currentInbdex = index % widget.list.length;
                });
              }),
              itemCount: 10000,
              itemBuilder: ((context, index) {
                //返回list的第index % list.length个元素
                return ImagePage(
                  src: widget.list[index % widget.list.length],
                );
              })),
        ),
        // 轮播图的点
        Positioned(
            left: 0, //设置left 0  和right0 就会占满整行
            right: 0,
            bottom: 2,
            child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: List.generate(widget.list.length, (index) {
                  return Container(
                    margin: const EdgeInsets.all(5),
                    width: 10,
                    height: 10,
                    decoration: BoxDecoration(
                      color:
                          _currentInbdex == index ? Colors.blue : Colors.grey,
                      shape: BoxShape.circle, //圆
                      // borderRadius: BorderRadius.circular(5)
                    ),
                  );
                }).toList()))
      ],
    );
  }
}

// image图片组件
class ImagePage extends StatefulWidget {
  final double width;
  final double height;
  final String src;
  const ImagePage(
      {super.key,
      this.width = double.infinity,
      this.height = 200,
      required this.src});

  
  State<ImagePage> createState() => _ImagePageState();
}

class _ImagePageState extends State<ImagePage> {
  
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.width,
      height: widget.height,
      child: Image.network(
        widget.src,
        fit: BoxFit.cover,
      ),
    );
  }
}

5.2、pageViewSwiper.dart

import 'dart:async';

import 'package:flutter/material.dart';
import './widget/Swiper.dart'; //swiper组件

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

  
  State<pageViewSwiper> createState() => _pageViewSwiperState();
}

class _pageViewSwiperState extends State<pageViewSwiper> {
  List<String> list = [];
  
  void initState() {
    // TODO: implement initState
    super.initState();
    list = const [
      'https://picsum.photos/500/200?id=1',
      'https://picsum.photos/500/200?id=2',
      'https://picsum.photos/500/200?id=3'
    ];
  }

  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('pageView实现轮播图')),
        body: ListView(
          children: [
            Swiper(
              list: list,
            )
          ],
        ));
  }
}

6、AutomaticKeepAliveClientMixin 缓存PageView页面

通过上面的例子我们会发现 每次滑动的时候都会触发子组件中的 build方法 print(widget.url);

可见 PageView 默认并没有缓存功能,一旦页面滑出屏幕它就会被销毁 ,实际项目开发中对页面进行缓

存是很常见的一个需求,下面我们就看看如何使用AutomaticKeepAliveClientMixin 缓存页面。

**注意:**使用时一定要注意是否必要,因为对所有列表项都缓存的会导致更多的内存消耗。

只需要两步:

6.1、添加with AutomaticKeepAliveClientMixin

class _MyContinerState extends State<MyContiner> with AutomaticKeepAliveClientMixin{
    ...
}

6.2、添加 bool get wantKeepAlive => true;(可按照提示生成)

  
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;

6.3、完整代码

import 'package:flutter/material.dart';

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

  
  State<pageViewKeepAlive> createState() => _pageViewKeepAliveState();
}

class _pageViewKeepAliveState extends State<pageViewKeepAlive> {
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('PageViewKeepAlive')),
        body: PageView.builder(
          scrollDirection: Axis.vertical,
          itemCount: 10,
          itemBuilder: ((context, index) {
            return MyContiner(
              num: index,
            );
          }),
        ));
  }
}

// 自定义组件
class MyContiner extends StatefulWidget {
  final int num;
  MyContiner({super.key, required this.num});

  
  State<MyContiner> createState() => _MyContinerState();
}

class _MyContinerState extends State<MyContiner>
    with AutomaticKeepAliveClientMixin {
  
  Widget build(BuildContext context) {
    print(widget.num); //默认数据煤油缓存,每次滑动都会执行build
    //
    return Center(
      child: Text(
        '第${widget.num + 1}屏',
        style: Theme.of(context).textTheme.headlineSmall,
      ),
    );
  }

  
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true; //返回true表示缓存页面
}

7、自定义KeepAliveWrapper 缓存页面

AutomaticKeepAliveClientMixin 可以快速的实现页面缓存功能,但是通过混入的方式实现不是很优

雅, 所以我们有必要对AutomaticKeepAliveClientMixin 混入进行封装

注意:如果页面比较多 缓存会耗费内存

7.1、添加tool/KeepAliveWrapper.dart

import 'package:flutter/material.dart';

// 缓存组件只需要引入传入要缓存的child
// 注意:如果页面比较多  缓存会耗费内存
class KeepAliveWrapper extends StatefulWidget {
  const KeepAliveWrapper(
      {Key? key,  this.child, this.keepAlive = true})
      : super(key: key);
  final Widget? child;
  final bool keepAlive;
  
  State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}

class _KeepAliveWrapperState extends State<KeepAliveWrapper>
    with AutomaticKeepAliveClientMixin {
  
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child!;
  }

  
  bool get wantKeepAlive => widget.keepAlive;
  
  void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
    if (oldWidget.keepAlive != widget.keepAlive) {
// keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中
      updateKeepAlive();
    }
    super.didUpdateWidget(oldWidget);
  }
}

7.2、使用缓存组件

import 'package:flutter/material.dart';
import 'package:bangda/toos/KeepAliveWrapper.dart'; //引入封装好的设置缓存组件

// 缓存组件的混入实例
class pageViewKeepAlive extends StatefulWidget {
  const pageViewKeepAlive({super.key});

  
  State<pageViewKeepAlive> createState() => _pageViewKeepAliveState();
}

class _pageViewKeepAliveState extends State<pageViewKeepAlive> {
  
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('PageViewKeepAlive')),
        body: PageView.builder(
          scrollDirection: Axis.vertical,
          itemCount: 10,
          itemBuilder: ((context, index) {
            return KeepAliveWrapper(
                child: MyContiner(
              num: index,
            ));
          }),
        ));
  }
}

// 自定义组件
class MyContiner extends StatefulWidget {
  final int num;
  MyContiner({super.key, required this.num});

  
  State<MyContiner> createState() => _MyContinerState();
}

class _MyContinerState extends State<MyContiner>
    with AutomaticKeepAliveClientMixin {
  
  Widget build(BuildContext context) {
    super.build(context);
    print(widget.num); //默认数据煤油缓存,每次滑动都会执行build
    //
    return Center(
      child: Text(
        '第${widget.num + 1}屏',
        style: Theme.of(context).textTheme.headlineSmall,
      ),
    );
  }

  
  // TODO: implement wantKeepAlive
  bool get wantKeepAlive => true; //返回true表示缓存页面
}

8、flutter的key

我们平时一定接触过很多的 Widget,比如 Container、Row、Column 等,它们在我们绘制界面的过程

中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同

的参数,它们通常在参数列表的第一个,那就是 Key。

在Flutter中,Key****是不能重复使用的,所以Key一般用来做唯一标识。组件在更新的时候,其状态的保

存主要是通过判断组件的类型或者key****值是否一致。因此,当各组件的类型不同的时候,类型已经足够

用来区分不同的组件了,此时我们可以不必使用key。但是如果同时存在多个同一类型的控件的时候,

此时类型已经无法作为区分的条件了,我们就需要使用到key。

8.1、没有 Key会发生什么奇怪现象?

如下面例: 定义了一个StatefulWidget的Box,点击Box的时候可以改变Box里面的数字,当我们重新

对Box排序的时候Flutter就无法识别到Box的变化了, 这是什么原因呢?

案例代码

import 'package:flutter/material.dart';

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

  
  State<FlutterKeyBoxPage> createState() => _FlutterKeyBoxPageState();
}

class _FlutterKeyBoxPageState extends State<FlutterKeyBoxPage> {
  List<Widget> list = [
    const Box(
      color: Colors.red,
    ),
    const Box(
      color: Colors.green,
    ),
    const Box(
      color: Colors.blue,
    ),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FlutterKeyBoxPage'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
      floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              list.shuffle(); //随机排列
            });
          },
          child: const Icon(Icons.refresh)),
    );
  }
}

// box组件
class Box extends StatefulWidget {
  final Color color;
  // 传递每个组件key
  const Box({super.key, required this.color});

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

class _BoxState extends State<Box> {
  int _count = 0;
  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 100,
      height: 100,
      child: ElevatedButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(widget.color)),
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text(
            '$_count',
            style: Theme.of(context).textTheme.headlineLarge,
          )),
    );
  }
}

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

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

List<Widget> list = [
    Box(
    color: Colors.blue,
    ),
    Box(
    color: Colors.blue,
    ),
    Box(
    color: Colors.blue,
    )
];

8.2、LocalKey、GlobalKey

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

Flutter key子类包含 LocalKey 和 GlobalKey 。

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

  1. ValueKey (值key)把一个值作为key
  2. UniqueKey(唯一key)程序生成唯一的Key
  3. ObjectKey(对象key)把一个对象实例作为key

改造上方代码

List<Widget> list = [
    const Box(
      color: Colors.red,
      key: ValueKey('red'), //指定唯一值   用得最多
    ),
    Box(
      color: Colors.green,
      key: UniqueKey(), //唯一值  运行时随机生成  如果不知道传什么就用UniqueKey
    ),
    const Box(
      color: Colors.blue,
      key: ObjectKey(Box(color: Colors.blue)), //对象key传一个对象
    ),
 ];


//box组件传递key  具体结合上方代码更改
// box组件
class Box extends StatefulWidget {
  final Color color;
  // 传递每个组件key
  const Box({Key? key, required this.color}) : super(key: key);

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

8.2.2、全局键(GlobalKey): GlobalKey、GlobalObjectKey

案例:监听屏幕旋转事件
//获取屏幕的选择方向
    print(MediaQuery.of(context).orientation);

获取到横屏打印:Orientation.portrait

获取到竖屏打印:Orientation.landscape

其余代码参照8、flutter的key

//...其余代码

  Widget build(BuildContext context) {
    //获取屏幕的选择方向
    print(MediaQuery.of(context).orientation);
    return Scaffold(
      appBar: AppBar(
        title: const Text('FlutterKeyBoxPage'),
      ),
      body: Center(
        // 横屏使用Column组件,竖屏使用Row组件
        child: MediaQuery.of(context).orientation == Orientation.portrait
            ? Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              )
            : Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              ),
      ),
      floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              list.shuffle(); //随机排列
            });
          },
          child: const Icon(Icons.refresh)),
    );
  }
//...其余代码

分析问题:

​ 点击每个盒子,添加给_count添加状态,然后旋转后从column变成row 后类型变了 因为没有指定key 所以重置了,所以需要使用全局key

使用全局key改造后

import 'package:flutter/material.dart';

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

  
  State<FlutterKeyBoxPage> createState() => _FlutterKeyBoxPageState();
}

class _FlutterKeyBoxPageState extends State<FlutterKeyBoxPage> {
  List<Widget> list = [];
  // 定义全局key
  final GlobalKey _globalKey1 = GlobalKey();
  final GlobalKey _globalKey2 = GlobalKey();
  final GlobalKey _globalKey3 = GlobalKey();
  
  void initState() {
    super.initState();
    list = [
      Box(
        color: Colors.red,
        key: _globalKey1,
      ),
      Box(
        color: Colors.green,
        key: _globalKey2,
      ),
      Box(
        color: Colors.blue,
        key: _globalKey3,
      ),
    ];
  }

  Widget build(BuildContext context) {
    //获取屏幕的选择方向
    print(MediaQuery.of(context).orientation);
    return Scaffold(
      appBar: AppBar(
        title: const Text('FlutterKeyBoxPage'),
      ),
      body: Center(
        // 横屏使用Column组件,竖屏使用Row组件
        child: MediaQuery.of(context).orientation == Orientation.portrait
            ? Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              )
            : Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              ),
      ),
      floatingActionButton: FloatingActionButton(
          onPressed: () {
            setState(() {
              list.shuffle(); //随机排列
            });
          },
          child: const Icon(Icons.refresh)),
    );
  }
}

// box组件
class Box extends StatefulWidget {
  final Color color;
  // 传递每个组件key
  const Box({Key? key, required this.color}) : super(key: key);

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

class _BoxState extends State<Box> {
  int _count = 0;
  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 100,
      height: 100,
      child: ElevatedButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(widget.color)),
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text(
            '$_count',
            style: Theme.of(context).textTheme.headlineLarge,
          )),
    );
  }
}

因为在box中指定了全局key,所以在box中的column变成row的时候,因为box的key没有改变,所以不会重置数据

8.3、GlobalKey 获取子组件

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

既然这样 那么父亲传子 和子传父都很容易实现...

import 'package:flutter/material.dart';

// 父组件获取子组件  (父组件)
class FlutterGlobalKeyGetChildPage extends StatefulWidget {
  const FlutterGlobalKeyGetChildPage({super.key});

  
  State<FlutterGlobalKeyGetChildPage> createState() =>
      _FlutterGlobalKeyGetChildPageState();
}

class _FlutterGlobalKeyGetChildPageState
    extends State<FlutterGlobalKeyGetChildPage> {
  // 定义全局key
  final GlobalKey _globalKey1 = GlobalKey();
  
  Widget build(BuildContext context) {
    return Scaffold(
      // 父组件按钮改变子组件的值
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          // 通过子组件的_globalKey1来获取的属性,注意要添加as 类型推导
          var boxState = _globalKey1.currentState as _BoxState;
          // 这样就可以在父组件来改变子组件的值
          setState(() {
            boxState._count++;
          });
          // 同样也能调用方法
          boxState.run();
        },
      ),
      appBar: AppBar(
        title: const Text('GlobalKey获取子组件'),
      ),
      body: Center(
        child: Center(
            child: Box(
          color: Colors.red,
          key: _globalKey1,
        )),
      ),
    );
  }
}

// box组件(子组件)
class Box extends StatefulWidget {
  final Color color;
  // 传递每个组件key
  const Box({Key? key, required this.color}) : super(key: key);
  
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  void run() {
    print('run方法执行:当前_count:$_count');
  }

  
  Widget build(BuildContext context) {
    return SizedBox(
      width: 100,
      height: 100,
      child: ElevatedButton(
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(widget.color)),
          onPressed: () {
            setState(() {
              _count++;
            });
          },
          child: Text(
            '$_count',
            style: Theme.of(context).textTheme.headlineLarge,
          )),
    );
  }
}

8.4、Widget Tree、Element Tree 和 RenderObject Tree

Flutter应用是由是Widget Tree、Element Tree 和 RenderObject Tree组成

Widget可以理解成一个类,Element可以理解成Widget的实例,Widget与Element的关系可以是一对

多,一份配置可以创造多个Element实例

属性描述
WidgetWidget就是一个类, 是Element 的配置信息。与Element的关系可以是一对
ElementWidget 的实例化,内部持有Widget和RenderObject。
RenderObject负责渲染绘制

默认情况下面,当Flutter同一个 Widget的大小,顺序变化的时候,FLutter不会改变Widget的state。

在案例([8.3、GlobalKey 获取子组件](#8.3、GlobalKey 获取子组件))的onPressed中加上以上代码

// 父组件按钮改变子组件的值
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          //1. 获取currentState Widget的属性
          // 通过子组件的_globalKey1来获取的属性,注意要添加as 类型推导
          var boxState = _globalKey1.currentState as _BoxState;
          // 这样就可以在父组件来改变子组件的值
          setState(() {
            boxState._count++;
          });
          // 同样也能调用方法
          boxState.run();

          // 2.获取子Widget(获取子组件的数据)
          var boxWidget = _globalKey1.currentWidget as Box;
          // 获取到它的color
          print(boxWidget.color);

          // 3.获取子组件渲染的属性
          var renderBox =
              _globalKey1.currentContext!.findRenderObject() as RenderBox;
          // 获取到它的count
          print(renderBox.size); //Size(100.0, 100.0)
        },
      ),