Flutter学习笔记5-StatelessWidget、StatefulWidget

本篇主要对StatelessWidget、StatefulWidget做一个简单的介绍,并实际运用来进行一些简单界面的构建

一、StatelessWidget

StatelessWidget子类代码块生成快捷键: stless
上篇已经简单介绍了StatelessWidget的基本使用,接下来简单介绍如何使用StatelessWidget创建一个商品列表,效果如下:


Simulator Screen Shot - iPhone 12 - 2021-08-22 at 10.20.36.png
  1. StatelessWidget参数传递
    与Dart语法一致,创建属性(final修饰),实现初始化方法,进行赋值
    class YWHomeProduct extends StatelessWidget {
      final String title;
      final String desc;
      final String imageUrl;
    
      YWHomeProduct(this.title, this.desc, this.imageUrl);
    
      @override
      Widget build(BuildContext context) {
        return Column(children: [Text(title), Text(desc), Image.network(imageUrl)]);
      }
    }
    
    调用:
    YWHomeProduct("瓜子", "焦糖瓜子,香甜可口",
          "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.yzcdn.cn%2Fupload_files%2F2018%2F08%2F21%2FFiLdNHrxKMohbPJ2QbtOTo9MhIMm.jpg%3FimageView2%2F2%2Fw%2F580%2Fh%2F580%2Fq%2F75%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.yzcdn.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632121924&t=b37df3a44a89fc3b768c0f3f69f94f2a")
    
  2. 竖向列表
  • column: 竖向布局多个控件,使用column布局,当竖向控件高度超出屏幕高度后,会显示条纹遮挡
  • ListView: 竖向可滚动控件
  1. 对象创建:
    函数内部可以创建局部变量,减小语句内部代码量,但是每次调用函数都会创建
    在类的内部创建变量,则只会在对象实例化时创建一次
    在类的外部创建变量,则在整个项目生命周期中都会存在

  2. 创建控件之间的边距
    可以通过SizedBox控件来实现,横向距离width,竖向距离height

    SizedBox(height: 8)
    
  3. 边框创建
    将某个控件放置到Container中,可以用Container来设置边框(decoration)以及内边距(padding)等

    return Container(
        padding: EdgeInsets.all(8),
        decoration:
            BoxDecoration(border: Border.all(color: Colors.purple, width: 8)),
        child: Column(children: [
          Text(title, style: titleStyle),
          SizedBox(height: 8),
          Text(desc, style: descStyle),
          SizedBox(height: 8),
          Image.network(imageUrl)
        ]));
    

    问题:BoxDecoration的主要用处?
    用于Widget的装饰

    color         颜色背景         Color类型
    image         图片背景         DecorationImage类型
    border        边界            BoxBorder类型
    borderRadius  圆角边界半径     BorderRadiusGeometry类型
    boxShadow     阴影            List<BoxShadow>类型
    gradient      渐变色          Gradient类型
    backgroundBlendMode 背景混合模式    BlendMode类型
    shape         形状            BoxShape类型
    
  4. 控制column竖向布局控件位置
    控件分为主轴(竖向)和交叉轴(横向)
    主轴使用MainAxisAlignment控制子控件位置
    交叉轴使用CrossAxisAlignment控制子控件位置
    通过Flex可以决定主轴和交叉轴

    Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          Text(title, style: titleStyle),
          SizedBox(height: 8),
          Text(desc, style: descStyle),
          SizedBox(height: 8),
          Image.network(imageUrl)
        ])
    

下面是完整的代码:

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(debugShowCheckedModeBanner: false, home: YWHomePage());
  }
}

class YWHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: Text("购物车")), body: YWBodyContent());
  }
}

class YWBodyContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(children: [
      YWHomeProduct("瓜子", "焦糖瓜子,香甜可口",
          "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.yzcdn.cn%2Fupload_files%2F2018%2F08%2F21%2FFiLdNHrxKMohbPJ2QbtOTo9MhIMm.jpg%3FimageView2%2F2%2Fw%2F580%2Fh%2F580%2Fq%2F75%2Fformat%2Fjpg&refer=http%3A%2F%2Fimg.yzcdn.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632121924&t=b37df3a44a89fc3b768c0f3f69f94f2a"),
      YWHomeProduct("花生", "盐水花生,补气养血",
          "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi2.chuimg.com%2F25d9caf08b6811e6a9a10242ac110002_1171w_801h.jpg%3FimageView2%2F2%2Fw%2F660%2Finterlace%2F1%2Fq%2F90&refer=http%3A%2F%2Fi2.chuimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632122384&t=f3859622328496babe08d62ffca276ba"),
      YWHomeProduct("八宝粥", "居家旅行,出门良品",
          "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fcp2.douguo.net%2Fupload%2Fdish%2F7%2F3%2F3%2F600_7355c671e3bf52100c61e043c6110ae3.jpg&refer=http%3A%2F%2Fcp2.douguo.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1632122038&t=cedcc17ba9ed999844c9c3c1bedf2ded")
    ]);
  }
}

