Flutter与Android中的对应

View相关

Flutter使用的是声明式UI,而Win32、Android或iOS框架中使用的是命令式UI,具体差异为:命令式UI需要开发者手动构建和控制一个UI控件的所有功能,然后在需要变更UI时,使用方法和设置器修改。而声明式UI只需要明确一个UI控件的当前状态即可,剩下的UI变换交给框架完成。

举个例子:

ViewB下包含的两个ViewC(c1和c2)需要变更成ViewB下包含一个ViewC(c3),并修改背景色。
命令式UI的写法应该是:

b.setColot(red);b.clearChildren();ViewC c3 = new View(...);b.add(c3);

而声明式UI的写法则是:

return ViewB {    color: red,    child: ViewC(...)}

View对应Flutter中的什么

在Android框架中,View是屏幕上显示的所有内容的基础,所有的控件(Button、Input等)都是一个View。在Flutter中,可以粗略的将Widget看做View的等价物,因为并不能完全映射到Android中的View。

  1. Widget没有生命周期,一经创建就是不可变的。
  2. 当Widget需要变更时,Flutter会重建一个新的Widget实例树。
  3. Widget本身不是视图,不直接绘制任何东西,只是对UI及其语义的描述。

Flutter包含了Material组件库和Cupertino组件库来构建Android或iOS风格的界面。

如何更新widget

在Android中,我们可以直接修改View来更新他们。然而在Flutter中,Widget是不可变的并且不能被直接更新,而是修改Widget的状态。

Flutter中的Widget分为有状态和无状态两种。

  • StatelessWidget 无状态控件。创建后就不会在运行中发生变更的UI。如用户的头像。

    Text(  'I like Flutter!',  style: TextStyle(fontWeight: FontWeight.bold),);
  • StatefulWidget 带状态控件。创建后需要依赖外部信息而动态修改的UI。如根据网络请求返回结果显示不同内容。

    import 'package:flutter/material.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  // Default placeholder text  String textToShow = "I Like Flutter";  void _updateText() {    setState(() {      // update the text      textToShow = "Flutter is Awesome!";    });  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("Sample App"),      ),      body: Center(child: Text(textToShow)),      floatingActionButton: FloatingActionButton(        onPressed: _updateText,        tooltip: 'Update Text',        child: Icon(Icons.update),      ),    );  }}

如何对多个widget布局?有没有xml布局文件?

Flutter中没有xml布局文件,而是通过widget树布局。

@overrideWidget build(BuildContext context) {  return Scaffold(    appBar: AppBar(      title: Text("Sample App"),    ),    body: Center(      child: MaterialButton(        onPressed: () {},        child: Text('Hello'),        padding: EdgeInsets.only(left: 10.0, right: 10.0),      ),    ),  );}

如何在布局中添加或删除文件

在Android中,可以通过父视图的addChild()或removeChild()方法来动态的添加、删除子视图。
在Flutter中,可以通过父视图的布尔值和返回值来修改绘制内容。

import 'package:flutter/material.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  // Default value for toggle  bool toggle = true;  void _toggle() {    setState(() {      toggle = !toggle;    });  }  _getToggleChild() {    if (toggle) {      return Text('Toggle One');    } else {      return MaterialButton(onPressed: () {}, child: Text('Toggle Two'));    }  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("Sample App"),      ),      body: Center(        child: _getToggleChild(),      ),      floatingActionButton: FloatingActionButton(        onPressed: _toggle,        tooltip: 'Update Text',        child: Icon(Icons.update),      ),    );  }}

如何对widget设置动画

在Android中可以使用XML创建动画或调用View的animate()方法。
在Flutter中,则使用动画库中支持动画的widget包裹想要实现动画的widget即可。

在Flutter中,使用AnimationController,它是一个动画,可以暂停,搜索,停止和反转动画。它需要一个Ticker,当vsync发生时发出信号,并在每个帧运行时产生0到1之间的线性插值。然后,您创建一个或多个动画并将它们附加到控制器。

例如,您可以使用CurvedAnimation沿插值曲线实现动画。从这个意义上说,控制器是动画进度的“主”源,而CurvedAnimation则计算代替控制器默认线性运动的曲线。像小部件一样,Flutter中的动画与组合一起工作。

构建窗口小部件树时,将动画分配给窗口小部件的动画属性,例如FadeTransition的不透明度,并告诉控制器启动动画。

下面的示例演示如何编写FadeTransition,当您按下FloatingActionButton时,该FadeTransition将窗口小部件淡化为徽标:

import 'package:flutter/material.dart';void main() {  runApp(FadeAppTest());}class FadeAppTest extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Fade Demo',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: MyFadeTest(title: 'Fade Demo'),    );  }}class MyFadeTest extends StatefulWidget {  MyFadeTest({Key key, this.title}) : super(key: key);  final String title;  @override  _MyFadeTest createState() => _MyFadeTest();}class _MyFadeTest extends State with TickerProviderStateMixin {  AnimationController controller;  CurvedAnimation curve;  @override  void initState() {    controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);    curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text(widget.title),      ),      body: Center(          child: Container(              child: FadeTransition(                  opacity: curve,                  child: FlutterLogo(                    size: 100.0,                  )))),      floatingActionButton: FloatingActionButton(        tooltip: 'Fade',        child: Icon(Icons.brush),        onPressed: () {          controller.forward();        },      ),    );  }}

如何使用Canvas来绘制内容

在Android中,可以使用Drawable和Canvas来绘制图片和图形。Flutter也有相似的Canvas API,而且是基于更底层的绘制引擎Skia。Flutter有两个类来帮助在Canvas上绘制:CustomPaint和CustomPainter。后者是向Canvas绘制的算法实现。
下面是一个使用Flutter实现的签名功能页面示例。

