Flutter使用小结

如何启动

启用安卓模拟器

$ 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 i;
    }
// 改为:

    class A {
    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 json) => _$TagFromJson(json);

  Map 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
    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: [
        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: [
      // 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 with SingleTickerProviderStateMixin {

}

/// 2.声明
  AnimationController controller;
  Animation 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 = [];
var addresses = {};

// 坏的范例
var points = new List();
var addresses = new Map();

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 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使用小结)