Flutter Widget框架及演示

1.介绍

Flutter Widget采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用widget构建你的UI。 Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改

Flutter应用本身就是一个widget,大部分widget都有一个build()方法。在应用程序的build方法中创建会在设备上显示的widget。

2.基础 Widget

Flutter有一套丰富、强大的基础widget,其中以下是很常用的:

  • Text:该 widget 可让创建一个带格式的文本。

  • Row、 Column: 这些具有弹性空间的布局类Widget可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。其设计是基于web开发中的Flexbox布局模型。

  • Stack: 取代线性布局 (译者语:和Android中的LinearLayout相似),Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝度定位(absolute positioning )布局模型设计的。

  • Container: Container 可让您创建矩形视觉元素。container 可以装饰为一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。

以下是一些简单的Widget,它们可以组合出其它的Widget:

// TODO 自定义appbar及material页面
// 在MyAppBar中创建一个Container,高度为56像素(像素单位独立于设备,为逻辑像素),其左侧和右侧均有8像素的填充。
// 在容器内部, MyAppBar使用Row 布局来排列其子项。
// 中间的title widget被标记为Expanded, ,这意味着它会填充尚未被其他子项占用的的剩余可用空间。
// Expanded可以拥有多个children, 然后使用flex参数来确定他们占用剩余空间的比例。
class MyAppBar extends StatelessWidget {
  MyAppBar({this.title});

  // Widget子类中的字段往往都会定义为"final"
  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56,
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue),
      //row 水平方向的线性布局(linear layout)
      child: Row(
        //列表项的类型是 
        children: <Widget>[
          IconButton(
            icon: Icon(Icons.menu),
            tooltip: 'navigatorMenu',
            onPressed: null, //null 会禁用button
          ),
          // Expanded expands its child to fill the available space.
          Expanded(
            child: title,
          ),
          IconButton(
            icon: Icon(Icons.search),
            tooltip: "search",
            onPressed: null,
          )
        ],
      ),
    );
  }
}

// MyScaffold 通过一个Column widget,在垂直方向排列其子项。
// 在Column的顶部,放置了一个MyAppBar实例,将一个Text widget作为其标题传递给应用程序栏。
// 将widget作为参数传递给其他widget是一种强大的技术,可以让您创建各种复杂的widget。
// 最后,MyScaffold使用了一个Expanded来填充剩余的空间,正中间包含一条message。
class MyScaffold extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //// Material 是UI呈现的“一张纸”
    return Material(
      //Column is 垂直方向的线性布局
      child: Column(
        children: <Widget>[
           MyAppBar(
            title: Text(
              'example title',
              style: Theme.of(context).primaryTextTheme.title,
            ),
          ),
          Expanded(
            child: Center(
              child: Text('hello world'),
            ),
          )
        ],
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //为了继承主题数据,widget需要位于MaterialApp内才能正常显示, 因此我们使用MaterialApp来运行该应用
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
     home: MyScaffold(),
    );
  }
}

运行效果:
Flutter Widget框架及演示_第1张图片

3.使用 Material 组件

Flutter提供了许多widgets,可帮助您构建遵循Material Design的应用程序。Material应用程序以MaterialApp widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator可以让您的应用程序在页面之间的平滑的过渡。 是否使用MaterialApp完全是可选的,但是使用它是一个很好的做法。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
   return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
     home: TutorialHome(),
    );
  }
}

// 从MyAppBar和MyScaffold切换到了AppBar和 Scaffold widget
class TutorialHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //该 Scaffold widget 需要许多不同的widget的作为命名参数,其中的每一个被放置在Scaffold布局中相应的位置
    return Scaffold(
      //我们给参数leading、actions、title分别传一个widget
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.menu),
          tooltip: 'Navigaton Menu',
          onPressed: null,
        ),
        title: Text('example Title'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            tooltip: 'search',
            onPressed: null,
          ),
        ],
      ),
      body: Center(
        child: Text('this is body center'),
      ),
      floatingActionButton: FloatingActionButton(
        tooltip: 'ADD',
        child: Icon(Icons.add),
        onPressed: null,
      ),
    );
  }
}

运行效果:
Flutter Widget框架及演示_第2张图片

4.处理手势

大多数应用程序包括某种形式与系统的交互。构建交互式应用程序的第一步是检测输入手势。让我们通过创建一个简单的按钮(自定义按钮)来了解它的工作原理:

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //该GestureDetector widget并不具有显示效果,而是检测由用户做出的手势。
    //当用户点击Container时, GestureDetector会调用它的onTap回调, 在回调中,将消息打印到控制台。
    //您可以使用GestureDetector来检测各种输入手势,包括点击、拖动和缩放。
    // 许多widget都会使用一个GestureDetector为其他widget提供可选的回调。 
    // 例如,IconButton、 RaisedButton、 和FloatingActionButton ,
    // 它们都有一个onPressed回调,它会在用户点击该widget时被触发
    return new 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 MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
   return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('test widget'),
        ),
        body: Center(
          child: MyButton(),
        ),
      ),
    );
  }
}