class YWHomeProduct extends StatelessWidget {
  final String title;
  final String desc;
  final String imageUrl;
  final titleStyle = TextStyle(fontSize: 20, color: Colors.orange);
  final descStyle = TextStyle(fontSize: 15, color: Colors.blue);

  YWHomeProduct(this.title, this.desc, this.imageUrl);

  @override
  Widget build(BuildContext context) {
    return Container(
        padding: EdgeInsets.all(8),
        decoration:
            BoxDecoration(border: Border.all(color: Colors.purple, width: 8)),
        child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          Text(title, style: titleStyle),
          SizedBox(height: 8),
          Text(desc, style: descStyle),
          SizedBox(height: 8),
          Image.network(imageUrl)
        ]));
  }
}

二、StatefulWidget

  1. StatefulWidget用法
    有状态需要改变的时候要使用StatefulWidget
  • StatefulWidget内部有一个抽象方法createState,此方法返回一个State类,继承自StatefulWidget的类需要实现此方法
  • 在自定义的State子类中需要实现build方法生成一个控件
  • StatefulWidget内部不可直接定义状态,但是可以在自定义的State子类中可以定义状态

问题:为什么Flutter在设计的时候,StatefulWidget的build方法放在State中?

  • build出来的widget是需要依赖State中的变量(状态/数据)
  • 在Flutter的运行过程中,widget是不断销毁和创建的,当我们的状态发生改变时,并不希望重新创建一个新的State
  1. 使用StatefulWidget构建一个计数器

    效果图如下:
    Simulator Screen Shot - iPhone 12 - 2021-08-21 at 20.08.41.png

    其中按钮可以使用ElevatedButton(RaisedButton已废弃)

    使用Column构建竖向组件,Row构建横向组件

import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(debugShowCheckedModeBanner: false, home: YWHomePage());
  }
}

class YWHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(appBar: AppBar(title: Text("购物车")), body: YWHomeContent());
  }
}

class YWHomeContent extends StatefulWidget {
  @override
  _YWHomeContentState createState() => _YWHomeContentState();
}

class _YWHomeContentState extends State<YWHomeContent> {
  int number = 0;
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
      getButtons(),
      Text("当前计数:$number", style: TextStyle(fontSize: 20))
    ]));
  }

  Widget getButtons() {
    return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
      ElevatedButton(
          child: Text("+", style: TextStyle(fontSize: 20)),
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(Colors.pink)),
          onPressed: () {
            setState(() {
              number++;
            });
          }),
      ElevatedButton(
          child: Text("-", style: TextStyle(fontSize: 20)),
          style: ButtonStyle(
              backgroundColor: MaterialStateProperty.all(Colors.purple)),
          onPressed: () {
            setState(() {
              if (number > 0) {
                number--;
              }
            });
          })
    ]);
  }
}

问题:数值改变能放在setState外面吗?

setState会触发widget build方法重新进行渲染

  1. StatefulWidget传值
    父类给子类传值,传值方式与StatelessWdight语法一致,创建属性(final修饰),实现对应初始化方法,进行赋值,将参数传递给Widget子类
    然后在State类中通过this.widget来实现取值
    class YWHomeContent extends StatefulWidget {
      final String message;
      YWHomeContent(this.message);
      @override
      _YWHomeContentState createState() =>_YWHomeContentState();
    }
    
    class _YWHomeContentState extends State<YWHomeContent> {
      int number = 0;
      @override
      Widget build(BuildContext context) {
        return Text("${this.widget.message}");
      }
    }
    

这也就是为什么State类定义的泛型需要跟之前创建的StatefulWidget子类保持一致的原因

三、StatelessWidget、StatefulWidget生命周期

  1. 生命周期的作用(意义)
  • 初始化一些数据、状态、变量
  • 发送网络请求时机
  • 进行一些事件的监听
  • 管理内存(手动销毁)
  1. StatelessWidget生命周期
    这里比较简单,只有两个生命周期
  • 构造函数被调用
  • 调用build方法
class TestWidget extends StatelessWidget {
  TestWidget() {
    print("构造函数被创建");
  }

