美文网首页
Flutter知识点整理

Flutter知识点整理

作者: 番茄tomato | 来源:发表于2020-06-11 16:13 被阅读0次

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

image.png
这里可以看到,在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

四 待解决...

  1. Flutter 项目不同编译环境的搭建 dev/qa/release
  2. Flutter 在IOS上运行(打包和发布)?
  3. 可以预见的会出现系统差异问题

相关文章

网友评论

      本文标题:Flutter知识点整理

      本文链接:https://www.haomeiwen.com/subject/jxxptktx.html