Flutter如何实现滑动头部折叠动画效果示例

Android和iOS中都有类似的滑动折叠效果,Flutter官方也提供了NestedScrollView控件来实现类似的效果,但是因为Flutter的一些特性

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];
 
 @override
 String 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;
 
 @override
 Widget 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';
 
 @override
 Widget 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[index];
    return new Padding(
     padding: const EdgeInsets.symmetric(
     vertical: 8.0,
     ),
     child: new _CardDataItem(
     page: page,
     data: data,
     ),
    );
    },
    childCount: _allPages.length,
    ),
    ),
   ),
   ],
   );
   },
  ),
  );
  }).toList(),
  ),
 ),
 ),
 );
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持好代码网。