1.文档
https://flutterchina.club/flutter-for-android/
2.重点
1. Views
在Android中,View是屏幕上显示的所有内容的基础。 在Flutter中,View相当于是Widget。
区别:
- 1.Widget仅支持一帧,并且在每一帧上,Flutter的框架都会创建一个Widget实例树(译者语:相当于一次性绘制整个界面)。
- 而在Android上View绘制结束后,就不会重绘,直到调用invalidate时才会重绘
- 2.与Android的视图层次系统不同(在framework改变视图),而在Flutter中的widget是不可变的,可以通过状态进行改变。这允许widget变得超级轻量。
- 在Android中通过直接对view进行改变来更新视图。
如何更新widget?
Flutter中Widget是不可变的,不会直接更新。无状态和有状态 widget的核心特性是相同的。每一帧它们都会重新构建,不同之处在于StatefulWidget有一个State对象,它可以跨帧存储状态数据并恢复它。
动画:
在Android中,可以通过View.animate()对视图进行动画处理。在Flutter中,可以通过动画库给widget添加动画,您可以将widget包装到Animation中。
有一个AnimationController和一个Interpolator, 它是Animation类的扩展,例如CurvedAnimation。您将控制器和动画传递到AnimationWidget中,并告诉控制器启动动画。
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
//初始化
controller = new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Container(
child: new FadeTransition(//布局包含在FadeTransition
opacity: curve,//透明度改变,采用动画的方式进行
child: new FlutterLogo(
size: 100.0,
)))),
floatingActionButton: new FloatingActionButton(
tooltip: 'Fade',
child: new Icon(Icons.brush),
onPressed: () {
controller.forward();//启动动画
},
),
);
}
}
如何使用Canvas draw/paint
在Android中,您可以使用Canvas在屏幕上绘制自定义形状。
Flutter有两个类可以帮助您绘制画布,CustomPaint和CustomPainter,它们实现您的算法以绘制到画布。
import 'package:flutter/material.dart';
class SignaturePainter extends CustomPainter {//自定义CustomPainter
SignaturePainter(this.points);
final List<Offset> points;
void paint(Canvas canvas, Size size) {
var paint = new Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null)
canvas.drawLine(points[i], points[i + 1], paint);
}
}
bool shouldRepaint(SignaturePainter other) => other.points != points;
}
class Signature extends StatefulWidget {
SignatureState createState() => new SignatureState();
}
class SignatureState extends State<Signature> {
List<Offset> _points = <Offset>[];
Widget build(BuildContext context) {
return new GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
_points = new List.from(_points)..add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
child: new CustomPaint(painter: new SignaturePainter(_points)),
);
}
}
class DemoApp extends StatelessWidget {
Widget build(BuildContext context) => new Scaffold(body: new Signature());
}
void main() => runApp(new MaterialApp(home: new DemoApp()));
如何自定义组件?
在Android中,您通常会继承View或已经存在的某个控件,然后覆盖其绘制方法来实现自定义View。
在Flutter中,一个自定义widget通常是通过组合其它widget来实现的,而不是继承。
Intents
在Android中,Intents主要有两种使用场景:在Activity之间切换,以及调用外部组件。
Flutter不具有Intents的概念,但如果需要的话,Flutter可以通过Native整合来触发Intents。
要在Flutter中切换屏幕,您可以访问路由以绘制新的Widget。 管理多个屏幕有两个核心概念和类:Route 和 Navigator。Route是应用程序的“屏幕”或“页面”的抽象(可以认为是Activity), Navigator是管理Route的Widget。Navigator可以通过push和pop route以实现页面切换。
Navigator.of(context).pushNamed('/b');
Intents的另一个主要的用途是调用外部组件,如Camera或File picker。为此,您需要和native集成(或使用现有的库)
如何在Flutter中处理来自外部应用程序传入的Intents
Flutter可以通过直接与Android层通信并请求共享的数据来处理来自Android的Intents。基本流程是我们首先处理Android端的共享文本数据,然后等待Flutter请求数据,然后通过MethodChannel发送。
startActivityForResult 在Flutter中等价于什么
处理Flutter中所有路由的Navigator类可用于从已经push到栈的路由中获取结果。 这可以通过等待push返回的Future来完成。
Map coordinates = await Navigator.of(context).pushNamed('/location');//要启动让用户选择其位置的位置的路由,则可以执行以下操作
//然后在你的位置路由中,一旦用户选择了他们的位置,你可以将结果”pop”出栈
Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});
2. 异步UI
runOnUiThread 在Flutter中等价于什么
Dart是单线程执行模型,支持Isolates(在另一个线程上运行Dart代码的方式)、事件循环和异步编程。 除非您启动一个Isolate,否则您的Dart代码将在主UI线程中运行,并由事件循环驱动(译者语:和JavaScript一样)。
您可以在UI线程上运行网络请求代码而不会导致UI挂起(译者语:因为网络请求是异步的)
loadData() async {\\异步 async
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);\\异步await
setState(() {
widgets = JSON.decode(response.body);
});
}
要更新UI,您可以调用setState,这会触发build方法再次运行并更新数据。
AsyncTask和IntentService在Flutter中等价于什么
在Android中,当你想访问一个网络资源时,你通常会创建一个AsyncTask,它将在UI线程之外运行代码来防止你的UI被阻塞。 AsyncTask有一个线程池,可以为你管理线程。
由于Flutter是单线程的,运行一个事件循环(如Node.js),所以您不必担心线程管理或者使用AsyncTasks、IntentServices
要异步运行代码,可以将函数声明为异步函数,并在该函数中等待这个耗时任务。 async await
在Android上,当您继承AsyncTask时,通常会覆盖3个方法,OnPreExecute、doInBackground和onPostExecute。 在Flutter中没有这种模式的等价物,因为您只需等待一个长时间运行的函数,而Dart的事件循环将负责其余的事情。
但是,有时您可能需要处理大量数据,导致UI可能会挂起。
在这种情况下,与AsyncTask一样,在Flutter中,可以利用多个CPU内核来执行耗时或计算密集型任务。这是通过使用Isolates来完成的。
是一个独立的执行线程,它运行时不会与主线程共享任何内存。这意味着你不能从该线程访问变量或通过调用setState来更新你的UI。
loadData() async {
ReceivePort receivePort = new ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends it's SendPort as the first message
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts");//数据
setState(() {
widgets = msg;//设置数据,完成了两个线程的数据交互。
});
}
// the entry point for the isolate
static dataLoader(SendPort sendPort) async {
// Open the ReceivePort for incoming messages.
ReceivePort port = new ReceivePort();
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
await for (var msg in port) {
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// Lots of JSON to parse
replyTo.send(JSON.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = new ReceivePort();
port.send([msg, response.sendPort]);
return response.first;//数据
}
“dataLoader”是在它自己的独立执行线程中运行的隔离区,您可以在其中执行CPU密集型任务。
3. Activities 和 Fragments
在Android中,Activity代表用户可以完成的一项重点工作。Fragment代表了一种模块化代码的方式,可以为大屏幕设备构建更复杂的用户界面,可以在小屏幕和大屏幕之间自动调整UI。 在Flutter中,这两个概念都等同于Widget。
如何监听Android Activity生命周期事件
在Android中,您可以覆盖Activity的方法来捕获Activity的生命周期回调。
在Flutter中您可以通过挂接到WidgetsBinding观察并监听didChangeAppLifecycleState更改事件来监听生命周期事件
- resumed - 应用程序可见并响应用户输入。这是来自Android的onResume
- inactive - 应用程序处于非活动状态,并且未接收用户输入。此事件在Android上未使用,仅适用于iOS
- paused - 应用程序当前对用户不可见,不响应用户输入,并在后台运行。这是来自Android的暂停
- suspending - 该应用程序将暂时中止。这在iOS上未使用
4. 手势实现
Flutter中的手势系统有两个独立的层。
第一层有原始指针(pointer)事件,它描述了屏幕上指针(例如,触摸,鼠标和触控笔)的位置和移动。
第二层有手势,描述由一个或多个指针移动组成的语义动作。
Pointers
指针(Pointer)代表用户与设备屏幕交互的原始数据。有四种类型的指针事
- PointerDownEvent 指针接触到屏幕的特定位置
- PointerMoveEvent 指针从屏幕上的一个位置移动到另一个位置
- PointerUpEvent 指针停止接触屏幕
- PointerCancelEvent 指针的输入事件不再针对此应用(事件取消)
在指针按下时,框架对您的应用程序执行_命中测试_,以确定指针与屏幕相接的位置存在哪些widget。 指针按下事件(以及该指针的后续事件)然后被分发到由_命中测试_发现的最内部的widget。 从那里开始,这些事件会冒泡在widget树中向上冒泡,这些事件会从最内部的widget被分发到到widget根的路径上的所有小部件(译者语:这和web中事件冒泡机制相似), 没有机制取消或停止冒泡过程(译者语:这和web不同,web中的事件冒泡是可以停止的)。
手势
手势表示可以从多个单独的指针事件(甚至可能是多个单独的指针)识别的语义动作(例如,轻敲,拖动和缩放)。 完整的一个手势可以分派多个事件。
手势消歧
在屏幕上的指定位置,可能会有多个手势检测器。所有这些手势检测器在指针事件流经过并尝试识别特定手势时监听指针事件流。 GestureDetectorwidget决定是哪种手势。
当屏幕上给定指针有多个手势识别器时,框架通过让每个识别器加入一个“手势竞争场”来确定用户想要的手势。“手势竞争场”使用以下规则确定哪个手势胜出
- 在任何时候,识别者都可以宣布失败并离开“手势竞争场”。如果在“竞争场”中只剩下一个识别器,那么该识别器就是赢家
- 在任何时候,识别者都可以宣布胜利,这会导致胜利,并且所有剩下的识别器都会失败
例如,在消除水平和垂直拖动的歧义时,两个识别器在接收到指针向下事件时进入“手势竞争场”。识别器观察指针移动事件。 如果用户将指针水平移动超过一定数量的逻辑像素,则水平识别器将声明胜利,并且手势将被解释为水平拖拽。
当只有水平(或垂直)拖动识别器时,“手势竞争场”是有益的。在这种情况下,“手势竞争场”将只有一个识别器,并且水平拖动将被立即识别,这意味着水平移动的第一个像素可以被视为拖动,用户不需要等待进一步的手势消歧。