美文网首页
Flutter使用小结

Flutter使用小结

作者: vincent_z | 来源:发表于2019-12-24 15:09 被阅读0次

    如何启动

    启用安卓模拟器

    $ emulator -list-avds   // 返回可用安卓模拟器列表
    $ emulator @AVD_name    // 启动某个模拟设备
    

    启用 ios 模拟器

    $ open -a simulator // 直接打开xcode 模拟器
    

    启动项目

    $ flutter devices   // 返回可用模拟器列表
    $ flutter run -d 模拟器xxx   // 启用id为xxx的模拟器
    

    热更新启动方式

    $ flutter run -d 模拟器xxx --hot  // 启用id为xxx的模拟器
    

    不能使用 Hot Reload 的场景

    1. 代码出现编译错误的不能使用 Hot Reload
    2. 代码更改会影响 APP 状态的不能使用 Hot Reload
    3. 全局变量( global variables)和静态字段(static fields)的更改不能使用 Hot Reload
    // 如下的代码:
    
        final sampleTable = [
        Table("T3"),
        ];
        /// 运行 App 之后,如果做了如下的更改:
    
        final sampleTable = [
        Table("T3"),
        Table("T10"),    // 修改这里的值
        ];
    
    1. main() 方法里的更改不能使用 Hot Reload
    2. 枚举类型更改为常规的类或者常规的类变为枚举类型也不能使用 HotReload
    3. 修改通用类型声明也不能使用 HotReload
    // 例如,如下的例子:
    
        class A<T> {
        T i;
        }
    // 改为:
    
        class A<T, V> {
        T i;
        V v;
        }
    

    如何定义 model(6 步)

    JSON 和序列化

    // 1.导入库
    import 'package:json_annotation/json_annotation.dart';
    
    // 在Dart中,可以在同一库中访问私有成员。 使用import可以导入库,并且只能访问其公共成员。 使用part/part of,可以将一个库分成几个文件,并且私有成员可访问这些文件中的所有代码。
    // 2. part xx.g.dart库,将在我们运行生成命令后自动生成
    part 'tag.g.dart';
    
    /// 3.装饰器引用,这个标注是告诉生成器,这个类是需要生成Model类的
    @JsonSerializable()
    
    /// 4.定义类
    class Tag {
      String id;
      String tagName;
    
      Tag(
        this.id,
        this.tagName,
      );
    
      /// 5.定义 方法名为 类名.fromJson和_$类名ToJson 方法,格式如下
      factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
    
      Map<String, dynamic> toJson() => _$TagToJson(this);
    
      /// 6.执行命令生成json序列化代码
      /// $ flutter packages pub run build_runner build
    }
    

    flutter packages pub run build_runner watch 在项目根目录下运行来启动watcher。只需启动一次观察器,然后并让它在后台运行,这是安全的

    相关部件(Widget)使用说明

    一、Scaffold 使用

     Scaffold({
        this.appBar,    // 顶部导航
        this.body,  // 主体
        this.drawer,    // 页面左抽屉,从左往右抽出
        this.endDrawer, // 页面右抽屉,从右往左抽出
        this.bottomNavigationBar,   // 底部导航
        this.backgroundColor,   // 背景色
      })
    

    二、AppBar 使用及扩展

    AppBar 设计上涵盖了左中右下四个方向的占位,对应的属性分别是 leading, title, actions, bottom,leading,actions 带有默认 padding 和 margin 值

    AppBar({
        this.leading,   // Widget
        this.automaticallyImplyLeading = true,  // bool 路由栈存在上一个路由的情况下,leading为返回箭头
        this.title, // Widget,通常是Text
        this.actions,   // List<Widget>
        this.bottom,    // PreferredSizeWidget,通常是TabBar
        this.elevation, // double 高度阴影
        this.backgroundColor,   // Color
        this.centerTitle,   // bool 居中标题
        ...
      })
    

    定制 AppBar

    首页 AppBar 模块 bottom 部分组成是 Tabbar + 频道选择,非 PreferredSizeWidget 类型的部件,实现方式是定制一个 PreferredSizeWidget,实现抽象类未实现的方法,在实现 build 的部分复合 Tabbar 和定制化的部分

    class ZnTabBar extends StatefulWidget implements PreferredSizeWidget {
      /// Creates a [Size] with the given [height] and an infinite [width].
      @override
      Size get preferredSize {
        return Size.fromHeight(/* ZnTabBar所依赖的高度 */);
      }
      /// ...
       @override
      Widget build(BuildContext context) {
        return Row(
          children: <Widget>[
            Expanded(child: TabBar()),
            CustomPart()        // 定制化部分
        )
      }
    
    

    三、使用 GestureDetector

    GestureDetector 是一个用于手势识别的功能性组件,通过它可以识别各种手势。GestureDetector 实际上是指针事件的语义化封装;

    使用场景

    1、类似 Text、TextField 等部件没有监听点击的事件;
    2、扁平化交互,Material 组件库中提供了多种按钮组件如 RaisedButton、FlatButton、OutlineButton 等,它们都是直接或间接对 RawMaterialButton 组件的包装定制,Material 库中的按钮都有如下相同点:

    • 按下时都会有“水波动画”
    • 有一个 onPressed 属性来设置点击回调,当按钮按下时会执行该回调,如果不提供该回调则按钮会处于禁用状态,禁用状态不响应用户点击;
        GestureDetector(
            child: Text('点击'),
            onTap: () {
                // 处理点击
            }
        )
    

    其它,如实现双击、长按、拖动、滑动等

    GestureDetector({
        this.onDoubleTap,   // 双击
        this.onLongPress,   // 长按
        this.onVerticalDragStart,// 垂直拖动
        this.onHorizontalDragStart, // 水平搬动
        this.onPanStart,    // 滑动
        this.onScaleStart,  // 缩放
        ...
    

    四、使用 SingleChildScrollView

    接收一个子组件,当显示内容超过屏幕且不超出太多的情况下使用,解决内容超出可视区域产生边界溢出的情况。

    使用场景

    1、线性布局下,Column 接收一组部件,但内容超出屏高产生垂直边界溢出的情况下;
    2、流式布局下,Row 接收一组部件,当内容超出屏宽且不折行的情况下;(可使用 Wrap 产生折行)
    3、期望的内容不会超过屏幕太多时,视口不会包含超出屏幕尺寸太多的内容时;(可使用 ListView 或 CustomScrollView 代替)

    五、使用 GridView

    GridView.count

    GridView.count 是 GridView 的命名构造函数,内部使用了 SliverGridDelegateWithFixedCrossAxisCount,通过它可以快速的创建横轴固定数量子元素的 GridView

    GridView.count(
        crossAxisCount: 2,  // 横轴子元素的数量
        crossAxisSpacing: 1.0,  // 横轴方向子元素的间距
        mainAxisSpacing: 1.0,   // 主轴方向的间距
        children: buildGridList(),
        primary:  true // 如果为 true,即使滚动视图没有足够的内容滚动视图也是可滚动的,默认为 false。
        shrinkWrap: true, // shrinkWrap 常用于内容大小不确定情况,如果是无界约束,则 shrinkWrap 必须为 true。
    )
    

    六、CustomScrollView

    CustomScrollView 是可以使用 Sliver 来自定义滚动模型(效果)的组件,这里 Sliver 通常指可滚动组件子元素。

    使用场景

    1、包含 类似 GridView 和 ListView 交互,同时要求整个页面的滑动效果是统一的;
    2、需要结合 FlexibleSpaceBar 实现 Material Design 中头部伸缩;

      CustomScrollView(
        controller: _scrollController,
        slivers: <Widget>[
          // AppBar,包含一个导航栏
          SliverAppBar(
            title: Text(...)   // Widget
          ),
          // GridView
          SliverGrid(
            /// 配置同GridView
            gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 250.0,
              mainAxisSpacing: 1.0,
              crossAxisSpacing: 1.0,
              childAspectRatio: 1.0,
            ),
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                // 返回Widget
              },
              childCount: ..., // 列表长度
            ),
          ),
          //List
          new SliverFixedExtentList(
            itemExtent: 50.0,
            delegate: new SliverChildBuilderDelegate(
                    (BuildContext context, int index) {
                //创建列表项
                return new Container(
                  alignment: Alignment.center,
                  color: Colors.lightBlue[100 * (index % 9)],
                  child: new Text('list item $index'),
                );
              },
              childCount: 50 //50个列表项
            ),
          ),
      )
    

    七、动画

    1、继承 SingleTickerProviderStateMixin 和 TickerProviderStateMixin,通过将 SingleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为 vsync 的值;
    2、两个对象:Animation 和 AnimationController;
    3、Animation 生成指导动画的值,AnimationController 管理 Animation;
    4、当创建一个 AnimationController 时,需要传递一个 vsync 参数,存在 vsync 时会防止屏幕外动画消耗不必要的资源;
    5、使用 Listeners 和 StatusListeners 监听动画状态改变;
    6、释放资源

    vsync 做了什么事?

    vsync 对象会绑定动画的定时器到一个可视的 widget,所以当 widget 不显示时,动画定时器将会暂停,当 widget 再次显示时,动画定时器重新恢复执行,这样就可以避免动画相关 UI 不在当前屏幕时消耗资源。

    使用

    /// 1.定义类
    class ZNAnimation extends StatefulWidget {
      static final String routeName = 'animaton';
    
      @override
      _ZNAnimation createState() => _ZNAnimation();
    }
    class _ZNAnimation extends State<ZNAnimation> with SingleTickerProviderStateMixin {
    
    }
    
    /// 2.声明
      AnimationController controller;
      Animation<double> animation;
    
    /// 3.初始化并设置动画监听
      @override
      initState() {
        super.initState();
    
        controller = AnimationController(
            duration: const Duration(milliseconds: 100), vsync: this);
        // 图片宽高从0变到300
        animation = Tween(begin: 30.0, end: 25.0).animate(controller);
    
        animation.addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            // 动画执行结束时反向执行动画
            controller.reverse();
          } else if (status == AnimationStatus.dismissed) {
            // 动画恢复到初始状态时执行动画(正向)
            setState(() {
              ...
            });
          }
        });
      }
    
    /// 4. 使用
    @override
    Widget build(BuildContext context) {
      return AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, Widget child) {
          return new Container(
              height: animation.value,
              width: animation.value,
              child: xxx, // 接收外部child再由AnimatedBuilder返回并插入widget树中
          );
        },
        child: xxx    // 外部Widget对象
      )
    }
    
    /// 5.释放资源
    
    ```dart
    @override
    dispose() {
      //路由销毁时需要释放动画资源
      controller.dispose();
      super.dispose();
    }
    

    八、其它部件

    1、底部弹窗 showModalBottomSheet
    2、尺寸限制类容器 ConstrainedBox
    3、装饰容器 DecorateBox 可以在其子组件绘制前(或后)绘制一些装饰(Decoration),如背景、边框、渐变等。
    4、剪裁 ClipRRect 将子组件剪裁为圆角矩形

    Effective Dart 及项目开发约定

    Effective Dart: 最佳实践

    一、dart 规范补充

    1、要 尽可能的使用集合字面量来定义集合。

    // 好的范例
    var points = [];
    var addresses = {};
    
    // 坏的范例
    var points = new List();
    var addresses = new Map();
    

    如果有必要还可以提供泛型类型。

    // 好的范例
    var points = <Point>[];
    var addresses = <String, Address>{};
    
    // 坏的范例
    var points = new List<Point>();
    var addresses = new Map<String, Address>();
    

    2、不要 使用 .length 来判断集合是否为空。
    Dart 提供了更加高效率和易用的 getter 函数: .isEmpty 和.isNotEmpty。使用这些函数并不需要对结果再次取非。

    /// 好的范例
    if (lunchBox.isEmpty) return 'so hungry...';
    if (words.isNotEmpty) return words.join(' ');
    
    /// 坏的范例
    if (lunchBox.length == 0) return 'so hungry...';
    if (!words.isEmpty) return words.join(' ');
    

    3、使用 map().toList() 或者 map().toSet() 来 强制立刻执行 map 的方法

    map() 函数返回的对象也是一个 Iterable,该对象是懒求值(lazily evaluated) 的,只有当访问里面的值的时候, map 的方法才被调用。

    4、普通方法使用位置参数定义默认值

      queryCommentList(String videoId, [int pageSize = 15, int pageNo = 1])  {
        // ...
      }
    
      // 调用
      queryCommentList('videoId');
    

    5、类构造方法使用命名参数

    class ZNVideoPlay extends StatefulWidget {
      final Video video;
    
      ZNVideoPlay({this.video});
    
      @override
      State<StatefulWidget> createState() => _VideoPlay();
    }
    
    // 调用
      ZNVideoPlay(video: widget.video),
    
    

    二、文件或库引入方式

    直接使用库导出对象,Flutter 编辑器插件自动导入库绝对路径;

    组件通信的两种方式:

    一、InheritedWidget使用(3 步)

    // 1、定义数据存储部件
    class ShareDataWidget extends InheritedWidget {
      ShareDataWidget({@required this.data, Widget child}) : super(child: child);
    
      final String data; //需要在子树中共享的数据,保存点击次数
    
      //定义一个便捷方法,方便子树中的widget获取共享数据
      static ShareDataWidget of(BuildContext context) {
        return context.inheritFromWidgetOfExactType(ShareDataWidget);
      }
    
      //该回调决定当data发生变化时,是否通知子树中依赖data的Widget
      @override
      bool updateShouldNotify(ShareDataWidget old) {
        //如果返回true,则子树中依赖(build函数中有调用)本widget
        //的子widget的`state.didChangeDependencies`会被调用
        return old.data != data;
      }
    }
    // 2、父Widget使用ShareDataWidget包装子Widget
      ShareDataWidget(
        data: tagId,
        child: VideoList(),
      ))
    // 3、子Widget使用并监听
      @override
      void didChangeDependencies() {
        _initData();
        super.didChangeDependencies();
      }
    
      _initData() async {
        var tagId = ShareDataWidget.of(context).data;
        // Fetch data with tagId
      }
    
    

    相关文章

      网友评论

          本文标题:Flutter使用小结

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