目录
- SingleChildScrollView(可滑动 View)
- ListView(列表 View)
- GridView(网格 View)
- CustomScrollView(自定义滑动 View)
- ScrollController(控制器)
SingleChildScrollView(可滑动 View)
SingleChildScrollView 类似 Android 中的 scrollview ,且同样的只可包含有一个子元素
const SingleChildScrollView({
Key key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.padding,
bool primary,
this.physics,
this.controller,
this.child,
this.dragStartBehavior = DragStartBehavior.down,
}) : assert(scrollDirection != null),
assert(dragStartBehavior != null),
assert(!(controller != null && primary == true),
'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
'You cannot both set primary to true and pass an explicit controller.'
),
primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
super(key: key);
- key:当前元素的唯一标识符(类似于 Android 中的 id)
- scrollDirection:滚动方向,默认是垂直
- reverse:是否按照阅读方向相反的方向滑动。
- padding:填充距离
- primary:是否使用 widget 树中默认的 PrimaryScrollController 。当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且controller没有指定时,primary默认为true
-
physics:此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK中包含了两个ScrollPhysics的子类可以直接使用:
ClampingScrollPhysics→Android下微光效果 / BouncingScrollPhysics→iOS下弹性效果 - controller:此属性接受一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件
- child:子元素
import 'package:flutter/material.dart';
/**
* @des Scroll Widget
* @author liyongli 20190506
* */
class SingleChildScrollViewWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() => new _StackState();
}
/**
* @des Scroll Widget State
* @author liyongli 20190506
* */
class _StackState extends State<SingleChildScrollViewWidget>{
String numberStr = "12345678909876543210123456789";
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Scroll Widget"),
),
body: Scrollbar(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Center(
child: Row(
children: numberStr.split("").map((c) => Text(c, textScaleFactor: 2.0,)).toList(),
),
),
),
)
),
);
}
}
import 'package:flutter/material.dart';
/**
* @des Scroll Widget
* @author liyongli 20190506
* */
class SingleChildScrollViewWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() => new _StackState();
}
/**
* @des Scroll Widget State
* @author liyongli 20190506
* */
class _StackState extends State<SingleChildScrollViewWidget>{
String numberStr = "1234567890987654321";
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Scroll Widget"),
),
body: Scrollbar(
child: SingleChildScrollView(
child: Center(
child: Column(
children: numberStr.split("").map((c) => Text(c, textScaleFactor: 2.0,)).toList(),
),
),
),
)
),
);
}
}
ListView(列表 View)
ListView 可以构建一个列表视图
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
}) : childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount ?? children.length,
dragStartBehavior: dragStartBehavior,
);
- key:当前元素的唯一标识符(类似于 Android 中的 id)
- scrollDirection:滚动方向,默认是垂直
- reverse:是否按照阅读方向相反的方向滑动。
- controller:控制器对象,主要作用是控制滚动位置和监听滚动事件
- primary:是否使用 widget 树中默认的 PrimaryScrollController 。当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且controller没有指定时,primary默认为true
-
physics:此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK中包含了两个ScrollPhysics的子类可以直接使用:
ClampingScrollPhysics→Android下微光效果 / BouncingScrollPhysics→iOS下弹性效果 - shrinkWrap:表示是否根据子 widget 的总长度设置 listview 的长度,默认为 false。
- padding:填充距离
- itemExtent:强制 listview 的 children 的长度 为 itemExtent 的值。指定 itemExtent 的值比让子元素决定自身长度在绘制时更高效,特别是在滚动位置频繁变化的状态下,因为设置 itemExtent 可以让滚动系统提前知道列表的长度。
- addAutomaticKeepAlives:表示是否将列表项包裹在 AutomaticKeepAlive widget 中。(在懒加载时,如果设置了包裹那么在此列表项滑出屏幕外时不会被GC。如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
- addRepaintBoundaries:表示是否将列表项包裹在 RepaintBoundary 中。(当选择将列表项包裹在 RepaintBoundary 时,在滚动过程中可以避免重绘,如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
- addSemanticIndexes:表示是否给子元素添加索引,默认为 true
- cacheExtent:设置预加载的区域,范围在窗口可见范围之前与之后。如果设置为 0.0,表示关闭预加载
- children:列表项集合
- semanticChildCount:提供语义信息的孩子的数量
item 数量固定的 listview 示例
listview 构造方法中的参数 children 表示子列表集,使用这种方式构建列表需要我们提前准备好子 widget 集合。这种方式只适合实现少量且数量固定的列表展示需求
import 'package:flutter/material.dart';
/**
* @des Listview Widget
* @author liyongli 20190506
* */
class ListViewWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new _ListViewWigetState();
}
}
/**
* @des Listview Widget State
* @author liyongli 20190506
* */
class _ListViewWigetState extends State<ListViewWidget>{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("Scroll Widget"),
),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
const Text("1111111111111111111111111111111111", style: TextStyle(color: Colors.blue)),
const Text("2222222222222222222222222222222222", style: TextStyle(color: Colors.blue)),
const Text("3333333333333333333333333333333333", style: TextStyle(color: Colors.blue)),
const Text("4444444444444444444444444444444444", style: TextStyle(color: Colors.blue)),
const Text("5555555555555555555555555555555555", style: TextStyle(color: Colors.blue)),
const Text("6666666666666666666666666666666666", style: TextStyle(color: Colors.blue)),
const Text("7777777777777777777777777777777777", style: TextStyle(color: Colors.blue)),
const Text("8888888888888888888888888888888888", style: TextStyle(color: Colors.blue)),
],
),
);
}
}
ListView.builder
当 listview 的列表项较多或数量未知时,就需要使用 ListView.builder 来构建列表了
import 'package:flutter/material.dart';
/**
* @des Listview.builder Widget
* @author liyongli 20190506
* */
class ListViewBuilderWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new _ListViewBuilderWidget ();
}
}
/**
* @des Listview.builder Widget State
* @author liyongli 20190506
* */
class _ListViewBuilderWidget extends State<ListViewBuilderWidget>{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("ListviewBuilder Widget"),
),
body: ListView.builder(
itemCount: 100,
itemExtent: 50.0, //强制高度为50.0
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(" $index - "));
}
),
);
}
}
ListView.separated
当 listview 的 item 间需要分割线时,我们就需要用到 ListView.separated
import 'package:flutter/material.dart';
/**
* @des Listview.builder Widget
* @author liyongli 20190506
* */
class ListViewBuilderWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new _ListViewBuilderWidget ();
}
}
/**
* @des Listview.builder Widget State
* @author liyongli 20190506
* */
class _ListViewBuilderWidget extends State<ListViewBuilderWidget>{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("ListviewBuilder Widget"),
),
body: ListView.separated(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(" $index - ", style: TextStyle(color: Colors.blue),));
},
separatorBuilder: (BuildContext context, int index) {
return Divider(color: Colors.blue, height: 10,);
}
),
);
}
}
ListView 分页加载
工程 yaml 文件中要添加 english_words 的依赖
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
english_words: ^3.1.0
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
/**
* @des Listview Widget
* @author liyongli 20190507
* */
class InfiniteListView extends StatefulWidget {
@override
_InfiniteListViewState createState() => new _InfiniteListViewState();
}
/**
* @des Listview Widget State
* @author liyongli 20190507
* */
class _InfiniteListViewState extends State<InfiniteListView> {
static const loadingTag = "##loading##"; //表尾标记
var _words = <String>[loadingTag];
@override
void initState() {
_retrieveData();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("listview 分页加载"),
),
body: ListView.separated(
itemCount: _words.length,
itemBuilder: (context, index) {
//如果到了表尾
if (_words[index] == loadingTag) {
//不足100条,继续获取数据
if (_words.length - 1 < 100) {
//获取数据
_retrieveData();
//加载时显示loading
return Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(strokeWidth: 2.0)
),
);
} else {
//已经加载了100条数据,不再获取数据。
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Text("没有更多了", style: TextStyle(color: Colors.grey),)
);
}
}
//显示单词列表项
return ListTile(title: Text(_words[index], style: TextStyle(color: Colors.blue),));
},
separatorBuilder: (context, index) => Divider(height: 1, color: Colors.blue,),
),
),
);
}
void _retrieveData() {
Future.delayed(Duration(seconds: 2)).then((e) {
_words.insertAll(_words.length - 1,
//每次生成20个单词
generateWordPairs().take(20).map((e) => e.asPascalCase).toList()
);
setState(() {
//重新构建列表
});
});
}
}
GridView(网格 View)
GridView 可以构建一个网格列表视图
GridView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
}) : assert(gridDelegate != null),
childrenDelegate = SliverChildBuilderDelegate(
itemBuilder,
childCount: itemCount,
addAutomaticKeepAlives: addAutomaticKeepAlives,
addRepaintBoundaries: addRepaintBoundaries,
addSemanticIndexes: addSemanticIndexes,
),
super(
key: key,
scrollDirection: scrollDirection,
reverse: reverse,
controller: controller,
primary: primary,
physics: physics,
shrinkWrap: shrinkWrap,
padding: padding,
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount ?? itemCount,
);
- key:当前元素的唯一标识符(类似于 Android 中的 id)
- scrollDirection:滚动方向,默认是垂直
- reverse:是否按照阅读方向相反的方向滑动。
- controller:控制器对象,主要作用是控制滚动位置和监听滚动事件
- primary:是否使用 widget 树中默认的 PrimaryScrollController 。当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且controller没有指定时,primary默认为true
-
physics:此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK中包含了两个ScrollPhysics的子类可以直接使用:
ClampingScrollPhysics→Android下微光效果 / BouncingScrollPhysics→iOS下弹性效果 - shrinkWrap:表示是否根据子 widget 的总长度设置 listview 的长度,默认为 false。
- padding:填充距离
- itemCount:子元素数量
- addAutomaticKeepAlives:表示是否将列表项包裹在 AutomaticKeepAlive widget 中。(在懒加载时,如果设置了包裹那么在此列表项滑出屏幕外时不会被GC。如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
- addRepaintBoundaries:表示是否将列表项包裹在 RepaintBoundary 中。(当选择将列表项包裹在 RepaintBoundary 时,在滚动过程中可以避免重绘,如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
- addSemanticIndexes:表示是否给子元素添加索引,默认为 true
- cacheExtent:设置预加载的区域,范围在窗口可见范围之前与之后。如果设置为 0.0,表示关闭预加载
- semanticChildCount:提供语义信息的孩子的数量
GridView 固定列数
import 'package:flutter/material.dart';
/**
* @des GridView 固定列数
* @author liyongli 20190508
* */
class GridViewTest extends StatefulWidget{
@override
State<StatefulWidget> createState() => new _GridViewState();
}
/**
* @des GridView Widget State
* @author liyongli 20190508
* */
class _GridViewState extends State<GridViewTest>{
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("GridView 固定列数"),
),
body: GridView.count(
crossAxisCount: 4,
childAspectRatio: 0.7,
children: <Widget>[
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
],
),
),
);
}
}
import 'package:flutter/material.dart';
/**
* @des GridView 等分宽度
* @author liyongli 20190508
* */
class GridViewTest extends StatefulWidget{
@override
State<StatefulWidget> createState() => new _GridViewState();
}
/**
* @des GridView Widget State
* @author liyongli 20190508
* */
class _GridViewState extends State<GridViewTest>{
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("GridView 等分宽度"),
),
body: GridView.extent(
maxCrossAxisExtent: 200,
childAspectRatio: 1.0,
children: <Widget>[
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
Icon(Icons.add_a_photo),
],
),
),
);
}
}
GridView 分页加载
import 'package:flutter/material.dart';
/**
* @des GridView 分页加载
* @author liyongli 20190508
* */
class GridViewTest extends StatefulWidget{
@override
State<StatefulWidget> createState() => new _GridViewState();
}
/**
* @des GridView Widget State
* @author liyongli 20190508
* */
class _GridViewState extends State<GridViewTest>{
List<IconData> _iconList = [];
@override
void initState() {
_getData();
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("GridView 分页加载"),
),
body: GridView.builder(
itemCount: _iconList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 5, childAspectRatio: 1.0),
itemBuilder: (context, index){
if(index == _iconList.length - 1 && _iconList.length < 200){
_getData();
}
return Icon(_iconList[index]);
}
)
),
);
}
// 请求数据
void _getData(){
Future.delayed(Duration(milliseconds: 200)).then((e) {
setState(() {
_iconList.addAll([
Icons.add_a_photo,
Icons.add_a_photo,
Icons.add_a_photo,
Icons.add_a_photo, Icons.cake,
Icons.add_a_photo
]);
});
});
}
}
CustomScrollView(自定义滑动 View)
Sliver
Sliver 是分片、分区的意思。当我们需要将不同的可滑动组件组合在一起时,就需要使用此对象来完成。ListView 和 GridView 都有对应的组合对象如:SliverList 和 SliverGrid。
import 'package:flutter/material.dart';
/**
* @des CustomScrollView Widget
* @author liyongli 20190509
* */
class CustomScrollViewTest extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Material(
child: CustomScrollView(
slivers: <Widget>[
// 跟随页面滑动的导航栏
SliverAppBar(
pinned: true, // 是否固定
expandedHeight: 200.0, // 高度
flexibleSpace: FlexibleSpaceBar(
title: Text("title"),
background: Image.asset("images/custom_scroll_title.png", fit: BoxFit.cover,)
),
),
// Grid
SliverPadding(
padding: EdgeInsets.all(10.0),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 4行
mainAxisSpacing: 20.0,
crossAxisSpacing: 10.0,
childAspectRatio: 5.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index){
return Container(
alignment: Alignment.center,
color: Colors.blue,
child: Text("grid Item $index", style: TextStyle(color: Colors.white),),
);
},
childCount: 12
),
),
),
// List
SliverFixedExtentList(
itemExtent: 20,
delegate: SliverChildBuilderDelegate((BuildContext context , int index){
return Container(
alignment: Alignment.center,
color: Colors.blue,
child: Text("list Item $index", style: TextStyle(color: Colors.white),),
);
},
childCount: 30
),
),
],
),
);
}
}
ScrollController(控制器)
可设置滑动 View 的滚动位置,还可监听并获取滑动 View 的滚动状态及数据
ScrollController({
double initialScrollOffset = 0.0,
this.keepScrollOffset = true,
this.debugLabel,
}) : assert(initialScrollOffset != null),
assert(keepScrollOffset != null),
_initialScrollOffset = initialScrollOffset;
- initialScrollOffset:初始位置
- keepScrollOffset:是否保存滚动位置
- ScrollController.jumpTo(0.0):直接滚动至指定位置
- ScrollController.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.decelerate):带动画滚动至指定位置
import 'package:flutter/material.dart';
/**
* des ScrollController Test
* @author liyongli 20190513
* */
class ScrollControllerTest extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new _ScrollControllerTestState();
}
}
/**
* des ScrollController Test State
* @author liyongli 20190513
* */
class _ScrollControllerTestState extends State<ScrollControllerTest>{
ScrollController _controller = new ScrollController();
double oldOffset = -1;
@override
void initState() {
super.initState();
_controller.addListener((){
print("$_controller.offset" + " / " + "$oldOffset");
if(oldOffset > _controller.offset){
print("向下滑");
}else{
print("向上滑");
}
oldOffset = _controller.offset;
});
}
@override
void dispose() {
super.dispose();
_controller.dispose(); // 释放资源
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("ScrollController"),
),
body: Scrollbar(
child: ListView.builder(
controller: _controller, // 控制器
itemCount: 50, // item count
itemExtent: 100.0, // item height
itemBuilder: (context, index){
return ListTile(title:Text("$index", style: TextStyle(color: Colors.blue),));
}
)
),
floatingActionButton: FloatingActionButton(
onPressed: _up,
child: Icon(Icons.arrow_upward)
),
),
);
}
// 滚动
void _up(){
// 带动画滚动
_controller.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.decelerate);
// 无动画滚动
// _controller.jumpTo(0.0);
}
}
本篇到此完结,更多 Flutter 跨平台移动端开发 原创内容持续更新中~
期待您 关注 / 点赞 / 收藏 向着 大前端工程师 晋级!