更新:关于Slivers的具体用法,请看这篇文章:Flutter:Slivers大家族,让滑动视图的组合变得很简单!
本文写的只是SliverAppBar的用法,实际上使用Slivers可以实现多种折叠效果。
Android和iOS中都有类似的滑动折叠效果,Flutter官方也提供了NestedScrollView控件来实现类似的效果,但是因为Flutter的一些特性,布局容易出现溢出,这些坑需要自己处理。
先上效果图:
头部折叠.gif
效果实现是基于Google的gallery demo中的tabs_demo来实现的,主要是通过NestedScrollView控件来实现。
头部为一个SliverAppBar,折叠部分的内容都放在了flexibleSpace中。
全部代码如下:
import 'package:flutter/material.dart';// Each TabBarView contains a _Page and for each _Page there is a list
// of _CardData objects. Each _CardData object is displayed by a _CardItem.const String _kGalleryAssetsPackage = 'flutter_gallery_assets';class _Page {_Page({this.label});final String label;String get id => label[0];@overrideString toString() => '$runtimeType("$label")';
}class _CardData {const _CardData({this.title, this.imageAsset, this.imageAssetPackage});final String title;final String imageAsset;final String imageAssetPackage;
}final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{new _Page(label: 'LEFT'): <_CardData>[const _CardData(title: 'Vintage Bluetooth Radio',imageAsset: 'shrine/products/radio.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Sunglasses',imageAsset: 'shrine/products/sunnies.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Clock',imageAsset: 'shrine/products/clock.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Red popsicle',imageAsset: 'shrine/products/popsicle.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Folding Chair',imageAsset: 'shrine/products/lawn_chair.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Green comfort chair',imageAsset: 'shrine/products/chair.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Old Binoculars',imageAsset: 'shrine/products/binoculars.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Teapot',imageAsset: 'shrine/products/teapot.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Blue suede shoes',imageAsset: 'shrine/products/chucks.png',imageAssetPackage: _kGalleryAssetsPackage,),],new _Page(label: 'RIGHT'): <_CardData>[const _CardData(title: 'Beachball',imageAsset: 'shrine/products/beachball.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Dipped Brush',imageAsset: 'shrine/products/brush.png',imageAssetPackage: _kGalleryAssetsPackage,),const _CardData(title: 'Perfect Goldfish Bowl',imageAsset: 'shrine/products/fish_bowl.png',imageAssetPackage: _kGalleryAssetsPackage,),],
};class _CardDataItem extends StatelessWidget {const _CardDataItem({this.page, this.data});static const double height = 272.0;final _Page page;final _CardData data;@overrideWidget build(BuildContext context) {return new Card(child: new Padding(padding: const EdgeInsets.all(16.0),child: new Column(crossAxisAlignment: CrossAxisAlignment.stretch,mainAxisAlignment: MainAxisAlignment.start,children: <Widget>[new Align(alignment:page.id == 'L' ? Alignment.centerLeft : Alignment.centerRight,child: new CircleAvatar(child: new Text('${page.id}')),),new SizedBox(width: 144.0,height: 144.0,child: new Image.asset(data.imageAsset,package: data.imageAssetPackage,fit: BoxFit.contain,),),new Center(child: new Text(data.title,style: Theme.of(context).textTheme.title,),),],),),);}
}class TabsDemo extends StatelessWidget {static const String routeName = '/material/tabs';@overrideWidget build(BuildContext context) {return new DefaultTabController(length: _allPages.length,child: new Scaffold(body: new NestedScrollView(headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {return <Widget>[new SliverOverlapAbsorber(handle:NestedScrollView.sliverOverlapAbsorberHandleFor(context),child: new SliverAppBar(pinned: true,expandedHeight: 300.0,// 这个高度必须比flexibleSpace高度大forceElevated: innerBoxIsScrolled,bottom: PreferredSize(child: new Container(child: new TabBar(tabs: _allPages.keys.map((_Page page) => new Tab(child: new Tab(text: page.label),),).toList(),),color: Colors.redAccent[200],),preferredSize: new Size(double.infinity, 46.0)),// 46.0为TabBar的高度,也就是tabs.dart中的_kTabHeight值,因为flutter不支持反射所以暂时没法通过代码获取flexibleSpace: new Container(child: new Column(children: <Widget>[new AppBar(title: Text("this is title"),),new Expanded(child: new Container(child: Image.asset("images/test.jpg",repeat: ImageRepeat.repeat,),width: double.infinity,),)],),),),),];},body: new TabBarView(children: _allPages.keys.map((_Page page) {return new SafeArea(top: false,bottom: false,child: new Builder(builder: (BuildContext context) {return new CustomScrollView(key: new PageStorageKey<_Page>(page),slivers: <Widget>[new SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),),new SliverPadding(padding: const EdgeInsets.symmetric(vertical: 8.0,horizontal: 16.0,),sliver: new SliverFixedExtentList(itemExtent: _CardDataItem.height,delegate: new SliverChildBuilderDelegate((BuildContext context, int index) {final _CardData data = _allPages[page][index];return new Padding(padding: const EdgeInsets.symmetric(vertical: 8.0,),child: new _CardDataItem(page: page,data: data,),);},childCount: _allPages[page].length,),),),],);},),);}).toList(),),),),);}
}