美文网首页
Flutter学习之第一个App页面

Flutter学习之第一个App页面

作者: echoSuny | 来源:发表于2020-04-20 00:56 被阅读0次

    Widget

    在Flutter中,几乎所有的对象都是一个widget。与原生开发中的控件不同的是Flutter中的widget概念更广泛。它不仅可以表示UI元素,也可以表示一些功能性的组件如:用户手势检测的GestureDetector Widget、用于应用数据传递的Theme等等。由于Flutter主要就是用于构建用户界面,所以绝大多数的时候可以认为widget就是一个控件。
    Widget的功能是描述一个UI元素的配置数据。Widget其实并不是表示最终绘制在设备屏幕上的显示元素,只是显示元素的一个配置数据。实际上在Flutter中真正代表屏幕上显示元素的类是Element。也就是说Widget是描述Element的一个配置。一个Widget可以对应多个Element,这是因为同一个Widget对象可以被添加到UI树的不同部分。到真正渲染的时候,UI树的每一个节点都会对应一个Element对象。
    StatelessWidget 和 StatefulWidget 是Flutter中的基础组件。日常开发中自定义Widget都是选择继承这两者之一。也是在开发中接触最多的Widget。

    • StatelessWidget:无状态的。展示信息,面向那些始终不变的UI控件。
    • StatefulWidget:有状态的。可以通过改变状态使UI发生变化,可以包含用户交互。

    在实际使用中Stateless和Stateful的选择取决于这个Widget是有状态还是无状态的,简单说就是看界面是否需要更新。

    StatelessWidget

    StatelessWidget用于不需要维护状态的场景,通常在build方法中通过嵌套其他Widget来构建UI。在构建的过程中会递归的构建其嵌套的Widget。


    项目结构

    如图所示,我们在lib下新建一个dart文件,命名为first_app。

    // 导包
    import 'package:flutter/cupertino.dart';
    // main函数
    void main()=>runApp(MyApp());
    // 创建自己的widget继承StatelessWidget
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 创建一个TextView()
        return Text("hello flutter");
      }
    }
    

    注释很详细,就不再解释代码是什么意思。根据上述代码我们想得到的效果就是在屏幕上显示一个TextView,并显示“hello flutter”。那么我们能不能得到呢?运行一下就知道了:


    第一次运行结果

    很不幸的是竟然抛出异常了。。。上图中红色框空圈住的异常信息的意思分别是widget 库发生了异常 和 widget 没有给定方向。既然如此,那么我们就去看一下 Text 的源码。



    这是Text类的一个常量构造方法。可以看到除了第一个参数 data(data是String类型)之外其他的参数都被 {} 括住了,这种用法叫可选命名参数。在这些可选参数中我们可以看到很多在Android中能见到的属性:maxLines,textAlign。其中有一个参数textDirection可能跟我们的异常有关。这个参数也确实是管理着文字的方向的。TextDirection是Text类中的一个枚举类: TextDirection

    rtl代表right-to-left,也就是从右往左。ltr表示left-to-right,也就是从左向右。那么我们给我们的text添加一个方向再运行一下:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Text("hello flutter",textDirection: TextDirection.ltr,);
      }
    }
    
    TextDirection

    终于看到结果了!!!虽然有点别扭,不过好歹看到效果了。可以看到text默认显示在了左上角。这也表示Flutter跟Android的坐标系是一样的,都是以左上角为原点。
    下面来解决位置的问题。由于Flutter没有像Android中的LinearLayout或者RelativeLayout一样可以直接给设置一下属性就可以把控件摆放到我们想要的位置。不过我们可以通过Center这个专门处理位置的类来解决:

    return Center(child: Text("hello flutter",textDirection: TextDirection.ltr,),);
    
    Center

    可以看到我们的hello flutter 成功的显示在了屏幕的正中间。解决了位置的问题,可是这界面也太难看了吧?首先应该知道Google在2014年推出了MaterialDesign的一种设计语言,说白了就是一种设计风格。我们现在的app大多也就是按到MaterialDesign这样来设计的。Flutter是Google推出的,自然也支持这种设计风格。那么我们看一下在Flutter中使用MaterialDesign的风格来美化一下我们的界面:

    return MaterialApp(home: Center(child: Text("hello flutter",textDirection: TextDirection.ltr,),),);
    
    MaterialApp

    呃!!!好像更难看了😂不仅变成了红色还出现了两条下划线。这其实就是由MaterialApp提供的。在Android开发中我们经常要定义很多的style文件,在style文件中我们可以定义背景色,文本样式等等。那么在Flutter中也是同样的有很多这种style样式,可能默认就是我们所看到的那样。

    return MaterialApp(
            home: Scaffold(
          body: Center(
            child: Text(
              "hello flutter",
              textDirection: TextDirection.ltr,
            ),
          ),
        ));
    
    Scaffold

    可以看到我们使用Scaffold再包装一下界面就变成了起码现在看起来算比较舒服的界面。Scaffold其实就是实现了MaterialDesign中的各种风格的配件:


    Scaffold源码

    可以看到有appBar,对应的就是我们Android中的ActionBar、还有drawer对应Android中的DrawerLayout等等。也就是说Scaffold包含了一个页面所需要很多元素。下面我们来尝试一下:

    return MaterialApp(
            home: Scaffold(
          appBar: AppBar(
            title: Text("第一个页面"),
          ),
          floatingActionButton: FloatingActionButton(child: Text("点击"),),
          body: Center(
            child: Text(
              "hello flutter",
              textDirection: TextDirection.ltr,
            ),
          ),
        ));
    

    可以看到我们添加了一个appBar,和一个悬浮按钮,按钮的文字为点击:


    StatefulWidget

    上面的例子中我们的界面都是写死的界面,是不可交互的。下面我们就展示一下如何使用StatefulWidget来实现一下可交互的界面:

    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(
                title: Text("第一个页面"),
              ),
              floatingActionButton: FloatingActionButton(child: Text("点击"),),
              body: Center(
                child: Text(
                  "hello flutter",
                  textDirection: TextDirection.ltr,
                ),
              ),
            ));
      }
    }
    

    可以看到使用StatefulWidget之后又多出了一个类并继承了State。这个State就是用来管理Widget的状态的。可是仅仅这样做还是静态的页面,依然没办法交互。下面我们就修改一下代码,让中间的Text的文字3秒之后发生变化,并给悬浮按钮加上点击事件。当3秒之后我们再点击按钮来改变文字:

    String data = "hello flutter"; // 初始文字
    int count = 0; // 统计点击次数
    class _MyAppState extends State<MyApp> {
      _MyAppState(){
        Future.delayed(Duration(seconds: 8)).then((t){
          data = "我是TextView,我被改变了";
          // 3秒之后触发刷新页面
          setState(() {
    
          });
        });
      }
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
              appBar: AppBar(
                title: Text("第一个页面"),
              ),
              floatingActionButton: FloatingActionButton(child: Text("点击"),onPressed: (){ // onPress()就是点击事件
                count++;
                // 点击一次就刷新页面
                setState(() {
                  
                });
              },),
              body: Center(
                child: Text(
                  "${data}${count}", // 拼接上点击次数
                  textDirection: TextDirection.ltr,
                ),
              ),
            ));
      }
    }
    
    动态交互效果

    其中需要注意的一点是第一个 setState()方法是写在Future.then()的方法体之中的,也就是说Future把一个延时任务放入了Event_Queue当中来延迟执行。可如果我们直接在构造方法中调用setState()是会发生异常的:



    红色框中的意思大概是setState()在构造方法中调用导致出现了异常。后面还告诉了我们生命周期状态是created,但是此刻没有widget,还没准备好。

    state的生命周期

    bool show = true;
    
    class _MyAppState extends State<MyApp> {
      _MyAppState(){
        print("-----> parent constructor");
      }
    
      @override
      Widget build(BuildContext context) {
        print("-----> parent build");
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: RaisedButton(
                onPressed: () {
                  setState(() {
                    show = !show;
                  });
                },
                child: show ? Child() : Text("1111111"),
              ),
            ),
          ),
        );
      }
    
      @override
      void reassemble() {
        print("-----> parent reassemble");
      }
    
      @override
      void didUpdateWidget(MyApp oldWidget) {
        print("-----> parent didUpdateWidget");
      }
    
      @override
      void initState() {
        print("-----> parent initState");
      }
    
      @override
      void didChangeDependencies() {
        print("-----> parent didChangeDependencies");
      }
    
      @override
      void deactivate() {
        print("-----> parent deactivate");
      }
    
      @override
      void dispose() {
        print("-----> parent dispose");
      }
    
    
    }
    
    class Child extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return ChildState();
      }
    }
    
    class ChildState extends State<Child> {
      @override
      Widget build(BuildContext context) {
        print("-----> child build");
        return Text(
          "echo",
          textDirection: TextDirection.ltr,
        );
      }
    
      @override
      void reassemble() {
        print("-----> child reassemble");
      }
    
      @override
      void didUpdateWidget(Child oldWidget) {
        print("-----> child didUpdateWidget");
      }
    
      @override
      void initState() {
        print("-----> child initState");
      }
    
      @override
      void didChangeDependencies() {
        print("-----> child didChangeDependencies");
      }
    
      @override
      void deactivate() {
        print("-----> child deactivate");
      }
    
      @override
      void dispose() {
        super.dispose();
        print("-----> child dispose");
      }
    }
    

    上面的代码其实很简单,在屏幕的中央有一个按钮,点击按钮的时候文字内容会在 echo 和 111111 之间切换,同时打印state的生命周期方法。



    上图中红色框内是运行完app之后state的生命周期流程。可以看到无论是parent还是child都依次执行了initState->didChangeDependencies->build。下面的黄色框是点击按钮之后的生命周期流程。parent又调用了 build 方法是因为在按钮的点击事件调用了setState()改变了UI界面,相当于parent重绘了一次。而child由于在点击事件内被换成了一个新的Text,相当于被销毁了,于是就执行了deactivate->dispose。


    生命周期对比
    上图是和Android中Activity的生命周期的对比,我们可以这样去记忆。

    相关文章

      网友评论

          本文标题:Flutter学习之第一个App页面

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