Introduction To Widgets

核心思想

Flutter是一个现代框架,它的灵感来自React。
核心思想是:

  • UI由元件构成。
  • 元件由状态及描述构成。
  • 状态更改,描述随之更改,随之比较新旧描述,得出底层渲染树的差异,
  • 最小化视图修改。

一个简单的Hello world

最简单的Flutter应用是调用runApp()来运行一个元件。示例如下

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}
  • runApp()接受一个元件作为参数,并使得这个元件成为树的根元件。
  • 根元件一般会强制充满屏幕;
  • 常用根元件是MeterialApp,它的子元件都会继承一些默认风格。
  • SafeArea元件一般用来防止屏幕顶部意外遮挡。

编写元件的基本过程:

  1. 声明一个StatelessWidget或StatefulWidget的子类;
  2. 实现build()方法,在其中组织元件及其子元件;
  3. 元件的构建过程是从顶级元件开始,一直到最底层的基础几何元件;
  4. 基础几何元件又对应着底层的渲染对象,称为RenderObject,它用来计算和描述元件的几何特性。

常用的基本元件

  • Text
  • Row, Column:它们的设计思想来自于web的弹性布局模式
  • Stack:设计思想来自于web的绝对定位布局模式
  • Container:一个矩形框,可以拥有内外边距,阴影,背景,边框,尺寸,还可以通过矩阵在三维空间中变形。
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(
        // <Widget> is the type of items in the list.
        children: <Widget>[
          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: <Widget>[
          MyAppBar(
            title: Text(
              'Example title',
              style: Theme.of(context).primaryTextTheme.headline6,
            ),
          ),
          Expanded(
            child: Center(
              child: Text('Hello, world!'),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    title: 'My app', // used by the OS task switcher
    home: SafeArea(
      child: MyScaffold(),
    ),
  ));
}
  • pubspec.yaml中设置uses-material-design: true,以使你可以使用 Material icons;
  • Material Design widgets必须置于MaterialApp元件内,才能正常继承主题配置,才能正确显示;
  • Expand元件的flex参数,是一个整数值,表示宽度占比 = 单个Expand的flex/flex之和;
  • 任何一个手写元件的build方法,都返回一个其它已有元件,然后这个已经有元件,通过填充其构造函数的参数来填充子元件。如果他的构造函数的参数不包括子元件,那它也没有子元件。
  • 一般布局类元件都包含子元件,

使用Material Components

一般使用MaterialApp作为根元件,它包含一个Navigator来管理多个页面组成的栈,这些栈中的页面又称为路由。
它的子元件有AppBar,Scafford,提供一致风格的UI。我们在设计中可以充分考虑如何有效利用。

我们说回调函数代表一种未知的待定的能力,其参数代表能力实施时所能获取的资源或能施加影响的对象,而属性代表一种状态,这种状态可以由这能种能力来改变。

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Flutter Tutorial',
    home: TutorialHome(),
  ));
}

