flutter笔记

238 阅读12分钟

1.flutter简介

跨平台技术的解决方案

特点

  • 快速开发
    Dart既可以是AOT编译,也可以是JIT编译,其JIT编译的特性使Flutter在开发阶段可以达到亚秒级有状态热重载,从而大大提升了开发效率。不像
    React Native 那样在跨平台 JavaScript 代码和原生 Android、iOS 代码之间建立低效的方法调用映射关系。
    ART和Dalvik都算是一种Android运行时环境,或者叫做虚拟机,用来解释dex类型文件。但是ART是安装时解释,Dalvik是运行时解释
  • 性能优越
    高性能渲染引擎(Skia)进行自绘,渲染速度和用户体验堪比原生。
    Dart使用预编译的方式编译多个平台的原生代码,这允许Flutter直接与平台通信,效率比RN高很多
  • 控件
    Flutter内置(iOS风格)小部件,开发者可快速构建精美的用户界面,以提供更好的用户体验。官方提供了300+
  • 社区

flutter 结构

在这里插入图片描述
Framework层:由Dart来实现,包含众多安卓Material风格和iOS Cupertino风格的Widgets小部件,还有渲染、动画、绘图和手势等。Framework包含日常开发所需要的大量API

Engine层:由C/C++实现,是Flutter的核心引擎,主要包括Skia图形引擎、Dart运行时环境Dart VM、Text文本渲染引擎等

Embedder层:主要处理一些平台相关的操作,如渲染Surface设置、本地插件、打包、线程设置等。

底下两层(Foundation和Animation、Painting、Gestures)在Google的一些视频中被合并为一个dart UI层,对应的是Flutter中的dart:ui包,它是Flutter引擎暴露的底层UI库,提供动画、手势及绘制能力。

Rendering层,这一层是一个抽象的布局层,它依赖于dart UI层,Rendering层会构建一个UI树,当UI树有变化时,会计算出有变化的部分,然后更新UI树,最终将UI树绘制到屏幕上,这个过程类似于React中的虚拟DOM。Rendering层可以说是Flutter UI框架最核心的部分,它除了确定每个UI元素的位置、大小之外还要进行坐标变换、绘制(调用底层dart:ui)。

Widgets层是Flutter提供的的一套基础组件库,在基础组件库之上,Flutter还提供了 Material 和Cupertino两种视觉风格的组件库

Widget、Elements

在这里插入图片描述

Widget: flutter用widget来构建页面,widget是不可变的,每个widget都代表一帧。在flutter中一切都是widget。所以每次也页面变化都widget都会到你widget会被重建。widget可以分为StatefulWidget 和 StatelessWidget两种,就是一个配件

在这里插入图片描述

DiagnosticableTree 这个类,它主要用于在调试时获取子类的各种属性和children信息
Widget 是一个抽象类;同时它被 immutable 注解修饰,说明它的各个属性一定是不可变的,这就是为什么我们写各种 Widget 时,所写的各个属性要加 final 的原因

Widget 是个抽象类,所有的 Widgets 都是它的子类,其抽象方法 createElement 需要子类实现
  @protected
  Element createElement();

StatefulWidget

@override
StatefulElement createElement() => StatefulElement(this);

StatelessWidget

@override
StatelessElement createElement() => StatelessElement(this);

Element 都会传入 this,也就是当前 Widget,然后返回对应的 Element,这些 Element 都是继承自 Element
Element: Element 是 树中特定位置 Widget 的一个实例化对象。Element 承载了视图构建的上下文数据,是连接结构化的配置信息到完成最终渲染的桥梁。

在这里插入图片描述
Element 类,这是个抽象类

 /// Typically called by an override of [Widget.createElement].
  Element(Widget widget)
    : assert(widget != null),
      _widget = widget;

  /// The configuration for this element.
  @override
  Widget get widget => _widget;
  Widget _widget;

在这里插入图片描述

