Flutter是Google一个新的用于构建跨平台的手机App的SDK。写一份代码,在Android 和iOS平台上都可以运行。
一.Flutter项目结构
配好环境 Android Studio安装好插件可以直接创建Flutter项目,项目主要有以下几个目录:
image.png
打开android文件夹就可以看到比较熟悉的Android项目结构
并且其内容只有一个继承于FlutterActivity的MainActivity
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
也就是说不管Flutter包含多少个界面,其在Android上都是在一个Ativity中完成
那么问题来了,Flutter和activity里的界面是什么关系,怎么承载的,可以看到FlutterActivity的onCreate()方法:在确定Activity视图的时候调用了createFlutterView()
方法
protected void onCreate(@Nullable Bundle savedInstanceState) {
....
this.setContentView(this.createFlutterView());
....
}
createFlutterView()
方法 返回了一个View作为Activity视图
@NonNull
private View createFlutterView() {
return this.delegate.onCreateView((LayoutInflater)null, (ViewGroup)null, (Bundle)null);
}
再进一步可以看到onCreateView()源码中返回了一个通过FlutterView创建的FlutterSplashView对象。
FlutterView是继承于FrameLayout的一个自定义View。
所以Flutter的所有界面只存放于Activity的一个View,也就是说可以通过FlutterView来将Flutter项目界面引入原生Android。
在原生Android项目中创建Flutter Module,并配置好依赖引入项目后,可以通过以下方式,完成原生Android和Flutter的交互:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 通过FlutterView引入Flutter编写的页面
View flutterView = Flutter.createView(this, getLifecycle(), "route1");
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
layout.leftMargin = 100;
layout.topMargin = 200;
addContentView(flutterView, layout);
}
在Flutter.createView(this, getLifecycle(), "route1");传入了三个参数分别是context,生命周期和路由值
在Flutter项目中就可以通过这个路由值来确定要显示哪个界面。
效果图
问题1.数据怎么传递?
问题2.方法调用?Flutter调用原生的JAVA或者Kotlin方法?例如Flutter调用原生Android网络请求的方法
二.Flutter入口和界面构建
2.1 Flutter项目入口
Flutter项目的代码全部都在lib文件夹下,其入口就是main.dart
这里可以看到,在main函数启动app后,构建了一个MaterialApp对象,并通过构造函数指定参数名的方式,指定了一些属性。它继承于Widget,所以可以在build中直接返回。
Widget就像是Android开发中的View,但是它的概念比View更广。不光是所有的界面控件都是继承与Widget,还包括布局比如线性布局,层叠布局。甚至还包括一些定位的控件比如Center。
在构建MaterialApp对象的时候,通过其home字段指定了主界面内容HomePage(),这个就是自己创建的主界面了
2.2 Flutter界面构建
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
ScreenUtil.init(context); //demo app 无设计稿
return Scaffold(
//将Demo入口列表封装在一个方法中 以后好改
body: _getDemoList());
}
Widget _getDemoList() {
return Container(
padding: EdgeInsets.all(2),
child: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1.0,
crossAxisSpacing: 5, //横轴间隔
mainAxisSpacing: 5, //主轴间隔
),
children: <Widget>[
OutlineButton.icon(
padding: EdgeInsets.all(0),
icon: Icon(Icons.http),
label: Text("网络请求"),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return WeatherPage();
}));
},
),
OutlineButton.icon(
padding: EdgeInsets.all(0),
icon: Icon(Icons.format_align_justify),
label: Text("数据库操作"),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return DatabasePage();
}));
},
),
OutlineButton.icon(
padding: EdgeInsets.all(0),
icon: Icon(Icons.camera_alt),
label: Text("拍照\n(文件操作)",textAlign: TextAlign.center,),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return CameraPage();
}));
},
),
OutlineButton.icon(
padding: EdgeInsets.all(0),
icon: Icon(Icons.settings_overscan),
label: Text("扫码"),
onPressed: () {},
),
],
),
);
}
}
image.png
在数百个Widget中灵活选择使用可以构建出好看的界面:
效果图
理论上Flutter所有代码都可以写在一个dart文件中,但这会导致代码可读性非常低,界面构建代码出现大堆的缩进,非常混乱。所以还是要多封装一下。
2.3 Flutter界面状态管理
界面也是Widget,Widget分为“有状态的"和"没有状态的",比较一下MyApp和HomePage:
//无状态
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
.............
}
}
//有状态
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
..................
}
}
StatelessWidget 中直接build构建界面。而StatefulWidget 中则是通过继承与State的类来build界面并进行界面状态管理。
StatelessWidget 界面构建时的什么样就一直是什么样
例如在屏幕中间显示一句话:
//无状态
class TestPage extends StatelessWidget {
var msg="Hello world";
@override
Widget build(BuildContext context) {
return Center(child: Text(msg),);
}
}
这个时候中间的内容无法改变
StatefulWidget 则可以setState
//有状态
class TestPage extends StatefulWidget {
@override
_TestPage State createState() => _TestPage State();
}
class _TestPage State extends State<TestPage > {
var msg="Hello world";
@override
Widget build(BuildContext context) {
return Center(child: Text(msg),);
}
void changeMsg(){
setState(() {
msg="Nice to meet you!";
});
}
}
二.Flutter中的网络请求
https://www.jianshu.com/p/8ed5283de696
三.Flutter持久化存储
2.1 单个字段存储
封装了一个工具类:
import 'dart:convert';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
class MySpHelper {
static MySpHelper _intance;
static SharedPreferences _prefs;
static Lock _lock = Lock();
static Future<MySpHelper> getInstance() async {
if (_intance == null) {
await _lock.synchronized(() async {
if (_intance == null) {
// 保持本地实例直到完全初始化。
var instance = MySpHelper._();
await instance._init();
_intance = instance;
}
});
}
return _intance;
}
// 私有构造函数
MySpHelper._();
Future _init() async {
_prefs = await SharedPreferences.getInstance();
}
/// put object.
static Future<bool> putObject(String key, Object value) {
if (_prefs == null) return null;
return _prefs.setString(key, value == null ? "" : json.encode(value));
}
/// get obj.
static T getObj<T>(String key, T f(Map v), {T defValue}) {
Map map = getObject(key);
return map == null ? defValue : f(map);
}
/// get object.
static Map getObject(String key) {
if (_prefs == null) return null;
String _data = _prefs.getString(key);
return (_data == null || _data.isEmpty) ? null : json.decode(_data);
}
/// put object list.
static Future<bool> putObjectList(String key, List<Object> list) {
if (_prefs == null) return null;
List<String> _dataList = list?.map((value) {
return json.encode(value);
})?.toList();
return _prefs.setStringList(key, _dataList);
}
/// get obj list.
static List<T> getObjList<T>(String key, T f(Map v),
{List<T> defValue = const []}) {
List<Map> dataList = getObjectList(key);
List<T> list = dataList?.map((value) {
return f(value);
})?.toList();
return list ?? defValue;
}
/// get object list.
static List<Map> getObjectList(String key) {
if (_prefs == null) return null;
List<String> dataLis = _prefs.getStringList(key);
return dataLis?.map((value) {
Map _dataMap = json.decode(value);
return _dataMap;
})?.toList();
}
/// get string.
static String getString(String key, {String defValue = ''}) {
if (_prefs == null) return defValue;
return _prefs.getString(key) ?? defValue;
}
/// put string.
static Future<bool> putString(String key, String value) {
if (_prefs == null) return null;
return _prefs.setString(key, value);
}
/// get bool.
static bool getBool(String key, {bool defValue = false}) {
if (_prefs == null) return defValue;
return _prefs.getBool(key) ?? defValue;
}
/// put bool.
static Future<bool> putBool(String key, bool value) {
if (_prefs == null) return null;
return _prefs.setBool(key, value);
}
/// get int.
static int getInt(String key, {int defValue = 0}) {
if (_prefs == null) return defValue;
return _prefs.getInt(key) ?? defValue;
}
/// put int.
static Future<bool> putInt(String key, int value) {
if (_prefs == null) return null;
return _prefs.setInt(key, value);
}
/// get double.
static double getDouble(String key, {double defValue = 0.0}) {
if (_prefs == null) return defValue;
return _prefs.getDouble(key) ?? defValue;
}
/// put double.
static Future<bool> putDouble(String key, double value) {
if (_prefs == null) return null;
return _prefs.setDouble(key, value);
}
/// get string list.
static List<String> getStringList(String key,
{List<String> defValue = const []}) {
if (_prefs == null) return defValue;
return _prefs.getStringList(key) ?? defValue;
}
/// put string list.
static Future<bool> putStringList(String key, List<String> value) {
if (_prefs == null) return null;
return _prefs.setStringList(key, value);
}
/// get dynamic.
static dynamic getDynamic(String key, {Object defValue}) {
if (_prefs == null) return defValue;
return _prefs.get(key) ?? defValue;
}
/// have key.
static bool haveKey(String key) {
if (_prefs == null) return null;
return _prefs.getKeys().contains(key);
}
/// get keys.
static Set<String> getKeys() {
if (_prefs == null) return null;
return _prefs.getKeys();
}
/// remove.
static Future<bool> remove(String key) {
if (_prefs == null) return null;
return _prefs.remove(key);
}
/// clear.
static Future<bool> clear() {
if (_prefs == null) return null;
return _prefs.clear();
}
///Sp is initialized.
static bool isInitialized() {
return _prefs != null;
}
}
用法:异步操作
void testSPHelper() async {
//初始化
await MySpHelper.getInstance();
//存储
MySpHelper.putString("data_key", "10010");
//读取
String msg = MySpHelper.getString("data_key");
print(msg);
}
和Android基本一样
2.2 本地数据库
搭建两个基本的工具类
SqlManager: 创建数据库,管理数据库名,管理数据库版本等操作
BaseDbProvider:表管理基类,确定数据库,完成一些表的基本操作比如创建表、查询所有数据、清空表等等
为每张表的管理创建一个Provider,继承与BaseDbProvider ,在这里配置表字段和完成一些业务上的处理。
class ProductDbProvider extends BaseDbProvider {
final String table = "ProductInfo"; //表名 产品信息
final String id = "id"; //id
final String name = "name"; //产品名称
final String price = "price"; //价格
@override
createTableString() {
//建表sql
return '''
create table $table (
$id integer primary key,$name text not null,
$price integer not null)
''';
}
@override
tableName() {
return table;
}
//查 根据主键查询
Future<List<Map<String, dynamic>>> queryByKey(
//查询语句通常都是查询多行 多个结果 每行的结果以一个map的形式表现
int key) async {
Database db = await getDataBase();
List<Map<String, dynamic>> maps =
await db.rawQuery("select * from $table where $id = $key");
return maps;
}
//改
Future<void> update(ProductEntity entity) async {
//将一个id更新
Database database = await getDataBase();
await database.rawUpdate('''update $table set
$name = ?,
$price = ?
where $id= ?''', [
entity.name,
entity.price,
entity.id,
]);
}
//增
Future insert(ProductEntity entity) async {
print("本次添加的数据name${entity.name}");
Database db = await getDataBase();
//检查此主键是否已有数据
if (queryByKey(entity.id) != null) {
//有两种方式操作数据库 一种如下 还有一种是直接执行原生的sql语句
//如果已存在数据 则删除
await db.delete(table, where: "$id = ?", whereArgs: [entity.id]);
}
//返回插入数据的值 行数
return await db.rawInsert('''insert into $table(
$id,
$name,
$price
) values (?,?,?)''', [
entity.id,
entity.name,
entity.price,
]);
}
//delete by id
Future<void> delete(int deleteId) async {
Database db = await getDataBase();
//检查此主键是否已有数据
if (queryByKey(deleteId) == null) {
return;
}
return db.delete(table, where: "$id = ?", whereArgs: [deleteId]);
}
}
使用:
Future<List<ProductEntity>> getAllProduct() async {
ProductDbProvider productDbProvider=ProductDbProvider();
List<ProductEntity> data = List();
//开始查询
List<Map<String, dynamic>> queryResult = await productDbProvider.queryAll();
//将数据转化为实体类
for (Map<String, dynamic> map in queryResult) {
//这里直接使用fromJson即可
data.add(ProductEntity().fromJson(map));
}
return data;
}
2.3 文件操作(系统有区别)
Android和iOS的应用存储目录不同,PathProvider 插件提供了一种兼容的方式来访问设备文件系统上的常用位置。该类当前支持访问2个文件系统位置:
临时目录:可以使用 getTemporaryDirectory() 来获取临时目录; 系统可随时清除的临时目录(缓存)。在iOS上,这对应于NSTemporaryDirectory() 返回的值。在Android上,这是getCacheDir()返回的值。
内部存储目录:可以使用getApplicationDocumentsDirectory()来获取应用程序的文档目录,该目录用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在iOS上,这对应于NSDocumentDirectory。在Android上,这是AppData目录。
外部存储目录:可以使用getExternalStorageDirectory()来获取外部存储目录,如SD卡;由于iOS不支持外部目录,所以在iOS下调用该方法会抛出UnsupportedError异常,而在Android下结果是android SDK中getExternalStorageDirectory的返回值。
以下例子,在文件counter.txt中保存一个按钮的点击次数,下次进入界面时读取
// _getLocalFile函数,获取本地文件目录
Future<File> _getLoaclFile() async{
//获取应用目录// 获取本地文档目录
String dir=(await getApplicationDocumentsDirectory()).path;
return new File('$dir/counter.txt');
}
Future<int> _readCounter() async{
try{
/*
* 获取本地文件目录
* await等待操作完成
*/
File file =await _getLoaclFile();
//读取点击次数(以字符串)
// 使用给定的编码将整个文件内容读取为字符串
String contents=await file.readAsString();
return int.parse(contents);//返回文件中的点击数
} on FileSystemException{
// 发生异常时返回默认值
return 0;
}
}
// _incrementCounter函数,点击增加按钮时的回调
Future<Null> _incrementCounter() async{
setState(() {
_counter++;
});
//将点击次数以字符串类型写到文件中
await (await _getLoaclFile()).writeAsString('$_counter');
}
image.png
四 待解决...
- Flutter 项目不同编译环境的搭建 dev/qa/release
- Flutter 在IOS上运行(打包和发布)?
- 可以预见的会出现系统差异问题
网友评论