import 'package:flutter/material.dart';void main() => runApp(MaterialApp(home: DemoApp()));class DemoApp extends StatelessWidget {  Widget build(BuildContext context) => Scaffold(body: Signature());}class Signature extends StatefulWidget {  SignatureState createState() => SignatureState();}class SignatureState extends State {  List _points = [];  Widget build(BuildContext context) {    return GestureDetector(      onPanUpdate: (DragUpdateDetails details) {        setState(() {          RenderBox referenceBox = context.findRenderObject();          Offset localPosition =          referenceBox.globalToLocal(details.globalPosition);          _points = List.from(_points)..add(localPosition);        });      },      onPanEnd: (DragEndDetails details) => _points.add(null),      child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite),    );  }}class SignaturePainter extends CustomPainter {  SignaturePainter(this.points);  final List points;  void paint(Canvas canvas, Size size) {    var paint = 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;}

如何自定义构建widget

在Android中,经常使用自定义View子类或使用现有View,自定义实现方法的方式来达到自定义行为。
在Flutter中,通过将多个widget组合起来,而不是继承他们来构建一个自定义的widget。
下面的示例中自定义了一个显示指定文本的按钮。我们是创建了一个CustomButton类型的widget,其内部组合了一个RaisedButton,而不是继承自RaisedButton。

class CustomButton extends StatelessWidget {  final String label;  CustomButton(this.label);  @override  Widget build(BuildContext context) {    return RaisedButton(onPressed: () {}, child: Text(label));  }}@overrideWidget build(BuildContext context) {  return Center(    child: CustomButton("Hello"),  );}

Intent相关

Intent对应Flutter中的什么

在Android中,Intent主要有两个作用:在Activity之间导航;和其他组件通信。
在Flutter中,没有Intent的概念,但是仍可以通过集成原生插件来发送一个Intent。
在Flutter中,并没有Activity和Fragment的直接对应。而是使用Navigator和Routes在多个界面之间导航,所有界面实际上在同一个Activity中。
Route是一个App的屏幕或页面的抽象,Navigator是管理routes的小部件。Route可以近似看做一个Activity,但是含义是不一样的。Navigator像一个栈,可以压入和弹出routes来切换屏幕显示。

在Android中,是在应用的AndroidManifest.xml文件中生命你的Activity。
在Flutter中,通过两个步骤在多个页面中导航:

  • 构建一个routes,实际上是一个页面名称和页面内容的Map
  • 通过Navigator直接导航到指定名称的页面
void main() {  runApp(MaterialApp(    home: MyAppHome(), // becomes the route named '/'    routes:  {      '/a': (BuildContext context) => MyPage(title: 'page A'),      '/b': (BuildContext context) => MyPage(title: 'page B'),      '/c': (BuildContext context) => MyPage(title: 'page C'),    },  ));}Navigator.of(context).pushNamed('/b');

而其他经常使用的Intent的场景,如打开相机、文件选择器,则需要添加原生平台的插件。

如何在Flutter中响应其他App发送的Intent

Flutter可以直接处理请求进来的Intent,因为接受Intent的部分是通过Android原生层处理的。

                  

然后需要用MethodChannel类将Intent传递过来的参数,等回调给Flutter层。

package com.example.shared;import android.content.Intent;import android.os.Bundle;import java.nio.ByteBuffer;import io.flutter.app.FlutterActivity;import io.flutter.plugin.common.ActivityLifecycleListener;import io.flutter.plugin.common.MethodCall;import io.flutter.plugin.common.MethodChannel;import io.flutter.plugins.GeneratedPluginRegistrant;public class MainActivity extends FlutterActivity {  private String sharedText;  @Override  protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    GeneratedPluginRegistrant.registerWith(this);    Intent intent = getIntent();    String action = intent.getAction();    String type = intent.getType();    if (Intent.ACTION_SEND.equals(action) && type != null) {      if ("text/plain".equals(type)) {        handleSendText(intent); // Handle text being sent      }    }    //添加一个"app.channel.shared.data"标识的回调    new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(      new MethodCallHandler() {        @Override        public void onMethodCall(MethodCall call, MethodChannel.Result result) {            //通过getSharedText标识共享数据          if (call.method.contentEquals("getSharedText")) {            result.success(sharedText);            sharedText = null;          }        }      });  }  void handleSendText(Intent intent) {    sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);  }}

然后在Flutter层获取共享的数据。

import 'package:flutter/material.dart';import 'package:flutter/services.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample Shared App Handler',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  static const platform = const MethodChannel('app.channel.shared.data');  String dataShared = "No data";  @override  void initState() {    super.initState();    getSharedText();  }  @override  Widget build(BuildContext context) {    return Scaffold(body: Center(child: Text(dataShared)));  }  getSharedText() async {    //通过getSharedText标识获取共享数据    var sharedData = await platform.invokeMethod("getSharedText");    if (sharedData != null) {      setState(() {        dataShared = sharedData;      });    }  }}

startActivityForResult()对应什么

在Flutter中,Navigator类处理页面路由和页面返回时获取返回参数。获取页面返回参数就是通过等待push()操作的返回值实现的。
如下实例,打开一个定位页面,并等待返回定位结果。

Map coordinates = await Navigator.of(context).pushNamed('/location');

当定位完成后,在pop方法中添加经纬度参数。

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

异步UI

Flutter中的runOnUiThread()

Flutter中有一个单线程执行模型Isolate(它支持在其他的线程中运行Dart代码)、一个事件循环Looper、和异步执行。除非使用Isolate,否则所有Dart代码均在主线程中执行,而且也是由事件循环驱动的。Flutter中的Looper和Android主线程的Looper是等价的,也是被附加在主线程的。

在Flutter中,不用像再Android中那样,把所有耗时的操作都放到异步线程中执行。而是可以使用Dart提供的异步执行特性,例如async/await。例如,你可以使用async/await执行网络请求,而不会引起UI线程挂起。

loadData() async {  String dataURL = "https://jsonplaceholder.typicode.com/posts";  http.Response response = await http.get(dataURL);  setState(() {    widgets = json.decode(response.body);  });}

一但等待的网络请求完成后,通过setState()方法来更新UI,触发重新绘制整个视图子树并更新数据。下面和这个例子模拟了一个网络请求后,将数据显示到列表中。

import 'dart:convert';import 'package:flutter/material.dart';import 'package:http/http.dart' as http;void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  List widgets = [];  @override  void initState() {    super.initState();    loadData();  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("Sample App"),      ),      body: ListView.builder(          itemCount: widgets.length,          itemBuilder: (BuildContext context, int position) {            return getRow(position);          }));  }  Widget getRow(int i) {    return Padding(      padding: EdgeInsets.all(10.0),      child: Text("Row ${widgets[i]["title"]}")    );  }  loadData() async {    String dataURL = "https://jsonplaceholder.typicode.com/posts";    http.Response response = await http.get(dataURL);    setState(() {      widgets = json.decode(response.body);    });  }}

如何将任务移至后台线程

Flutter是一个单线程并且运行一个事件循环(类似于Node.js),我们不用担心线程挂你或线程切换。如果是简单的IO阻塞任务,例如磁盘访问、网络请求,仅仅使用async/await就可以了。
如果async/await不能满足需求,例如需要计算密集型任务,会导致CPU忙碌,你可以将它移动到Isolate来避免阻塞主线程的事件循环。

Isolate是单独执行线程,它不会共享主线程的堆内存。这也就意味着不能再主线程中访问Isolate线程的变量,或者在Isolate线程中调用setState()方法更新UI。
下面的示例中展示了一个简单的isolate,如果和主线程分享数据并更新UI。

loadData() async {  ReceivePort receivePort = ReceivePort();  await Isolate.spawn(dataLoader, receivePort.sendPort);  // The 'echo' isolate sends its SendPort as the first message  SendPort sendPort = await receivePort.first;  List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts");  setState(() {    widgets = msg;  });}static dataLoader(SendPort sendPort) async {  // Open the ReceivePort for incoming messages.  ReceivePort port = 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 = ReceivePort();  port.send([msg, response.sendPort]);  return response.first;}

dataLoader()是Isolate在单独线程执行的入口,可以在里面执行CPU密集型运算,如json解析、加解密等。
下面是一个可以执行的完整示例。

import 'dart:convert';import 'package:flutter/material.dart';import 'package:http/http.dart' as http;import 'dart:async';import 'dart:isolate';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  List widgets = [];  @override  void initState() {    super.initState();    loadData();  }  showLoadingDialog() {    if (widgets.length == 0) {      return true;    }    return false;  }  getBody() {    if (showLoadingDialog()) {      return getProgressDialog();    } else {      return getListView();    }  }  getProgressDialog() {    return Center(child: CircularProgressIndicator());  }  @override  Widget build(BuildContext context) {    return Scaffold(        appBar: AppBar(          title: Text("Sample App"),        ),        body: getBody());  }  ListView getListView() => ListView.builder(      itemCount: widgets.length,      itemBuilder: (BuildContext context, int position) {        return getRow(position);      });  Widget getRow(int i) {    return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));  }  loadData() async {    ReceivePort receivePort = ReceivePort();    await Isolate.spawn(dataLoader, receivePort.sendPort);    // The 'echo' isolate sends its 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 = 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 = ReceivePort();    port.send([msg, response.sendPort]);    return response.first;  }}

在Flutter中OkHttp的等价物

在Flutter中,可以使用一个流行的http package非常容易的创建一个网络请求。虽然http package没有实现OkHttp中所有的特性,但它抽象了你通常自己实现的大部分网络,使其成为一种简单的网络呼叫方式。

在pubspec.yaml中添加依赖。

dependencies:  ...  http: ^0.11.3+16

在http.get()方法上调用await发起一个网络请求。

import 'dart:convert';import 'package:flutter/material.dart';import 'package:http/http.dart' as http;[...]  loadData() async {    String dataURL = "https://jsonplaceholder.typicode.com/posts";    http.Response response = await http.get(dataURL);    setState(() {      widgets = json.decode(response.body);    });  }}

为长时间的任务显示进度

在Flutter中使用ProgressIndicator小部件来显示任务的执行进度。

import 'dart:convert';import 'package:flutter/material.dart';import 'package:http/http.dart' as http;void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  List widgets = [];  @override  void initState() {    super.initState();    loadData();  }  showLoadingDialog() {    return widgets.length == 0;  }  getBody() {    if (showLoadingDialog()) {      return getProgressDialog();    } else {      return getListView();    }  }  getProgressDialog() {    return Center(child: CircularProgressIndicator());  }  @override  Widget build(BuildContext context) {    return Scaffold(        appBar: AppBar(          title: Text("Sample App"),        ),        body: getBody());  }  ListView getListView() => ListView.builder(      itemCount: widgets.length,      itemBuilder: (BuildContext context, int position) {        return getRow(position);      });  Widget getRow(int i) {    return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));  }  loadData() async {    String dataURL = "https://jsonplaceholder.typicode.com/posts";    http.Response response = await http.get(dataURL);    setState(() {      widgets = json.decode(response.body);    });  }}

工程结构和资源

在哪存储与分辨率相关的图片文件

在Android中,resources和assets是两种不同类型的资源,也会被不同的对待,而在Flutter app中只有assets。

Flutter遵循简单的基于屏幕密度的格式,类似于iOS。assets可能是1.0x,2.0x,3.0x或其他倍数。Flutter没有dp单位,但是有逻辑像素,这和与设备无关的像素基本相同。其中,devicePixelRatio表示单个逻辑像素中的物理像素的比率。
下面表格说明了Android中的分辨率限定符和Flutter像素比率的对应关系。

Android密度限定符 Flutter像素比率
ldpi 0.75x
mdpi 1.0x
hdpi 1.5x
xhdpi 2.0x
xxhdpi 3.0x
xxxhdpi 4.0x

在Flutter1.0 beta2之前,Flutter中定义的资源不能被原生代码访问,原生资源也能在Flutter中访问,因为它们位于不同的文件夹中。
从Flutter beta2开始,assets资源被存储在原生的asset文件夹中,可以在Android中使用原生API AssetManager访问它们。

val flutterAssetStream = assetManager.open("flutter_assets/assets/my_flutter_asset.png")

为了在Flutter中也能访问它们,需要在pubspec.yaml文件中逐一添加声明。

在Flutter中的资源分为两种,一种是项目自有资源,第二种是外部依赖包中的资源。当使用第二种资源时需要指定包名,而第一种不能指定。

//项目资源路径为{project}/images/a_dot_burr.jpeg//实际资源路径为{apk}/assets/flutter_assets/images/a_dot_burr.jpegreturn AssetImage("images/a_dot_burr.jpeg");//资源路径为{project}/packages/extrapackagename/images/a_dot_burr.jpeg//实际资源路径为{apk}/assets/flutter_assets/packages/extrapackagename/images/a_dot_burr.jpegreturn AssetImage("images/a_dot_burr.jpeg", package:'extrapackagename');

在哪存储字符串资源并处理国际化

在Flutter中并没有类似Android中的资源管理系统,所以当前比较好的处理方式是将字符串资源统一写到一个类中,并以静态常量方式去访问。

class Strings {  static String welcomeMessage = "Welcome To Flutter";}Text(Strings.welcomeMessage)

至于国际化和本地化的处理可以使用intl库。该软件包提供国际化和本地化功能,包括文本转换,复数和性别,日期/数字格式和解析以及双向文本。
字符串资源国际化实现简单来说分为三步:

  1. 追踪当前设备的语言
  2. 根据语言动态加载字符串资源到一个Map集合中
  3. 使用key,从Map中获取指定字符串

Gradle的等价物是什么以及如何添加依赖

在Android中,使用Gradle构建脚本添加依赖。
在Flutter中,使用Dart自带的构建系统和Pub包管理。这个工具代理了Android和iOS各自平台构建原生代码的过程。
当需要为原生平台单独添加依赖时,可以在Flutter工程下对应的android/iOS目录下找到对应的依赖文件。
一般情况下,只需要在pubspec.yaml文件中添加Flutter依赖。

Activity和Fragment

Activity和Fragment在Flutter中的等价物

在Flutter中,没有Activity和Fragment,所有页面均是在一个Activity(FlutterActivity)显示的,显示的控件继承自SurfaceView,SurfaceView里显示的所有内容都是由widget组合出来的。页面切换和控件操作均是通过设置一个或多个widget不同的状态和返回值做到的。

如何监听Android中Activity的声明周期

在Android中,我们可以通过覆盖Activity的声明周期方法或在Application中注册ActivityLifecycleCallbacks回调来获取Activity的声明周期变化。
当然,Flutter的设计者也将原生应用的声明周期事件传递到了Flutter中,只不过对大部分事件做了屏蔽,最终可监测到的事件只有四个:

  • inactive 应用处于未活动状态,不会接受用户输入。该事件是iOS专用的,Android平台没有对应的映射状态
  • paused 应用对用户不可见,不会响应用户输入,并且在后台运行。对应Android中的onPause()事件。
  • resumed 应用可见并且可以响应用户输入。对应Android中onPostResume()事件
  • suspending 应用被短暂的挂起,对应Android中的onStop()事件。但是在iOS中没有对应映射,所以在iOS平台上不会触发此事件

为什么Flutter没有将所有生命周期事件开放给调用者?
因为Flutter负责为开发者启动和关闭绘制引擎。在大多数情况下,几乎没有理由需要观察Flutter侧的活动生命周期。如果需要依据生命周期获取或释放native资源,那么应该去native侧去做。

下面代码展示了如何监测仅有的四个生命周期。

import 'package:flutter/widgets.dart';class LifecycleWatcher extends StatefulWidget {  @override  _LifecycleWatcherState createState() => _LifecycleWatcherState();}class _LifecycleWatcherState extends State with WidgetsBindingObserver {  AppLifecycleState _lastLifecycleState;  @override  void initState() {    super.initState();    WidgetsBinding.instance.addObserver(this);  }  @override  void dispose() {    WidgetsBinding.instance.removeObserver(this);    super.dispose();  }  @override  void didChangeAppLifecycleState(AppLifecycleState state) {    setState(() {      _lastLifecycleState = state;    });  }  @override  Widget build(BuildContext context) {    if (_lastLifecycleState == null)      return Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);    return Text('The most recent lifecycle state this widget observed was: $_lastLifecycleState.',        textDirection: TextDirection.ltr);  }}void main() {  runApp(Center(child: LifecycleWatcher()));}