  @override
  Widget build(BuildContext context) {
    print("build方法被调用");
    return Container();
  }
}
  1. StatefulWidget生命周期


    image.png
  • 第一步:调用StatefulWidget子类的构造方法
  • 第二步:调用StatefulWidget子类的createState方法
  • 第三步:调用State子类的构造方法
  • 第四步 调用State子类的initState方法
    用于状态的初始化,会在State类完成构造之后,build方法执行之前,进行执行
    initState方法使用@mustCallSuper标记,必须调用父类initState方法
  • 第五步:执行State子类的build方法
  • 最后,当前的Widget子类不再使用时,会执行State子类的dispose方法,进行内存的释放

此外:

  • didChangeDependencies方法 在initState方法执行后,或者从其他对象中依赖一些数据发生改变时,比如inheritedWidget,会执行此方法
  • didUpdateWidget方法 当重新创建Widget子类(rebuild),会触发state子类的didUpdateWidget方法

代码如下所示:

class YWHomeContent extends StatefulWidget {
  YWHomeContent() {
    print("1.调用YWHomeContent的初始化方法");
  }
  @override
  _YWHomeContentState createState() {
    print("2.调用YWHomeContent的createState方法");
    return _YWHomeContentState();
  }
}

class _YWHomeContentState extends State<YWHomeContent> {
  _YWHomeContentState() {
    print("3.调用_YWHomeContentState的初始化方法");
  }

  @override
  void initState() {
    super.initState();
    print("4.调用_YWHomeContentState的initState方法");
  }

  @override
  void didChangeDependencies() {
    print("调用_YWHomeContentState的didChangeDependencies方法");
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant YWHomeContent oldWidget) {
    print("调用_YWHomeContentState的didUpdateWidget方法");
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    print("5.调用_YWHomeContentState的build方法");
    return Text("Hello World");
  }

  @override
  void dispose() {
    super.dispose();
    print("6.调用_YWHomeContentState的dispose方法");
  }
}
  • setState方法会在内部触发重新运行build方法,根据最新的状态返回新的控件,所以状态的修改需要在setState方法中进行
  1. StatefulWidget生命周期的复杂版
    在StatefulWidget普通生命周期的基础上,还有一些复杂的过程未提及到
  • mounted
  • dirty state & clean state
    疑问:意义及用法?

5.生命周期扩展

createState
当构建一个 StatefulWidget 会立即调用该方法
@override
_MyStatefulPageState createState() {
print("lxf -- createState");
return _MyStatefulPageState();
}
initState
在 Widget 被创建出来并插入到树中时被调用的方法,只会被调用一次,等价于安卓的 onCreate() 或 iOS 中的 viewDidLoad(),所以在此时视图还未被渲染,但是 Widget 已经被插入到树中,一般用于执行一些初始化操作

@override
void initState() {
super.initState();
print("lxf -- initState");

// 初始化操作
...
}

mounted
所有的 Widget 都拥有这个属性,当 buildContext 被分配且当前在树中时,该值为 true,直到调用 dispose 时重置为 false

addPostFrameCallback
单次 Frame(帧) 绘制回调,在当前帧绘制完成后进行回调,用于在 Widget 渲染完毕之后做一些操作,该回调只会进行一次,如果需要再次监听则需要再次设置。
@override
void initState() {
super.initState();
print("lxf -- initState");

// 单次帧绘制回调(只会回调一次,如果要再次监听需要再设置)
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
print("lxf -- addPostFrameCallback");
});
}
如果希望在实时绘制帧时进行回调,则使用 addPersistentFrameCallback
// 实时帧绘制回调(每次绘制帧结束后都会回调)
WidgetsBinding.instance?.addPersistentFrameCallback((timeStamp) {
print("lxf -- addPersistentFrameCallback");
});

didChangeDependencies
在 initState() 方法被执行后立刻被调用,之后当依赖的 InheritedWidget 发生变化时,框架将会再次调用该方法将变化通知给当前对象。

build
在 didChangeDependencies 之后被调用,在该方法中会将 Widget 进行渲染,且由于再次渲染的操作是廉价的,所以每次 UI 需要被渲染时该方都会被调用,但是为了避免影响渲染效率,请不要在这里做创建 Widget 的其它操作!

didUpdateWidget
当父 Widget 发生改变并需要进行重绘时,该方法就会被调用,该方法还接收一个 oldWidget 参数,可以使用它与当前 Widget 进行比较来做一些额外的逻辑处理。
@override
void didUpdateWidget(covariant LXFStatefulPage oldWidget) {
super.didUpdateWidget(oldWidget);
print("lxf -- didUpdateWidget");
}

deactivate
当 State 对象从树中被移除时会调用该方法(包括从树中移除又被插入到树中其它位置的情况)

dispose
当 State 对象从树中被移除并且不再被构建时会调用该方法,常用于取消对 streams 的订阅等释放资源的操作

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容