运行效果:
Flutter Widget框架及演示_第3张图片

5.根据用户输入改变widget

statefulWidget, statelessWidget

在Flutter中,我们平时自定义的widget,一般都是继承自StatefulWidget或StatelessWidget(并不是只有这两种)。

StatelessWidget

如果一个控件自身状态不会去改变,创建了就直接显示,不会有色值、大小或者其他属性的变化,无状态widget从它们的父widget接收参数,当一个widget被要求构建时,它使用这些存储的值作为参数来构建widget,这种widget一般都是继承自StatelessWidget,常见的有Container、ScrollView等。

  • 对于StatelessWidget,build方法会在如下三种情况下调用,
    1.widget第一次被插入到树中;
    2.widget的父节点更改了配置(configuration);
    3.widget依赖的InheritedWidget改变了
StatefulWidget

如果一个控件需要动态的去改变或者相应一些状态,例如点击态、色值、内容区域等,那么一般都是继承自StatefulWidget,常见的有CheckBox、AppBar、TabBar等。

  • StatefulWidget的两个主要类别:
    1.在initState中创建资源,在dispose中销毁,但是不依赖于InheritedWidget或者调用setState方法,这类widget基本上用在一个应用或者页面的root;
    2.使用setState或者依赖于InheritedWidget,这种在营业生命周期中会被重建(rebuild)很多次。
示例:
///statefulWidget
//无状态widget从它们的父widget接收参数, 它们被存储在final型的成员变量中。
// 当一个widget被要求构建时,它使用这些存储的值作为参数来构建widget。
//为了构建更复杂的体验 - 例如,以更有趣的方式对用户输入做出反应 - 应用程序通常会携带一些状态。
// Flutter使用StatefulWidgets来满足这种需求。
class Counter extends StatefulWidget {
  @override
  //createState函数require
  // State createState() {
  //   return _CounterState();
  // }
  //简写
  _CounterState createState() => _CounterState();
}

//您可能想知道为什么StatefulWidget和State是单独的对象。
//在Flutter中,这两种类型的对象具有不同的生命周期: Widget是临时对象,用于构建当前状态下的应用程序,
//而State对象在多次调用build()之间保持不变,允许它们记住信息(状态)。
class _CounterState extends State {
  int _counter = 0;
  void _increment() {
    setState(() {
      _counter++;
    });
  }
  /*
  // 不拆分widget
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        RaisedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
        new Text('count: $_counter'),
      ],
    );
  }
  */
  //widget 功能分离
  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
         CounterIncrementor(onPressed: _increment),//new对象并传入回调
         CounterDisplay(count: _counter,),
      ],
    );
  }
  /*注意我们是如何创建了两个新的无状态widget的!
  我们清晰地分离了 显示 计数器(CounterDisplay)和 更改 计数器(CounterIncrementor)的逻辑。 
  尽管最终效果与前一个示例相同,但责任分离允许将复杂性逻辑封装在各个widget中,同时保持父项的简单性。*/
}
/*
widget结构层次的不同部分可能有不同的职责;
 例如,一个widget可能呈现一个复杂的用户界面,其目标是收集特定信息(如日期或位置),
 而另一个widget可能会使用该信息来更改整体的显示。
 在Flutter中,事件流是“向上”传递的,而状态流是“向下”传递的,重定向这一流程的共同父元素是State。
 */
//无状态的widget 显示count字符串
class CounterDisplay extends StatelessWidget {
  CounterDisplay({this.count});
  final int count;
  @override
  Widget build(BuildContext context) {
    return Text('Count: $count');
  }
}
//无状态的widget 显示按钮并接收回调参数
class CounterIncrementor extends StatelessWidget{

  CounterIncrementor({this.onPressed}); //接受调用参数 ->this.
  final VoidCallback onPressed; //接受的参数,无返回的回调
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: onPressed,
      child: Text('increment'),
    );
  }
}

运行效果:
Flutter Widget框架及演示_第4张图片
在Flutter中,StatefulWidget 和 state 这两种类型的对象具有不同的生命周期: Widget是临时对象,用于构建当前状态下的应用程序,
而State对象在多次调用build()之间保持不变,允许它们记住信息(状态)。

  • state在statefulWidget创建时可以被同步读取
  • state在widget声明周期中可能会被改变

state生命周期

State的生命周期有四种状态:

  • created:当State对象被创建时候,State.initState方法会被调用;
  • initialized:当State对象被创建,但还没有准备构建时,State.didChangeDependencies在这个时候会被调用;
  • ready:State对象已经准备好了构建,State.dispose没有被调用的时候;
  • defunct:State.dispose被调用后,State对象不能够被构建。
    Flutter Widget框架及演示_第5张图片

