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类型
    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 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 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 {
  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 {
      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 {
  _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 的订阅等释放资源的操作

你可能感兴趣的:(Flutter学习笔记5-StatelessWidget、StatefulWidget)