Flutter 学习笔记3 - Widget 框架

通过 widgets 构建 UI,描述当前的配置和状态,当状态 State 改变时,框架找出前后的变化,以确定底层渲染树从一个状态转换到下一个状态所需的最小更改。

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

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

Widget 分两种:无状态的 StatelessWidget 和有状态的 StatefulWidget,主要工作就是实现 build 方法,用于描述更低级的 Widget 以构建自身。最底层的 widget 通常为 RenderObject,它会计算并描述 widget 的几何形状。

基础的 Widget

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

下面是一个示例代码,首先确保 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 Container(
      height: 56.0, // in logical pixels
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // Row is a horizontal, linear layout.
      child: Row(
        //  is the type of items in the list.
        children: [
          IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'Navigation menu',
            onPressed: null, // null disables the button
          ),
          // Expanded expands its child to fill the available space.
          Expanded(
            child: title,
          ),
          IconButton(
            icon: 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 Material(
      // Column is a vertical, linear layout.
      child: Column(
        children: [
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.title,
            ),
          ),
          Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'My app', // used by the OS task switcher
    home: MyScaffold(),
  ));
}

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

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

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

import 'newroute.dart';

结果成功跳转。

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

手势处理

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

修改显示的内容:

class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Material(
      child: MyButton(),
    };
  }
}

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

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

Flutter 学习笔记3 - Widget 框架_第1张图片
IMB_9pMJ4C.GIF

交互

class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});

  final int count;

  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}

class CounterIncrementor extends StatelessWidget {
  CounterIncrementor({this.onPressed});

  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: onPressed,
      child: Text('Increment'),
    );
  }
}

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State {
  int _counter = 0;

  void _increment() {
    setState(() {
      ++_counter;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(children: [
      CounterIncrementor(onPressed: _increment),
      CounterDisplay(count: _counter),
      RaisedButton(
        onPressed: () {
          Navigator.of(context).pop(this);
        },
        child: Text('返回'),
      ),
    ]);
  }
}

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

_CounterState 里调用是 CounterIncrementor(onPressed: _increment)_increment_counter 加 1,setState 会引起再次 build,于是 CounterDisplay 里的 Text 内容也就变了。

IMB_4rbGFZ.GIF

综合例子

// 一个产品类,一个 name 属性
class Product {
  const Product({this.name});
  final String name;
}

typedef void CartChangedCallback(Product product, bool inCart);

// 一个 StatelessWidget
class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({Product product, this.inCart, this.onCartChanged})
      : product = product,
        super(key: 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 TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListTile(
      onTap: () {
        // 触摸的时候,inCart 取反,然后回调外界传入的 onCartChanged
        onCartChanged(product, !inCart);
      },
      leading: CircleAvatar( 
        backgroundColor: _getColor(context), // 调上面的方法返回不同的背景色
        child: Text(product.name[0]),
      ),
      title: Text(product.name, style: _getTextStyle(context)), // 这个 item 的文字
    );
  }
}

这是一个列表的 item,onCartChanged 是回调函数,由父 Widget 传入,函数被调用后,父 Widget 更新内部的 State,然后导致利用这个新的 inCart重新创建一个 ShoppingListItem 实例

class ShoppingList extends StatefulWidget {
  ShoppingList({Key key, this.products}) : super(key: key);

  final List products;

  @override
  _ShoppingListState createState() => _ShoppingListState();
}

class _ShoppingListState extends State {
  Set _shoppingCart = Set();

  // 传给 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 Scaffold(
      appBar: AppBar(
        title: Text('Shopping List'),
      ),
      body: ListView(
        padding: EdgeInsets.symmetric(vertical: 8.0),
        children: widget.products.map((Product product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChanged: _handleCartChanged,
          );
        }).toList(),
      ),
    );
  }
}

// void main() {
//   runApp(MaterialApp(
//     title: 'Shopping App',
//     home: ShoppingList(
//       products: [
//         Product(name: 'Eggs'),
//         Product(name: 'Flour'),
//         Product(name: 'Chocolate chips'),
//       ],
//     ),
//   ));
// }

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

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

当 ShoppingList 首次插入到树中时,框架会调用其 createState 函数以创建一个新的 _ShoppingListState 实例来与该树中的相应位置关联。当这个 widget 的父级重建时,父级将创建一个新的 ShoppingList 实例,但是 Flutter 框架将重用已经在树中的 _ShoppingListState 实例,而不是再次调用 createState 创建一个新的。

调用 setState 将该 widget 标记为 dirty,并且计划在下次应用程序需要更新屏幕时重新构建它。通过这种方式管理状态,不需要编写用于创建和更新子 widget 的单独代码。相反,只需实现可以处理这两种情况的 build 函数。

Flutter 学习笔记3 - Widget 框架_第2张图片
IMB_OyXeZQ.gif

大致流程

Flutter 学习笔记3 - Widget 框架_第3张图片
屏幕快照 2018-12-26 下午10.54.44.png

你可能感兴趣的:(Flutter 学习笔记3 - Widget 框架)