- 项目集成
- Material 主题常用 Widget
- 页面跳转
- 导航栏
- 异步请求
- 平台通信
- Native 集成 Flutter
- 数据存储
- 常见问题
- https://stackoverflow.com/questions/54556381/flutter-error-could-not-navigate-to-initial-route">One or more of those objects was null, and therefore the initial route specified will be ignored and “/“ will be used instead. see https://stackoverflow.com/questions/54556381/flutter-error-could-not-navigate-to-initial-route
- https://stackoverflow.com/questions/54556381/flutter-error-could-not-navigate-to-initial-route">One or more of those objects was null, and therefore the initial route specified will be ignored and “/“ will be used instead. see https://stackoverflow.com/questions/54556381/flutter-error-could-not-navigate-to-initial-route
项目集成
Android 集成 Flutter 项目
- 通过 Android Studio 创建 Andriod 原生应用 AndroidApp
- 通过 Android Studio 创建 New Flutter Project, 选择 Flutter Module,创建 Flutter Module FlutterApp
- Android Studio 打开 AndroidApp 项目,然后 New -> Module-> Import Flutter Module 添加 Flutter Module FlutterApp 依赖
开发调试
纯 Flutter 项目开发调试
- 方式一: vscode 打开 Flutter 项目, 按 F5 进 DEBUG,CTRL+F5 进行 HOT RESTART 可以实现 Hot Reload (如果是 flutter run, 修改文件后,需要安 r)
- 方式二:Android Studio 打开 Flutter 项目,

Android 集成 Flutter 开发调试
Android 与 Flutter 混合模式: Android 项目集成 Flutter Module
- 首先,进入 Flutter Module 目录, 命令行执行
flutter attach修改代码后,press “r” 即可实现 Hot Reload

