美文网首页Flutter圈子
移动端混合开发Flutter入坑(二)widgets介绍

移动端混合开发Flutter入坑(二)widgets介绍

作者: fzkun | 来源:发表于2019-01-19 16:01 被阅读9次

    widgets介绍

    • 编写“Hello World”
    • 基础Widget
    • 使用Materia组件
    • 手势处理
    • Widget状态响应处理
    • 购物车综合例子

    一、编写“Hello World”

    最小的Flutter应用程序只需调用runApp()函数使用widget

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(
        Center(
          child: Text(
            'Hello, world!',
            textDirection: TextDirection.ltr,
          ),
        ),
      );
    }
    
    image.png

    runApp()函数返回Widget并使其成为Widget树的root父级。 在此示例中,Widget树由两个Widget小部件组成,即Center Widget小部件及其子窗口Text Widget小部件。 框架强制root父级widget覆盖屏幕,这意味着Text Widget “Hello,world”最终以屏幕为中心。 在此实例中需要指定文本方向textDirection; 当使用MaterialApp widget时,这将由你负责。

    二、基础widgets

    Flutter附带了一套功能强大的基础widget,其中以下是非常常用的widget:

    • Text: 创建一系列样式文本。
    • Row, 水平(行)和垂直(列)方向上创建灵活的布局。 它的设计基于web的flexbox布局模型。
    • Stack: Stack widget不是线性定向(水平或垂直),而是允许您按照绘制顺序将widget堆叠在一起。 然后,您可以在堆栈的子项上使用Positioned(坐标) widget,以相对于堆栈的顶部,右侧,底部或左侧边缘定位它们。 堆栈基于Web的绝对定位布局模型。
    • Container: Container widget允许您创建矩形可视元素。 容器可以使用BoxDecoration进行装饰,例如背景,边框或阴影。 Container也可以应用其大小的边距,填充和约束。 另外,可以使用矩阵在三维空间中变换容器。

    下面是组合多种widget的例子:

    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: 88.0, // in logical pixels
          padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 44.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.title,
                ),
              ),
              Expanded(
                child: Center(
                  child: Text('Hello, world!'),
                ),
              ),
            ],
          ),
        );
      }
    }
    
    void main() {
      runApp(MaterialApp(
        title: 'My app', // used by the OS task switcher
        home: MyScaffold(),
      ));
    }
    
    image.png

    三、使用Material组件

    使用Material组件可以快速搭建app页面架构, Material应用程序以MaterialApp为root 父级widget,该widget在应用程序的根目录下构建了许多有用的widget,包括Navigator,它管理“路由”。 Navigator可让您在应用程序的屏幕之间平滑过渡。 使用MaterialApp Widget是一个构建app页面的好方法。

    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,
          ),
        );
      }
    }
    
    image.png

    四、手势处理

    基于上一段代码自定义一个带相应效果的button:

    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'),
            ),
          ),
        );
      }
    }
    
    image.png

    GestureDetector Widget没有可视化,是是检测用户做出的手势。 当用户点击Container时,GestureDetector会调用其onTap回调,在这种情况下会向控制台打印一条消息。 您可以使用GestureDetector检测各种输入手势,包括点击,拖动和缩放。

    许多Widget使用GestureDetector为其他Widget提供可选的回调。 例如,IconButtonRaisedButtonFloatingActionButton Widget具有onPressed回调,当用户点击小部件时会触发这些回调。

    五、widget状态响应处理

    到目前为止,我们只使用了StatelessWidgetStatelessWidget从其父widget接收参数,它们存储在成员变量中。 当要求构建widget时,它使用这些存储的值为它创建的widget派生新的参数。

    为了构建更复杂的widget 例如:

    • 以更有趣的方式对用户输入做出反应
    • 应用程序通常带有一些状态。

    Flutter使用StatefulWidgets实现这个方法。 StatefulWidgets是特殊的Widget,它们知道如何生成State对象,然后用于保存状态。
    考虑下面基本的例子:

    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 we changed _counter without calling
          // setState(), then the build method would not 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>[
            RaisedButton(
              onPressed: _increment,
              child: Text('Increment'),
            ),
            Text('Count: $_counter'),
          ],
        );
      }
    }
    
    image.png

    可能你们会疑惑为什么StatefulWidgetState是单独的声明。 在Flutter中,这两种类型的对象具有不同的生命周期。 Widget是临时对象,用于构造应用程序当前状态的表示。 另一方面,状态对象在调用build()之间是持久的,允许它们缓存信息。

    以下稍微复杂的示例显示了它在实践中的工作原理:

    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<Counter> {
      int _counter = 0;
    
      void _increment() {
        setState(() {
          ++_counter;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Row(children: <Widget>[
          CounterIncrementor(onPressed: _increment),
          CounterDisplay(count: _counter),
        ]);
      }
    }
    

    注意我们是如何创建两个新的StatelessWidget,干净地分离显示计数器(CounterDisplay)更改计数器(CounterIncrementor)的问题。 尽管最终结果与前面的示例相同,但是责任分离允许将更大的复杂性封装在各个widget中,同时保持父级的简单性。

    六、购物车综合例子

    以下是一个更完整的购物车示例,汇集了上面介绍的知识点,
    显示出售的各种产品,并维护购物车的库存。

    • 首先定义表示类ShoppingListItem:
    class Product {
      const Product({this.name});
      final String name;
    }
    
    typedef void CartChangedCallback(Product product, bool inCart);
    
    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) {
        // 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 Widget遵循StatelessWidget的通用模式。 它将它在构造函数中接收的值存储在最终成员变量中,然后在构建函数中使用它们。 例如,inCart bool值在两个视觉外观之间切换:一个使用当前主题的主要颜色,另一个使用灰色。

    当用户点击列表item时,Widget不会直接修改其inCart值。 相反,Widget调用从其父Wdiget接收的onCartChanged函数。 此模式允许您在Widget层次结构中存储更高的状态,这会导致状态持续更长时间。 在极端情况下,传递给runApp()Widget上存储的状态在应用程序的生命周期内持续存在。

    当父级接收到onCartChanged回调时,父级会更新其内部状态,从而触发父级重建并使用新的inCart值创建ShoppingListItem的新实例。 尽管父级在重建时会创建ShoppingListItem的新实例,但该操作低耗,因为该框架将新构建的Widget与先前构建的Widget进行比较,并仅将差异应用于基础RenderObject

    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, we 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'),
          ],
        ),
      ));
    }
    
    image.png
    image.png

    ShoppingList类继承了StatefulWidget,这意味着此Widget存储可变状态。 当ShoppingList Widget首次插入树中时,框架会调用createState函数来创建_ShoppingListState的新实例,以与树中的该位置相关联。 (请注意,State的子类通常以前导下划线命名,表示它们是私有实现细节。)当此Widget的父窗口重建时,父窗口创建ShoppingList的新实例,但框架重用已在树中的_ShoppingListState实例 而不是再次调用createState

    欢迎关注我的公众号,了解更多技术博文。


    kk工作室

    相关文章

      网友评论

        本文标题:移动端混合开发Flutter入坑(二)widgets介绍

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