ListView Widget

ListView

  • ListView是一个视图组,显示可滚动项目的列表。并将每个项目结果转换为放置到列表中的视图
  • 搭一个基本的结构后加入ListView组件,在body代码处加入下面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
title:'JSPang Flutter Demo',
home:Scaffold(
appBar:new AppBar(
title:new Text('ListView Widget')
),
body: new Text('ListView Text')
),
);
}
}
1
2
3
4
5
6
7
8
body: new ListView(
children:<Widget>[
new ListTile(
leading:new Icon(Icons.access_time),
title:new Text('access_time')
)
]
),
  • 使用了ListView,然后在他的内部children中,使用了widget数组,因为是一个列表,所以它接受一个数组,然后有使用了listTite组件(列表瓦片),在组件中放置了图标和文字
  • 当然我们还可以多加入几行列表,比如我们再加入一行
1
2
3
4
5
6
7
8
9
10
11
12
body: new ListView(
children:<Widget>[
new ListTile(
leading:new Icon(Icons.access_time),
title:new Text('access_time')
),
new ListTile(
leading:new Icon(Icons.account_balance),
title:new Text('account_balance')
),
]
),
  • 也可以插入图片组成图片列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
body: new ListView(
children:<Widget>[
new Image.network(
'http://jspang.com/static/upload/20181111/G-wj-ZQuocWlYOHM6MT2Hbh5.jpg'
),
new Image.network(
'http://jspang.com/static/upload/20181109/1bHNoNGpZjyriCNcvqdKo3s6.jpg'
),
new Image.network(
'http://jspang.com/static/myimg/typescript_banner.jpg'
),new Image.network(
'http://jspang.com/static/myimg/smile-vue.jpg'
)

]
),

横向列表

  • 其实还是使用我们的ListView组件,只是在ListView组件里加一个ScrollDirection属性
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
import 'package:flutter/material.dart';
void main () => runApp(MyApp());

class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context ){
return MaterialApp(
title:'Text widget',
home:Scaffold(
body:Center(
child:Container(
height:200.0,
child:new ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
new Container(
width:180.0,
color: Colors.lightBlue,
), new Container(
width:180.0,
color: Colors.amber,
), new Container(
width:180.0,
color: Colors.deepOrange,
),new Container(
width:180.0,
color: Colors.deepPurpleAccent,
),
],
)
),
),
),
);
}
}
  • 我们先是加入了Center组件,作用是让我们的横向列表可以居中到屏幕的中间位置,然后在center组件的下面加入了Container容器组件,并设置了容器组件的高是200,在容器组件里我们加入了ListView组件,然后设置了组件的scrollDirection属性。然后再ListView的子组件里加入了Container容器组件,然后设置了不同颜色
  • ListView组件的scrollDirection属性只有两个值,一个是横向滚动,一个是纵向滚动。默认的就是垂直滚动,所以如果是垂直滚动,我们一般都不进行设置
    • Axis.horizontal:横向滚动或者叫水平方向滚动
    • Axis.vertical:纵向滚动或者叫垂直方向滚动
  • 现在把列表组件独立定义成一个类,然后我们再加入到主组件中。再工作中会把组件分的很细,这样既能很好的复用有便于维护,还有利于分工。我们声明一个MyList的类,然后把嵌套的代码放到这个类里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyList extends StatelessWidget{
@override
Widget build(BuildContext context){
return ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
new Container(
width:180.0,
color: Colors.lightBlue,
), new Container(
width:180.0,
color: Colors.amber,
), new Container(
width:180.0,
color: Colors.deepOrange,
),new Container(
width:180.0,
color: Colors.deepPurpleAccent,
),
],
);
}
}
  • 然后在MyAPP类里直接使用这个类,这样就减少了嵌套
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import 'package:flutter/material.dart';
void main () => runApp(MyApp());

