Fighting!

潜行者的沉默

Protocol Buffer 介绍

Protocol Buffer 是一种轻便高效的结构化数据格式,可以用于结构化数据的序列号,适合做数据存储和RPC数据交换格式,他是平台无关,语言无关,可扩展的序列号结构数据格式

结构化数据格式

  1. XML, 通过标签定义的
  2. JSON,通过键值对定义的
  3. DB,数据库

序列化

  1. Serilizable
  2. Parseable

用途

  1. RPC数据交换格式 即网络通讯
  2. 数据存储

跨平台,语言

如json,不限操作平台和编程语言同可以使用json

优点

  1. 序列化后的体积相比json和xml很小,适合网络传输 40M的json数据 17M Protobuffer
  2. 支持跨平台,多语言
  3. 消息格式升级和兼容性不错
  4. 序列化反序列化快 40M的json数据 10s, Protobuffer 0.8s

使用

Android studio 环境配置

配置应用build

1
2
3
4
5
6
7
8
9
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}

配置module build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.8.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.protobuf:protobuf-javalite:3.9.1'
...
}

构建消息

消息由至少一个字段组合而成:字段 = 字段修饰符 + 字段类型 + 字段名 + 标识符
标识符TAG:每个字段的唯一标识数字,用于说明二进制文件的对应关系,不能修改,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
syntax = "proto3";

//option java_package = "www.dcf.com.vo";
package tutorial;

option java_package = "com.zyx.proto";
option java_outer_classname = "TestProto";

message test {
int32 id = 1; // ID
string str = 2; // str
int32 opt = 3; //optional field
}

Studio 自动生成代码

通过Android Studio build,Protobuf插件会帮助我们自动生成TestProto类,类结构如下

Protobuf帮助我们自动生成了testOrBuilder接口,主要定义了个字段的get,set方法,并生成了test类,核心逻辑,通过writeTo(CodedOutputStream)接口序列化到CodedOutputStream,通过parseFrom(InputStream) 接口从InputStream中反序列化

ProtoBuffer 原理

ProtoBuffer不管在时间还是空间上更加高效,是怎么做到的?

消息经过ProtoBuffer序列化后会成为二进制数据流,通过key-Value组成方式写入到二进制数据流。

编码机制

Base 128 Varints

是一种可变字节序列化整形的方法

  1. 每个byte的最高位是标志位(msb), 如果是1,则表示后面还有byte,否则为结束byte
  2. 每个byte的低7位用来存储数值的位
  3. Varints方法用Litte-Endian(小端)字节序列

消息结构

编码类型
Type Meaning Used For
0 Varint int32,int64,uinit32,uint64,sint32,sint64,bool,enum
1 64-bit fixed64,sfixed64,double
2 Length-delimited string,bytes,embedded messages
3
4
5 32-bit fixed32,sfixed32,float
key

key的具体值为 (field_number << 3) | wire_type,

以上面的例子来说,如字段id定义:

1
int32 id = 2; // 150

在序列化时,并不会把字段id写进二进制流中,而是把field_number=2通过上述Key的定义计算后写进二进制流中,这就是Protobuf可读性差的原因,也是其高效的主要原因。

1
2
3
4
5
6
7
8
9
10
11
12
13
key = (field_number << 3) | wire_type = 2 << 3 | 0 = 10000 |0 = 16  = 0x10
value 150 二进制位1001 0110
最高位 为msb,将它分为一个一组
1 0010110 进行补齐 0000001 0010110
小端序存储则为0010110 0000001
补齐 表示为msb 10010110 00000001 = 0x96 0x01
则最后的存储为 10 96 01

如果value 300 二进制为 100101100
最高位为msb, 进行7位一组分组
10 0101100 进行补齐0000010 0101100
小端序存储 10101100 00000010 = 0xac 0x2
则最后存储为 10 ac 02

key的范围:wire_type只有六种类型,用3bit表示,在一个byte里,去掉mbs,以及3bit的wire_type,只剩下4bit来表示field_number,因此一个Byte里,field_number只能表达0-15,超过15个需要多个byte表示

负数

所谓ZigZag编码即将负数转换成正数,而所有正数都乘2,如0编码成0,-1编码成1,1编码成2,-2编码成3,以此类推,因而它对负数的编码依然保持比较高的效率。

