Flutter入门系列-开发经验贴

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 可以大于屏幕高度,也可以小于屏幕高度,是确定可滚动范围使用的。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值