class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context ){
return MaterialApp(
title:'ListView widget',
home:Scaffold(
body:Center(
child:Container(
height:200.0,
child:MyList()
),
),
),
);
}
}

动态列表

  • List是Dart的集合类型之一,其实你可以把它简单理解为数组(反正我是这么认为的),其他语言也都有这个类型
  • 这里使用的是一个List传递,然后直接用List中的generate方法进行生产List里的元素。最后的结果是生产了一个带值的List变量
1
2
3
void main () => runApp(MyApp(
items: new List<String>.generate(1000, (i)=> "Item $i")
));
  • 说明:在main函数的runApp中调用了MyApp类,再使用类的使用传递了一个items参数,并使用generate生成器对items进行赋值。generate方法传递两个参数,第一个参数是生成的个数,第二个是方法
  • 我们已经传递了参数,那MyApp这个类是需要接收的
1
2
3
4
5
final List<String> items;
MyApp({
Key key,
@required this.items
}):super(key:key);
  • 这是一个构造函数,除了Key,我们增加了一个必传参数,这里的@required意思就必传。:super如果父类没有无名无参数的默认构造函数,则子类必须手动调用一个父类构造函数
  • 这样我们就可以接收一个传递过来的参数了,当然我们要事先进行声明。接受了值之后,就可以直接调用动态列表进行生成了
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
import 'package:flutter/material.dart';
void main () => runApp(MyApp(
items: new List<String>.generate(1000, (i)=> "Item $i")
));

class MyApp extends StatelessWidget{

final List<String> items;
MyApp({Key? key, required this.items}):super(key:key);
@override
Widget build(BuildContext context ){
return MaterialApp(
title:'ListView widget',
home:Scaffold(
body:new ListView.builder(
itemCount:items.length,
itemBuilder:(context,index){
return new ListTile(
title:new Text('${items[index]}'),
);
}
)
),
);
}
}

GridView Widget

  • 网格列表经常用来显示多张图片,比如我们经常使用的手机里的相册功能,大部分形式都是网格列表
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
import 'package:flutter/material.dart';
void main () => runApp(MyApp());

class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context ){
return MaterialApp(
title:'ListView widget',
home:Scaffold(
body:GridView.count(
padding:const EdgeInsets.all(20.0),
crossAxisSpacing: 10.0,
crossAxisCount: 3,
children: <Widget>[
const Text('I am Jspang'),
const Text('I love Web'),
const Text('jspang.com'),
const Text('我喜欢玩游戏'),
const Text('我喜欢看书'),
const Text('我喜欢吃火锅')
],
)
),
);
}
}
  • 我们在body属性中加入了网格组件,然后给了一些常用属性
    • padding:表示内边距,这个小伙伴们应该很熟悉
    • crossAxisSpacing:网格间的空当,相当于每个网格之间的间距
    • crossAxisCount:网格的列数,相当于一行放置的网格数量

图片网格列表

  • childAspectRatio:宽高比,这个值的意思是宽是高的多少倍,如果宽是高的2倍,那我们就写2.0,如果高是宽的2倍,我们就写0.5
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
import 'package:flutter/material.dart';
void main () => runApp(MyApp());

class MyApp extends StatelessWidget{
@override
Widget build(BuildContext context ){
return MaterialApp(
title:'ListView widget',
home:Scaffold(
body:GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 2.0,
crossAxisSpacing: 2.0,
childAspectRatio: 0.7
),
children: <Widget>[
new Image.network('http://img5.mtime.cn/mt/2018/10/22/104316.77318635_180X260X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/10/10/112514.30587089_180X260X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/11/13/093605.61422332_180X260X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/11/07/092515.55805319_180X260X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/11/21/090246.16772408_135X190X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/11/17/162028.94879602_135X190X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/11/19/165350.52237320_135X190X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/11/16/115256.24365160_180X260X4.jpg',fit: BoxFit.cover),
new Image.network('http://img5.mtime.cn/mt/2018/11/20/141608.71613590_135X190X4.jpg',fit: BoxFit.cover),

],
)
),
);
}
}