- Andorid Studio 打开 Android 项目,点击绿色三角按钮
进入 原生 App Debug 模式
安装插件
安装第三方依赖需要在 pubspec.yaml 的 dependencies 节点添加插件配置,类似与 npm
dependencies:flutter:sdk: fluttercupertino_icons: ^0.1.2# flutter webview 插件flutter_webview_plugin: 0.3.3# sqlite 数据库sqflite: ^1.1.0
添加好以后,点击 AS 右上方的 Packages get 安装依赖或者进入项目根目录执行 flutter packages get 安装依赖
入口函数
- pubspec.yaml文件中,将
flutter的值设置为:uses-material-design: true。这允许我们可以使用一组预定义Material icons - 为了继承主题数据,widget需要位于
MaterialApp内才能正常显示, 因此我们使用MaterialApp来运行该应用。
import 'package:flutter/material.dart';class App extends StatelessWidget {// This widget is the root of your application.@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo');}}void main() {runApp(App());}
Widget
一切 VIew 都为 Widget:Widget 分是无状态的
StatelessWidget或者是有状态的StatefulWidget
实现自定义 widget
- 实现继承自StatefulWidget的类来表示你要自定义的可变控件
- 实现继承自 State 的类来处理可变控件的状态和样式(build方法)
- 当用户交互发生(onPressed), 可以调用setState方法告诉组件需要重绘
class MyStatefulWidget extends MyStatefulWidget {final String title;final String content;@override_MyStatefulState createState() => _MyStatefulState();}class _MyStatefulState extends State<MyStatefulWidget> {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title),centerTitle: true,),body: Center(child: Text(widget.content),),);}}
常用布局 https://flutterchina.club/widgets/material/
MaterialApp Widget
Material Design 风格应用,封装了 MD 应用常用的组件。MaterialApp 一般作为顶层的 Widget 使用,可以用于应用主题配置
new MaterialApp(title: 'Flutter应用',theme: new ThemeData(//主题色primarySwatch: Colors.blue,),routes: {'/home':(BuildContext context) => HomePage(),'/category':(BuildContext context) => CategoryPage(),//....},initialRoute: '/home',......);
Scaffold Widget
Scaffold是 Material Design 布局结构的基本实现, 定义好了基本的页面结构(appBar, body, bottomNavigationBard等),只需要配置相关信息即可快速实现一个框架页面。
@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(widget.title),centerTitle: true,),body: Center(child: _widgetOptions.elementAt(_selectedIndex),),bottomNavigationBar: BottomNavigationBar( // 典型的底部 Tab 模式items: <BottomNavigationBarItem>[BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),BottomNavigationBarItem(icon: Icon(Icons.category), title: Text('Category')),BottomNavigationBarItem(icon: Icon(Icons.person), title: Text('Profile')),],currentIndex: _selectedIndex,fixedColor: Colors.deepPurple,onTap: _onItemTapped,),);}
Material 主题常用 Widget
Scaffold 标准的页面骨架
提供 appBar, body, bottomNavigationBar 等配置入口
class About extends StatelessWidget {@overrideWidget build(BuildContext context) {return new Scaffold(appBar: new AppBar(title: new Text("关于"),centerTitle: true,leading: new IconButton(icon: new Icon(Icons.arrow_back),onPressed: () {SystemNavigator.pop(); // remove this activity from the stack})),body: new Center(child: new Text("Flutter About Page"),),);}}
刷新与分页
- 结合 RefreshIndicator,通过 onRefresh 实现下拉刷新
- 结合 RefreshIndicator,通过 controller (ScrollController)实现上拉分页加载
- 通过 ListView.builder 和 ListTile 构建列表
@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("列表加载测试"),centerTitle: true,leading: new IconButton(icon: new Icon(Icons.arrow_back),onPressed: () {SystemNavigator.pop(); // remove this activity from the stack})),body: new RefreshIndicator(child: ListView.builder(itemCount: listItems.length,itemBuilder: (context, index) {return ListTile(leading: new Icon(Icons.list),title: Text("列表加载测试-$index"),onTap:(){});},controller: _scrollController, // 使用 ScrollController 组件实现 上拉分页),onRefresh: _pullRefresh, // 下拉刷新回掉,默认是转圈loading),);}
页面跳转
静态路由
在 main.dart 文件 app 启动入口通过 routes 配置静态路由或者 onGenerateRoute 动态处理路由。 注意:默认路由是 /,不支持参数参数, 支持 home/list 模式,但不能使用 /home/list 模式,这样会导致匹配到 / 路由
- 静态路由定义
void main() => runApp(App());class App extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(theme: ThemeData(),home: MyHomePage(title: 'HappyFlutter'),routes: <String, WidgetBuilder> {// 定义静态路由,不能传递参数'home': (BuildContext context) => new Home(false),'profile': (BuildContext context) => new Profile(),},onGenerateRoute: (settings) {print('--name' + settings.name);print(settings.arguments);});}}
- 静态路由跳转
通过 Navigator.of(context).pushNamed 方式进行静态路由跳转
Navigator.of(context).pushNamed('home');Navigator.of(context).pushNamed('home/dialog').then((value) {// 获取 view 返回值})
动态路由
Navigator.of(context).push(new PageRouteBuilder(pageBuilder:(BuildContext context,Animation<double> animation,Animation<double> secondaryAnimation) {// 可以通过构造函数传递参数return new Home();}));
Native 跳转 Flutter 传递参数
目前在 Native 中直接打开 FlutterActivity 是不支持直接传递参数的(唯一的参数就是 route) , 不过可以把 route 以 url 的形式传递参数是可行的, 然后在 Flutter 的 onGenerateRoute 函数对 url 进行解析,就能变相实现参数传递。
- Android 代码
Intent intent = new Intent(this, NativeFlutterActivity.class);intent.setAction(Intent.ACTION_RUN);intent.putExtra("route", "test?msg=Native跳转Flutter参数测试&id=111111");startActivity(intent);
- Flutter 代码
// This widget is the root of your application.class App extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(theme: ThemeData(primaryColor: Color(0xFF008577),),home: MyHomePage(title: 'HappyFlutter'),routes: <String, WidgetBuilder> {'home': (BuildContext context) => new Home()},onGenerateRoute: (settings) {Uri uri = Uri.parse(settings.name);String route = uri.path;Map<String, String> params = uri.queryParameters;switch(route) {case 'test':return MaterialPageRoute(builder: (context)=> Detail(params));break;}});}}
导航栏
Navigator.pop(context) 关闭当前 FlutterView SystemNavigator.pop() 关闭当前Flutter 所在的 Activity
import 'package:flutter/services.dart';@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("导航栏标题"),centerTitle: true, // 标题居中leading: new IconButton( // 顶部导航栏右边返回箭头图片icon: new Icon(Icons.arrow_back),onPressed: () { //SystemNavigator.pop(); // remove this activity from the stack}),actions:[ // 右边菜单new IconButton(icon: new Icon(Icons.settings),onPressed: () {Scaffold.of(context).showSnackBar(new SnackBar(content: new Text("设置")));})]));}
异步请求
Dart 语言中使用 Future (类比Promise) 实现异步操作,一般配合 async和 await 使用。
// 多个 Future 执行,注意不是顺序执行Future.wait([f1(), f2()]).then((List responses) => {}).catchError((e) => {});// 延迟 2sFuture.delayed(Duration(seconds: 2), () {});
- 从数据库查询用户信息
```dart
// 定义返回值 Future 函数
Future
- >> getFavoriteList() async {
Database dbClient = await db;
return await dbClient.rawQuery(‘SELECT * FROM user_info’);
}
// 调用 //直接调用
getFavoriteList().then(((rows){
});
// async 函数内部调用 void getFavoriteListTest() async { List
<a name="lUSSW"></a>### 数据请求> 可以通过 **FutureBuilder** 异步数据请求与UI数据绑定```dartFuture<List<Map<String, dynamic>>> getFavoriteList() async {return [];}@overrideWidget build(BuildContext context) {return Scaffold(body: FutureBuilder<List<Map<String, dynamic>>>(future: favoriteDB.getFavoriteList(),builder: (context, snapshot) {if (snapshot.hasError) print(snapshot.error);List<Map<String, dynamic>> list = snapshot.data;return ListView.builder(itemCount: snapshot.data.length,itemBuilder: (_, int position) {Map item = list[position];return Card(child: ListTile(title: Text(item['title']),onTap: () {NavigationChannel.pushWebRoute({"title": item['title'].toString(), "url": item['url'].toString()});},),);},);},),),};
平台通信
https://flutterchina.club/platform-channels/
Flutter平台特定的API支持不依赖于代码生成,而是依赖于灵活的消息传递的方式:
- 应用的Flutter部分通过平台通道(platform channel)将消息发送到其应用程序的所在的宿主(iOS或Android)。
- 宿主监听的平台通道,并接收该消息。然后它会调用特定于该平台的API(使用原生编程语言) - 并将响应发送回客户端,即应用程序的Flutter部分。
在客户端,MethodChannel (API)可以发送与方法调用相对应的消息。 在宿主平台上,MethodChannel 在Android((API) 和 FlutterMethodChannel iOS (API) 可以接收方法调用并返回结果。这些类允许您用很少的“脚手架”代码开发平台插件。
Flutter 调用 Native
- Dart 发起信道调用
import 'package:flutter/services.dart';static const MethodChannel methodChannel = MethodChannel('samples.flutter.io/battery');Future<void> _getBatteryLevel() async {String batteryLevel;try {final int result = await methodChannel.invokeMethod('getBatteryLevel');batteryLevel = 'Battery level: $result%.';} on PlatformException {batteryLevel = 'Failed to get battery level.';}}
- Java 注册信道
- 直接继承 FlutterActivity 可以快速建立 Native 与 Flutter 的通信桥梁
- 非 FlutterActivity 时,需要自己处理 FlutterView 的应用,可以在创建 FlutterView 时,创建在 Android Application 里面
public class NativeFlutterActivity extends FlutterActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);GeneratedPluginRegistrant.registerWith(this);// Native 与 Flutter 通信依赖 FlutterViewnew MethodChannel(getFlutterView(), 'samples.flutter.io/battery').setMethodCallHandler(new MethodCallHandler() {@Overridepublic void onMethodCall(MethodCall call, Result result) {if (call.method.equals("getBatteryLevel")) {int batteryLevel = getBatteryLevel();if (batteryLevel != -1) {result.success(batteryLevel);} else {result.error("UNAVAILABLE", "Battery level not available.", null);}} else {result.notImplemented();}}});}}
see code: https://github.com/flutter/flutter/examples/platform_channel
Native 调用 Flutter
通过 MethodChannel.invokeMethod 可以主动调用 Flutter 通信
- java 代码
```java package com.easy.team.module;
import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.Nullable; import android.util.Log; import java.util.HashMap; import java.util.Map; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel;
public class NativeActivity extends Activity {
public static String CHANNEL = "com.happy.message/notify";private MethodChannel channel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_native);channel = new MethodChannel(MessageChannel.getFlutterView(), CHANNEL);// Native 主动调用 FlutterMap<String,String> map = new HashMap<String, String>();map.put("from", "native");notifyFlutter("getFlutterVersion", map);}protected void notifyFlutter(String method, Object args) {channel.invokeMethod(method, args, new MethodChannel.Result() {@Overridepublic void success(@Nullable Object o) {Toast.makeText(NativeActivity.this, "message:" + o.toString(), Toast.LENGTH_SHORT).show();}@Overridepublic void error(String s, @Nullable String s1, @Nullable Object o) {Log.d("--NativeActivity:error--", s1);}@Overridepublic void notImplemented() {Log.d("--NativeActivity:notImplemented--","");}});}
}
- Flutter 代码- <br />```dartstatic const MethodChannel methodChannel = MethodChannel('com.happy.message/notify');Future<dynamic> settingChannelHandler(MethodCall methodCall) async {switch (methodCall.method) {case 'getFlutterVersion':return '1.0.0';default:}}@overridevoid initState() {super.initState();methodChannel.setMethodCallHandler(this.settingChannelHandler);}
Native 集成 Flutter
Native Activity 模式
Native 继承 FlutterActivity 就可以快速与 Flutter 建立通道。Flutter 中通过消息通知 Native 用 Activity 的方式打开 Flutter View,这种方式可以解决Native 和 Flutter 返回键问题,也就是统一交给 Native 处理。
// 自定义 FlutterActivitypublic class NativeFlutterActivity extends FlutterActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);GeneratedPluginRegistrant.registerWith(this);}}// Native 使用 FlutterActivity 打开指定 Flutter ViewIntent intent = new Intent(getActivity(), NativeFlutterActivity.class);// 需要指定 ACTION_RUN,FlutterActiviy 底层处理intent.setAction(Intent.ACTION_RUN);// FlutterActiviy 会 从Intent 里面取 route 参数,目前不支持直接传递参数intent.putExtra("route", route);startActivity(intent);
Native Fragment 模式
在 Native 中 Fragment 根据 Flutter 路由动态创建 Fragment
public class NativeFlutterFragment extends Fragment {private FlutterView flutterView;public static Fragment newInstance(String route) {NativeFlutterFragment fragment = new NativeFlutterFragment();Bundle bundle = new Bundle();bundle.putString("route", route);fragment.setArguments(bundle);return fragment;}public static Fragment newInstance(String route, Bundle bundle) {NativeFlutterFragment fragment = new NativeFlutterFragment();if (bundle == null) {bundle = new Bundle();}bundle.putString("route", route);fragment.setArguments(bundle);return fragment;}@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return this.getFlutterView();}@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);this.registerMethodChannel(this.getFlutterView());// 保存当前活动窗口 FlutterView 实例引用,Activity 可以通过 MessageChannel.getFlutterView() 与 Flutter 通信MessageChannel.setFlutterView(this.flutterView);}@Overridepublic void onHiddenChanged(boolean hidden) {Log.d("--onHiddenChanged--", String.valueOf(hidden));super.onHiddenChanged(hidden);if (!hidden) {MessageChannel.setFlutterView(this.flutterView);}}protected void registerMethodChannel(FlutterView flutterView) {new MethodChannel(flutterView, "com.happy/navigation").setMethodCallHandler(new MethodChannel.MethodCallHandler() {@Overridepublic void onMethodCall(MethodCall call, MethodChannel.Result result) {Log.i("--MethodChannel--", "method:" + call.method);if (call.method.equals("pushRoute")) {String route = call.argument("route");HashMap args = (HashMap)call.arguments;Log.i("--MethodChannel--", "route:" + route);if ("native".equals(route)) {Intent intent = new Intent(getActivity(), NativeActivity.class);intent.putExtra("args", args);startActivity(intent);} else if ("web".equals(route)) {Intent intent = new Intent(getActivity(), NativeWebViewActivity.class);intent.putExtra("title", args.get("title").toString());intent.putExtra("url", args.get("url").toString());startActivity(intent);} else { // 每个 Flutter View 使用一个 Native Activity 方式打开页面Intent intent = new Intent(getActivity(), NativeFlutterActivity.class);intent.setAction(Intent.ACTION_RUN);intent.putExtra("route", route);startActivity(intent);}result.success("success");} else if (call.method.equals("pop")) {result.success("success");} else {result.notImplemented();}}});}public FlutterView getFlutterView(){if (this.flutterView == null) {String route = getArguments().getString("route");this.flutterView = Flutter.createView(getActivity(), getLifecycle(), route);// 解决 Flutter 加载黑屏问题this.flutterView.setZOrderOnTop(true);// this.flutterView.setZOrderMediaOverlay(true);this.flutterView.getHolder().setFormat(PixelFormat.TRANSLUCENT);}return this.flutterView;}}
数据存储
sqfite 数据库
通过 sqflite 插件可以完成 sqlite 数据库操作。 经过测试,如果 Native 已经创建的数据库,通过如下方式是可以直接链接 Native 的数据库,也就是 Native 和 Flutter 数据库是可以互通操作。
import 'package:sqflite/sqflite.dart';init() async {String databasesPath = await getDatabasesPath();String path = join(databasesPath, "app_data");return await openDatabase(path, version: 1, onCreate: _onCreate);}Future<Database> get db async {if (_db != null) return _db;_db = await init();return _db;}// 使用Future<List<Map<String, dynamic>>> getList() async {Database client = await db;return await client.rawQuery('SELECT * FROM user_info');}Future<int> delete(int id) async {Database dbClient = await db;return await client.rawDelete('DELETE FROM user_info WHERE id = ?', [id]);}
常见问题
- 静态路由路径匹配问题
静态路由定义时,不能以 /home/test 多级 / 方式定义,否则出现如下错误,最终默认会指向 / 路由。
One or more of those objects was null, and therefore the initial route specified will be ignored and “/“ will be used instead. see https://stackoverflow.com/questions/54556381/flutter-error-could-not-navigate-to-initial-route
- Android项目中嵌入Flutter工程时,切换 FlutterFragment页面时会出现黑屏问题
解决方案见:https://github.com/alibaba/flutter_boost/issues/105
- Android Native 页面如何给 Flutter 页面发送消息?
Native 与 Flutter 通信,可以通过 MethodChannel 实现,而 MethodChannel 是依赖 FlutterView,但 Native Activity 并没有 FlutterView 的实例。目前是采用如下思路处理:在 Native FlutterView 创建时,保存当前最顶部的 FlutterView 全局引用,然后 Native Activity 拿到 FlutterView 全局引用后就可以进行通信了。
Native 与 混合栈以及返回键问题
我们知道,默认 FlutterView 所有页面切换都是通过 View 实现的,而且都在一个 Activity 上,这样导致点击返回键时,导致所有页面都退出了,当然这里可以通过判断是拦截返回键解决。当遇到 Native ->Flutter->Native 这种交替时,整个堆栈的管理还是比较复杂的。目前是在创建 FlutterView 时,都通过一个 Activity 方式去承载,从而解决堆栈问题。当然这样处理也有不好的地方,会占用过多的内存,这一块需要继续研究一些,比如闲鱼提到的 FlutterView 重用机制。
