上一篇 Flutter开篇
widget组合与自绘
-
组合--返回widget
class UpdateItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Center(
child: Text("组合demo"),
),
),
body: Column(
children: [
buildTop(),
buildBottom()
],
),
);
}
buildTop() {
return Row(
children: [
Padding(
padding: EdgeInsets.all(10),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.asset(
"images/xiecheng.jpg",
width: 80,
height: 80,
fit: BoxFit.fill,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center, //垂直居中
crossAxisAlignment: CrossAxisAlignment.start, //水平居左
children: [
Text(
"携程你值的拥有携程你值的拥有值的拥有",
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
"2019年12月16日",
maxLines: 1,
style: TextStyle(color: Colors.grey),
),
)
],
),
),
),
Padding(
padding: EdgeInsets.only(right: 10),
child: RaisedButton(
child: Text("下载"),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(18))),//背景
onPressed: onPressed, //点击回调
),
)
],
);
}
buildBottom() {
return Padding(
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, //水平方向距左对齐
children: [
Text(
"""8.4.0新功能介绍:
【酒店】上线低价组合推荐,智能搭配房型,为你省钱又省心
【旅游】一年游季在于夏,快乐出发678,特惠一暑假""",
), //更新文案
Padding(
padding: EdgeInsets.only(top: 15),
child: Text(
"Version 8.4.0·165.3M",
style: TextStyle(color: Colors.grey),
),
)
],
),
);
}
void onPressed() {}
}
- 组合--继承widget
Scaffold
class Scaffold extends StatefulWidget {
/// Creates a visual scaffold for material design widgets.
const Scaffold({
Key key,
this.appBar,
this.body,
this.floatingActionButton,
this.floatingActionButtonLocation,//位置
this.floatingActionButtonAnimator,//动画
this.persistentFooterButtons,//在底部呈现一组button,显示于[bottomNavigationBar]之上,[body]之下
this.drawer,//侧边栏,默认左边
this.endDrawer,//侧边栏 右边
this.bottomNavigationBar,//底部导航栏
this.bottomSheet,//底部持久化提示框
this.backgroundColor,
this.resizeToAvoidBottomPadding,//弃用,使用[resizeToAvoidBottomInset]
this.resizeToAvoidBottomInset,//重新计算布局空间大小
this.primary = true,//是否显示到底部,默认为true将显示到顶部状态栏
this.drawerDragStartBehavior = DragStartBehavior.start,
this.extendBody = false,
this.extendBodyBehindAppBar = false,
this.drawerScrimColor,
this.drawerEdgeDragWidth,
}) : assert(primary != null),
assert(extendBody != null),
assert(extendBodyBehindAppBar != null),
assert(drawerDragStartBehavior != null),
super(key: key);
- 自绘
既然是绘制,那就需要用到画布与画笔。在 Flutter 中,画布是 Canvas,画笔则是 Paint,CustomPaint 是用以承接自绘控件的容器,并不负责真正的绘制。
对于画笔 Paint,我们可以配置它的各种属性,比如颜色、样式、粗细等;而画布 Canvas,则提供了各种常见的绘制方法,比如画线 drawLine、画矩形 drawRect、画点 DrawPoint、画路径 drawPath、画圆 drawCircle、画圆弧 drawArc 等。
class WheelPainter extends CustomPainter {
// 设置画笔颜色 根据颜色返回不同的画笔
Paint getColoredPaint(Color color) {
Paint paint = Paint();
paint.color = color; //设置画笔颜色
return paint;
}
@override
void paint(Canvas canvas, Size size) {
//绘制逻辑
double wheelSize = min(size.width, size.height) / 2; //饼图的尺寸
double nbElem = 6; //分成6份
double radius = (2 * pi) / nbElem; //1/6圆
Rect boundingRect = Rect.fromCircle(center: Offset(wheelSize, wheelSize), radius: wheelSize); //包裹饼图这个圆形的矩形框
// 每次画1/6个圆弧
canvas.drawArc(boundingRect, 0, radius, true, getColoredPaint(Colors.orange));
canvas.drawArc(boundingRect, radius, radius, true, getColoredPaint(Colors.black38));
canvas.drawArc(boundingRect, radius * 2, radius, true, getColoredPaint(Colors.green));
canvas.drawArc(boundingRect, radius * 3, radius, true, getColoredPaint(Colors.red));
canvas.drawArc(boundingRect, radius * 4, radius, true, getColoredPaint(Colors.blue));
canvas.drawArc(boundingRect, radius * 5, radius, true, getColoredPaint(Colors.pink));
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// 判断是否需要重绘
return oldDelegate != this;
}
}
动画
为了实现动画,需要做三件事:
1.确定画面变化的规律;
2.根据这个规律,设定动画周期,启动动画;
3.定期获取当前动画的值,不断地微调、重绘画面。
这三件事情对应到 Flutter 中,就是 Animation、AnimationController 与 Listener:
1.Animation 是 Flutter 动画库中的核心类。Animation 仅仅是用来提供动画数据,而不负责动画的渲染。
2.AnimationController 用于管理 Animation,可以用来设置动画的时长、启动动画、暂停动画、反转动画等。
3.Listener 是 Animation 的回调函数,用来监听动画的进度变化,我们需要在这个回调函数中,根据动画的当前值重新渲染组件,实现动画的渲染。
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State with SingleTickerProviderStateMixin {
AnimationController controller;
Animation animation;
@override
void initState() {
//创建动画周期为1秒的AnimationController对象
controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000));
// 创建从50到200线性变化的Animation对象
animation = Tween(begin: 50.0, end: 200.0).animate(controller)
..addListener(() {
setState(() {}); //刷新界面
});
controller.forward(); //启动动画
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Animaiton demo")),
body: Center(
child: Container(
width: animation.value, // 将动画的值赋给widget的宽高
height: animation.value,
child: FlutterLogo()),
));
}
@override
void dispose() {
controller.dispose(); // 释放资源
super.dispose();
}
}
Tween 默认是线性变化的,可以创建 CurvedAnimation 来实现非线性曲线动画。CurvedAnimation 提供了很多常用的曲线,比如震荡曲线 elasticOut:
//创建动画周期为1秒的AnimationController对象
controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000));
//创建一条震荡曲线
final CurvedAnimation curve = CurvedAnimation(parent: controller, curve: Curves.elasticOut);
// 创建从50到200跟随振荡曲线变化的Animation对象
animation = Tween(begin: 50.0, end: 200.0).animate(curve)
..addListener(() {
setState(() {}); //刷新界面
});
- 怎么重复执行动画?
controller.repeat(reverse: true);//让动画重复执行
AnimatedWidget 与 AnimatedBuilder
- AnimatedWidget
封装了animation.addListener
class AnimatedLogo extends AnimatedWidget {
//AnimatedWidget需要在初始化时传入animation对象
AnimatedLogo({Key key, Animation animation}) : super(key: key, listenable: animation);
Widget build(BuildContext context) {
//取出动画对象
final Animation animation = listenable;
return Center(
child: Container(
height: animation.value, //根据动画对象的当前状态更新宽高
width: animation.value, child: FlutterLogo(),
));
}
}
- AnimatedBuilder
将动画和渲染职责分离
body: Center(
child: AnimatedBuilder(
animation: animation, //传入动画对象
child: FlutterLogo(),
builder: (context, child) => Container(
width: animation.value, //使用动画的当前状态更新UI
height: animation.value, child: child, //child参数即FlutterLogo()
))),
);
Animated执行状态
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
//动画执行完成
print("completed");
controller.reverse();//动画结束时反向执行
}else if(status == AnimationStatus.forward) {
//动画开始执行
print("forward");
}else if(status == AnimationStatus.reverse){
//动画重复执行
print("reverse");
}
else if (status == AnimationStatus.dismissed) {
//动画执行一个循环
print("dismissed");
// controller.forward();//动画反向执行完毕时,重新执行
}
});
hero 动画
实现在两个页面之间切换的过渡动画
跨页面共享的控件动画效果有一个专门的名词,即“共享元素变换”(Shared Element Transition)。
通过 Hero,我们可以在两个页面的共享元素之间,做出流畅的页面切换效果。
class Page1 extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page1"),),
body: GestureDetector(//手势监听点击
child: Hero(
tag: 'hero',//设置共享tag
child: Center(
child:Container(
width: 100, height: 100,
child: FlutterLogo())),
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (_)=>Page2()));//点击后打开第二个页面
},
)
);
}
}
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Page2"),),
body: Hero(
tag: 'hero',//设置共享tag
child: Center(
child: Container(
width: 300, height: 300,
child: FlutterLogo()
),
),
)
);
}
}
手势操作
- 第一类是原始的指针事件(Pointer Event),即原生开发中常见的触摸事件,表示屏幕上触摸(或鼠标、手写笔)行为触发的位移行为;
- 第二类则是手势识别(Gesture Detector),表示多个原始指针事件的组合操作,如点击、双击、长按等,是指针事件的语义化封装。
指针事件
在手指接触屏幕,触摸事件发起时,Flutter 会确定手指与屏幕发生接触的位置上究竟有哪些组件,并将触摸事件交给最内层的组件去响应。与浏览器中的事件冒泡机制类似,事件会从这个最内层的组件开始,沿着组件树向根节点向上冒泡分发,也就是事件分发。
Listener(
child: Container(
color: Colors.red,//背景色红色
width: 300,
height: 300,
),
onPointerDown: (event) => print("down $event"),//手势按下回调
onPointerMove: (event) => print("move $event"),//手势移动回调
onPointerUp: (event) => print("up $event"),//手势抬起回调
);
子widget不冒泡
Stack(
children: [
Container(
color: Colors.red, //父 背景色红色
width: 300,
height: 300,
),
Positioned(
child: Listener(
child: Container(
color: Colors.blueAccent,//子 背景蓝色
width: 100,
height: 100,
),
onPointerDown: (event) => print("down $event"), //手势按下回调
onPointerMove: (event) => print("move $event"), //手势移动回调
onPointerUp: (event) => print("up $event"), //手势抬起回调
)),
],
),
手势识别
响应用户交互行为,会使用Gesture。如点击 onTap、双击 onDoubleTap、长按 onLongPress、拖拽 onPanUpdate、缩放 onScaleUpdate 等。
Gesture 是手势语义的抽象,而如果我们想从组件层监听手势,则需要使用 GestureDetector。GestureDetector的冒泡机制已经处理过,如果想冒泡(子widget事件父widget能收到),得引入Arena(手势竞技场)概念。
class _MyHomePageState extends State {
//红色container坐标
double _top = 0.0;
double _left = 0.0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Interaction demo"),
),
body: Stack(
//使用Stack组件去叠加视图,便于直接控制视图坐标
children: [
Positioned(
top: _top,
left: _left,
child: GestureDetector(
//手势识别
child: Container(color: Colors.red, width: 50, height: 50),
//红色子视图
onTap: () => print("Tap"),
//点击回调
onDoubleTap: () => print("Double Tap"),
//双击回调
onLongPress: () => print("Long Press"),
//长按回调
onPanUpdate: (e) {
//拖动回调
setState(() {
//更新位置
_left += e.delta.dx;
_top += e.delta.dy;
});
},
),
)
],
),
);
}
}
跨组件传递数据
Flutter 还提供了三种方案:InheritedWidget、Notification 和 EventBus。
InheritedWidget
InheritedWidget 是 Flutter 中的一个功能型 Widget,适用于在 Widget 树中共享数据的场景。InheritedWidget 的数据流动方式是从父 Widget 到子 Widget 逐层传递
class _MyHomePageState extends State {
int count = 0;
//修改计数器
_incrementCounter() => setState(() {
count++;
});
@override
Widget build(BuildContext context) {
return CountContainer(count: count, increment: _incrementCounter, child: Counter());
}
}
class CountContainer extends InheritedWidget {
//方便其子Widget在Widget树中找到它
static CountContainer of(BuildContext context) => context.dependOnInheritedWidgetOfExactType();
final int count;
final Function() increment;
CountContainer({
Key key,
@required this.count,
@required this.increment,
@required Widget child,
}) : super(key: key, child: child);
// 判断是否需要更新
@override
bool updateShouldNotify(CountContainer oldWidget) => count != oldWidget.count;
}
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State {
@override
Widget build(BuildContext context) {
// //获取InheritedWidget节点
CountContainer state = CountContainer.of(context);
return Scaffold(
appBar: AppBar(title: Text("InheritedWidget demo")),
body: Text(
'父widget传递的数据: ${state.count}',
),
floatingActionButton: FloatingActionButton(onPressed: state.increment),
);
}
}
Notification
Notification数据流动方式是从子 Widget 向上传递至父 Widget。
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
String _msg = "通知:";
@override
Widget build(BuildContext context) {
//监听通知
return Scaffold(
appBar: AppBar(
title: Text("Notification demo"),
),
body: NotificationListener(
onNotification: (notification) {
setState(() {
_msg += notification.msg + " ";
}); //收到子Widget通知,更新msg
return true;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_msg),
CustomChild()
], //将子Widget加入到视图树中
)),
);
}
}
class CustomNotification extends Notification {
CustomNotification(this.msg);
final String msg;
}
//子Widget用来发通知
class CustomChild extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RaisedButton(
//按钮点击时分发通知
onPressed: () => CustomNotification("来自子widget").dispatch(context), child: Text("Fire Notification"),
);
}
}
EventBus
无论是 InheritedWidget 还是 Notificaiton,它们的使用场景都需要依靠 Widget 树,也就意味着只能在有父子关系的 Widget 之间进行数据共享。那如果不是父子关系呢?事件总线 EventBus。
EventBus遵循发布 / 订阅模式,允许订阅者订阅事件,当发布者触发事件时,订阅者和发布者之间可以通过事件进行交互。发布者和订阅者之间无需有父子关系,甚至非 Widget 对象也可以发布 / 订阅。
EventBus 是一个第三方插件,因此我们需要在 pubspec.yaml 文件中声明它:
dependencies:
event_bus: 1.1.0
- EventBus FirstPage
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
String msg = "通知:";
StreamSubscription subscription;
@override
void initState() {
//监听CustomEvent事件,刷新UI
subscription = eventBus.on().listen((event) {
setState(() {
msg += event.msg;
}); //更新msg
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("EventBus FirstPage")),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(msg),
RaisedButton(
child: Text("跳转EventBus SecondPage"),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => EventBusSecond()),
)),
],
));
}
@override
void dispose() {
subscription.cancel(); //销毁
super.dispose();
}
}
class CustomEvent {
String msg;
CustomEvent(this.msg);
}
- EventBus SecondPage
class _MyHomePageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("EventBus SecondPage")),
body:RaisedButton(onPressed: () {
eventBus.fire(CustomEvent("来自第二个页面的数据 "));
},child: Text("点击发送Event给FirstPage"),));
}
}
- 事件混乱问题
加一层判断
subscription = eventBus.on().listen((event) {
if(event.runtimeType == CustomEvent){
setState(() {
msg += event.msg;
}); //更新msg
}
});
异步
Dart是单线程的,意味着Dart 代码是有序的,所以耗时加载都是靠异步。
在 Dart 中,实际上有两个队列,一个事件队列(Event Queue),另一个则是微任务队列(Microtask Queue)。在每一次事件循环中,Dart 总是先去第一个微任务队列中查询是否有可执行的任务,如果没有,才会处理后续的事件队列的流程。微任务队列优先级最高。
微任务顾名思义,表示一个短时间内就会完成的异步任务。
微任务是由 scheduleMicroTask 建立的。
scheduleMicrotask(() => print('This is a microtask'));//创建微任务
Future(() => null).then((_) => print('then 4'));//加入微任务
Dart 为 Event Queue 的任务建立提供了一层封装,叫作 Future。
Future(() => print('Running in Future 1'));//下一个事件循环输出字符串
Future(() => print(‘Running in Future 2'))
.then((_) => print('and then 1'))
.then((_) => print('and then 2’));//上一个事件循环结束后,连续输出三段字符串
输出??
Dart 会将异步任务的函数执行体放入事件队列,然后立即返回,后续的代码继续同步执行。而当同步执行的代码执行完毕后,事件队列会按照加入事件队列的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的 then。
所以 then 与 Future 函数体共用一个事件循环
Future(() => print('f2')).then((_) {
print('f3');
scheduleMicrotask(() => print('f4'));
}).then((_) => print('f5'));
输出??
Flutter中异步一般的写法
//声明了一个延迟3秒返回Hello的Future,并注册了一个then返回拼接后的Hello 2019
Future fetchContent() =>
Future.delayed(Duration(seconds:3), () => "Hello")
.then((x) => "$x 2019");
main() async{
print(await fetchContent());//等待Hello 2019的返回
}
Dart 中的 await 并不是阻塞等待,而是异步等待。Dart 会将调用体的函数也视作异步函数,将等待语句的上下文放入 Event Queue 中,一旦有了结果,Event Loop 就会把它从 Event Queue 中取出,等待代码继续执行。
Future(() => print('f1'))
.then((_) async => await Future(() => print('f2')))
.then((_) => print('f3'));
Future(() => print('f4'));
输出??
数据的持久化
文件
Flutter 提供了两种文件存储的目录,即临时(Temporary)目录与文档(Documents)。
- 临时目录是操作系统可以随时清除的目录,通常被用来存放一些不重要的临时缓存数据。这个目录在 iOS 上对应着 NSTemporaryDirectory 返回的值,而在 Android 上则对应着 getCacheDir 返回的值。
- 文档目录则是只有在删除应用程序时才会被清除的目录,通常被用来存放应用产生的重要数据文件。在 iOS 上,这个目录对应着 NSDocumentDirectory,而在 Android 上则对应着 AppData 目录。
//先依赖
path_provider: ^1.5.1
//创建文件目录
Future get _localFile async {
final directory = await getApplicationDocumentsDirectory();
final path = directory.path;
return File('$path/content.txt');
}
//将字符串写入文件
Future writeContent(String content) async {
final file = await _localFile;
return file.writeAsString(content);
}
//从文件读出字符串
Future readContent() async {
try {
final file = await _localFile;
String contents = await file.readAsString();
return contents;
} catch (e) {
return "";
}
}
除了字符串读写之外,Flutter 还提供了二进制流的读写能力,可以支持图片、压缩包等二进制文件的读写。官方文档
SharedPreferences
缓存少量的键值对信息。在 iOS 上使用 NSUserDefaults,在 Android 使用 SharedPreferences。
//先依赖
shared_preferences: ^0.5.6
setSP() async {
SharedPreferences sp = await SharedPreferences.getInstance();
await sp.setInt('counter', 123456);
print("setSP");
}
getSP() async {
SharedPreferences sp = await SharedPreferences.getInstance();
counter = sp.getInt("counter") ?? 0;
print("getSP:$counter");
return counter;
}
clearSp() async {
SharedPreferences sp = await SharedPreferences.getInstance();
sp.clear();
}
数据库
//先依赖
sqflite: ^1.2.0
Future create() async {
database = openDatabase(
join(await getDatabasesPath(), 'students_database.db'),
onCreate: (db, version) => db.execute("CREATE TABLE students(id TEXT PRIMARY KEY, name TEXT, score INTEGER)"),
onUpgrade: (db, oldVersion, newVersion) {
//升级
//dosth for migration
},
version: 1,
);
}
//增
Future insert() async {
var student1 = Student(id: '123', name: '张三', score: 90);
var student2 = Student(id: '456', name: '李四', score: 80);
var student3 = Student(id: '789', name: '王五', score: 85);
Future insertStudent(Student std) async {
final Database db = await database;
await db.insert(
'students', std.toJson(), //插入冲突策略,新的替换旧的
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
//插入3个Student对象
await insertStudent(student1);
await insertStudent(student2);
await insertStudent(student3);
}
//删
Future delete() async {
Future deleteStudent(String id) async {
final Database db = await database;
//await db.delete("students",where: "id=?",whereArgs:[id] );
await db.rawDelete('DELETE FROM students WHERE id = ?', ['$id']);
}
//删除id=123
await deleteStudent("123");
}
//改
Future update() async{
Future updateStudent(String newName,int score,String oldName) async {
final Database db = await database;
await db.rawUpdate('UPDATE students SET name = ?, score = ? WHERE name = ?', ['$newName','$score','$oldName']);
}
await updateStudent("张xiao三",123456,"张三");
}
//查
Future query() async {
Future> students() async {
final Database db = await database;
final List
Isolate 多线程并发
尽管 Dart 是基于单线程模型的,但为了进一步利用多核 CPU,将 CPU 密集型运算进行隔离,Dart 也提供了多线程机制,即 Isolate。在 Isolate 中,资源隔离做得非常好,每个 Isolate 都有自己的 Event Loop 与 Queue,Isolate 之间不共享任何资源,只能依靠消息机制通信,因此也就没有资源抢占问题。
Isolate isolate;
start() async {
ReceivePort receivePort= ReceivePort();//创建管道
//创建并发Isolate,并传入发送管道
isolate = await Isolate.spawn(getMsg, receivePort.sendPort);
//监听管道消息
receivePort.listen((data) {
print('Data:$data');
receivePort.close();//关闭管道
isolate?.kill(priority: Isolate.immediate);//杀死并发Isolate
isolate = null;
});
}
//并发Isolate往管道发送一个字符串
getMsg(sendPort) => sendPort.send("Hello");
HTTP dio
//创建网络调用示例
Dio dio = Dio()
//设置URI及请求user-agent后发起请求
var response = await dio.get("https://flutter.dev", options:Options(headers: {"user-agent" : "Custom-UA"}));
var postResponse = await dio.post("http://192.168.112.72:8080/apis/tm/timeline/getTimeLineListByMeetingId",data: {"meetingId":360});
//打印请求结果
if(response.statusCode == HttpStatus.ok) {
print(postResponse.data.toString());
} else {
print("Error: ${response.statusCode}");
}
我们的页面由多个并行的请求响应结果构成,这就需要等待这些请求都返回后才能刷新界面。
//同时发起两个并行请求
List responseListData= await Future.wait([dio.get("https://flutter.dev",options: Options(headers: {"user-agent" : "Custom-UA"})),dio.post("http://192.168.112.72:8080/apis/tm/timeline/getTimeLineListByMeetingId",data: {"meetingId":360})]);
json解析
Flutter 不支持运行时反射,因此并没有提供像 Gson、Mantle 这样自动解析 JSON 的库来降低解析成本。
{
"id":"123",
"name":"张三",
"score" : 95
}
class Student{
//属性id,名字与成绩
String id;
String name;
int score;
//构造方法
Student({
this.id,
this.name,
this.score
});
//JSON解析工厂类,使用map为对象初始化赋值
factory Student.fromJson(Map parsedJson){
return Student(
id: parsedJson['id'],
name : parsedJson['name'],
score : parsedJson ['score']
);
}
}
Map toJson() {
final Map data = new Map();
data['score'] = this.score;
data['name'] = this.name;
data['id'] = this.id;
return data;
}
FlutterJsonBeanFactory
- 在线解析
- flutter中json序列化和反序列化
//序列化
String json = JSON.encode(user);
//反序列化
Map userMap = JSON.decode(json);
var user = new User.fromJson(userMap);
Flutter与IOS/Android通信
由于 Flutter 只接管了应用渲染层,因此这些系统底层能力是无法在 Flutter 框架内提供支持的;而另一方面,Flutter 还是一个相对年轻的生态,因此原生开发中一些相对成熟的 Java、C++ 或 Objective-C 代码库,比如图片处理、音视频编解码等,可能在 Flutter 中还没有相关实现。因此,为了解决调用原生系统底层能力以及相关代码库复用问题,Flutter 为开发者提供了一个轻量级的解决方案,即逻辑层的方法通道(Method Channel)机制。基于方法通道,我们可以将原生代码所拥有的能力,以接口形式暴露给 Dart,从而实现 Dart 代码与原生代码的交互,就像调用了一个普通的 Dart API 一样。
MethodChannel
- Flutter
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("MethodChannel Demo"),), body: Column(children: [RaisedButton(onPressed: () {
handleButtonClick();
}, child: Text("MethodChannel"),),
],));
}
//处理按钮点击
handleButtonClick() async {
//声明MethodChannel
const platform = MethodChannel('tami');
var result;
//异常捕获
try {
//异步等待方法通道的调用结果
result = await platform.invokeMethod('123456');
} catch (e) {
result = -1;
}
print("Result:$result");
}
}
- Android
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
//创建与调用方标识符一样的方法通道
MethodChannel(flutterEngine.dartExecutor, "tami").setMethodCallHandler { call, result ->
//判断方法名是否支持
if(call.method == "123456") {
Log.e("twb","flutter --> android")
result.success("flutter->Android,sucess")
}else{
result.notImplemented()
}
}
}
- IOS
EventChannel
- Flutter
class _MyHomePageState extends State {
//EventChannel
static const EventChannel eventChannel = EventChannel('eventchannel');
String _chargingStatus = 'Battery status: unknown.';
@override
void initState() {
super.initState();
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
void _onEvent(Object event) {
setState(() {
_chargingStatus =
"Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
});
}
void _onError(Object error) {
setState(() {
_chargingStatus = 'Battery status: unknown.';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("EventChannel Demo"),
),
body: Center(
child: Text(
_chargingStatus,
),
)
);
}
}
- Android
public class EventChannelActivity extends FlutterActivity {
private static final String CHARGING_CHANNEL = "eventchannel";
@Override
public void configureFlutterEngine(FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new EventChannel(flutterEngine.getDartExecutor(), CHARGING_CHANNEL).setStreamHandler(
new EventChannel.StreamHandler() {
private BroadcastReceiver chargingStateChangeReceiver;
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
chargingStateChangeReceiver = createChargingStateChangeReceiver(events);
registerReceiver(
chargingStateChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
@Override
public void onCancel(Object arguments) {
unregisterReceiver(chargingStateChangeReceiver);
chargingStateChangeReceiver = null;
}
}
);
}
private BroadcastReceiver createChargingStateChangeReceiver(final EventChannel.EventSink events) {
return new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
events.error("UNAVAILABLE", "Charging status unavailable", null);
} else {
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
events.success(isCharging ? "charging" : "discharging");
}
}
};
}
}
BasicMessageChannel,原生嵌套Flutter
https://www.jianshu.com/p/39575a90e820
- Flutter
class _MyHomePageState extends State {
static const String _channel = 'BasicMessageChannel';
static const String _pong = 'pong';
static const String _emptyMessage = '';
///详细参考 https://www.jianshu.com/p/39575a90e820 或者源码
//StandardMessageCodec() //BasicMessageChannel的默认编解码器,其支持基础数据类型、二进制数据、列表、字典
//BinaryCodec() //二进制格式 Android中为ByteBuffer,iOS中为NSData
//JSONMessageCodec() //adnroid org.json ios NSJSONSerialization
static const BasicMessageChannel basicMessageChannel = BasicMessageChannel(_channel, StringCodec());
int _counter = 0;
@override
void initState() {
super.initState();
basicMessageChannel.setMessageHandler(_handlePlatformIncrement);
}
Future _handlePlatformIncrement(String message) async {
setState(() {
_counter++;
});
return _emptyMessage;
}
void _sendFlutterIncrement() {
basicMessageChannel.send(_pong);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Center(
child: Text(
'Platform button tapped $_counter time${ _counter == 1 ? '' : 's' }.',
style: const TextStyle(fontSize: 17.0)),
),
),
Container(
padding: const EdgeInsets.only(bottom: 15.0, left: 5.0),
child: Row(
children: [
FlutterLogo(),
const Text('Flutter', style: TextStyle(fontSize: 30.0)),
],
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _sendFlutterIncrement,
child: const Icon(Icons.add),
),
);
}
}
- Android
//注册
flutter_view.attachToFlutterEngine(flutterEngines!!)
//销毁
override fun onDestroy() {
flutter_view.detachFromFlutterEngine()
super.onDestroy()
}
messageChannel = BasicMessageChannel(flutterEngines!!.dartExecutor, CHANNEL, StringCodec.INSTANCE)
messageChannel?.setMessageHandler { s, reply ->
Log.e("twb", "Flutter发送:$s")
onFlutterIncrement()
reply.reply("")
}
//发送
messageChannel?.send("ping")
- IOS
Flutter 调用原生view?
- Flutter
class SampleView extends StatelessWidget {
@override
Widget build(BuildContext context) {
//使用Android平台的AndroidView,传入唯一标识符sampleView
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(viewType: 'sampleView');
} else {
//使用iOS平台的UIKitView,传入唯一标识符sampleView
return UiKitView(viewType: 'sampleView');
}
}
}
- Android
if (flutterEngine!=null){
// flutterEngine!!.platformViewsController.registry.registerViewFactory("sampleView",object : PlatformViewFactory(StandardMessageCodec.INSTANCE){
// override fun create(p0: Context?, p1: Int, p2: Any?): PlatformView {
// return SimpleViewControl(mContext)
// }
// })
val shimPluginRegistry = ShimPluginRegistry(flutterEngine!!)
shimPluginRegistry.registrarFor("samples.flutter/native_views")
.platformViewRegistry()
.registerViewFactory("sampleView",object : PlatformViewFactory(StandardMessageCodec.INSTANCE){
override fun create(p0: Context?, viewId: Int, p2: Any?): PlatformView {
return SimpleViewControl(mContext)
}
})
}
//原生视图封装类
internal class SimpleViewControl(context: Context) : PlatformView {
private val view: View = View(context)//缓存原生视图
init {
view.setBackgroundColor(Color.rgb(255, 0, 0))
}
//返回原生视图
override fun getView(): View {
return view
}
//原生视图销毁回调
override fun dispose() {}
}
- IOS
Flutter跳转原生
- Flutter
static const MethodChannel _methodChannel = MethodChannel('PlatformView');
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
Future _launchPlatformCount() async {
final int platformCounter = await _methodChannel.invokeMethod('switchView', _counter);
setState(() {
_counter = platformCounter;
});
}
- Android
//MethodChannel PlatformView 跳转页面
MethodChannel(flutterEngine.dartExecutor, "PlatformView").setMethodCallHandler { call, result ->
this.result = result
//判断方法名是否支持
if (call.method == "switchView") {
val count = call.arguments as Int
onLaunchFullScreen(count)
} else {
result.notImplemented()
}
}
}
//MethodChannel PlatformView 跳转页面
private fun onLaunchFullScreen(count: Int) {
val fullScreenIntent = Intent(this, PlatformViewActivity::class.java)
fullScreenIntent.putExtra(PlatformViewActivity.EXTRA_COUNTER, count)
startActivityForResult(fullScreenIntent, COUNT_REQUEST)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
if (requestCode == COUNT_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
result.success(data.getIntExtra(PlatformViewActivity.EXTRA_COUNTER, 0))
} else {
result.error("ACTIVITY_FAILURE", "Failed while launching activity", null)
}
}
}
- IOS
状态管理 Provider
Provider 是一个用来提供数据的框架
提供了依赖注入的功能,允许在 Widget 树中更加灵活地处理和传递数据
什么是依赖注入?简单来说,依赖注入是一种可以让我们在需要时提取到所需资源的机制,即:预先将某种“资源”放到程序中某个我们都可以访问的位置,当需要使用这种“资源”时,直接去这个位置拿即可,而无需关心“资源”是谁放进去的。
如果我们的应用足够简单,数据流动的方向和顺序是清晰的,我们只需要将数据映射成视图就可以了。作为声明式的框架,Flutter 可以自动处理数据到渲染的全过程,通常并不需要状态管理。
但,随着产品需求迭代节奏加快,项目逐渐变得庞大时,我们往往就需要管理不同组件、不同页面之间共享的数据关系。当需要共享的数据关系达到几十上百个的时候,我们就很难保持清晰的数据流动方向和顺序了,导致应用内各种数据传递嵌套和回调满天飞。在这个时候,我们迫切需要一个解决方案,来帮助我们理清楚这些共享数据的关系,于是状态管理框架便应运而生。
因为 Provider 实际上是 InheritedWidget 的封装,所以通过 Provider 传递的数据从数据流动方向来看,是由父到子(或者反过来)。这时我们就明白了,原来需要把资源放到 FirstPage 和 SecondPage 的父 Widget,也就是应用程序的实例 MyApp 中。
//依赖
provider: ^3.2.0
//定义需要共享的数据模型,通过混入ChangeNotifier管理
class CounterModel with ChangeNotifier {
int _count = 0;
//读方法
int get counter => _count;
//写方法
void increment() {
_count++;
notifyListeners(); //通知刷新
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
//ChangeNotifierProvider 具有读写 Provider只有读
return ChangeNotifierProvider.value(
value: CounterModel(),//需要共享的数据资源
child: MaterialApp(title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,), home: MyHomePage(),),);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出资源
final _counter = Provider.of(context);
return Scaffold(
appBar: AppBar(title: Text("First Provider Screen"),),
body:Center(child: Text('Counter: ${_counter.counter}')),
floatingActionButton: FloatingActionButton( onPressed: () =>
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondProviderPage())),
child: Icon(Icons.arrow_forward_ios),)
);
}
}
class SecondProviderPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出资源
final _counter = Provider.of(context);
return Scaffold(
appBar: AppBar(
title: Text('Second Provider Screen'),
),
body: Center(child: Text('Counter: ${_counter.counter}')),
//用资源更新方法来设置按钮点击回调
floatingActionButton: FloatingActionButton(
onPressed: _counter.increment,
child: Icon(Icons.add),
),
);
}
}
Consumer
滥用 Provider.of 方法有很大的副作用,那就是当数据更新时,页面中其他的子 Widget 也会跟着一起刷新。
那么,有没有办法能够在数据资源发生变化时,只刷新对资源存在依赖关系的 Widget,而其他 Widget 保持不变呢?
class SecondProviderPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Provider Screen'),
),
body: Consumer(
builder: (BuildContext context, CounterModel value, Widget child) {
return Center(child: Text('Value: ${value.counter}'));
},
),
floatingActionButton:Consumer(
builder: (BuildContext context, CounterModel value, Widget child) {
return FloatingActionButton(
//用资源更新方法来设置按钮点击回调
onPressed: value.increment,
child: Icon(Icons.add),
);
},
)
);
}
}
多状态的资源封装
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
//ChangeNotifierProvider 具有读写 Provider只有读
return MultiProvider(
providers: [
Provider.value(value: 30.0),//注入字体大小
ChangeNotifierProvider.value(value: CounterModel())//注入计数器实例
],
child: MaterialApp(title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue,), home: MyHomePage(),),);
}
}
//取出资源
final _counter = Provider.of(context);//获取计时器实例
final textSize = Provider.of(context);//获取字体大小
Scaffold(
appBar: AppBar(
title: Text('Second Provider Screen'),
),
body: Consumer2(
builder: (BuildContext context, double textSize,CounterModel value, Widget child) {
return Center(child: Text('Value: ${value.counter}',style: TextStyle(fontSize: textSize,),));
},
),
floatingActionButton:Consumer2(
builder: (BuildContext context, double textSize, CounterModel value,Widget child) {
return FloatingActionButton(
//用资源更新方法来设置按钮点击回调
onPressed: value.increment,
child: Icon(Icons.add),
);
},
)
);
Consumer2 与 Consumer 的使用方式基本一致,只不过是在 builder 方法中多了一个数据资源参数。注意最多共享 6 个数据资源,从Consumer 到 Consumer 6。
横竖屏
@override
Widget build(BuildContext context) {
return Scaffold(
//使用OrientationBuilder的builder模式感知屏幕旋转
body: OrientationBuilder(
builder: (context, orientation) {
//根据屏幕旋转方向返回不同布局行为
return orientation == Orientation.portrait
? _buildVerticalLayout()
: _buildHorizontalLayout();
},
),
);
}
OrientationBuilder 提供了 orientation 参数可以识别设备方向,而如果我们在 OrientationBuilder 之外,希望根据设备的旋转方向设置一些组件的初始化行为,也可以使用 MediaQueryData 提供的 orientation 方法:
if(MediaQuery.of(context).orientation == Orientation.portrait) {
//dosth
}
Flutter 应用默认支持竖屏和横屏两种模式。如果我们的应用程序不需要提供横屏模式,也可以直接调用 SystemChrome 提供的 setPreferredOrientations 方法告诉 Flutter,这样 Flutter 就可以固定视图的布局方向了,注意setPreferredOrientations 是全局生效的。
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
- 问题:
如果A页面需要横屏怎么办? - 解决思路:
在A页面initState 中设置横屏,在 dispose 时设置竖屏。
判断屏宽度
if (MediaQuery.of(context).size.width > 480) {
//大屏 或 平板
}else{
//普通手机
}