widget 里面 createElement 方法的时候可以看到会传入 this,这里从 Element 的构造方法中可以看到,this 最后传给了 Element 里面的 _widget,
StatefulElement 里面不仅有对 Widget 的引用,也有对 StatefulWidget 的 State 的引用。并且在构造函数里面还将 widget 赋值给了 _state 里面的 _widget。所以我们在 State 里面可以直接使用 widget 就可以拿到 State 对应的 Widget
执行步骤:

_firstBuild() -> rebuild() -> performRebuild() -> build()
类的顺序
【ComponentElement】-》【Element】-》【ComponentElement】-》【StatelessElement】

在这里插入图片描述
BuildContext 其实就是这个 Widget 所对应的 Element,也就是在使用的时候element

widget又一个很重要的方法
在这里插入图片描述
Element 里面有一个 _widget 作为其配置信息,当widget变化或重新生成以后,Element 是销毁重建,还是直接将新生成的 Widget 替换旧的 Widget。通过这个判断可以
所以变更重建不直接影响渲染,对性能影响很小

原生接入flutter

作为module被原生依赖。或者打成arr包

//第三种
 FlutterActivity.withCachedEngine("my_engine_id").build(this)
 //第二种
 FlutterActivity.withNewEngine().initialRoute("/my_route").build(this)
 //第一种
 FlutterActivity.createDefaultIntent(this)

和原生数据交互

结构图
在这里插入图片描述
Flutter定义了三种不同类型的PlatformChannel,它们分别是:

MethodChannel:用于传递方法调用,是比较常用的PlatformChannel。双向通道
EventChannel: 用于传递事件。
BasicMessageChannel:用于传递数据。

每种Channel均有三个重要成员变量:
name: String类型,代表Channel的名字,也是其唯一标识符。
messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具。
codec: MessageCodec类型或MethodCodec类型,代表消息的编解码器。
Channel name
Channel在创建时必须指定唯一标示name,消息会根据其传递过channel name找到该Channel
消息信使:BinaryMessenger
BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。

Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。

Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。

当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端

消息编解码器:Codec
消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据,Flutter定义了两种Codec:MessageCodec和MethodCodec。
- MessageCodec用于二进制格式数据与基础数据之间的编解码。BasicMessageChannel所使用的编解码器就是MessageCodec。

  • MethodCodec用于二进制数据与方法调用(MethodCall)和返回结果之间的编解码。MethodChannel和EventChannel所使用的编解码器均为MethodCodec。
    消息处理器:Handler
    二进制格式消息并使用Codec将其解码为Handler能处理的消息后
  • MessageHandler
  • MethodHandler
  • StreamHandler

传值使用

    private static final String METHED_CHANNEL_PLATFORM_NATIVE ="methed_channel_platform_native";

    MethodChannel   methodChannel = new MethodChannel(flutterEngine.getDartExecutor(), METHED_CHANNEL_PLATFORM_NATIVE);

    methodChannel.setMethodCallHandler(
                (call, result) -> {
                
                    switch (call.method) {
                        case "router":
                            Map args = (Map) call.arguments;
                            result.success("回传给Flutter的参数");
                            Log.d("methodChannelXXXX", args.toString());
                            break;
                       
                        case  "backListener":
                            result.success(type==(int)call.arguments);//false 不拦截
                            type= (int) call.arguments; //type
                            break;
                      
                        default:
                            break;
                    }
                });


//dart
channelFlutter() async {
    await print(platformMethodChannel.invokeMethod('router',
        <String, dynamic>{"Aaaaa": "AAAAA", "bbbb": "BVVBB"}).then((data) {
      print(data);
    }));
  }

数据渲染

响应式编程

_onClick(){
  setState(){
      title = "123";
  }
}
@override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Text("$title");
  }

redux 组成

  • Store :用于存储和管理State,所有的状态都储存在Store里,Store会放在App顶层
  • Action:用户触发的一种行为
  • Reducer:根据Action产生新的State,State状态是由reducer生成并储存在Store里面的。Store更新状态的时候,并不是更改原来的状态对象,而是将reducer生成的新的状态对象替换掉老的状态对,
    过程:
    通过发起一个action来告诉Reducer,状态要发生变化了,这是
    Reducer会收到 一个action,然后根据收到的action 去匹配action,并且生成新的action并把新的状态放到Store中,Store丢弃了老的状态对象,储存了新的状态对象后,就通知所有使用到了这个状态的View更新(类似setState)

