可滚动组件
我们在开发过程中,如果组件内容超过当前显示视口时,不做特殊处理的话Flutter会提示Overflow错误,这个时候我们就需要使用滑动组件.
Scrollbar是一个Material风格的滚动指示器(滚动条),如果要给可滚动组件添加滚动条,只需将Scrollbar作为可滚动组件的任意一个父级组件即可,通过child为其添加子控件
SingleChildScrollView
SingleChildScrollView类似于Android中的ScrollView,它只能接收一个子组件。一般是某个控件非常大的时候我们需要用到这个,但是不能超过屏幕太多,主要属性有
scrollDirection:滚动方向,默认是垂直方向,Axis.verticalreverse:是否反向
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Scrollbar(
child: SingleChildScrollView(
child: Container(
height: 2000,
alignment: Alignment.center,
color: Colors.yellow,
child: Text("Hello World"),
),
),
));
}
}
我们改下方向
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Scrollbar(
child: SingleChildScrollView(
///滚动方向
scrollDirection: Axis.horizontal,
child: Container(
width: 2000,
height: 200,
alignment: Alignment.center,
color: Colors.yellow,
child: Text("Hello World"),
),
),
));
}
}
ListView
基础使用
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
//item个数
itemCount: 50,
//item的宽度或者高度,取决于滑动方向
itemExtent: 50,
//item的构建器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}));
}
}
添加固定的头
如果我们想在上卖弄加一个固定的头部怎么做呢?
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
ListTile(
title: Text("固定头部"),
),
ListView.builder(
//item个数
itemCount: 50,
//item的宽度或者高度,取决于滑动方向
itemExtent: 50,
//item的构建器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
})
],
));
}
}
这样写之后我们发现运行会报错
Error caught by rendering library, thrown during performResize()。 Vertical viewport was given unbounded height ...
大概的意思就是需要我们指定个高度,但是我们想让整个列表撑满屏幕,怎么弄?一个就是动态算,去除状态栏、导航栏、ListTile高度,另一个就是使用Flex,推荐后面这种
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
ListTile(
title: Text("固定头部"),
),
Expanded(
child: ListView.builder(
//item个数
itemCount: 50,
//item的宽度或者高度,取决于滑动方向
itemExtent: 50,
//item的构建器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
})),
],
));
}
}
添加分割线
这里就需要使用ListView.separated
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
//下划线widget预定义以供复用。
Widget divider1 = Divider(color: Colors.blue);
Widget divider2 = Divider(color: Colors.red);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
ListTile(
title: Text("固定头部"),
),
Expanded(
child: ListView.separated(
//item个数
itemCount: 50,
//item的构建器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
//分割器构造器
separatorBuilder: (BuildContext context, int index) {
return index % 2 == 0 ? divider1 : divider2;
},
)),
],
));
}
}
GridView
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: GridView(
//控制子widget
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//横轴子元素的数量
crossAxisCount: 3,
//子元素在横轴长度和主轴长度的比例
childAspectRatio: 1,
//横轴方向子元素的间距
crossAxisSpacing: 20,
//主轴方向的间距
mainAxisSpacing: 20,
),
children: [
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("1"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("2"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("3"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("4"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("5"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("6"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("7"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("8"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("9"),
),
],
),
);
}
}
上面的可用GridView.count代替
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: GridView.count(
//横轴子元素的数量
crossAxisCount: 3,
//子元素在横轴长度和主轴长度的比例
childAspectRatio: 1,
//横轴方向子元素的间距
crossAxisSpacing: 20,
//主轴方向的间距
mainAxisSpacing: 20,
children: [
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("1"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("2"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("3"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("4"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("5"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("6"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("7"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("8"),
),
Container(
alignment: Alignment.center,
color: Colors.red,
child: Text("9"),
),
],
),
);
}
}
CustomScrollView
如果我们想一个页面存在多个可滑动的组件,并且滑动效果统一,就需要用过CustomScrollView实现
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: CustomScrollView(
slivers: [
//grid
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return new Container(
alignment: Alignment.center,
color: Colors.blue,
child: new Text('grid item $index'),
);
},
childCount: 20,
),
),
//List
SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//创建列表项
return new Container(
alignment: Alignment.center,
color: Colors.lightGreenAccent,
child: new Text('list item $index'),
);
},
childCount: 50,
),
),
],
),
);
}
}
滑动监听
使用ScrollController
offset:可滚动组件当前的滚动位置。jumpTo(double offset)、animateTo(double offset,...):这两个方法用于跳转到指定的位置,它们不同之处在于,后者在跳转时会执行一个动画,而前者不会。
class _MyHomePageState extends State<MyHomePage> {
ScrollController _scrollController = ScrollController();
//是否显示FloatButton
bool showFloatButton = false;
@override
void initState() {
super.initState();
_scrollController.addListener(() {
print(_scrollController.offset);
//判断滑动距离
if (_scrollController.offset < 200 && showFloatButton) {
setState(() {
showFloatButton = false;
});
} else if (_scrollController.offset >= 200 && showFloatButton == false) {
setState(() {
showFloatButton = true;
});
}
});
}
@override
void dispose() {
//为了避免内存泄露,需要调用_controller.dispose
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
controller: _scrollController,
//item个数
itemCount: 50,
//item的宽度或者高度,取决于滑动方向
itemExtent: 50,
//item的构建器
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}),
floatingActionButton: !showFloatButton
? null
: FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: () {
//返回到顶部时执行动画
_scrollController.animateTo(0.0,
duration: Duration(milliseconds: 200), curve: Curves.ease);
},
),
);
}
}