优缺点
  • Varint适用于表达比较小的整形,当数字很大时,采用定长编码类型(64bit,32bit)
  • 不利于表达负数,负数采用补码表示,会占用更多字节,确定出现负数用sint32,sint64,他会采用ZigZig编码将负数映射成整数,之后再使用Varint编码

Length-delimited

  • Length-delimited编码格式会将数据的length也编码进最终的数据,编码格式有string,bytes,自定义消息

  • 在消息中将str = “testing”,

    序列化的打印结果 为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     private void test() {
    TestProto.test.Builder test = TestProto.test.newBuilder();
    TestProto.test test1 = test.setStr("testing").build();
    // 序列化
    byte[] bytes = test1.toByteArray();

    for (byte aByte : bytes) {
    System.out.print(aByte +" ");
    }
    }
    // 18 7 116 101 115 116 105 110 103
    str 的field_number = 2; str = "testing"
    key = (field << 3) | wire_type = 2 << 3 | 2 = 10000 | 10 = (十进制) 18 = (16进制)0x12
    7 为 value的长度testing长度为7
    然后计算value的每个字母 t 在ASCII中的 十进制数是 116, e 为101, s为115 以此类推
    发现每个byte存储的是计算后的10进制数字,和网上说16进制 byte存储方式不一致 12 07 74 65 73 74 69 6e 67
    现在按照16进制存储计算
    key = 12 已经确定
    t 的二进制为 0111 0100 16 进制为74
    e 则为65 ..
    所以string的转换方式是 key的16进制 + 字符串长度16进制 + 字符串的每个字母的16进制
    结论

    通过原理破解,在通过观察源码Protobuffer的序列化和反序列化同时通过几个位运算实现的,所以他的效率高,体积小

使用指南

  • 尽量不要修改tag
  • 字段数量不要超过16个,否则会采用2个字节编码, (1个字节最大值为128, key的计算会通过field_number << 3 | wire_type, 当field_number 为16时刚好使用了1个字节计算,否则就需要两个字节计算)
  • 如果确定使用负数,采用sint32/sint64

FastJson对比

参考

Google Protocol Buffer 的使用和原理

Flutter的渲染阶段

VSync->Animation->Build->Layout->Paint->Display List(GPU)

  • Build : Widget,Element树
  • Layout,Paint: RenderObject树的创建
  • Display List(GPU),是对Layer图层的创建

检查Flutter的渲染

Debug模式和最终的生产模式,有很大的性能特点,所以在做真实的测量之前都用Profile模式

  • Debug模式
  • Profile模式

RenderObject

它不是一个顶层Api,并且充分利用了组合的性质,所以他的公共方法比Android的View少

  • 是Flutter的UI单位,有很长的生命周期和状态
  • 主要方法
    • createRenderObject、updateRenderObject
    • performLayout
    • paint

Element

  • ComponentElement,主要是做组合的,不直接参与布局绘制
    • StatelessElement
    • StatefulElement
  • RenderObjectElement,会对RenderObject树上的RenderObject做连接

同类型更新

修改某个Text的值

  • Build
    • 在Flutter中Widget树是不可改变的,这也包括树节点之间的父子关系
    • 在开始build时,会创造一个新的树
    • 遍历Element,通过Element.updateChild(),观察子节点,若子节点类型发生改变,则会扔掉老节点,创造一个新的节点
    • 更新过程中,若Element是Component则build即可,若是RenderObjectElement则会updateRenderObject
    • 若内容发生改变会进行标脏处理

Buid性能测试工具

  • 观望台,TimeLine,类似安卓的systrace,设置debugProfileBuildsEnable = true,检查build过程的性能损耗
  • 遍历的触发
    • setState方法
    • 依赖了InHeritedWidget,当InHeritedWidget发生改变会影响其他
    • 热重载,所有节点都会被更新
  • 提高build效率
    • 通过Extra单独的Widget,减少遍历的节点
    • 停止遍历

Paint

在工程完成后会对RenderObject的某些节点进行标脏,让他重新绘制。
debugProfileBuildsEnabled = true;
debugProfilePaintsEnabled = true;
debugPaintLayerBordersEnabled = true;

  • 如何知道多少其他节点需要跟被标脏的树一起被更新呢?
    • 基于图层树Layer
    • 更新指定图层
  • Layer种类
    • PictureLayer
    • ContainerLayer:主要用于做PictureLayer的连接