布局

LinearLayout的替代品

在Flutter中,使用Row widget和Column widget达到和LinearLayout相同的效果。

@overrideWidget build(BuildContext context) {  return Row(    mainAxisAlignment: MainAxisAlignment.center,    children: [      Text('Row One'),      Text('Row Two'),      Text('Row Three'),      Text('Row Four'),    ],  );}@overrideWidget build(BuildContext context) {  return Column(    mainAxisAlignment: MainAxisAlignment.center,    children: [      Text('Column One'),      Text('Column Two'),      Text('Column Three'),      Text('Column Four'),    ],  );}

RelativeLayout的替代品

在Flutter中有多钟方式可以实现RelativeLayout布局的效果。

  1. 使用Column和Row的组合
  2. 使用Stack小部件

下面是使用Stack的示例

class _HomeScreenState extends State {  PageController _pageController = new PageController(initialPage: 2);  @override  build(BuildContext context) {    final Map pages = {      'My Music': new Center(        child: new Text('My Music not implemented'),      ),      'Shared': new Center(        child: new Text('Shared not implemented'),      ),      'Feed': new Feed(),    };    TextTheme textTheme = Theme      .of(context)      .textTheme;    return new Stack(      children: [        new Container(          decoration: new BoxDecoration(            gradient: new LinearGradient(              begin: FractionalOffset.topCenter,              end: FractionalOffset.bottomCenter,              colors: [                const Color.fromARGB(255, 253, 72, 72),                const Color.fromARGB(255, 87, 97, 249),              ],              stops: [0.0, 1.0],            )          ),          child: new Align(            alignment: FractionalOffset.bottomCenter,            child: new Container(              padding: const EdgeInsets.all(10.0),              child: new Text(                'T I Z E',                style: textTheme.headline.copyWith(                  color: Colors.grey.shade800.withOpacity(0.8),                  fontWeight: FontWeight.bold,                ),              ),            )          )        ),        new Scaffold(          backgroundColor: const Color(0x00000000),          appBar: new AppBar(            backgroundColor: const Color(0x00000000),            elevation: 0.0,            leading: new Center(              child: new ClipOval(                child: new Image.network(                  'http://i.imgur.com/TtNPTe0.jpg',                ),              ),            ),            actions: [              new IconButton(                icon: new Icon(Icons.add),                onPressed: () {                  // TODO: implement                },              ),            ],            title: const Text('tofu\'s songs'),            bottom: new CustomTabBar(              pageController: _pageController,              pageNames: pages.keys.toList(),            ),          ),          body: new PageView(            controller: _pageController,            children: pages.values.toList(),          ),        ),      ],    );  }}

ScrollView的替代品

在Flutter中使用ListView小部件达到ScrollView和RecycleView的效果。

@overrideWidget build(BuildContext context) {  return ListView(    children: [      Text('Row One'),      Text('Row Two'),      Text('Row Three'),      Text('Row Four'),    ],  );}

在Flutter中如何处理屏幕朝向

如果AndroidManifest.xml包含以下内容,FlutterView将处理配置更改:

android:configChanges="orientation|screenSize"

手势检测和触摸事件处理

如何为widget添加点击事件

在Android中可以通过setOnClinkListener为View添加点击事件。
在Flutter中有两种方式添加触摸事件:

  1. 对于支持事件监测的widget,直接添加一个回调方法即可。如RaisedButton包含一个onPressed参数。

    @overrideWidget build(BuildContext context) {  return RaisedButton(      onPressed: () {        print("click");      },      child: Text("Button"));}
  2. 如果widget不支持事件检测,则需要包裹一个GestureDetector小部件,然后给onTap参数设置一个回调方法。

    class SampleApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(        body: Center(          child: GestureDetector(            child: FlutterLogo(              size: 200.0,            ),            onTap: () {              print("tap");            },          ),        ));  }}

如何为widget检测其他手势

使用GestureDetector除了可以检测点击事件,还可以检测其他几种手势。

  1. Tap 单击

    1.1 onTapDown 按下
    1.2 onTapUp 抬起
    1.3 onTap 点击
    1.4 onTapCancel 点击取消

  2. Double tap 双击

    2.1 onDoubleTap

  3. Long press 长按

    3.1 onLongPree

  4. Vertical drag 垂直拖拽

    4.1 onVerticalDragStart 开始拖拽
    4.2 onVerticalDragUpdate 拖拽位置更新
    4.3 onVerticalDragEnd 拖拽结束

  5. Horizontal drag 水平拖拽

    5.1 onHorizontalDragStart 开始
    5.2 onHorizontalDragUpdate 更新
    5.3 onHorizontalDragEnd 结束

下面代码显示了使用GestureDetector的双击手势翻转一个图片。

AnimationController controller;CurvedAnimation curve;@overridevoid initState() {  controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);  curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);}class SampleApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return Scaffold(        body: Center(          child: GestureDetector(            child: RotationTransition(                turns: curve,                child: FlutterLogo(                  size: 200.0,                )),            onDoubleTap: () {              if (controller.isCompleted) {                controller.reverse();              } else {                controller.forward();              }            },        ),    ));  }}

列表和适配器

ListView的替代品

前面在ScrollView的部分说到,在Flutter中的ListView的替代品也是 ListView。
Android中的ListView用到了Item布局重用和回收的技术,防止出现错乱和内存占用的问题。

在Flutter中,由于widget不可变的特性,将由Flutter负责快速平滑的滚动。

import 'package:flutter/material.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("Sample App"),      ),      body: ListView(children: _getListData()),    );  }  _getListData() {    List widgets = [];    for (int i = 0; i < 100; i++) {      widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));    }    return widgets;  }}

如何知道点击哪一个Item

在Android中有两种为列表设置点击事件的方式:第一种是为ListView添加点击事件,由ListView计算当前点击位置。第二种是每个ItemView添加点击事件。
在Flutter中使用的是第二种方式。

import 'package:flutter/material.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("Sample App"),      ),      body: ListView(children: _getListData()),    );  }  _getListData() {    List widgets = [];    for (int i = 0; i < 100; i++) {      widgets.add(GestureDetector(        child: Padding(            padding: EdgeInsets.all(10.0),            child: Text("Row $i")),        onTap: () {          print('row tapped');        },      ));    }    return widgets;  }}

异步刷新ListView

在Android中,可以通过更新Adapter的数据源,然后调用notifyDataSetChanged方法通知页面刷新。

在Flutter中,如果要更新setState()中的小部件列表,您会很快发现您的数据没有直观地改变。 这是因为当调用setState()时,Flutter渲染引擎会查看窗口小部件树以查看是否有任何更改。 当它到达ListView时,它执行==检查,并确定两个ListView是相同的。 没有任何改变,因此不需要更新。

有关更新ListView的简单方法,请在setState()内创建一个新List,并将旧列表中的数据复制到新列表中。 虽然这种方法很简单,但不建议用于大型数据集,如下一个示例所示。

import 'package:flutter/material.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  List widgets = [];  @override  void initState() {    super.initState();    for (int i = 0; i < 100; i++) {      widgets.add(getRow(i));    }  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("Sample App"),      ),      body: ListView(children: widgets),    );  }  Widget getRow(int i) {    return GestureDetector(      child: Padding(          padding: EdgeInsets.all(10.0),          child: Text("Row $i")),      onTap: () {        setState(() {          widgets = List.from(widgets);          widgets.add(getRow(widgets.length + 1));          print('row $i');        });      },    );  }}

当数据集比较大或列表动态加载时,需要使用更高效的方式ListView.Builder。这种方式基本上相当于Android的RecycleView,可以为你动态回收列表元素。

import 'package:flutter/material.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  List widgets = [];  @override  void initState() {    super.initState();    for (int i = 0; i < 100; i++) {      widgets.add(getRow(i));    }  }  @override  Widget build(BuildContext context) {    return Scaffold(        appBar: AppBar(          title: Text("Sample App"),        ),        body: ListView.builder(            itemCount: widgets.length,            itemBuilder: (BuildContext context, int position) {              return getRow(position);            }));  }  Widget getRow(int i) {    return GestureDetector(      child: Padding(          padding: EdgeInsets.all(10.0),          child: Text("Row $i")),      onTap: () {        setState(() {          widgets.add(getRow(widgets.length + 1));          print('row $i');        });      },    );  }}

使用ListView.builder更高效的原因是:1.没有重新创建ListView。2.没有重新创建item列表,而是直接添加。
ListView.builder中的itemBuilder参数类似于Android 列表适配器中的getView方法,它根据指定位置并返回需要渲染的widget。

使用文本

在Text视图上自定义字体

在Android SDK(从Android O开始)中,您创建一个Font资源文件并将其传递给TextView的FontFamily参数。

在Flutter中,将字体文件放在文件夹中,并在pubspec.yaml文件中引用它,类似于导入图像的方式。

fonts:   - family: MyCustomFont     fonts:       - asset: fonts/MyCustomFont.ttf       - style: italic

然后给Text小部件赋值字体。

@overrideWidget build(BuildContext context) {  return Scaffold(    appBar: AppBar(      title: Text("Sample App"),    ),    body: Center(      child: Text(        'This is a custom font text',        style: TextStyle(fontFamily: 'MyCustomFont'),      ),    ),  );}

如何设置Text视图的样式

除了字体,你可以为Text小部件自定义其他样式属性。

  • fontSize
  • fontStyle
  • fontFamily
  • fontWeight
  • color
  • height
  • inherit 继承
  • letterSpacing 字符间距
  • textBaseline 文本基线
  • wordSpacing 字间距
  • hashCode
  • decoration
  • decorationColor
  • decorationStyle

表单输入

输入框中hint的等价物

使用InputDecoration为一个Text小部件添加一个hintText属性。

body: Center(  child: TextField(    decoration: InputDecoration(hintText: "This is a hint"),  ))

如何显示文本非法的错误描述

类似于hint,向InputDecoration添加一个errorText参数。

import 'package:flutter/material.dart';void main() {  runApp(SampleApp());}class SampleApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: SampleAppPage(),    );  }}class SampleAppPage extends StatefulWidget {  SampleAppPage({Key key}) : super(key: key);  @override  _SampleAppPageState createState() => _SampleAppPageState();}class _SampleAppPageState extends State {  String _errorText;  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(        title: Text("Sample App"),      ),      body: Center(        child: TextField(          onSubmitted: (String text) {            setState(() {              if (!isEmail(text)) {                _errorText = 'Error: This is not an email';              } else {                _errorText = null;              }            });          },          decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),        ),      ),    );  }  _getErrorText() {    return _errorText;  }  bool isEmail(String em) {    String emailRegexp =        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';    RegExp regExp = RegExp(emailRegexp);    return regExp.hasMatch(em);  }}

Flutter插件

访问GPS传感器

使用geolocator插件
这个插件提供了访问各个平台的定位服务的功能,具体包含如下属性:

  • 获取当前设备位置
  • 获取上一次定位
  • 获取位置的持续更新
  • 检查设备的定位服务是否开启
  • 地址与地理坐标的相互转换
  • 计算两个坐标之间的距离
  • 在Android平台上,插件Google Play服务是否可用
添加依赖
dependencies:  geolocator: '^2.1.1'
定位
//获取当前位置import 'package:geolocator/geolocator.dart';Position position = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high);//获取上次定位位置Position position = await Geolocator().getLastKnownPosition(desiredAccuracy: LocationAccuracy.high);//监听位置更新var geolocator = Geolocator();var locationOptions = LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 10);StreamSubscription positionStream = geolocator.getPositionStream(locationOptions).listen(    (Position position) {        print(_position == null ? 'Unknown' : _position.latitude.toString() + ', ' + _position.longitude.toString());    });    //检查定位服务是否开启GeolocationStatus geolocationStatus  = await Geolocator().checkGeolocationPermissionStatus();//如果不使用google的定位服务,可以设置强制使用Android平台的定位管理Geolocator geolocator = Geolocator()..forceAndroidLocationManager = true;GeolocationStatus geolocationStatus  = await geolocator.checkGeolocationPermissionStatus();
地址转换
import 'package:geolocator/geolocator.dart';//地址转经纬度List placemark = await Geolocator().placemarkFromAddress("Gronausestraat 710, Enschede");//经纬度转地址List placemark = await Geolocator().placemarkFromCoordinates(52.2165157, 6.9437819);

上面两个方法还可以接受一个可选参数,用来指定语言和国家,示例格式如下:

区域标识 描述
en 全英
en_US 美式英语
en_UK 英式英语
nl_NL
nl_BE
距离计算
import 'package:geolocator/geolocator.dart';//两个经纬度位置的距离 startLatitude,startLongitude,endLatitude,endLongitudedouble distanceInMeters = await Geolocator().distanceBetween(52.2165157, 6.9437819, 52.3546274, 4.8285838);
权限声明

Android

iOS

NSLocationAlwaysUsageDescriptionThis app needs access to location when in the background.NSLocationAlwaysAndWhenInUseUsageDescriptionThis app needs access to location when open and in the background.

定位经度说明

Android iOS
lowest 500m 3000m
low 500m 1000m
medium 100 - 500m 100m
high 0 - 100m 10m
best 0 - 100m ~0m
bestForNavigation 0 - 100m Optimized for navigation

访问相机

使用image_picker来从相册中选择图片或通过相机拍照。

添加依赖
dependencies:  image_picker: '^0.4.10'

iOS设备还要添加额外的keys到info.plist文件中。

  • NSPhotoLibraryUsageDescription - describe why your app needs permission for the photo library. This is called Privacy - Photo Library Usage Description in the visual editor.
  • NSCameraUsageDescription - describe why your app needs access to the camera. This is called Privacy - Camera Usage Description in the visual editor.
  • NSMicrophoneUsageDescription - describe why your app needs access to the microphone, if you intend to record videos. This is called Privacy - Microphone Usage Description in the visual editor.
示例
import 'package:image_picker/image_picker.dart';class MyHomePage extends StatefulWidget {  @override  _MyHomePageState createState() => new _MyHomePageState();}class _MyHomePageState extends State {  File _image;  Future getImage() async {    var image = await ImagePicker.pickImage(source: ImageSource.camera);    setState(() {      _image = image;    });  }  @override  Widget build(BuildContext context) {    return new Scaffold(      appBar: new AppBar(        title: new Text('Image Picker Example'),      ),      body: new Center(        child: _image == null            ? new Text('No image selected.')            : new Image.file(_image),      ),      floatingActionButton: new FloatingActionButton(        onPressed: getImage,        tooltip: 'Pick Image',        child: new Icon(Icons.add_a_photo),      ),    );  }}

在Flutter应用中使用NDK

当前在Flutter还不支持直接访问c/c++代码。
只能通过编写插件,先调用Android的代码,再通过jni调用c/c++代码。

主题

如何为App设置主题

开箱即用,Flutter带有一个漂亮的Material Design实现,它可以满足您通常所需的大量样式和主题需求。与在Android中声明主题然后使用AndroidManifest.xml将其分配给应用程序的Android不同,在Flutter中,您可以在顶级窗口小部件中声明主题。

要在应用程序中充分利用Material Components,您可以将顶级窗口小部件MaterialApp声明为应用程序的入口点。 MaterialApp是一个便利小部件,它包含了许多小部件,这些小部件通常是实现Material Design的应用程序所必需的。它通过添加特定于材料的功能构建在WidgetsApp上。

您还可以使用WidgetApp作为应用程序小部件,它提供了一些相同的功能,但不如MaterialApp丰富。

要自定义任何子组件的颜色和样式,请将ThemeData对象传递给MaterialApp小部件。例如,在下面的代码中,主样本设置为蓝色,文本选择颜色为红色。

class SampleApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Sample App',      theme: ThemeData(        primarySwatch: Colors.blue,        textSelectionColor: Colors.red      ),      home: SampleAppPage(),    );  }}

数据库和本地存储

如何访问SharedPreferences

在Android中,您可以使用SharedPreferences API存储一小组键值对。

在Flutter中,使用Shared_Preferences插件访问此功能。 此插件包含共享首选项和NSUserDefaults(iOS等效项)的功能。

import 'package:flutter/material.dart';import 'package:shared_preferences/shared_preferences.dart';void main() {  runApp(    MaterialApp(      home: Scaffold(        body: Center(          child: RaisedButton(            onPressed: _incrementCounter,            child: Text('Increment Counter'),          ),        ),      ),    ),  );}_incrementCounter() async {  SharedPreferences prefs = await SharedPreferences.getInstance();  int counter = (prefs.getInt('counter') ?? 0) + 1;  print('Pressed $counter times.');  prefs.setInt('counter', counter);}

如何访问SQLite

使用SQFlite插件,同时支持Android和iOS。

  • 支持事务和批处理
  • 开启时自动版本管理
  • 更删改查的帮助类
  • 在后台线程执行数据库操作
添加依赖
dependencies:  ...  sqflite: any
原始sql查询
// Get a location using getDatabasesPathvar databasesPath = await getDatabasesPath();String path = join(databasesPath, 'demo.db');// Delete the databaseawait deleteDatabase(path);// open the databaseDatabase database = await openDatabase(path, version: 1,    onCreate: (Database db, int version) async {  // When creating the db, create the table  await db.execute(      'CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, num REAL)');});// Insert some records in a transactionawait database.transaction((txn) async {  int id1 = await txn.rawInsert(      'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');  print('inserted1: $id1');  int id2 = await txn.rawInsert(      'INSERT INTO Test(name, value, num) VALUES(?, ?, ?)',      ['another name', 12345678, 3.1416]);  print('inserted2: $id2');});// Update some recordint count = await database.rawUpdate(    'UPDATE Test SET name = ?, VALUE = ? WHERE name = ?',    ['updated name', '9876', 'some name']);print('updated: $count');// Get the recordsList list = await database.rawQuery('SELECT * FROM Test');List expectedList = [  {'name': 'updated name', 'id': 1, 'value': 9876, 'num': 456.789},  {'name': 'another name', 'id': 2, 'value': 12345678, 'num': 3.1416}];print(list);print(expectedList);assert(const DeepCollectionEquality().equals(list, expectedList));// Count the recordscount = Sqflite    .firstIntValue(await database.rawQuery('SELECT COUNT(*) FROM Test'));assert(count == 2);// Delete a recordcount = await database    .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']);assert(count == 1);// Close the databaseawait database.close();
SQL帮助类使用
final String tableTodo = 'todo';final String columnId = '_id';final String columnTitle = 'title';final String columnDone = 'done';class Todo {  int id;  String title;  bool done;  Map toMap() {    var map = {      columnTitle: title,      columnDone: done == true ? 1 : 0    };    if (id != null) {      map[columnId] = id;    }    return map;  }  Todo();  Todo.fromMap(Map map) {    id = map[columnId];    title = map[columnTitle];    done = map[columnDone] == 1;  }}class TodoProvider {  Database db;  Future open(String path) async {    db = await openDatabase(path, version: 1,        onCreate: (Database db, int version) async {      await db.execute('''create table $tableTodo (   $columnId integer primary key autoincrement,   $columnTitle text not null,  $columnDone integer not null)''');    });  }  Future insert(Todo todo) async {    todo.id = await db.insert(tableTodo, todo.toMap());    return todo;  }  Future getTodo(int id) async {    List maps = await db.query(tableTodo,        columns: [columnId, columnDone, columnTitle],        where: '$columnId = ?',        whereArgs: [id]);    if (maps.length > 0) {      return Todo.fromMap(maps.first);    }    return null;  }  Future delete(int id) async {    return await db.delete(tableTodo, where: '$columnId = ?', whereArgs: [id]);  }  Future update(Todo todo) async {    return await db.update(tableTodo, todo.toMap(),        where: '$columnId = ?', whereArgs: [todo.id]);  }  Future close() async => db.close();}
事务

不要在事务过程中直接操作数据库。

await database.transaction((txn) async {  // Ok  await txn.execute('CREATE TABLE Test1 (id INTEGER PRIMARY KEY)');    // DON'T  use the database object in a transaction  // this will deadlock!  await database.execute('CREATE TABLE Test2 (id INTEGER PRIMARY KEY)');});
批处理

为避免在dart和原生代码中频繁通信,可以使用批处理:

batch = db.batch();batch.insert('Test', {'name': 'item'});batch.update('Test', {'name': 'new_item'}, where: 'name = ?', whereArgs: ['item']);batch.delete('Test', where: 'name = ?', whereArgs: ['item']);results = await batch.commit();

如果不关心批处理的返回值,可以为commit添加一个参数:

await batch.commit(noResult: true);

当把批处理添加到事务中时,批处理的执行时机是等待事务的提交。

await database.transaction((txn) async {  var batch = txn.batch();    // ...    // commit but the actual commit will happen when the transaction is commited  // however the data is available in this transaction  await batch.commit();    //  ...});

在批处理过程中,可以设置忽略错误而继续执行。

await batch.commit(continueOnError: true);
表名和列名

一般来说,最好避免使用SQLite关键字作为实体名字。

"add","all","alter","and","as","autoincrement","between","case","check","collate","commit","constraint","create","default","deferrable","delete","distinct","drop","else","escape","except","exists","foreign","from","group","having","if","in","index","insert","intersect","into","is","isnull","join","limit","not","notnull","null","on","or","order","primary","references","select","set","table","then","to","transaction","union","unique","update","using","values","when","where"
支持的SQLite数据类型

SQLite不支持DateTime和bool类型,可以用int/string和INTEGER代替

INTERGET int类型,-263~263-1
REAL num类型
TEXT String类型
BLOB Unit8List类型

通知

如何设置推送通知

目前只能使用firebase_messaging插件来使用Firebase云消息推送。

更多相关文章

  1. Android中Touch事件分析--解决HorizontalScrollView滑动和按钮事
  2. Android中aar和jar文件的认识
  3. Android中的脑残设计
  4. 在android中使用proguard混淆代码出现“Conversion to Dalvik fo
  5. 整合谷歌Admob在Unity3D的Android和iOS游戏
  6. android view 上下左右滑动 事件
  7. android ContentProvider 使用实例
  8. Android模仿QQ空间图片上传——原理
  9. android click事件注解

随机推荐

  1. Android权威官方屏幕适配全攻略
  2. 如何提高Android的性能
  3. Android(安卓)设计的几处硬伤
  4. Android(安卓)APK应用安装位置选择与对Wi
  5. android init.rc 修改方法---adb shell
  6. Native C of Android
  7. android - 为安全而设计 - 3 - 开发文档
  8. Andriod是什么
  9. Android(安卓)在应用中使用用户凭证(PIN码
  10. Android(安卓)原始资源文件的使用详解