Form Widget

FormField

  • FormField是一个表单控件,此控件包含表单的状态,方便更新UI,通常情况下,我们不会直接使用FormField,而是使用TextFormField
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
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("组件学习"),
elevation: 10.0,
centerTitle: true,
),
body: InputDemo(),
),
);
}
}

class InputDemo extends StatefulWidget {
@override
_InputDemoState createState() => _InputDemoState();
}

class _InputDemoState extends State<InputDemo> {
// 表单状态
GlobalKey _key = GlobalKey<FormState>();
TextEditingController _controller = TextEditingController();

// 生命周期
@override
void dispose(){
super.dispose();
_controller.dispose();
}

@override
Widget build(BuildContext context) {
return Form(
key: _key,
child: Column(
children: [
// 输入框
TextField(
// 保存值
controller: _controller,
// 装饰器
decoration: InputDecoration(
// icon: Icon(Icons.add),
prefixIcon: Icon(Icons.add), // 另一种风格的图标
labelText: "label", // 顶部的小提示
hintText: "default", // 默认文字
),
obscureText: true, // 为true 密码框,false普通输入框
),
SizedBox(height: 16), // 间隔
RaisedButton(
onPressed: (){
// _controller中保存了输入的内容
print(_controller.text);
},
child: Text("提交"),
color:Colors.blue
)
],
),
);
}
}

TextFormField

  • TextFormField继承自FormField,是一个输入框表单,因此TextFormField中有很多关于TextField的属性
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
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("组件学习"),
elevation: 10.0,
centerTitle: true,
),
body: InputDemo(),
),
);
}
}

class InputDemo extends StatefulWidget {
@override
_InputDemoState createState() => _InputDemoState();
}

class _InputDemoState extends State<InputDemo> {
// 表单状态
GlobalKey _key = GlobalKey<FormState>();
TextEditingController _username = TextEditingController();
TextEditingController _password = TextEditingController();

// 生命周期,退出时要关闭,防止占用内存
@override
void dispose(){
super.dispose();
_username.dispose();
_password.dispose();
}

@override
Widget build(BuildContext context) {
return Form(
key: _key,
child: Column(
children: [
TextFormField(
controller: _username,
decoration: InputDecoration(
prefixIcon: Icon(Icons.add),
labelText: 'label',
hintText: '请输入账号'
),
// 支持校验
validator: (v){
if(v==null || v.isEmpty){
return "账号必须输入";
}
},
),
SizedBox(height: 16), // 间隔
TextFormField(
controller: _password,
decoration: InputDecoration(
prefixIcon: Icon(Icons.add),
labelText: 'label',
hintText: '请输入密码'
),
// 支持校验
validator: (v){
if(v==null || v.length < 5){
return "密码必须输入且长度大于5";
}
},
textInputAction: TextInputAction.search,
obscureText: true,
),
SizedBox(height:16),
RaisedButton(
onPressed: (){
// 校验状态 true / false
print((_key.currentState as FormState).validate());
print('用户名:${_username.text} 密码:${_password.text}');
},
child: Text("提交"),
color:Colors.blue
)
],
),
);
}
}

输入框焦点

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
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("组件学习"),
elevation: 10.0,
centerTitle: true,
),
body: InputDemo(),
),
);
}
}

class InputDemo extends StatefulWidget {
@override
_InputDemoState createState() => _InputDemoState();
}