yum -y install wget
wget -N –no-check-certificate https://softs.fun/Bash/ssr.sh && chmod +x ssr.sh && bash ssr.sh

指针

描述屏幕上由触摸板,鼠标,指示笔等触发的指针的位置和移动

指针事件

  • PointerDownEvent:指针在特定位置与屏幕接触
  • PointerMoveEvent:指针从屏幕的一个尾椎移动到另一个位置
  • PointerUpEvent:指针与屏幕停止接触
  • PointerCancelEvent:指针的输入已经不在指向此应用

Listener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const Listener({
Key key,
this.onPointerDown,
this.onPointerMove,
// We have to ignore the lint rule here in order to use deprecated
// parameters and keep backward compatibility.
// TODO(tongmu): After it goes stable, remove these 3 parameters from Listener
// and Listener should no longer need an intermediate class _PointerListener.
// https://github.com/flutter/flutter/issues/36085
@Deprecated(
'Use MouseRegion.onEnter instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerEnter, // ignore: deprecated_member_use_from_same_package
@Deprecated(
'Use MouseRegion.onExit instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerExit, // ignore: deprecated_member_use_from_same_package
@Deprecated(
'Use MouseRegion.onHover instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerHover, // ignore: deprecated_member_use_from_same_package
this.onPointerUp,
this.onPointerCancel,
this.onPointerSignal,
this.behavior = HitTestBehavior.deferToChild,
Widget child,
})

HitTestBehavior

  • translucent:层叠布局时,可以使布局都收到事件
  • opaque: 布局透明也可以收到事件
  • deferToChild:当布局透明时收不到事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    enum HitTestBehavior {
    /// Targets that defer to their children receive events within their bounds
    /// only if one of their children is hit by the hit test.
    deferToChild, // 子组件会一个接一个的进行命中测试,如果子组件收到,那父组件也能收到该事件

    /// Opaque targets can be hit by hit tests, causing them to both receive
    /// events within their bounds and prevent targets visually behind them from
    /// also receiving events.
    opaque, // 在命中测试中,将当前组件当成不透明处理

    /// Translucent targets both receive events within their bounds and permit
    /// targets visually behind them to also receive events.
    translucent, // 当点击组件透明区域时,可以对自身边界内及底部可视区域,都进行命中测试
    }

忽略指针事件

  • IgnorePointer:阻止子树接受指针事件,本身不会参与命中测试
  • AbsorbPointer:阻止子树接受指针事件,本身参与命中测试

手势

Gesture代表的是语义操作(比如点击,拖动,缩放),通过一系列单独的指针事件组成,甚至是一系列指针组成。Gesture可以分发多种事件,对应着指针的生命周期(比如开始拖动,拖动更新,结束拖动)

手势类别

  • GestureDetector:用于手势识别的功能性组件,可以识别各种手势,是指针事件的语义分装,内部使用了一个或者多个GestureRecognizer
  • GestureRecognizer:通过Listener将原始指针事件转化为语义手势

GestureDetector

  • 点击:onTapDown,onTapUp,onTap,onTapCancel
  • 双击:onDoubleTap
  • 长按:onLongPress
  • 纵向拖动:onVerticalDragStart,onVerticalDragUpdate,onVerticalDragEnd
  • 横向拖动:onHorizontalDragStart,onHorizontalDragUpdate,onHorizontalDragEnd
  • 移动:onPanStart,onPanUpdate,onPanEnd
  • 移动和横向,纵向拖动互斥

手势消歧处理

  • 在屏幕的指定位置上,可能有多个手势捕捉器。所有的手势捕捉器监听了指针输入流事件并判断出特定的手势
  • 在任何时候,识别器都可以宣告失败并离开竞技场。如果竞技场中只有一个识别器,那么这个识别器就是胜者。
  • 在任何时候,任何识别器都可以宣告胜利,这将导致这个识别器胜出,其他识别器失败

基本概念

  • Animation:Flutter动画库中的核心类,插入用于指导动画的值
  • AnimationController:管理Animation
  • CurvedAnimation:定义动画的曲线
  • Tween:为动画对象插入一个范围值

Animation

  • 提供了每一帧动画变化的监听事件和移除事件,VoidCallback
    • addListener
    • removeListener
  • 提供了动画状态的监听和移除事件,AnimationStatusListener
    • addStatusListener
    • removeStatusListener
  • 动画的四种状态
    • dismiss,在动画开始时停止
    • forward,动画从开始向结束运行
    • reverse,动画从结束向开始运行
    • completed,动画在结束时运行完成
  • 获取动画的状态
  • 泛型参数为范围值,一般是double类型

AnimationController

  • 继承自Animation,是一个特殊的Animation,当硬件准备新帧时,它都会生成一个新值
  • 需要一个vsync参数,vsync的存在防止后台动画消耗不必要的资源
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    AnimationController({
    double value,
    this.duration, // 动画时长
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0, // 最小值
    this.upperBound = 1.0, // 最大值
    this.animationBehavior = AnimationBehavior.normal,
    @required TickerProvider vsync, // 每一帧同步时的回调
    })

CurvedAnimation

怎么运动,运动的过程,动画运行的曲线,可以继承Curve,重写transformInternal

  • Curve曲线分类
    • linear:匀速的
    • decelerate:匀减速
    • ease:开始加速后面减速
    • easeIn:开始慢后面快
    • easeOut:开始快,后面慢
    • easeInOut:开始慢,先加速,后减速

Tween

可以自定义移动的类型,如移动像素等

  • 配置动画插入不同的范围和数据类型

AnimatedBuilder

动画的构建器

1
2
3
4
5
6
const AnimatedBuilder({
Key key,
@required Listenable animation, // 具体动画
@required this.builder, // 实现动画的对象
this.child,
})

路由切换动画

  • Android提供默认的MaterialPageRoute
  • IOS提供默认的CupertionPageRoute
  • 自定义实现采用PageRouteBuilder
    1
    2
    3
    Navigator.push(context, PageRouteBuilder(pageBuilder: (context, animation, econdaryAnimation){
    return FadeTransition(opacity: animation, child: SyncAnim(),);
    }));

Hero动画

指在页面之间飞行的Widget,相当于转场动画,Hero在动画切换的时候,有一个共享的Widget可以在新旧路由之间切换

  • InkWell:水波纹效果的widget
  • 将需要共享的元素放入Hero Widget中
  • 需要指定相同的tag
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    InkWell(
    child: Hero(tag: "avator", child: ClipOval(
    child: Image.network("http://yangxin.online/images/head.jpeg", width: 50, height: 50 , ),
    )),
    onTap: (){
    Navigator.of(context).push(MaterialPageRoute(builder: (context){
    return Scaffold(
    body: Center(
    child: Hero(tag: "avator", child: Image.network("http://yangxin.online/images/head.jpeg", width: 300, height: 300 , ),
    ),
    ));
    }));
    },
    )

交织动画

设计复杂动画,动画序列,重叠动画

  • 使用多个动画对象
  • 一个AnimationControl控制所有的动画
  • 每个动画对象指定间隔时间
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    class Stagger extends StatelessWidget {
    final AnimationController controller;

    final Animation<double> opacity, width, height;

    final Animation<EdgeInsets> padding;

    final Animation<BorderRadius> borderRadius;

    final Animation<Color> color;

    Stagger({Key key, this.controller})
    : opacity = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(
    parent: controller,
    curve: Interval(0.0, 0.1000, curve: Curves.linear))),
    width = Tween(begin: 50.0, end: 150.0).animate(CurvedAnimation(
    parent: controller,
    curve: Interval(0.125, 0.250, curve: Curves.linear))),
    height = Tween(begin: 50.0, end: 150.0).animate(CurvedAnimation(
    parent: controller,
    curve: Interval(0.250, 0.375, curve: Curves.linear))),
    padding = EdgeInsetsTween(
    begin: EdgeInsets.only(bottom: 10),
    end: EdgeInsets.only(bottom: 50))
    .animate(CurvedAnimation(
    parent: controller,
    curve: Interval(0.250, 0.375, curve: Curves.linear))),
    borderRadius = BorderRadiusTween(
    begin: BorderRadius.circular(5), end: BorderRadius.circular(15))
    .animate(CurvedAnimation(
    parent: controller,
    curve: Interval(0.375, 0.500, curve: Curves.linear))),
    color = ColorTween(begin: Colors.blue, end: Colors.red).animate(
    CurvedAnimation(
    parent: controller,
    curve: Interval(0.500, 0.750, curve: Curves.linear))),
    super(key: key);

    @override
    Widget build(BuildContext context) {
    return AnimatedBuilder(
    animation: controller,
    builder: (context, child) {
    return Container(
    padding: padding.value,
    alignment: Alignment.bottomCenter,
    child: Opacity(
    opacity: opacity.value,
    child: Container(
    width: width.value,
    height: height.value,
    decoration: BoxDecoration(
    color: color.value,
    border: Border.all(color: Colors.blue, width: 3),
    borderRadius: borderRadius.value),
    ),
    ),
    );
    },
    );
    }
    }

    class StaggerF extends StatefulWidget {
    @override
    State<StatefulWidget> createState() {
    // TODO: implement createState
    return StaggerS();
    }
    }

    class StaggerS extends State<StaggerF> with TickerProviderStateMixin {
    AnimationController _controller;

    _play() async {
    await _controller.forward().orCancel;
    await _controller.reverse().orCancel;
    }

    @override
    void initState() {
    super.initState();
    _controller =
    AnimationController(duration: Duration(seconds: 10), vsync: this);
    _controller.addStatusListener((status) {
    switch (status) {
    case AnimationStatus.dismissed:
    _controller.forward();
    break;
    case AnimationStatus.completed:
    _controller.reverse();
    break;
    case AnimationStatus.forward:
    case AnimationStatus.reverse:
    break;
    }
    });
    }

    @override
    Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
    body: GestureDetector(
    onTap: () {
    _play();
    },
    child: Center(
    child: Container(
    width: 300,
    height: 300,
    color: Colors.yellow,
    child: Stagger(
    controller: _controller,
    ),
    ),
    ),
    ),
    );
    }
    }