添加依赖

flutter_redux: 0.6.0
redux: 4.0.0

创建State

状态是由reducer生成并储存在Store里面的。Store更新状态的时候,并不是更改原来的状态对象,而是直接将reducer生成的新的状态对象替换掉老的状态对象。所以,我们的状态应该是immutable的
创建State

创建State
import 'package:meta/meta.dart';
/**
 * State中所有属性都应该是只读的
 */
@immutable
class CountState{
  int _count;
  get count => _count;

  CountState(this._count);
}

创建action

/**
 * 定义操作该State的全部Action
 * 这里只有增加count一个动作
 */
enum Action{
  increment
}

创建reducer

/**
 * reducer会根据传进来的action生成新的CountState
 */
CountState reducer(CountState state,action){
  //匹配Action
    if(action == Action.increment){
      return CountState(state.count+1);
    }
    return state;
}

创建store

Store接收一个reducer,以及初始化State,我们想用Redux管理全局的状态的话,需要将store储存在应用的入口才行。而在应用打开时要先初始化一次应用的状态。所以在State中添加一个初始化的函数。

//这段代码写在State中
CountState.initState(){ _count = 0;}
//应用顶层
void main() {
  final store =
      Store<CountState>(reducer, initialState: CountState.initState());
  runApp(new MyApp(store));
}

将Store放入顶层

flutter_redux提供了widget叫做StoreProvider,用法接收一个store,和child Widget。

class MyApp extends StatelessWidget {
  final Store<CountState> store;

  MyApp(this.store);

  @override
  Widget build(BuildContext context) {
    return StoreProvider<CountState>(
      store: store,
      child: new MaterialApp(
        title: 'Flutter Demo',
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: TopScreen(),
      ),
    );
  }
}

页面中获取Store中的state

StoreConnector<CountState,int>(
              converter: (store) => store.state.count,
              builder: (context, count) {
                return Text(
                  count.toString(),
                  style: Theme.of(context).textTheme.display1,
                );
              },
            ),
  • 要想获取store我们需要使用StoreConnector<S,ViewModel>。StoreConnector能够通过StoreProvider找到顶层的store。而且能够在state发生变化时rebuilt Widget。
  • 首先这里需要强制声明类型,S代表我们需要从store中获取什么类型的state,ViewModel指的是我们使用这个State时的实际类型。
  • 然后我们需要声明一个converter<S,ViewModel>,它的作用是将Store转化成实际ViewModel将要使用的信息,比如我们这里实际上要使用的是count,所以这里将count提取出来。
  • builder是我们实际根据state创建Widget的地方,它接收一个上下文context,以及刚才我们转化出来的ViewModel,所以我们就只需要把拿到的count放进Text Widget中进行渲染就好了。
    主要是这个
    www.jianshu.com/p/5d7e2dbda…

接受store就比较简单了
在这里插入图片描述
外层用一个StoreProvide,里面如下图 又一个StoreConnector 当然也可以用StoreBuilder
在这里插入图片描述
看看就懂了,但是 我们不这样用 !!!!!

在前面提到创建store,在这里我们这样做,放在main.dart里面,我们看看store 的构造方法

在这里插入图片描述
构造里面需要一个 reducer、State、 middleware 。同时泛型约束为,那我们就在构造方法中传入 State
在这里插入图片描述
我们看看appReducer 和 MainState是怎么实现的
reducer的作用是根据Action产生新的State,
在这里插入图片描述
不是根据action生成state吗 怎么现在这个?那我们看看Reducer的源码
在这里插入图片描述
其实,其实就是appReducer 就是一个reducer。不同的是,我们把很多状态放在一起,统一做多个状态的管理,
至于MainState就什么东西了,就是里面定义了不同的状态
在这里插入图片描述
但是,里面的不同状态很重要,我们看这UserState
在这里插入图片描述
UserInfoModel 和ConnectState看代码没什么好说的

