1. State中 build 重复调用
在有状态的 StatefulWidget中,build 方法会重复调用两次,所有如果在build 方法中使用 FutureBuilder 这种延迟加载控件【即先获取网络或者本地数据,然后再去创建Widget的组件】,future 对象不能在构造方法中进行调用,而是应该放在 initState 方法中进行。
@override
void initState() {
_future = _loadDefaultData(); //这么写,避免重复调用两次
super.initState();
}
Future<dynamic> _loadDefaultData() async {
HiCache hiCache = await HiCache.preInit();
String defaultUserName = hiCache?.get(LoginDao.USER_NAME)?? '';
String defaultPassword = hiCache?.get(LoginDao.USER_PWD)??'';
Map<String, String> map = {"name": defaultUserName, "pwd": defaultPassword};
return map;
}
return Scaffold(
appBar: appBar('密码登录', '注册', widget.onJumpToRegister),
body: FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot){
if (snapshot.connectionState == ConnectionState.done){
if (snapshot.hasData){
String name;
String pwd;
if (isDefaultLoaded) {
name = userName;
pwd = password;
} else {
Map userData = snapshot.data;
name = userData['name'];
pwd = userData['pwd'];
isDefaultLoaded = true;
}
return _loginBody(name, pwd);
} else {
print("没有数据.....");
return _loginBody("", ""); // 没有数据.....
}
} else {
print("加载进行中.....");
return _loginBody("", ""); //加载进行中.....
}
})
2. 判断当前App运行模式
判断当前App是否以 releaseMode 模型运行
print("kReleaseMode $kReleaseMode");
3. 自建 Flutter 系统错误处理框架
自建的Flutter 系统错误处理框架
//run(Widget app){...}
//框架异常 即Flutter 框架自生所产生的Error,不是我们的代码产生的Error
FlutterError.onError = (FlutterErrorDetails details) async {
//线上环境,走上报逻辑
if (kReleaseMode){
Zone.current.handleUncaughtError(details.exception, details.stack);
} else {
//开发期间,走console 抛出
FlutterError.dumpErrorToConsole(details);
}
};
处理自己写出的错误框架
runZonedGuarded(() {
runApp(app);
}, (e, s){
//print('HiZone $e');
_reportError(e, s);
});
//通过接口上报异常信息
_reportError(Object error, StackTrace s){
print("kReleaseMode $kReleaseMode");
print("object $error");//可以自己搭建一个错误处理框架
}
总的代码如下:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class HiZone {
run(Widget app) {
//框架异常 即Flutter 框架自生所产生的Error,不是我们的代码产生的Error
FlutterError.onError = (FlutterErrorDetails details) async {
//线上环境,走上报逻辑
if (kReleaseMode){
Zone.current.handleUncaughtError(details.exception, details.stack);
} else {
//开发期间,走console 抛出
FlutterError.dumpErrorToConsole(details);
}
};
runZonedGuarded(() {
runApp(app);
}, (e, s){
//print('HiZone $e');
_reportError(e, s);
});
}
//通过接口上报异常信息
_reportError(Object error, StackTrace s){
print("kReleaseMode $kReleaseMode");
print("object $error");
}
}
4. Flutter 接口
Flutter 中接口和抽象类类似,都是用 abstract 关键字来标明
//弹幕组件
abstract class IBarrage {
//发送弹幕消息
void send(String message);
//暂停
void pause();
//播放弹幕消息
void play();
}
5. Flutter 中 typedef 的使用
typedef 的用法有点类似Java中的接口,但是Java接口中的是例如 xxInterface.on(data),这种写法,而Flutter中是直接 xxxCallback(data) 这种形式来callback,感觉认为函数也是一等公民,可以直接作为引用直接调用,也可以直接作为参数传递。
class NetError {
final int statusCode;
final String message;
NetError(this.statusCode, this.message);
}
typedef NetErrorCallback(NetError netError);
class HttpRequest {
void sendRequest() {
Future.delayed(Duration(seconds: 1), (){
try {
if (callback!=null) {
callback(NetError(101, '发生未知错误'));
}
return 10+11;
} catch (e) {
throw Exception(e);
}
}).then((value) => print(value)).onError((error, stackTrace) => print(error));
}
NetErrorCallback callback;
void addNetErrorCallback(NetErrorCallback callback){
this.callback = callback;
}
}
//使用
var httpRequest = HttpRequest();
httpRequest.addNetErrorCallback((netError) => print(netError.message));
httpRequest.sendRequest();
6. 函数类型的参数
void doFunction(int doAdd(int age), int num){
int result = doAdd(num);
print("doAdd result is $result");
}
void main() {
print("hello ");
int add(int count){
return 10+count;
}
doFunction(add, 100);
doFunction((age) => 12+age, 100);
}
7. Flutter 设置图片缓存
Flutter 应该是没有磁盘缓存,只有内存缓存,所以为了提高图片加载的效率,需要设置图片缓存的大小。
//在初始化偏好设置的init方法中调用如下代码
//图片缓存大小设置
//image_cache.dart
//const int _kDefaultSize = 1000;
//const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
PaintingBinding.instance.imageCache.maximumSize = 2000; //默认是1000张图片 现在改成2000张
PaintingBinding.instance.imageCache.maximumSizeBytes = 200 << 20; //改成200M
8. 关于多层次异步任务调用
void main() {
/** 如果不在 Business.sleep() 前面加上 await 那么就会报如下错误 async 标记的方法其实返回的是Future对象
* Unhandled exception:
type 'Future<dynamic>' is not a subtype of type 'int'
#0 main.doBusiness (file:///Users/zhangfengzhou/AndroidProjects/Android/flutter_bilibili/main.dart:14:9)
#1 main.doBusiness (file:///Users/zhangfengzhou/AndroidProjects/Android/flutter_bilibili/main.dart:13:13)
#2 main (file:///Users/zhangfengzhou/AndroidProjects/Android/flutter_bilibili/main.dart:17:13)
#3 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:283:19)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
* */
doBusiness() async {
int result = await Business.sleep(); //因为sleep方法返回的是Future类型的,所以要使用await来等待并获取返回值,同时用async来标记方法
print("result is $result");
}
//另外,从该例子中学习到了,只要Dart中还有异步任务[事件任务,不是微任务],那么就不会结束进程,休息了5秒之后,才回结束掉进程
doBusiness(); //这里执行的是异步任务,不会阻塞主线程,但是不同与Java中的异步任务,Java是多线程,Dart是单线程,异步是基于事件任务队列执行的
print("main线程结束...");
}
class Business {
static sleep(){ //返回的数据类型是Future类型的
return doSomeSleep();
}
static doSomeSleep() async{ //async 标记该方法是异步任务,返回的数据是Future类型的
await Future.delayed(Duration(seconds: 5));
return 100; //
}
}
9. Positioned 组件的使用
//positioned widget 用于在Stack控件中定位位置,left, right, bottom, top
stack(
children:[
Positioned(
left:0, right:0, bottom:0,
chiild:Container(...),
),
...
]
)
10. 渐变色 LinearGradient
//在Container中利用decoration来添加渐变色
decoration: BoxDecoration (
//渐变
gradient: LinearGradient (
begin: Alignment.bottomCenter, //渐变色开始的位置
end: Alignment.topCenter,//渐变色结束的位置
colors: [Colors.black54, Colors.transparent]//分别对应开始和结束位置的颜色
)
)
11. ScrollController 的使用
ScrollController scrollController = ScrollController();
scrollController.addListener(() {
// maxScrollExtent 最大的可滚动距离 pixels 是当前的已经滚动距离
// dis = _scrollController.position.maxScrollExtent - _scrollController.position.pixels
// 当前距离页面底部还有剩余多少距离
// 怎么看 pixels 这个参数呢?
// 可以这么看,就是顶部控件超过顶部这条线到距离大小是多少,然后用总到大小减去就是剩余底部的距离
var dis = scrollController.position.maxScrollExtent - scrollController.position.pixels;
if ( dis < 200
&& !isLoadingMore
&& scrollController.position.maxScrollExtent!=0
&& scrollController.position.pixels>0) {
print('dis $dis position.pixels ${scrollController.position.pixels}');
loadData(loadMore: true);//开始加载更多
}
});
12. const widget 常量Widget
1. 利用 const 构造无状态Widget
class ChildWidget extends StatelessWidget {
final String myText;
const ChildWidget(this.myText, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Text(myText),
);
}
}
2. 在父Widget 中的 State 中使用 ChildWidget
class ParentWidget extends StatefulWidget {
const ParentWidget({Key key}) : super(key: key);
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
static const String myText = "hello world";
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(child: Column(
children: [
const ChildWidget(myText),
],
)),
);
}
}
3. const Widget 的优势
首先总结下,const 在Dart 语言中特点:
1. 一个Dart 类的对象是否能用 const 修饰,取决于类的构造方法是否被 const 修饰;
2. 使用const 修饰的构造方法中,所有的成员变量必须被 final 修饰;
3. 构造 const 对象时,传参也必须是 const 的常量;
4. const 修饰的构造方法,不能有方法体;
对于Flutter来说,const 修饰的优化点:
利用常量池复用 Widget,在更新频繁的 Widget 场景中,有优化作用,避免了 Widget 的回收和重建;const 对GC 有一定的抑制作用,在创建大量相同对象的场景下,创建的对象少了,自然GC也会变少。
const 对GC的优化
在Flutter 中,Widget 作为配置信息,本身被设计的非常轻量,就是为了适应频繁的销毁重建,这个操作必然会引起对旧 Widget 对象的回收。
当我们使用 const 关键字声明常量的时候,背后是利用的类似常量池的概念,将 const 的对象缓存在常量池中,以待后续复用。
常量会具备普通对象更长的生命周期,这有好处也有坏处。好处就是常量对象会对 GC不那么敏感,也就是不需要频繁的触发GC。坏处是常量池中的生命周期较长,可能会导致不常用的对象被缓存后,没有适合释放时机,导致内存占用过高。
实测下来,const 确实对 GC 有一些影响。
13. Flutter 中 function 和 method 区别
Flutter 中function 和 method 是有区别的,function 表示一个功能,可以独立使用,是一等公民,而 method 不能单独使用,必须由类的实例来进行调用。
14. 强制改变屏幕方向
dependencies:
orientation: ^1.2.0
//使用
OrientationPlugin.forceOrientation(DeviceOrientation.portraitUp);
15. 属性扩展
enum RouteStatus {homePage, videoListPage, videoDetailPage, loginPage, registrationPage, unknownPage}
//给枚举扩展属性
extension RouteStatusExtension on RouteStatus{
String get name =>
['homePage', 'videoListPage', 'videoDetailPage','loginPage','registrationPage','unknownPage'][index];
}
16. 创建Model类
在AndroidStudio中使用JsonToDart插件,在创建好xx_moel.dart 文件之后,在文件空白地方,右键 generate -> jsonToDart, 贴入json字符串就可以生成对应的model类。
17. 操作符
// A?.B 如果A为null, 那么A?.B 返回的就是null 反之 如果A不为null, 那么A?.B的就是A.B
// A??B 如果A为null, 那么A??B 返回的就是B 否则就是不做任何处理 返回的就是A
18. ViewPort
ViewPort 可以大于屏幕高度,也可以小于屏幕高度,是确定可滚动范围使用的。