路由管理

在Flutter中,屏于页面都叫做路由。路由管理,就是管理页面之间如何跳转,在Flutter中维护一个路由栈,路由入栈操作对应着就是打开一个新页面,路由出栈操作就是对应页面的关闭操作

  • Navigator.push()跳转到第二个页面
  • Navigator.pop()退回到第一个页面
    1
    2
    3
    4
    5
    6
     Navigator.push(context, MaterialPageRoute(builder: (context) {
    return SecondRoute2("hello word");
    }));

    Navigator.pop(context);

路由传值

  • Navigator.push()返回Flutter对象,用以接收新路由出栈时的返回数据
  • Navigator.pop()将栈顶路由出栈,参数Result为页面关闭时返回给上一个页面的数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Scaffold(
    appBar: AppBar(
    title: Text("SendRoute route"),
    ),
    body: Center(
    child: Column(
    children: <Widget>[
    Text(arg),
    RaisedButton(
    child: Text(
    "go back",
    ),
    onPressed: () {
    Navigator.pop(context, "hello world");
    },
    ),
    ],
    ),
    ),
    );

命名路由

  • 注册路由表
  • Navigator.pushNamed()跳转到第二个界面。
  • Navigator.pop()回退到第一个路由
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class MyApp2 extends StatelessWidget{
    @override
    Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
    title: 'Flutter Demo',
    theme: ThemeData(
    primarySwatch: Colors.blue,
    ),
    routes: {
    '/': (context) => FirstRoute(),
    '/second_route': (context) => SecondRoute()
    },
    initialRoute: '/',
    );
    }
    }

    // 跳转
    Navigator.pushNamed(context, '/second_route', arguments: '卡啦啦啦');

    // 获取命名跳转的传值
    final String arg = ModalRoute.of(context).settings.arguments;


    // 优先级最低路由列表
    onGenerateRoute: (settings) {
    if(settings.name == '/second_route4'){
    var arg = settings.arguments;
    return MaterialPageRoute(builder: (context) => SecondRoute4(arg), settings: settings);
    }
    },