class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Scaffold is a layout for the major Material Components.
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigation menu',
          onPressed: null,
        ),
        title: Text('Example title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'Search',
            onPressed: null,
          ),
        ],
      ),
      // body is the majority of the screen.
      body: Center(
        child: Text('Hello, world!'),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'Add', // used by assistive technologies
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

基本手势元件

手势是用户与UI交互的主要形式。交互逻辑的第一步是检测手势(点击,拖动,缩放等),然后是作用对象,最后是在回调函数定义要做什么。这是一个事件机制:行为触发事件,事件触发进一步行为。

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('MyButton was tapped!');
      },
      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 Counter extends StatefulWidget {
  // This class is the configuration for the state. It holds the
  // values (in this case nothing) provided by the parent and used
  // by the build  method of the State. Fields in a Widget
  // subclass are always marked "final".

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

class _CounterState extends State<Counter> {
  int _counter = 0;

  void _increment() {
    setState(() {
      // This call to setState tells the Flutter framework that
      // something has changed in this State, which causes it to rerun
      // the build method below so that the display can reflect the
      // updated values. If you change _counter without calling
      // setState(), then the build method won't be called again,
      // and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called,
    // for instance, as done by the _increment method above.
    // The Flutter framework has been optimized to make rerunning
    // build methods fast, so that you can just rebuild anything that
    // needs updating rather than having to individually change
    // instances of widgets.
    return Row(
      children: <Widget>[
        ElevatedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        Text('Count: $_counter'),
      ],
    );
  }
}

状态元件与状态对象有不同的生命周期。所有的元件都是一个临时对象,用来利用当前的状态构造一个视图节点。而状态对象是相对持久的,用来存储与管理持续状态。

在一个相对复杂的应用中,我们应该分离关注点,不同的对象应关注不同的点。比如一个元件用来采集用户输入并刷新状态,另一个元件利用这些输入信息来呈现总体结果。一般来说,无状态元件会作为状态元件的子元件,以被状态元件操控。

在Flutter中,通过setState来触发状态更改事件(通知),状态改变时,重构状态所绑定的元件的子树的描述对象,这叫***通知自下而上***。当新的描述对象与旧的描述对象进行比较时得出差异,再基于差异部分,对渲染树进行重渲,这叫***状态自上而下***。 而这个自下而上的通知流或自下而上的状态流的导向都是由State对象来主导执行的。

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 ElevatedButton(
      onPressed: onPressed,
      child: Text('Increment'),
    );
  }
}

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

class _CounterState extends State<Counter> {
  int _counter = 0;

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

  @override
  Widget build(BuildContext context) {
    return Row(children: <Widget>[
      CounterIncrementor(onPressed: _increment),
      CounterDisplay(count: _counter),
    ]);
  }
}

综合示例

 class Product {
  const Product({this.name});
  final String name;
}

typedef void CartChangedCallback(Product product, bool inCart);

class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({this.product, this.inCart, this.onCartChanged})
      : super(key: ObjectKey(product));

  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;

  Color _getColor(BuildContext context) {
    // The theme depends on the BuildContext because different parts
    // of the tree can have different themes.
    // The BuildContext indicates where the build is
    // taking place and therefore which theme to use.

    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: () {
        onCartChanged(product, inCart);
      },
      leading: CircleAvatar(
        backgroundColor: _getColor(context),
        child: Text(product.name[0]),
      ),
      title: Text(product.name, style: _getTextStyle(context)),
    );
  }
}

ShoppingListItem 是一个无状态元件。它的数据由构造函数的参数传入。
它还接受一个回调函数的参数,绑定到它的点击事件。这个回调函数在它的父元件中定义,使得可以控制父层或更高层元件的状态,从而使得这个状态更持久(而不是随着自己的重构或消失而变化)。如果在runApp()中使用这个元件,状态的存续期则与应用相同。这就是本身不持有状态,但本身预留潜在的更高层节点的状态的控制能力。

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

  final List<Product> products;

  // The framework calls createState the first time a widget
  // appears at a given location in the tree.
  // If the parent rebuilds and uses the same type of
  // widget (with the same key), the framework re-uses the State object
  // instead of creating a new State object.

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

class _ShoppingListState extends State<ShoppingList> {
  Set<Product> _shoppingCart = Set<Product>();

  void _handleCartChanged(Product product, bool inCart) {
    setState(() {
      // When a user changes what's in the cart, you need to change
      // _shoppingCart inside a setState call to trigger a rebuild.
      // The framework then calls build, below,
      // which updates the visual appearance of the app.

      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>[
        Product(name: 'Eggs'),
        Product(name: 'Flour'),
        Product(name: 'Chocolate chips'),
      ],
    ),
  ));
}

ShoppingList 是一个状态元件,它使用 ShoppingListItem 作为子元件,来呈现视图。
当状态元件被插入树,那么它在树中的位置将绑定到它所创建的状态对象。
如果该状态元件(ShoppingList )的父元件发生重构,那么ShoppingList 元件也创建 新的实例重构,但是之前所创建的状态对象不会重构,会根据位置重新绑定到新的状态元件,而不是调用createState再来创建一个状态对象。

