Flutter中常用的滑动布局 ScrollView 有 SingleChildScrollView、NestedScrollView、CustomScrollView。
SingleChildScrollView 用来处理简单可滑动的页面布局视图,如一般的数据详情页面,当内容足够多时,一屏显示不下时,就需要滑动处理。
NestedScrollView 滑动组件是用来处理复杂情况下的滑动应用场景,如向上滑动视图时,要折叠隐藏一部分内容,这时候就需要使用到 NestedScrollView 与 SliverAppBar 的结合使用。
CustomScrollView 用来处理更为复杂的布局结合 SliverAppBar,SliverList和SliverGrid SliverPadding SliverToBoxAdapter SliverPersistentHeader, SliverFillRemaining,SliverFillViewport等来使用。
如一个详情页面中 即需要 GridView 来实现二维宫格效果,也需要 ListView 列表效果,如下图所示的图片效果,当使用 CustomScrollView 结合 SliverList 和SliverGrid 就可轻松实现,当然结合一下 SliverAppBar 也能实现折叠效果的头部布局,所以说 CustomScrollView 很强大。
在实际应用开发中,如果只是一个简单的适配页面滑动,建议码农使用 SingleChildScrollView 就可以。
本章重点介绍NestedScrollView:
NestedScrollView
可以在其内部嵌套其他滚动视图的组件,其滚动位置是固有链接的。
在普通的ScrollView
中, 如果有一个Sliver组件容纳了一个TabBarView
,它沿相反的方向滚动(例如,允许用户在标签所代表的页面之间水平滑动,而列表则垂直滚动),则该TabBarView
内部的任何列表都不会相互作用 与外部ScrollView
。例如,浏览内部列表以滚动到顶部不会导致外部ScrollView
中的SliverAppBar
折叠以展开。
滚动隐藏AppBar
_buildNestedScrollView(){
return NestedScrollView(
headerSliverBuilder: (BuildContext context,bool innerBoxIsScrolled){
return [
SliverAppBar(title: Text('导航测试'),)
];
},
body: ListView.builder(itemBuilder: (BuildContext context,int index){
return Container(
height: 120,
color: Colors.primaries[index%Colors.primaries.length],
alignment: Alignment.center,
child: Text(
'组合ListView $index',
style: TextStyle(color: Colors.white,fontSize: 30),
),
);
}));
}
效果:
上图 有个小bug。ListView和Appbar之间存在一个很大的间距,这个时候就需要MediaQuery去移除ListView的padding,才能取出间隔:代码如下:
_buildNestedScrollView(){
return NestedScrollView(
headerSliverBuilder: (BuildContext context,bool innerBoxIsScrolled){
return [
SliverAppBar(title: Text('导航测试'),)
];
},
body: MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(itemBuilder: (BuildContext context,int index){
return Container(
height: 120,
color: Colors.primaries[index%Colors.primaries.length],
alignment: Alignment.center,
child: Text(
'组合ListView $index',
style: TextStyle(color: Colors.white,fontSize: 30),
),
);
}))
);
}
效果如图:
SliverAppBar展开折叠
_buildNestedScrollView1(){
return NestedScrollView(
headerSliverBuilder: (BuildContext context,bool innerBoxIsScrolled){
return [
SliverAppBar(
expandedHeight: 230,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('复仇者联盟'),
background: Image.network(
'http://img.haote.com/upload/20180918/2018091815372344164.jpg',
fit: BoxFit.fitHeight,
),
),
)
];
},
body: ListView.builder(itemBuilder: (BuildContext context,int index){
print('create $index');
return Container(
height: 100,
color: Colors.primaries[index%Colors.primaries.length],
alignment: Alignment.center,
child: Text('$index 测试ListView',style: TextStyle(fontSize: 30),
),
);
}));
}
效果:
与TabBar配合使用
用法如下:
_buildNestedScrollViewTabBar() {
return NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return [
const SliverAppBar(
expandedHeight: 230,
pinned: true,
flexibleSpace: Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: WyPageView(),
),
),
SliverPersistentHeader(
pinned: true,
delegate: StickyTabBarDelegate(
TabBar(
labelColor: Colors.black,
controller: this._tabController,
tabs: [
const Tab(text: '资讯',),
const Tab(text: '技术',),
],
),
),
)
];
},
body: TabBarView(
controller: this._tabController,
children: [
RefreshIndicator(
child: _buildTabNewsList('----资讯类----'),
onRefresh: _handelRefresh,
),
_buildTabNewsList('----技术类----'),
])
);
}
//Refresh异步刷新方法
Future<Null >_handelRefresh()async{
print('加载数据');
return null;
}
//构建newstlist列表
_buildTabNewsList(String name) {
return ListView.separated(itemBuilder: (context, int index) {
return Column(
children: [
Text('$name $index 通过scrollDirection和reverse参数控制其滚动方向,用法如下:',
style: TextStyle(fontSize: 18),),
Text(
'作者 csdn账号 ', style: TextStyle(fontSize: 12, color: Colors.grey),),
],
);
},
separatorBuilder: (context, index) => Divider(),
itemCount: 50);
}
}
//StickyTabBarDelegate 代码如下:
class StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
final TabBar child;
StickyTabBarDelegate(this.child);
@override
Widget build(BuildContext context, double shrinkOffset,
bool overlapsContent) {
// TODO: implement build
return Container(
color: Theme
.of(context)
.backgroundColor,
child: this.child,
);
}
@override
// TODO: implement maxExtent
double get maxExtent => this.child.preferredSize.height;
@override
// TODO: implement minExtent
double get minExtent => this.child.preferredSize.height;
@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
// TODO: implement shouldRebuild
throw true;
}
}
效果:
controller
为滚动控制器,可以监听滚到的位置,设置滚动的位置等,用法如下:
_scrollController = ScrollController();
//监听滚动位置
_scrollController.addListener((){
print('${_scrollController.position}');
});
//滚动到指定位置
_scrollController.animateTo(20.0);
CustomScrollView(
controller: _scrollController,
...
)
physics
表示可滚动组件的物理滚动特性,系统提供的ScrollPhysics有:
-
AlwaysScrollableScrollPhysics:总是可以滑动
-
NeverScrollableScrollPhysics:禁止滚动
-
BouncingScrollPhysics :内容超过一屏 上拉有回弹效果
-
ClampingScrollPhysics :包裹内容 不会有回弹