美文网首页Flutter圈子Flutter开发圈Flutter
Flutter 学习记3 - Widget 框架

Flutter 学习记3 - Widget 框架

作者: 三流之路 | 来源:发表于2018-04-10 22:56 被阅读123次

    通过 widgets 构建 UI,描述当前的配置和状态,当状态改变时,框架找出前后的变化,以确定底层 Render Tree 要做的最小更改,在内部变成另一个状态来更新外面的 Widget Tree。

    一个最简单的 Flutter 程序就是在入口方法 main 中调用 runApp,这个函数将参数的 Widget 作为整个应用的 Widget Tree 的根 Widget,将这个根 Widget 完全覆盖到屏幕上。

    Widget 分两种:StatelessWidgetStatefulWidget,主要工作就是实现 build 方法,用于描述更低级的 Widget。

    基础的 Widget

    • Text
    • Row, Column: flex 布局,类似 Web 的 flexbox,Android 的 FlexboxLayout
    • Stack:堆叠,类似 Web 的 absolute,也像 Android 的 RelativeLayout,内部使用 Positioned 并控制其在 Stack 内的上下左右的边距
    • Container: 创建一个矩形可见元素,可以通过 BoxDecoration 来做样式,如背景,边框,阴影。可以设置 margin,padding 或者尺寸 size。可以通过矩阵转换变成三维的。

    下面是一个示例代码,首先 pubspec.yaml 要配置

    flutter:
      uses-material-design: true
    

    新创建的工程默认就是这个配置。

    import 'package:flutter/material.dart';
    
    class MyAppBar extends StatelessWidget {
      MyAppBar({this.title});
    
      // Fields in a Widget subclass are always marked "final".
    
      final Widget title;
    
      @override
      Widget build(BuildContext context) {
        return new Container(
          height: 56.0, // in logical pixels
          padding: const EdgeInsets.symmetric(horizontal: 8.0),
          decoration: new BoxDecoration(color: Colors.blue[500]),
          // Row is a horizontal, linear layout.
          child: new Row(
            // <Widget> is the type of items in the list.
            children: <Widget>[
              new IconButton(
                icon: new Icon(Icons.menu),
                tooltip: 'Navigation menu',
                onPressed: null, // null disables the button
              ),
              // Expanded expands its child to fill the available space.
              new Expanded(
                child: title,
              ),
              new IconButton(
                icon: new Icon(Icons.search),
                tooltip: 'Search',
                onPressed: null,
              ),
            ],
          ),
        );
      }
    }
    
    class MyScaffold extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // Material is a conceptual piece of paper on which the UI appears.
        return new Material(
          // Column is a vertical, linear layout.
          child: new Column(
            children: <Widget>[
              new MyAppBar(
                title: new Text(
                  'Example title',
                  style: Theme.of(context).primaryTextTheme.title,
                ),
              ),
              new Expanded(
                child: new Center(
                  child: new Text('Hello, world!'),
                ),
              ),
            ],
          ),
        );
      }
    }
    
    void main() {
      runApp(new MaterialApp(
        title: 'My app', // used by the OS task switcher
        home: new MyScaffold(),
      ));
    }
    

    现在还不知道怎么加按钮,将上面的代码放到一个新建的 newroute.dart 文件中,去掉 main 方法,在 main.dart 的 AppBar 上添加一个按钮

    new IconButton(icon: new Icon(Icons.add), onPressed: _toOther),
    
    void _toOther() {
      Navigator.of(context).push(
        new MaterialPageRoute(
            builder: (context) {
              return new MyScaffold();
            })
      );
    }
    

    要使用另一个文件的 MyScaffold 类,需要 import

    import 'newroute.dart';
    

    结果成功跳转。

    由于语法还不懂,先简单的分析下:

    1. MyScaffoldStatelessWidget,它的 build 方法返回一个 Material,这就是页面显示的内容。
    2. Material 里只有一个 child,就是一个 Column,一个垂直的线性布局。包含一个 MyAppBarExpanded
    3. ExpandedColumeRowFlex 的 child,意思就占据所有剩下的空间,如果多个 child 都是 Expanded,通过 flex 来指定各自的比例,有些类似 Android 的 weight
    4. MyAppBar 是一个 StatelessWidget,内部是 Container,然后设置它的高度,内边距。child 是 Row,和 Column 类似,只是水平的。左右各一个 IconButton

    手势处理

    class MyButton extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new GestureDetector(
          onTap: () {
            print('MyButton was tapped!');
            Navigator.of(context).pop(this);
          },
          child: new Container(
            height: 36.0,
            padding: const EdgeInsets.all(8.0),
            margin: const EdgeInsets.symmetric(horizontal: 8.0),
            decoration: new BoxDecoration(
              borderRadius: new BorderRadius.circular(5.0),
              color: Colors.lightGreen[500],
            ),
            child: new Center(
              child: new Text('Engage'),
            ),
          ),
        );
      }
    }
    

    当用户触摸 GestureDector 的 child,即这里的 Container,就会调用 onTapGestureDector 可以发现包括 tap,drag 和 scales 事件。

    onTap 里让页面返回,效果如下:

    IMB_9pMJ4C.GIF

    交互

    class CounterDisplay extends StatelessWidget {
      CounterDisplay({this.count});
    
      final int count;
    
      @override
      Widget build(BuildContext context) {
        return new Text('Count: $count');
      }
    }
    
    class CounterIncrementor extends StatelessWidget {
      CounterIncrementor({this.onPressed});
    
      final VoidCallback onPressed;
    
      @override
      Widget build(BuildContext context) {
        return new RaisedButton(
          onPressed: onPressed,
          child: new Text('Increment'),
        );
      }
    }
    
    class Counter extends StatefulWidget {
      @override
      _CounterState createState() => new _CounterState();
    }
    
    class _CounterState extends State<Counter> {
      int _counter = 0;
    
      void _increment() {
        setState(() {
          ++_counter;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return new Row(children: <Widget>[
          new CounterIncrementor(onPressed: _increment),
          new CounterDisplay(count: _counter),
          new RaisedButton(
            onPressed: () {
              Navigator.of(context).pop(this);
            },
            child: new Text('返回'),
          ),
        ]);
      }
    }
    

    在前一篇里已经遇到了 StatefulWidget,首先 CountercreateState() 返回一个 _CounterState 实例。

    看 CounterIncrementor 里的 CounterIncrementor({this.onPressed});,这一句应该是构造方法,在 _CounterState 里调用是 new CounterIncrementor(onPressed: _increment),因此应该是用 _increment 这个函数赋给 CounterIncrementoronPressed,类型是 VoidCallback

    /// Signature of callbacks that have no arguments and return no data.
    typedef void VoidCallback();

    然后当用户点击按压 RaisedButton 时,调用 onPressed 也就是 _increment,将 _counter 加 1,setState 会引起再次 build,于是 CounterDisplay 里的 Text 内容也就变了。

    IMB_4rbGFZ.GIF

    综合例子

    // 一个产品类,一个 name 属性
    class Product {
      const Product({this.name});
      final String name;
    }
    
    // 这个看后面代码,代表的是一个函数,有点像 C 语言的函数声明,但又不一样,不清楚这个语法
    typedef void CartChangedCallback(Product product, bool inCart);
    
    // 一个 StatelessWidget
    class ShoppingListItem extends StatelessWidget {
      // 这个构造三个参数,但 product 好像是后面的 product = product 来赋值
      // this.inCart 应该就是传入的参数就覆盖自己的属性 inCart
      ShoppingListItem({Product product, this.inCart, this.onCartChanged})
          : product = product,
            super(key: new ObjectKey(product));
    
      final Product product;
      final bool inCart;
      final CartChangedCallback onCartChanged; 
    
      Color _getColor(BuildContext context) {
        // 根据布尔值返回不同的颜色
        return inCart ? Colors.black54 : Theme.of(context).primaryColor;
      }
    
      TextStyle _getTextStyle(BuildContext context) {
        if (!inCart) return null;
    
        return new TextStyle(
          color: Colors.black54,
          decoration: TextDecoration.lineThrough,
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return new ListTile(
          onTap: () {
            // 触摸的时候,inCart 取反,然后回调外界传入的 onCartChanged
            onCartChanged(product, !inCart);
          },
          leading: new CircleAvatar( // 不太清楚干什么的
            backgroundColor: _getColor(context), // 调上面的方法返回不同的背景色
            child: new Text(product.name[0]),
          ),
          title: new Text(product.name, style: _getTextStyle(context)), // 这个 item 的文字
        );
      }
    }
    

    这是一个列表的 item,onCartChanged 应该就是一种回调函数,由父 Widget 传入,函数被调用后,父 Widget 更新内部的 State,然后导致利用这个新的 inCart重新创建一个 ShoppingListItem 实例。然后看下这句牛逼哄哄的话:

    Although the parent creates a new instance of ShoppingListItem when it rebuilds, that operation is cheap because the framework compares the newly built widgets with the previously built widgets and applies only the differences to the underlying RenderObject.

    class ShoppingList extends StatefulWidget {
      ShoppingList({Key key, this.products}) : super(key: key);
    
      final List<Product> products;
    
      @override
      _ShoppingListState createState() => new _ShoppingListState();
    }
    
    class _ShoppingListState extends State<ShoppingList> {
      Set<Product> _shoppingCart = new Set<Product>();
    
      // 传给 item 的回调函数,setState 去重新 build
      void _handleCartChanged(Product product, bool inCart) {
        setState(() {
           if (inCart)
            _shoppingCart.add(product);
          else
            _shoppingCart.remove(product);
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Shopping List'),
          ),
          body: new ListView(
            padding: new EdgeInsets.symmetric(vertical: 8.0),
            children: widget.products.map((Product product) {
              return new ShoppingListItem(
                product: product,
                inCart: _shoppingCart.contains(product),
                onCartChanged: _handleCartChanged,
              );
            }).toList(),
          ),
        );
      }
    }
    
    // void main() {
    //   runApp(new MaterialApp(
    //     title: 'Shopping App',
    //     home: new ShoppingList(
    //       products: <Product>[
    //         new Product(name: 'Eggs'),
    //         new Product(name: 'Flour'),
    //         new Product(name: 'Chocolate chips'),
    //       ],
    //     ),
    //   ));
    }
    

    在前面的页面加个按钮跳过来

    new RaisedButton(
        onPressed: () {
            Navigator.of(context).push(
                new MaterialPageRoute(
                    builder: (context) {
                    new ShoppingList(
                        products: <Product>[
                            new Product(name: 'Eggs'),
                            new Product(name: 'Flour'),
                            new Product(name: 'Chocolate chips'),
                        ],
                    );
                })  
            );
        },
        ...
    )
    

    如果 ShoppingList 的父 Widget 重新构建一个 ShoppingList,但是不会再次调用 createState() 去重新创建一个 _ShoppingListState,而是会复用已经存在的 State 实例。但它会根据新的属性值重新构建,相当于 _ShoppingListState 实例对象只有一个,但是在重新创建 ShoppingList 时会再次调用 State 的 build 方法。嗯,应该是这样。

    IMB_OyXeZQ.gif

    参考:A Tour of the Flutter Widget Framework

    相关文章

      网友评论

        本文标题:Flutter 学习记3 - Widget 框架

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