class UserInfoModel {
  int id;
  int gender;

  UserInfoModel({this.id, this.gender});

factory UserInfoModel.fromJson(Map<String, dynamic> json) =>
UserInfoModel(
id: json['id'] as int,
    gender: json['gender'] as int,
);

Map<String, dynamic> toJson() => <String, dynamic> {
  'id': id,
  'gender': gender,
};
}

关键在combineReducers和TypedReducer 比较重点,我们看看这俩
combineReducers:
看下源码
在这里插入图片描述
里面就是放一堆列表作用。combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数
合并后的 reducer 可以调用各个子 reducer,并把它们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。

然后怎么发送action使用呢 ? 如图
在这里插入图片描述
这是一个公共的工具类,所有的store都在这里,然后调用
store.dispatch(Action action)就可以发送action了,

TypedReducer官方的意思是说用来根据action过滤执行指定的reducer,那就很明白了,
整个流程
1.创建store 传入 appReducer和 MainState,appReducer生成新的MainState,
2. 看看MainState 里面 就是创建不同的reducer。不同的是这里根据combineReducers和TypedReducer 来根据不同的action ,来创建不同的状态(可以理解不同的状态对应不同更新ui的操作)
3. 然后接受和上面没什么不同,我用的StoreBuilder

flutter 坑

flutter 键盘弹出和收起页面bug

MediaQueryData.fromWindow(WidgetsBinding.instance.window) ,之后又恰好在有键盘的页面打开后触发了 MaterialApp 的更新,导致了 PageRoute 重新 builder, 使得没有键盘的 Scaffold 使用了弹出键盘的 viewInsets.bottom。
所以这里只需要将 MediaQueryData.fromWindow 换成 MediaQuery.of(context) 就可以解决问题,而当在没有 context 或者需要直接使用 MediaQueryData.fromWindow 时。

flutter 黑屏的坑

1.一个Flutter执行环境 是dart运行在Android上面最小的容器
2.在FlutterEngine中的dart代码 既可以运行在后台,也可以进行(FlutterRenderer)ui交互
3.初始化FlutterEngine时,加载FlutterEngine的native libs并且启动DartVM
4.DartVM中有多个FlutterEngine。
5.FlutterEngine运行在各自的Isolate中(类似于Android中的线程
),他们的内存数据不共享,需要通过Isolate事先设置的port(顶级函数)通讯。
1.通过提前新建flutter engine 并把flutter engine 存储在flutterEngineCache中
2.背景色和背景图

flutter 全局BuildContext坑

路由跳转、弹窗、媒体查询,全部依赖于 BuildContext。MediaQuery 、Navigator 、Overlays 的 BuildContext 不是一个。所以在页面回退的时候会出现 找不到nativitor.buildContext()的问题
1.解决:GlobalKey

@override
Widget build(BuildContext context) {
  return MaterialApp(
    navigatorKey: globalNavigatorKey, // GlobalKey()
  )
}
globalNavigatorKey.currentState.push(
  MaterialPageRoute(builder: (context) => SomePage()),
);

会出现bug
Overlays和MediaQuery的问题,提示context不合法

第二种
OneContext 解决方案:
OneContext 重写了 Widget 结构中的 MediaQuery 和 Navigator 的初始化配置,并在每个页面的 Widget 外层包了一层OneContextWidget,推荐这种。

native 跳转flutterActivity的问题

现象:不同跳转页面配置和插件失效

methodChannel = new MethodChannel(flutterEngine.getDartExecutor(), METHED_CHANNEL_PLATFORM_NATIVE);
        //z注册插件 防止三方插件不能使用
        GeneratedPluginRegistrant.registerWith(getFlutterEngine());
        
    public static NewMyEngineIntentBuilder withNewEngine(Class<? extends FlutterActivity> activityClass) {
        return new NewMyEngineIntentBuilder(activityClass);
    }

    //重写创建引擎方法
    public static class NewMyEngineIntentBuilder extends NewEngineIntentBuilder {
        protected NewMyEngineIntentBuilder(Class<? extends FlutterActivity> activityClass) {
            super(activityClass);
        }
    }