环境搭建
- 下载解压 Flutter SDK ,并配置环境变量;
- 在终端输入: flutter doctor 来检查开发环境是否完善。如下信息表示环境搭建OK。
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, v1.12.13+hotfix.9, on Mac OS X 10.15.5 19F101, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 11.5)
[✓] Android Studio (version 4.0)
[✓] IntelliJ IDEA Ultimate Edition (version 2017.3.2)
[✓] Connected device (1 available)
• No issues found!
3.编辑工具 安装 Android Studio 并安装 Flutter 和 Dart 插件(建议使用Android studio, vs也可以使用功能不如as 强大)
初始化项目
####命令创建
$ flutter create [package_name] // eg: 'com_fengjr_flutter_mobile'
####IDEA创建 AS 安装完插件重启后,File -> New ->New Flutter Project 选择 Application 填写项目名称, 下一步 填写包名 finish。
项目结构
android //安卓项目
ios // ios 项目
asset // 资源目录 比如图片 文件 json 等
lib // dart 代码目录main.dart // 入口文件
pubspec.yaml // 项目配置
name: flutter_app # 项目名称
description: A new Flutter application. # 项目描述
version: 1.0.0+1 # 项目版本号
environment: # dart语言sdk 版本限制
sdk: ">=2.7.0 <3.0.0"
dependencies: # 三方依赖
flutter:
sdk: flutter
dev_dependencies: # 开发时需要的依赖,如代码生成器
flutter_test:
sdk: flutter
build_runner: ^1.10.0
flutter: # flutter 的配置
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets: # flutter 项目中引用到的资源都要在此配置路径。
- assets/images/abc.jpg
- assets/images/bcd.jpg
- assets/images/meinv.webp
- assets/json/env.json
# fonts: # flutter 字体配置
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
UI组件
Widget 构建UI 界面基本构建单元。
Object (dart.core)
Diagnosticable (diagnostics.dart)
DiagnosticableTree (diagnostics.dart)
Widget (framework.dart)
StatelessWidget (framework.dart) // 无状态的 内部属性全部是final的,无需改变状态
StatefulWidget (framework.dart) // 有状态的
RenderObjectWidget (framework.dart)
ProxyWidget (framework.dart)
!
Widget 的生命周期事件
在 StatefulWidget 上调用 createState() 之后,框架将新的状态对象插入到树中,然后在状态对象上调用 initState()。
State 的子类可以重写 initState 来完成只需要发生一次的工作。 例如,重写 initState 来配置动画或订阅平台服务。 实现 initState 需要调用父类的 super.initState 方法来开始。
Widget 的 build(BuildContext context);构建widget树。
当不再需要状态对象时,框架会调用状态对象上的 dispose() 方法。可以重写dispose 方法来清理状态。 例如,重写 dispose 以取消计时器或取消订阅平台服务。 实现 dispose 时通常通过调用 super.dispose 来结束。
#### 常用widget
屏幕适配
(获取到屏幕的宽度,计算屏幕宽度与设计稿的宽度之间的比例,可利用拓展函数拓展Int,float,使用时只需在尺寸上加上拓展函数名)
异步操作
Future and Stream
dart:async 库中有两个类型 Stream 和 Future。 Future 用于表示不会立即完成单个运算的结果,而 Stream 则表示多个结果的序列。你可以监听 Stream 以获取其结果(包括数据和错误)或其关闭事件。也可以在 Stream 完成前对其暂停或停止监听。
Future 1.通过 async 函数默认返回的就是Future对象。
Future<String> createF() async {
.......
return 'name';
}
2.通过构造函数创建
// Future(FutureOr<T> computation())
Future<String> future = Future(() => '你好 !Future');
// Future.delayed(Duration duration, [FutureOr<T> computation()])
Future<String> future = Future.delayed(Duration(seconds: 10), () => '你好 !Future');
// Future.error(Object error, [StackTrace stackTrace])
Future<String> future = Future.error('Future error obj');
// Future.microtask(FutureOr<T> computation())
Future<String> future = Future.microtask(() => '你好 !Future');
// Future.sync(FutureOr<T> computation())
Future<String> future = Future.sync(() => '你好 !Future');
// Future.value([FutureOr<T> value])
Future<String> future = Future.value('你好 !Future');
3.静态方法
- Future.any(); // 返回最先完成的Future
- Future.doWhile() // 重复执行一步任务直到返回值为false
- Future.forEach() // 逐个执行
- Future.wait() // 执行所有任务等全部完成,并收集结果。
4.Future 的执行:
var value = await future;
// 或者 l
future.timeout(Duration(seconds: 10), onTimeout: () {
print('onTimeout');
return 'timeout';
}).then((value) {
print(value);
}).catchError((e) {
print('catchError');
}).whenComplete(() {
print('whenComplete');
});
Stream
1.通过 async * 函数创建。
// async* + yield 创建 Stream
Stream<int> countStream(int to) async* {
for (int i = 1; i <= to; i++) {
yield i;
}
}
2.通过构造函数创建。
Stream() // 默认构造
Stream.empty() // 创建一个空的stream 不包含事件。
Stream.error(Object error, [StackTrace stackTrace]) // 创建包含error事件的stream
Stream.fromFuture(Future<T> future) // 通过Future创建
Stream.fromIterable(Iterable<T> elements) // 通过可迭代对象创建
Stream.periodic(Duration period, [T computation(int computationCount)]) //每隔一段时间触发一次
Stream.value(T value) // 单一的值创建
3.接收 Stream 事件
Future<int> sumStream(Stream<int> stream) async {
var sum = 0;
await for (var value in stream) {
sum += value;
}
return sum;
}
listen() 方法 最后一个重要的方法是 listen()。这是一个“底层”方法,其它所有的 Stream 方法都根据 listen() 方法定义。
var stream = timedCounter(Duration(seconds: 2), 3);
stream.listen((a) {
print(a);
},onError: (){
},onDone: (){
},cancelOnError:true);
4.使用 StreamController
// 注意:该实现有缺陷。
// 它在它拥有订阅者之前开始并且它没有实现暂停逻辑。
Stream<int> timedCounter(Duration interval, [int maxCount]) {
var controller = StreamController<int>();
int counter = 0;
void tick(Timer timer) {
counter++;
controller.add(counter); // 请求 Stream 将计数器值作为事件发送。
if (maxCount != null && counter >= maxCount) {
timer.cancel();
controller.close(); // 请求 Stream 关闭并告知监听器。
}
}
Timer.periodic(interval, tick); // 缺点:在其拥有订阅者之前开始了。
return controller.stream;
}
5.Stream的方法
* any(bool test(T element)) → Future<bool>
* asBroadcastStream({void onListen(StreamSubscription<T> subscription), void onCancel(StreamSubscription<T> subscription)}) → Stream<T>
* asyncExpand<E>(Stream<E> convert(T event)) → Stream<E>
* asyncMap<E>(FutureOr<E> convert(T event)) → Stream<E>
* cast<R>() → Stream<R>
* contains(Object needle) → Future<bool>
* distinct([bool equals(T previous, T next)]) → Stream<T>
* drain<E>([E futureValue]) → Future<E>
* elementAt(int index) → Future<T>
* every(bool test(T element)) → Future<bool>
* expand<S>(Iterable<S> convert(T element)) → Stream<S>
* firstWhere(bool test(T element), {T orElse()}) → Future<T>
* fold<S>(S initialValue, S combine(S previous, T element)) → Future<S>
* forEach(void action(T element)) → Future
* handleError(Function onError, {bool test(dynamic error)}) → Stream<T>
* join([String separator = ""]) → Future<String>
* lastWhere(bool test(T element), {T orElse()}) → Future<T>
* listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError}) → StreamSubscription<T>
* map<S>(S convert(T event)) → Stream<S>
* pipe(StreamConsumer<T> streamConsumer) → Future
* reduce(T combine(T previous, T element)) → Future<T>
* singleWhere(bool test(T element), {T orElse()}) → Future<T>
* skip(int count) → Stream<T>
* skipWhile(bool test(T element)) → Stream<T>
* take(int count) → Stream<T>
* takeWhile(bool test(T element)) → Stream<T>
* timeout(Duration timeLimit, {void onTimeout(EventSink<T> sink)}) → Stream<T>
* toList() → Future<List<T>>
* toSet() → Future<Set<T>>
* transform<S>(StreamTransformer<T, S> streamTransformer) → Stream<S>
* where(bool test(T event)) → Stream<T>
6.Stream 有两种类型:Single-Subscription 和 Broadcast
-
Single-Subscription 类型的 Stream 只能被一个订阅者监听
-
Broadcast 类型的 Stream 可以被多个订阅者监听
异步Widget FutureBuilder 和 StreamBuilder
状态存储
页面状态
StatefulWidgets createState 保存在state中 ,随着State dispose时销毁。
全局状态
-
引入provider包
dependencies: ... provider: ^4.0.0 # 全局状态管理 -
创建model
继承 ChangeNotifier 提供数据和改变数据的方法。
-
注册priveder 在创建APP的时候注册priveder
void main() => runApp(MultiProvider(providers: [ ChangeNotifierProvider(create: (context) => HostModel()), ... ], child: MyApp())); -
使用
- 在widget构建时使用 Consumer
Consumer<HostModel>( child: Text("$host"), builder: (context, hostModel, child) { print(hostModel.hostMap); var curHost = hostModel.hostMap[widget.host.hostId]; return RadioListTile( activeColor: Colors.blue, title: Text( host, style: textBlack16, ), value: host, onChanged: (value) { hostModel.setHost(widget.host.hostId, value); }, groupValue: curHost, ); })- 在没有UI的情况下使用
var model = Provider.of<HostModel>(appContext, listen: false)
路由导航
页面 和 退出 (push 和 pop):从 CurrentPage跳转到 TargetPage 为例
// CurrentPage 跳转传参 两种方式:1 构造函数传参 2 使用RouteSettings传参 (推荐使用这种,更灵活)
Navigator.push(pageContext, MaterialPageRoute(
builder: (context) => TargetPage(),
settings: RouteSettings(arguments: [arguments])
));
// TargetPage 参数提取
final var arguments = ModalRoute.of(context).settings.arguments;
//TargetPage 返回结果
Navigator.pop(pageContext, [resunt]);
// CurrentPage 结果提取
var result = await Navigator.push(...)
序列化与反序列化
dart:convert
- jsonDecode() 将Json String转化成List 或者 Map
- jsonEncode() 将List 或者 Map转化成Json String
json_annotation 自定义数据类型转换
build_value
网络请求 dio 与 retrofit
dio是一个强大的Dart Http请求库,支持Restful API,FormData,拦截器,请求取消,Cookie管理,文件上传/下载,超时,自定义适配器等...
retrofit是一个拓展包。
### NoSql 数据库 Hive
##Widgets
Layout
可滚动的长列表
####ScrollView
StatelessWidget
|__SingleChildScrollView
|__ScrollView (scroll_view.dart)
|__BoxScrollView (scroll_view.dart)
| |__ListView (scroll_view.dart)
| |__GridView (scroll_view.dart)
|__CustomScrollView (scroll_view.dart)
|__ _NestedScrollViewCustomScrollView(nested_scroll_view.dart)
在mainAxis 方向是无限的(没有最大边界)。内部不可嵌套相同方向上无界的widget。
####Gradient
- LinearGradient
- RadialGradient
- SweepGradient
####Box***
- BoxDecoration
- BoxBorder
- BoxShadow
- BoxPainter
####*** TextBox ListWheelScrollView ShapeBorder BoxBorder Border
##Flutter 打包部署 ###Android ####准备:
- 添加启动图
- 创建签名密钥:
//macOS 或者 Linux
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
//Windows
keytool -genkey -v -keystore c:\Users\USER_NAME\key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias key
构建appbundle
flutter build appbundle --target-platform android-arm,android-arm64,android-x64
构建apk
flutter build apk --target-platform android-arm,android-arm64,android-x64 --split-per-abi