美文网首页
《Flutter实战》第三章

《Flutter实战》第三章

作者: 番茄tomato | 来源:发表于2020-04-29 16:26 被阅读0次
    • 本篇参考资料《Flutter实战》
    • 本篇文章只是本人看书的理解和整理的笔记,更完整的内容还在书上!
    • 电子书链接:https://book.flutterchina.club/
    • Flutter中文社区链接:https://flutterchina.club/
    • 尊重原作者,能支持购买实体书当然最好
      3.1~3.2

    一.Flutter项目的基本结构和文件命名方式

    我们来看一个例子:github上5k星的flutter项目:在flutter上实现豆瓣APP
    https://github.com/kaina404/FlutterDouBan

    项目结构
    这个是一个值得学习的大型Flutter项目结构,有很多代码d本人没有看懂,以后再补充,我们可以看到,这个项目结构是根据代码的不同功能区分的很清楚
    同时补充一下dart文件和class命名:我们打开pages包
    image.png
    那么在我们的dart文件是按不用的功能放在不同的包里,那android studio怎么新建dart包呢?如下图: image.png
    这里我们新建了一个error_dart,显示错误信息界面、
    image.png
    在main使用需引入:为引入就使用时会报错直接alt+enter就可以看到IDE提供的解决方案
    import 'package:tomatoflutterapp/pages/error_page.dart';
    

    二 StatelessWidget和StatefulWidget

    之前已经提到过,书上也讲的很清楚,复制黏贴没有意义,直接跳转
    https://book.flutterchina.club/chapter3/flutter_widget_intro.html
    StatelessWidget:是没有状态,不可改变的界面
    StatefulWidget:是有状态的,界面内容可以根据状态进行改变

    其中无状态界面StatelessWidget是直接在其内部重写build方法构建出界面,
    但是有状态界面内通常是有两个类组成StatefulWidgetState,build在State中,同时State中还可以调用setState进行刷新

    2.1 State

    一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态,State中的保存的状态信息可以:

    • 在widget 构建时可以被同步读取。
    • 在widget生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter framework状态发生改变,Flutter framework在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的。

    State中有两个常用属性:

    • widget,它表示与该State实例关联的widget实例,由Flutter framework动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI树上的某一个节点的widget实例在重新构建时可能会变化,但State实例只会在第一次插入到树中时被创建,当在重新构建时,如果widget被修改了,Flutter framework会动态设置State.widget为新的widget实例。
      通过weiget可以获取到StatefulWidget的一些属比如weiget.title

    • context。StatefulWidget对应的BuildContext,作用同StatelessWidget的BuildContext。

    State的生命周期

    State生命周期
    为了更好的理解我们在计数器里重写生命中周期函数
      //生命周期
      @override
      void initState() {
        super.initState();
        _counter = widget.initValue; //使用widget属性 获得与之关联的widget实例初始值
        print("initState");
      }
    
    @override
      Widget build(BuildContext context) {
    print("build");
    ...
    }
    
      @override
      void didUpdateWidget(MyStatefulWidgetPage oldWidget) {
        super.didUpdateWidget(oldWidget);
        print("didUpdateWidget");
      }
    
      @override
      void deactivate() {
        super.deactivate();
        print("deactive");
      }
    
      @override
      void dispose() {
        super.dispose();
        print("dispose");
      }
      @override
      void reassemble() {
        super.reassemble();
        print("reassemble");
      }
      @override
      void didChangeDependencies() {
        super.didChangeDependencies();
        print("didChangeDependencies");
      }
    

    运行程序进入计数器界面我们可以看到生命周期initState-->didChangeDependencies-->build

    I/flutter ( 5042): initState
    I/flutter ( 5042): didChangeDependencies
    I/flutter ( 5042): build
    

    点击“+”我们可以看到:build被重复调用 界面重构了

    D/SettingsInterface( 5042):  from settings cache , name = sound_effects_enabled , value = 0
    I/flutter ( 5042): build
    D/SettingsInterface( 5042):  from settings cache , name = sound_effects_enabled , value = 0
    I/flutter ( 5042): build
    D/SettingsInterface( 5042):  from settings cache , name = sound_effects_enabled , value = 0
    I/flutter ( 5042): build
    

    我们点击热加载时:

    Performing hot reload...                                               
    |I/flutter ( 5042): reassemble
    /I/flutter ( 5042): didUpdateWidget
    I/flutter ( 5042): build
    

    当我们退出界面:

    I/flutter ( 5042): deactive
    I/flutter ( 5042): dispose
    

    下边是书上对各个生命周期的解释:

    • initState:当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后,Widget树中的InheritFromWidget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。
    • didChangeDependencies():当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。
    • build():此回调读者现在应该已经相当熟悉了,它主要是用于构建Widget子树的,会在如下场景被调用:

    在调用initState()之后。
    在调用didUpdateWidget()之后。
    在调用setState()之后。
    在调用didChangeDependencies()之后。
    在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。

    • reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。
    • didUpdateWidget():在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。
    • deactivate():当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
    • dispose():当State对象从树中被永久移除时调用;通常在此回调中释放资源。

    3.2 暂时跳过

    3.3~3.8是关于每个单独控件的使用,书上是说明很清楚,例子也很丰富,建议先全部浏览完毕后,对于某个控件想深入了解再去搜索

    补充点:

    3.6:单选框和复选框

    注意每个单选框/复选框 都需要有一个value 布尔变量对应,首先给初始值,然后点击的时候需要
    onChanged中调用setState方法来刷新界面才能看到点击效果

    class _MySwitchPageState extends State<MySwitchPage> {
      bool _switchSelected = true; //维护单选开关状态
    .....
                Switch(
                  value: _switchSelected, //给予一个初始值
                  onChanged: (value) {
                    //在点击后会调用onChanged 传入改变后的值
                    //重新构建页面
                    setState(() {
                      //修改状态值
                      _switchSelected = value;
                    });
                  },
                )
    
    .....}
    

    单独使用Switch和Checkbox就只是一个开关


    image.png

    那我们想要有一些文字说明怎么做呢?


    image.png

    这时候要用到Switch和Checkbox的上层封装类 SwitchListTile / CheckboxListTile

                SwitchListTile(
                  secondary: Icon(
                    Icons.block,
                    color: Colors.pink[200],
                  ),
                  title: Text("关闭通知"),
    
                  value: _switchSelected, //给予一个初始值
                  onChanged: (value) {
                    //在点击后会调用onChanged 传入改变后的值
                    //重新构建页面
                    setState(() {
                      //修改状态值
                      _switchSelected = value;
                    });
                  },
                ),
                CheckboxListTile(
                  title: Text("硬件加速"),
                  secondary: Icon(
                    Icons.access_alarm,
                    color: Colors.pink[200],
                  ),
                  value: _checkboxSelected,
                  onChanged: (value) {
                    setState(() {
                      //修改状态值
                      _checkboxSelected = value;
                    });
    
                  },
                )
    

    3.7:输入框和表单

    书上的不是很好理解
    参考链接:https://www.jianshu.com/p/54419a143d70

    3.7.1 controller的一般用法:
    class _MyInputPageState extends State<MyInputPage> {
    
      //定义一个Controller
      TextEditingController _myDemoController = TextEditingController();
    
      void initDemoController() {
        //设置默认值
        _myDemoController.text = "hello world!";
        //第三个字符开始选中后面的字符
        _myDemoController.selection = TextSelection(
            baseOffset: 2, extentOffset: _myDemoController.text.length);
        //监听输入改变
        _myDemoController.addListener(() {
          //这里是只要输入框的内容变了都可以监听到
          print(_myDemoController.text);
        });
      }
      @override
      void initState() {
        super.initState();
        initDemoController();
      }
    .....
    }
    

    在TextField中设置controller

                TextField(
                  //设置controller,可以通过controller完成很多很多工作
                  controller: _myDemoController,
                  textInputAction: TextInputAction.search,
                )
    
    3.7.2 onEditingComplete和onSubmitted

    onEditingComplete和onSubmitted:这两个回调都是在输入框输入完成时触发,比如按了键盘的完成键(对号图标)或搜索键(🔍图标)。不同的是两个回调签名不同,onSubmitted回调是ValueChanged<String>类型,它接收当前输入内容做为参数,而onEditingComplete不接收参数。

                TextField(
                  //设置controller,可以通过controller完成很多很多工作
                  controller: _myDemoController,
                  textInputAction: TextInputAction.search,
                  //输入完成时调用,比如点击了搜索,但是无参数
                  //可以使用controller获取到输入内容
                  onEditingComplete: (){
                    print("onEditingComplete: ${_myDemoController.text}");
                  },
    
                  //输入完成时调用,这里有参数是输入的内容input
                  onSubmitted: (input){
                    print("onSubmitted:$input");
                  },
                )
    
    image.png

    我突然发现这种decoration设计属性可以简单封装一下,每次调用就会节省很多时间,风格也统一了

    3.7.3 用很笨的一个方式来实现一个错误输入提醒

    效果(主要学习整理一个思想,慢慢改进,记录一个代码优化的思路)


    错误提示

    我们知道error信息是在InputDecoration中显示的,其实如果在InputDecoration构造函数中设置了errorText,并传入null,那么和不设置其实是一个效果,所以我们设置一个errorMsg变量,让他随输入的内容变化:

      //错误信息
      String errorMsg;
      void initDemoController() {
        //设置默认值
        _myDemoController.text = "hello world!";
        //监听输入改变
        _myDemoController.addListener(() {
          //这里是只要输入框的内容变了都可以监听到
          print(_myDemoController.text);
          setState(() {
            //每次输入字符则改变界面状态
            if (_myDemoController.text.length > 18) {
              //如果长度大于18就显示
              errorMsg = "输入内容太长";
            } else {
              errorMsg = null;
            }
          });
        });
      }
    

    在TextField中设置

            TextField(
                  //设置controller,可以通过controller完成很多很多工作
                  controller: _myDemoController,
                  textInputAction: TextInputAction.search,
                  decoration: InputDecoration(
                      labelText: "搜索",
                      hintText: "输入搜索内容",
                      prefixIcon: Icon(Icons.search),
                      fillColor: Colors.blue[200],
                      filled: true,
                      errorText: errorMsg),
                )
    

    但是我们发现这个错误提示显示了还是可以继续输入,不许用户输入怎么操作?

                  maxLength: 18,//设置最长输入
                  maxLengthEnforced: true,//true的话就阻止超过,false就会错误提示
    

    相关文章

      网友评论

          本文标题:《Flutter实战》第三章

          本文链接:https://www.haomeiwen.com/subject/qiwgwhtx.html