在状态对象中,我们可以利用widget成员变量来访问有状态元件。如果状态元件的父元件发生重构,创建一个新的状态元件,那么状态对象会重新绑定这个状态元件,并同时调用didUpdateWidget() 。

setState是用来通知状态更改的,虽然状态有没有真的更改不知道,但他一定会重建状态元件的子元件。其背后的行为是标记该状态元件为脏,并将其加入到重建点节点名单,在屏幕刷新时就会执行此部分的重渲(所谓的渲染,就是得到一堆的显示编码字,发送给显示硬件,后面的显示是显示设备的事情了)。

元件的生命周期

State对象的生命周期
1、元件在构建时,会被同步读取
2、在元件的存在期可能会被改变。元件的实现者来保证 state 改变时,使用 setState 发布通知
3、State 对象是在当一个 StatefulWidget 被充气并被插入到元件树时,通过调用 StatefulWidget.createState() 方法来创建的。
4、一个 StatefulWidget 实例可以被多次充气,比如,这个元件出现在树中多个位置,所以就可能创建多个State对象。
5、同样,如果一个 StatefulWidget 元件被从元件树上移除,然后又插入到树中,将会重新创建一个新的 state 对象。

1、[mounted]
当State对象被创建时,它被关联到一个 BuildContext 对象中。state 对象与 BuildContext 对象的关联关系是永恒不变的。
但是 BuildContext 对象与自身的子树,可以在树中自由移动。从这一点,我们可以认为 state 对象此时的周期是【挂载】。
2、[initState] 调用State.initState方法,重写这个方法,可以在State 对象创建时,做一些初始化的执行操作。此时可通过访问 State的context 与 widget 两个成员变量来初始化一些信息。
3、[didChangeDependencies] 当 InheritedWidget 元件发生改变或元件在树中发生位置移动时,会再次调用此方法。
4、此时,State对象初始化完成,然后可以任意次调用State.build方法获取子树对应的用户UI的描述信息。State对象可以自然地通过State.setState方法来请求重建子树,这表示State的内部状态的改变对子树及用户UI产生了影响。
5、[didUpdateWidget] 自身的setState不会引发此方法,只有父元件的setState才会引发子元件的此方法。在请求重建状态对象的其间,父元件会在这个位置重绘状态元件,但是使用与原来相同的runtimeType与Widget.key。
widget属性会指向新的元件,然后调用State.didUpdateWidget方法,用来反馈这个变化,比如执行过渡动画。然后始终会跟随调用build.
在setState方法中手动调用didUpdateWidget是多余的。
6、在开发期间使用热重载时,reassemble方法会被调用,此时提供了一个机会来初始化数据,以弥补不能再执行initState方法的不足。
7、如果状态元件的子树中包含的子状态元件,该子状态元件被移除,子状态对象的deactivate被调用。此时可以用来做一些与该对象的清除工作。比如某个祖先对象指各这个对象的链接。
8、动画结束时,该子状态元件可能重新插入到树中的其它部分。此时,会调用build方法,来使得State对象有一次机会来适配新的位置。
所以State对象会延迟释放,直到调用dispose方法。
9、如果子状态元件直接被移除,并未重新插入到新的位置,则会调用dispose方法。这表示State对象也永久释放。此时可以执行一些清除工作,比如停止动画。
10、当dispose方法执行完后,我们认为State对象已经卸载(unmounted),mounted属性为false,此时调用setState方法会报异常。State对象的生命周期已经结束。

Keys与全局Keys

利用keys可以在元件重构时进行准确的逻辑对比,否则框架使用屏幕的视觉位置进行对比。
在重渲时,如何对比差异?如果利用key进行对比,效率会高很多。尤其是状态绑定时,当屏幕滚动,状态对象是根据位置来识别它应该绑定到哪个对象,还是根据key来绑定到哪个对象呢?当然是根据key来判断绑定到哪个对象更高效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值