路由动画

  • MaterialPageRoute:继承自PageRoute,是MaterIAL组件库提供的组件,它可以针对不同的平台,实现与平台页面切换动画一致的路由切换动画
  • PageRouteBuilder
    1
    2
    3
    4
    5
    6
    Navigator.push(context, PageRouteBuilder(
    transitionDuration: Duration(milliseconds: 800),
    pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation){
    return FadeTransition(opacity: animation, child: SecondRoute5(),);
    }
    ));

ListView

  • 少量数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ListView({
    Key key,
    Axis scrollDirection = Axis.vertical, // 滚动方向
    bool reverse = false, // 是否反向展示数据
    ScrollController controller,
    bool primary,
    ScrollPhysics physics, // 物理滚动,默认根据不同平台采用不同对象
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent, // item 有效范围
    bool addAutomaticKeepAlives = true, // 自动保存视图缓存
    bool addRepaintBoundaries = true, // 添加重绘边界
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    })
  • 长列表数据

    • itemBuilder:它是列表项的的构造器,返回值是一个Wideget,当列表滚动到具体的index时,会调用改构造器构建列表
    • itemCount:列表项的数量,数量为null,则为无限列表
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      ListView.builder({
      Key key,
      Axis scrollDirection = Axis.vertical,
      bool reverse = false,
      ScrollController controller,
      bool primary,
      ScrollPhysics physics,
      bool shrinkWrap = false,
      EdgeInsetsGeometry padding,
      this.itemExtent,
      @required IndexedWidgetBuilder itemBuilder,
      int itemCount,
      bool addAutomaticKeepAlives = true,
      bool addRepaintBoundaries = true,
      bool addSemanticIndexes = true,
      double cacheExtent,
      int semanticChildCount,
      DragStartBehavior dragStartBehavior = DragStartBehavior.start,
      })
  • 分割组件生成器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    ListView.separated({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required IndexedWidgetBuilder itemBuilder,
    @required IndexedWidgetBuilder separatorBuilder,
    @required int itemCount,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    })