class _InputDemoState extends State<InputDemo> {
// 表单状态
GlobalKey _key = GlobalKey<FormState>();
TextEditingController _username = TextEditingController();
TextEditingController _password = TextEditingController();
FocusNode _u = FocusNode();
FocusNode _p = FocusNode();
// 焦点对象
FocusScopeNode _focusScopeNode = FocusScopeNode();

// 生命周期,退出时要关闭,防止占用内存
@override
void dispose(){
super.dispose();
_username.dispose();
_password.dispose();
_u.dispose();
_p.dispose();
if(_focusScopeNode != null) _focusScopeNode.dispose();
}

@override
Widget build(BuildContext context) {
return Form(
key: _key,
child: Column(
children: [
TextFormField(
autofocus: true, // 自动获取焦点
focusNode: _u,
controller: _username,
decoration: InputDecoration(
prefixIcon: Icon(Icons.add),
labelText: 'label',
hintText: '请输入账号'
),
// 支持校验
validator: (v){
if(v==null || v.isEmpty){
return "账号必须输入";
}
},
textInputAction: TextInputAction.next,
// 回车监听
onFieldSubmitted: (v){
print("回车");
},
),
SizedBox(height: 16), // 间隔
TextFormField(
focusNode: _p,
controller: _password,
decoration: InputDecoration(
prefixIcon: Icon(Icons.add),
labelText: 'label',
hintText: '请输入密码'
),
// 支持校验
validator: (v){
if(v==null || v.length < 5){
return "密码必须输入且长度大于5";
}
},
textInputAction: TextInputAction.send,
obscureText: true,
),
SizedBox(height:16),
RaisedButton(
onPressed: (){
if(_focusScopeNode == null){
_focusScopeNode = FocusScope.of(context);
}
_focusScopeNode.requestFocus(_u); // 使第一个输入框获取焦点
//_focusScopeNode.unfocus(); // 使所有焦点消失
// 校验状态 true / false
print((_key.currentState as FormState).validate());
},
child: Text("提交"),
color:Colors.blue
)
],
),
);
}
}

开关复选框

  • 开关复选框是有状态的,所以需要有状态的组件;状态的改变事件赋值一定要在setState(() {});
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
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("组件学习"),
elevation: 10.0,
centerTitle: true,
),
body:CheckDemo(),
),
);
}
}

class CheckDemo extends StatefulWidget {

@override
_CheckDemoState createState() => _CheckDemoState();
}

class _CheckDemoState extends State<CheckDemo> {
bool _check = false;
bool _switch = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
Checkbox(value: _check, onChanged: (v){
setState(() {
_check = v!;
});
}),
Switch(value: _switch, onChanged: (v){
setState(() {
_switch = v;
});
})
],
);
}
}

Other Widget

进度指示器

  • 进度指示器也就是加载效果
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
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("组件学习"),
elevation: 10.0,
centerTitle: true,
),
body: ProgressDome(),
),
);
}
}

class ProgressDome extends StatelessWidget {

@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child:Column(
children: [
LinearProgressIndicator(
value: .5,
valueColor: AlwaysStoppedAnimation(Colors.yellow), // 已完成的颜色值
),
SizedBox(height: 16),
// 加载效果
CircularProgressIndicator(
value: .5,
valueColor: AlwaysStoppedAnimation(Colors.red),
),
// 配合容器设置大小
Container(
width: 100.0,
height: 100.0,
child: CircularProgressIndicator()
),
SizedBox(height: 16),
// ios效果
CupertinoActivityIndicator()
],
)
);
}
}

点击事件

  • Flutter中除了少部分组件,如Button相关的组件可以直接通过onPressed实现点击事件。其余组件想实现点击、长按等事件,都需要借助GestureDetector来实现手势监听
  • 常用的手势如onTap(点击)、onDoubleTap(双击)、onLongPress(长按)
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
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 去掉右上角debug
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("组件学习"),
elevation: 10.0,
centerTitle: true,
),
body: ClickEventDome(),
),
);
}
}

class ClickEventDome extends StatelessWidget {

@override
Widget build(BuildContext context) {
// 组件
return GestureDetector(
// 点击事件
onTap: (){
print('tag');
},
// 双击事件
onDoubleTap: (){
print('doubleTap');
},
child: Text('data'),
);
}
}