setState

State中比较重要的一个方法是setState,当修改状态时,widget会被更新。比方说点击CheckBox,会出现选中和非选中状态之间的切换,就是通过修改状态来达到的。

查看setState源码,在一些异常的情况下将会抛出异常:

  • 传入的为null;

  • 处在defunct阶段;

  • created阶段还没有被加载(mounted);

  • 参数返回一个Future对象。

检查完一系列异常后,最后调用代码如下:

_element.markNeedsBuild();
markNeedsBuild内部,则是通过标记element为diry,在下一帧的时候重建(rebuild)。可以看出setState并不是立即生效,它只是将widget进行了标记,真正的rebuild操作,则是等到下一帧的时候才会去进行

6. 综上练习->购物车demo

class Product {  //product 模型
//TODO const 初始化一次
  const Product({this.name});
  final String name;
}
//定义回调
// TODO 注意小写的void
typedef void CartChangeCallBack(Product product, bool inCart);

//cell  StatelessWidget  不做状态更改回调状态给父widget进行状态更改(刷新列表)
class ShoppingListItem extends StatelessWidget {

  ShoppingListItem({Product product, this.inCart, this.onCartChange})
      : product = product,
      super(key: ObjectKey(product)); //object key,oc中的itentifier理解为重用
  //参数带_ 为私有
  final Product product;
  final bool inCart;
  final CartChangeCallBack onCartChange;

  //根据上下文获取颜色、字体 
  Color _getColor(BuildContext context) {
    //主题依赖于BuildContext因为树的不同部分有不同的主题,BuildContext表示当前主题
    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( //cell
      //当用户点击列表项时,widget不会直接修改其inCart的值。相反,widget会调用其父widget给它的onCartChanged回调函数。 
      //此模式可让您在widget层次结构中存储更高的状态,从而使状态持续更长的时间。
      onTap: () {
        onCartChange(product, !inCart);  //点击列表回调商品及购物车状态
      },
      leading: CircleAvatar( //头像
        backgroundColor: _getColor(context),
        child: Text(product.name[0]), //第一个字符
      ),
      title: Text(product.name, style: _getTextStyle(context),),  //名称
    );
  }
}

//StatefulWidget
class ShoppingList extends StatefulWidget {
  ShoppingList({Key key, this.products}) : super(key: key);
  final List<Product> products;
  @override
  // State createState() {
  //   return new _ShoppingListState();
  // }
   _ShoppingListState createState() => _ShoppingListState();
}

//Scaffold
//TODO  State 一定要指明父类要不然会找不到products
class _ShoppingListState extends State<ShoppingList> {
  Set<Product> _shoppingCart = Set<Product>();

  void _handleCartChange(Product product, bool inCart) {
    setState(() {
      if (inCart) {
        _shoppingCart.add(product);
      } else {
        _shoppingCart.remove(product);
      }
    });
  }


  ///响应widget生命周期事件
  /*
  在StatefulWidget调用createState之后,框架将新的状态对象插入树中,然后调用状态对象的initState。
   子类化State可以重写initState,以完成仅需要执行一次的工作。
    例如,您可以重写initState以配置动画或订阅platform services。
    initState的实现中需要调用super.initState。*/
  // @override
  // void initState() {
  //   // TODO: implement initState
  //   super.initState();
  // }

  /*当一个状态对象不再需要时,框架调用状态对象的dispose。 您可以覆盖该dispose方法来执行清理工作。
  例如,您可以覆盖dispose取消定时器或取消订阅platform services。
   dispose典型的实现是直接调用super.dispose。*/
  // @override
  // void dispose() {
  //   // TODO: implement dispose
  //   super.dispose();
  // }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:  AppBar(
        title:  Text('Shooping List'),
      ),
      body: ListView(
        padding: EdgeInsets.symmetric(vertical: 8.0),//垂直cell间距
        children: widget.products.map((Product product) {
          return ShoppingListItem(
            product: product,
            inCart: _shoppingCart.contains(product),
            onCartChange: _handleCartChange,
          );
        }).toList(),
      ),
    );    
  }
}

// TODO APP端的入口
/// APP端的入口
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
     home: ShoppingList(
      products: <Product>[
        Product(name: 'Eggs'),
        Product(name: 'Flour'),
        Product(name: 'Chocolate chips'),
      ],
    ),
    );
  }
}

运行效果:
Flutter Widget框架及演示_第6张图片
关于key的使用及理解入口
demo代码入口

参考链接:
https://flutterchina.club/widgets-intro/
https://www.imooc.com/article/details/id/31493

你可能感兴趣的:(Flutter)