Json的序列化与反序列化
由于
Flutter
不支持运行时反射,无法实现像Gson
等ORM框架。在Flutter
中,官方给出了两种方式:借助
dart:convert
库进行JSON
序列化并手动设置到模型。它跟下面注解生成代码后的原理一样。一个是手动写的,一个是自动生成的。利用注解
JsonSerialization
来自动生成代码添加依赖:
1 2 3 4 5 6
dependencies: json_annotation: ^2.0.0 dev_dependencies: build_runner: ^1.0.0 json_serializable: ^2.0.0
写一个模型。其中
part
的部分,标识代码需要生成的dart文件名称,作为该源码的一部分。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import 'package:json_annotation/json_annotation.dart'; part 'user.g.dart'; @JsonSerializable() class User { String name; String email; User(this.name, this.email); factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); Map<String, dynamic> toJson() => _$UserToJson(this); }
接着,命令行执行代码生成。有两种方式:
1 2 3 4 5
# 一次性生成 flutter packages pub run build_runner build # 持续性生成,会在后台运行一个watcher。后续的都会自动生成代码。 flutter packages pub run build_runner watch
运行完命令之后,会看到同目录下多了一个dart文件:
user.g.dart
。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'user.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** User _$UserFromJson(Map<String, dynamic> json) { return User(json['name'] as String, json['email'] as String); } Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{'name': instance.name, 'email': instance.email};
使用示例如下:
1 2 3 4 5 6
//反序列化 Map userMap = jsonDecode(jsonString); var user = User.fromJson(userMap); //序列化 //这里不需要再调用`user.toJson()`,因为`jsonEncode` 会内部调用该方法。 String json = jsonEncode(user);
状态栏、底部虚拟按键、全屏的代码设置
1 2 3 4 5 6 7 8
static fullScreen() { //不显示顶部状态栏以及底部虚拟按键 SystemChrome.setEnabledSystemUIOverlays([]); } static normalScreen() { SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]); }
ThemeData 简单解析
MaterialApp
下的theme
可以设置一系列的主题颜色,包括暗黑主题、ButtonThemeData、InputDecorationTheme等。后续再深入。Button控件
按钮控件有
RaisedButton
、DropdownButton
、SimpleDialogOption
、IconButton
、InkWell
、RawMaterialButton
。通过包裹ButtonTheme
可以设置按钮的宽高、背景颜色等。让子控件充满父控件的宽度
通过设置
width: double.infinity
可以实现,某些情况下会报错(跟父控件的BoxConstraints有关,待深入)。关于
Stack
控件类似android中的
FrameLayout
, 适合有视图重叠的绝对布局它是多孩子模型;它的孩子又分成:
positioned
widget。 即用Positioned
控件包裹的widget。non-positioned
widget
stack
会调整自身的size, 去包含所有的non-positioned
的widget。然后,positioned
的widget根据该size去布局。positioned
widget 通过调整top/bottom/left/right
来改变位置大小。如设置left:0;right:0
可使自身与stack
同宽。
TabBar 的简单使用
先看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
class ... with TickerProviderStateMixin { TabController _tabController = TabController(length: 2, vsync: this); TabBar( tabs: [Tab(text: "外部用户"), Tab(text: "内部用户")], controller: _tabController, isScrollable: true, indicatorColor: Colors.white, indicatorWeight: 2, indicatorSize: TabBarIndicatorSize.tab, labelPadding: EdgeInsets.only(left: 45, right: 45), labelColor: Colors.white, labelStyle: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), unselectedLabelColor: Colors.white, unselectedLabelStyle: TextStyle(fontSize: 16), ) }
TabBar
涉及到动画,需要minixTickerProviderStateMixin
TabBar
中indicator相关的都是与底部线条相关, label与tab文件相关:indicatorColor
: 底部线条颜色indicatorWeight
: 底部线条厚度indicatorSize
: 底部线条大小。有两种:TabBarIndicatorSize.tab
(跟Tab同宽)、TabBarIndicatorSize.label
(跟字体同宽)
通过
TabController.index
可以拿到当前选中的tab
位置
TextField的简单使用
obscureText
设置输入是否隐藏- 通过
TextEditingController.text
获取输入值 InputDecoration
中的border有三种类型:UnderlineInputBorder
下划线,默认是这个OutlineInputBorder
长方形的边框InputBorder.none
无边框
键盘弹出时window resize导致页面变形
通过设置
Scaffold.resizeToAvoidBottomPadding
false 来避免该问题。转场动画
在
Flutter
中默认的转场动画是自下而上,而主流的转场动画是水平移动。借助PageRouteBuilder
可自定义转场动画,代码示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import 'package:flutter/material.dart'; class HorizontalSlideRoute extends PageRouteBuilder { HorizontalSlideRoute(Widget widget) : super( opaque: true, pageBuilder: (_, __, ___) => widget, transitionDuration: Duration(milliseconds: 200), transitionsBuilder: (_, animation, secondaryAnimation, child) { return SlideTransition( position: Tween<Offset>(begin: Offset(1.0, 0.0), end: Offset.zero) .animate(animation), child: child); }); }
其中最主要是
transitionsBuilder
属性,其中:Animation<double> animation
指新入栈的页面的过程动画,Animation<double> secondaryAnimation
指当前页面的过程动画。child
就是pageBuilder
返回的widget, 也就是新的页面。- 借助
SlideTransition
来实现水平移动动画。
更进一步,封装一个转场类,避免每次都要传入该类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
import 'package:demo1/util/HorizontalSlideRoute.dart'; import 'package:flutter/widgets.dart'; class PRoute { static PRoute _instance; Map<String, Widget> routeMap = {}; PRoute._default(); factory PRoute.get() { if (_instance == null) { _instance = PRoute._default(); } return _instance; } pushNameH(BuildContext context, String name) { if (routeMap.containsKey(name)) { pushH(context, routeMap[name]); } } replaceNameH(BuildContext context, String name) { if (routeMap.containsKey(name)) { replaceH(context, routeMap[name]); } } pushH(BuildContext context, Widget widget) { Navigator.push(context, HorizontalSlideRoute(widget)); } replaceH(BuildContext context, Widget widget) { Navigator.pushReplacement(context, HorizontalSlideRoute(widget)); } back(BuildContext context) { Navigator.canPop(context)? Navigator.pop(context) : print("can not back"); } dismiss(BuildContext context) { Navigator.canPop(context)? Navigator.pop(context) : print("can not dismiss"); } }
在
main()
开始时可设置route映射:1 2 3 4 5 6
void main() { PRoute.get().routeMap = { "login": Login(), }; runApp(MyApp()); }
最后使用:
1
PRoute.get().replaceNameH(context, "login");
关于Redux
在
react-native
中接触到这个概念,然后Flutter
中也有这个。对它的印象不太良好,在Flutter
中使用的话,UI入侵性很强,几个概念也比较晦涩难懂,总的来说就是不好用。作为替代框架,我自己撸了一个简易版,借鉴了android中
EventBus
的概念。源代码如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
typedef EventFunction<T> = Function(T t); class Driver { static Driver _instance; factory Driver.get() { if (_instance == null) { _instance = Driver._default(); } return _instance; } Driver._default(); Map<Type, Set<Function>> registers = {}; register<T>(EventFunction<T> func) { Type t = T; if (registers.containsKey(t)) { var result = registers[t].add(func); print("add result: $result"); } else { print("register $t, $func"); registers[t] = Set(); registers[t].add(func); } } unRegister<T>(EventFunction<T> func) { Type t = T; if (registers.containsKey(t)) { bool result = registers[t].remove(func); print("unregister $t $func, result $result"); } } dispatch(dynamic event) { print("start dispatch: $event, ${event.runtimeType.toString()}"); if (registers.containsKey(event.runtimeType)) { registers[event.runtimeType].forEach((f) { print("run $f"); f(event); }); } } }
过程是这样:
- 将方法注册到
Driver
,方法体内可以调用setState
以及event自身带的值来改变UI。 - 在其他地方,生成一个event(任何类型)。
- 通过
Driver.dispatch(event)
分发出去,符合要求的已注册的方法会自动运行,相当于一个事件冒泡。
代码示例:
- 创建一个event
1 2 3 4 5 6 7 8 9 10 11
class TestEvent { int count; TestEvent(this.count); @override String toString() { return "count: $count"; } }
- 在某个
StatefulWidget State
中创建一个事件并注册:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
class _LoginState extends State<Login> { _LoginState() { Driver.get().register(eventFunc); } void dispose() { super.dispose(); Driver.get().unRegister(eventFunc); } eventFunc(TestEvent event) { setState() { counter = event.count; } } }
- 在其他页面中,通知页面更新:
1 2
TestEvent event = TestEvent(2019); Driver.get().dispatch(event);
- 将方法注册到
Flutter开发总结一
This post is licensed under CC BY 4.0 by the author.
Contents