Flutter学习日记-log4

本文最后更新于:2 年前

Flutter中文网


日志随笔

一、复习

  • 如果使用Navigator.push方法传参,那么接收回调参数时,可以使用确定类型例如String等接收,也可以使用var接收,如果使用的命名路由传参,那么只能使用var接收回调参数,否则会报错
  • =>代表调用无参数的函数,并获取返回值
1
2
3
4
5
6
7
8
9
10
11
'HomePage': (context) => HomePage(),
// 另外写法
'HomePage': (context) {
return HomePage();
},

_HomePageState createState() => _HomePageState();
// 另外写法
State<StatefulWidget> createState() {
return _HomePageState();
}

二、容器类组件

  • 布局类组件需要一个widget数组(children),直接或间接继承自(或包含)MultiChildRenderObjectWidget;而容器类组件只需要一个子Widget (child),直接或间接继承自(或包含)SingleChildRenderObjectWidget
  • 布局类组件是按照一定的排列方式来对其子Widget进行排列;而容器类组件一般只是包装其子Widget,对其添加一些修饰(补白或背景色等)、变换(旋转或剪裁等)、或限制(大小等)

1、填充(Padding)

EdgeInsets属性

  • fromLTRB(double left, double top, double right, double bottom):分别指定四个方向的填充
  • all(double value): 所有方向均使用相同数值的填充
  • only({left, top, right ,bottom }):可以设置具体某个方向的填充(可以同时指定多个方向)
  • symmetric({ vertical, horizontal }):用于设置对称方向的填充,verticaltopbottomhorizontalleftright

2、尺寸限制类容器

  • ConstrainedBox用于对子组件添加额外的约束
  • SizedBox用于给子元素指定固定的宽高
  • UnconstrainedBox不会对子组件产生任何限制,它允许其子组件按照其本身大小绘制,例如去除父级限制

3、装饰容器

1. DecoratedBox
  • decoration:代表将要绘制的装饰,它的类型为DecorationDecoration是一个抽象类。
  • position:此属性决定在哪里绘制Decoration,它接收DecorationPosition的枚举类型,该枚举类有两个值:
    • background:在子组件之后绘制,即背景装饰
    • foreground:在子组件之上绘制,即前景
2. BoxDecoration

通常会直接使用BoxDecoration类,它是一个Decoration的子类

4、变换(Transform)

Transform变换是应用在绘制阶段,而并不是应用在布局阶段,所以无论对子组件应用何种变化,其占用空间的大小和在屏幕上的位置都是固定不变的,因为这些是在布局阶段就确定的

1. 平移
  • Transform.translate接收一个offset参数,可以在绘制时沿x、y轴对子组件平移指定的距离
2. 旋转
  • Transform.rotate可以对子组件进行旋转变换
3. 缩放
  • Transform.scale可以对子组件进行缩小或放大

5、Container

Container是一个组合类容器,它本身不对应具体的RenderObject,它是DecoratedBox、ConstrainedBox、Transform、Padding、Align等组件组合的一个多功能容器,所以只需通过一个Container组件可以实现同时需要装饰、变换、限制的场景

6、Scaffold、TabBar、底部导航

1. Scaffold

Scaffold是一个路由页的骨架,包含导航栏、抽屉菜单以及底部Tab导航菜单等

2. AppBar
  • AppBar是一个Material风格的导航栏,可以设置导航栏标题、导航栏菜单、导航栏底部的Tab标题等
3. TabBar
  • bottom属性来添加一个导航栏底部Tab按钮组
4. TabBarView
  • TabBar我们只能生成一个静态的菜单,我们需要通过TabController去监听Tab菜单的切换去切换Tab
5. 抽屉菜单Drawer
  • ScaffolddrawerendDrawer属性可以分别接受一个Widget来作为页面的左、右抽屉菜单
6. FloatingActionButton
  • FloatingActionButtonMaterial设计规范中的一种特殊Button,通常悬浮在页面的某一个位置作为某种常用动作的快捷入口

7、剪裁(Clip)

  • CustomClipper自定义剪裁区域

三、可滚动组件

  • 当组件内容超过当前显示视口ViewPort时,如果没有特殊处理,Flutter则会提示Overflow错误
  • Flutter提供了多种可滚动组件用于显示列表和长布局。可滚动组件都直接或间接包含一个Scrollable组件

1、简介

  • axisDirection:滚动方向。
  • physics:此属性接受一个ScrollPhysics类型的对象,它决定可滚动组件如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定一个固定的ScrollPhysics,可以直接使用:
    • ClampingScrollPhysics:Android下微光效果。
    • BouncingScrollPhysics:iOS下弹性效果。
  • controller:此属性接受一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件。默认情况下,Widget树中会有一个默认的PrimaryScrollController,如果子树中的可滚动组件没有显式的指定controller,并且primary属性值为true时(默认就为true),可滚动组件会使用这个默认的PrimaryScrollController。这种机制带来的好处是父组件可以控制子树中可滚动组件的滚动行为,例如,Scaffold正是使用这种机制在iOS中实现了点击导航栏回到顶部的功能。我们将在本章后面“滚动控制”一节详细介绍ScrollController
