前言
这是笔者作为一个Android
工程师入门Flutter
的学习笔记,笔者不想经过一种安分守己的方法来学习:先学Dart
言语,然后学习Flutter
的根本运用,再到实践运用这样的过程。这样的方法有点无趣且效率较低。
笔者觉得对于已经有Android
根底的来说,经过类比Android
的方法来学习Flutter
,掌握核心根底概念后,直接开发实践运用,在这个过程中去学习其间的常识比如Dart
语法、深入的常识点。这是笔者的一次学习尝试,并将其记录下来:
给Android工程师的Flutter入门手册(一)
本篇是该系列的第二篇,主要内容是:
(1)工程结构和资源文件:在哪里放置分辨率相关的图片文件?字符串存储在哪里?
(2)Activity和Fragment
(3)数据库和本地存储
资源文件和财物文件
Android
中是区别对待资源文件 (resources) 和财物文件 (assets)的。
在 Flutter
运用只要财物文件 (assets)。所有原本在 Android 中应该放在res/drawable-*
文件夹中的资源文件,在 Flutter
中都放在一个assets
文件夹中。
Flutter放置图片资源
Flutter
遵循一个简略的类似iOS
的密度相关的格局。文件可所以一倍 (1x
)、两倍 (2x
)、三倍 (3x
) 或其它的恣意倍数。
Flutter
没有dp
单位,可是有逻辑像素尺度,根本和设备无关的像素尺度是相同的。
区别逻辑像素和设备像素:
逻辑像素也称为与设备无关或与分辨率无关的像素。设备像素也称为物理像素。
devicePixelRatio
表示在单一逻辑像素规范下设备物理像素的份额。或者能够理解成,显现此视图屏幕的每个逻辑像素的设备像素数。
devicePixelRatio 回来的值最终是从硬件自身、设备驱动程序或存储在操作体系或固件中的硬编码值取得的,而且或许不准确,有时差错很大。
PS:Flutter 结构以逻辑像素为单位进行操作,因而很少需求直接处理该属性。
Android
的密度分类与 Flutter
像素份额的对照表如下:
Android的密度 | Flutter 像素份额 |
---|---|
ldpi | 0.75x |
mdpi | 1.0x |
hdpi | 1.5x |
xhdpi | 2.0x |
xxhdpi | 3.0x |
xxxhdpi | 4.0x |
假如在 Flutter
项目中增加一个新的叫my_icon.png
的图片资源,而且将其放入咱们随便起名的叫做images
的恣意文件夹中。Flutter 没有预先界说好的文件夹结构。你在pubspec.yaml
文件中界说文件(包括方位信息),Flutter 担任找到它们。
你需求将根底图片(1.0x)放在images
文件夹中,并将其它倍数的图片放入以特定倍数作为名称的子文件夹中:
images/my_icon.png // Base: 1.0x image
images/2.0x/my_icon.png // 2.0x image
images/3.0x/my_icon.png // 3.0x image
而且需求在pubspec.yaml
文件中界说这些图片:
flutter:
assets:
- images/my_image.jpg
PS:这里注意assets
的缩进格局,缩进有问题会呈现Unable to load asset问题
假如要包括一个目录下的所有 assets,需求在目录名称的结尾加上/
flutter:
assets:
- images/
假如想要增加子文件夹中的文件,请为每个目录创立一个条目。
字符串储存在哪里?
Flutter
当下并没有一个特定的办理字符串的资源办理体系。现在来讲,最好的办法是将字符串作为静态域存放在类中,并经过类拜访它们。例如:
class Strings {
static String welcomeMessage = 'Welcome To Flutter';
}
接着在代码中能够这样拜访字符串:
Text(Strings.welcomeMessage);
当然,这样只能处理一些本地化的言语,假如想处理多言语场景,这种方法处理起来就很费劲了。走国际化路线能够运用intl 包进行国际化和本地化。关于这部分不是本篇博客要害,后续有需求单独开一篇博客介绍。
Gradle 文件的对应物是什么?我该怎么增加依靠?
Android
中Gradle
非常重要,咱们在 Gradle
构建脚本中增加依靠。
那么在Flutter
中,咱们是运用 Dart
自己的构建体系以及 Pub 包办理器。构建东西会将原生 Android
和 iOS
壳运用的构建代理给对应的构建体系。
尽管在Flutter
项目的android
文件夹下有 Gradle
文件,可是它们只用于给对应渠道的集成增加原生依靠。能够在pubspec.yaml
文件中界说在 Flutter
里运用的外部依靠。
Activiy和Fragment
Activity 和 Fragment 在 Flutter 中的对应什么?
在 Android 中,一个Activity
代表用户能够完成的一件独立任务。一个Fragment
代表一个行为或者用户界面的一部分。 Fragment 用于模块化你的代码,为大屏组合复杂的用户界面,并适配运用的界面。
正如 Flutter
中一切皆为 Widget
,这两个概念也都对应于Widget
。
怎么监听Android Activity 的生命周期事情
在 Android
中,能够经过覆写Activity
的生命周期方法来监听其生命周期,也能够在Application
上注册ActivityLifecycleCallbacks
。
在 Flutter
中,这两种方法都没有,可是你能够经过绑定WidgetsBinding
调查者并监听didChangeAppLifecycleState()
的变化事情来监听生命周期。
一个监听生命周期的代码示例:
import 'package:flutter/widgets.dart';
class LifecycleWatcher extends StatefulWidget {
const LifecycleWatcher({super.key});
@override
State<LifecycleWatcher> createState() => _LifecycleWatcherState();
}
class _LifecycleWatcherState extends State<LifecycleWatcher>
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 const 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(const Center(child: LifecycleWatcher()));
}
尽管FlutterActivity
在内部捕获了简直所有的 Activity
生命周期事情并将它们发送给 Flutter
引擎,可是它们大部分都向你屏蔽了。
能够被调查的生命周期事情有:
-
inactive
:运用处于非活泼状态而且不接收用户输入。 -
detached
:运用仍然保存 flutter engine,可是悉数宿主 view 均已脱离。 -
paused
:运用当时对用户不可见,无法响运用户输入,并运行在后台。这个事情对应于 Android 中的onPause()
; -
resumed
:运用对用户可见而且能够响运用户的输入。这个事情对应于 Android 中的onPostResume()
; -
suspending
:运用暂时被挂起。这个事情对应于 Android 中的onStop
; iOS 上因为没有对应的事情,因而不会触发此事情。
Flutter
为你办理引擎的启动和停止,在大部分情况下没有理由要在 Flutter
一端监听 Activity
的生命周期。
假如你需求经过监听生命周期来获取或开释原生的资源,是应该在原生端做这件事的。
数据库和本地存储
本地存储
在 Android
中,能够运用 SharedPreferences
API 来存储少量的键值对。
在 Flutter
中,运用Shared_Preferences 插件完成此功用。这个插件同时包装了 Shared Preferences
和 NSUserDefaults
(iOS 渠道对应 API)的功用。可是数据或许会异步耐久化到磁盘,不确保写入回来后必定会耐久化到磁盘,所以这个插件必定不要用于存储要害数据。
举个简略的演示代码:增加一个按钮计数,每次在存储值的根底上+1
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter'),
),
),
),
),
);
}
Future<void> _incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
debugPrint("counter: $counter");
await prefs.setInt('counter', counter);
}
最终在data文件夹的运用包名下有个FlutterSharedPreferences.xml
文件,存储了SP的数据
这是我的路径:
/data/data/com.example.flutter_enter_door/shared_prefs/FlutterSharedPreferences.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<long name="flutter.counter" value="6" />
</map>
数据库
在 Android
中,你会运用 SQLite
来存储能够经过 SQL
进行查询的结构化数据。
在 Flutter
中,运用SQFlite插件完成此功用。该插件支撑支撑 iOS、Android 和 MacOS:
- 支撑业务和批处理
- 翻开时主动进行版别办理
- 刺进/查询/更新/删去查询的帮手
- 在 iOS 和 Android 的后台线程中执行的数据库操作
此外,其他渠道支撑:
- 运用 sqflite_common_ffi 的 Linux/Windows/DartVM 支撑
- 运用 sqflite_common_ffi_web 的实验性 Web 支撑。
举个狗子相关数据库的例子:
界说狗子bean:
class Dog {
final int id;
final String name;
final int age;
const Dog({
required this.id,
required this.name,
required this.age,
});
Map<String, dynamic> toMap(){
return {
'id': id,
'name': name,
'age': age,
};
}
@override
String toString() {
return 'Dog{id: $id, name: $name, age: $age}';
}
}
创立数据库和狗子表:
void createDogTable() async {
WidgetsFlutterBinding.ensureInitialized();
database = openDatabase(
// 创立数据库
join(await getDatabasesPath(), 'doggie_database.db'),
onCreate: (db, version) {
// 创立dogs表
return db.execute(
'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
);
},
version: 1,
);
}
狗子数据的增修改查:
// 新增一条狗子数据
Future<void> insertDog(Dog dog) async {
final db = await database;
await db.insert(dogTableName, dog.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
// 查询狗子数据
Future<List<Dog>> getDogs() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query(dogTableName);
return List.generate(maps.length, (i) {
return Dog(id: maps[i]['id'], name: maps[i]['name'], age: maps[i]['age']);
});
}
// 删去狗子数据
Future<void> deleteDog(int id) async {
// Get a reference to the database.
final db = await database;
// Remove the Dog from the database.
await db.delete(
dogTableName,
// Use a `where` clause to delete a specific dog.
where: 'id = ?',
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [id],
);
}
// 更新狗子数据
Future<void> updateDog(Dog dog) async {
// Get a reference to the database.
final db = await database;
// Update the given Dog.
await db.update(
dogTableName,
dog.toMap(),
// Ensure that the Dog has a matching id.
where: 'id = ?',
// Pass the Dog's id as a whereArg to prevent SQL injection.
whereArgs: [dog.id],
);
}
参考
1.字符串部分
intl包:intl
2.生命周期部分
AppLifecycleState: AppLifecycleStateenum
3.数据存储部分:
用 SQLite 做数据耐久化
运用 Database Inspector 调试数据库