GridView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GridView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate, // 表格处理类,SliverGridDelegateWithFixedCrossAxisCount,SliverGridDelegateWithMaxCrossAxisExtent
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
})

线性布局

线性布局,指的是沿水平或者垂直方向排布子组件。Flutter中通过Row和Column来实现线性布局。

  • 主轴和纵轴的区分,依赖于布局方向
    • 布局是水平方向,主轴就是水平方向(Main Axis)
    • 反之,主轴就是竖直方向

Row

1
2
3
4
5
6
7
8
9
10
Row({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, // 子组件在水平方向上的对齐方式
MainAxisSize mainAxisSize = MainAxisSize.max, // 主轴占用的空间
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, //纵轴方向上的对齐方式
TextDirection textDirection, // 文字方向
VerticalDirection verticalDirection = VerticalDirection.down, // 表示row纵轴的对齐方式,down:自上而下,up:自下而上
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})

弹性布局

弹性布局允许子组件按照一定比例来分配父容器空间。Flutter中的弹性布局主要通过Flex和Expanded来配合实现

Flex

Flex组件可以沿着水平或者垂直方向排列子组件

1
2
3
4
5
6
7
8
9
10
11
Flex({
Key key,
@required this.direction,
this.mainAxisAlignment = MainAxisAlignment.start,
this.mainAxisSize = MainAxisSize.max,
this.crossAxisAlignment = CrossAxisAlignment.center,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
this.textBaseline,
List<Widget> children = const <Widget>[],
})

Expanded

可以按照比例”扩伸”,Row、Column和Flex子组件所占空间

1
2
3
4
5
const Expanded({
Key key,
int flex = 1,
@required Widget child,
})

层叠布局

层叠布局能够将子控件层叠排列。Flutter中Stack允许子控件堆叠,而positioned用于根据Stack的四个角来确定子组件的位置

Stack

1
2
3
4
5
6
7
8
Stack({
Key key,
this.alignment = AlignmentDirectional.topStart, // 对齐方式
this.textDirection,
this.fit = StackFit.loose, // 如何占满Stack
this.overflow = Overflow.clip, // 超出部分的显示
List<Widget> children = const <Widget>[],
})

Positioned

分别表示离Stack的 上下左右的的间距, 以及指定元素的大小

1
2
3
4
5
6
7
8
9
10
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
})
0%