1. Scrollbar
  • Scrollbar是滚动指示器(滚动条),如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件即可
  • CupertinoScrollbar如果你使用的是Scrollbar,那么在iOS平台它会自动切换为CupertinoScrollbar
2. ViewPort视口
  • 指一个Widget的实际显示区域
3. 基于Sliver的延迟构建
  • Flutter中提出一个Sliver(中文为“薄片”的意思)概念,如果一个可滚动组件支持Sliver模型,那么该滚动可以将子组件分成好多个“薄片”(Sliver),只有当Sliver出现在视口中时才会去构建它,这种模型也称为“基于Sliver的延迟构建模型”

2、SingleChildScrollView

  • 通常SingleChildScrollView只用在不会超过屏幕太多的场景,因为SingleChildScrollView不支持基于Sliver的延迟实例化模型

3、ListView

1. 默认构造函数

默认构造函数有一个children参数。这种方式适合只有少量的子组件的情况,通过此方式创建的ListView和使用SingleChildScrollView+Column的方式没有本质的区别

2. ListView.builder

ListView.builder适合列表项比较多(或者无限)的情况,因为只有当子组件真正显示的时候才会被创建,也就说通过该构造函数创建的ListView是支持基于Sliver的懒加载模型的

3. ListView.separated

ListView.separated可以在生成的列表项之间添加一个分割组件,它比ListView.builder多了一个separatorBuilder参数,该参数是一个分割组件生成器

4. 添加固定列表头

以上示例代码如下:

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import 'dart:ffi';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

int _pageSize = 20;
int _pageIndex = 1;
List<String> _dataList = [];

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '上拉下拉刷新',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('1111'),
),
body: InfiniteListView(),
);
}
}

class InfiniteListView extends StatefulWidget {
@override
_InfiniteListViewState createState() {
return _InfiniteListViewState();
}
}

class _InfiniteListViewState extends State<InfiniteListView> {
// 下拉刷新
void headerRefresh() {
_pageSize = 20;
_pageIndex = 1;
getData(true);
}

// 上拉加载
void footerRefresh() {
_pageIndex++;
getData(false);
}

// 获取数据
void getData(bool headerRefresh) {
// 模拟接口请求2秒延迟
Future.delayed(
Duration(seconds: 2),
).then((e) {
// 模拟数据,第3页只返回5条
List<String> currentPageList = [];
if (_pageIndex < 3) {
for (int i = ((_pageIndex - 1) * _pageSize);
i < (_pageIndex * _pageSize);
i++) {
currentPageList.add(i.toString());
}
} else if (_pageIndex == 3) {
currentPageList.add('第三页数据');
}

if (headerRefresh) {
_dataList = [];
} else {
_dataList.removeLast();
}
_dataList.addAll(currentPageList);

if (currentPageList.length == _pageSize) {
// 数据满一页
_dataList.add('###FooterRefreshTag###');
} else {
// 数据不满一页
_dataList.add('###DataNone###');
}

setState(() {});
});
}

@override
void initState() {
super.initState();

// 初始值
_dataList.add('###HeaderRefreshTag###');
}

@override
Widget build(BuildContext context) {
double _cellHeight = 50;

return Container(
color: Colors.grey[200],
child: Scrollbar(
child: ListView.separated(
itemBuilder: (context, index) {
if ((_dataList[index] == '###HeaderRefreshTag###') ||
(_dataList[index] == '###FooterRefreshTag###')) {
if (_dataList[index] == '###HeaderRefreshTag###') {
// 下拉刷新
headerRefresh();
} else {
// 上拉加载
footerRefresh();
}
// 显示loading的样式
return Container(
height: _cellHeight,
child: Center(
child: Container(
width: 25,
height: 25,
child: CircularProgressIndicator(
backgroundColor: Colors.grey,
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
),
),
);
} else if (_dataList[index] == '###DataNone###') {
return Container(
height: _cellHeight,
child: Center(
child: Text(
'没有更多数据了',
style: TextStyle(
color: Colors.grey,
),
),
),
);
} else {
return Container(
color: Colors.white,
height: _cellHeight,
// 约束布局
child: Container(
child: Flex(
direction: Axis.vertical,
children: <Widget>[
Expanded(
child: Container(
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(
left: 16,
top: 4,
right: 8,
),
child: Text(
'主标题' + index.toString(),
style: TextStyle(
fontSize: 20,
),
),
),
),
flex: 3,
),
Expanded(
child: Container(
width: double.infinity,
child: Padding(
padding: EdgeInsets.only(
left: 16,
),
child: Text(
'副标题',
style: TextStyle(
fontSize: 13,
),
),
),
),
flex: 2,
),
],
),
),
);
}
},
separatorBuilder: (context, index) {
return Divider(
color: Colors.grey,
height: 0.5,
);
},
itemCount: (_dataList.length),
),
),
);
}
}

4、GridView

暂不学习

5、CustomScrollView

假设有一个页面,顶部需要一个GridView,底部需要一个ListView,而要求整个页面的滑动效果是统一的,即它们看起来是一个整体。如果使用GridView+ListView来实现的话,就不能保证一致的滑动效果,因为它们的滚动效果是分离的,所以这时就需要一个“胶水”,把这些彼此独立的可滚动组件“粘”起来,而CustomScrollView的功能就相当于“胶水”

6、滚动监听及控制

未完待续