美文网首页android进阶
Flutter:实现一个滑动头部折叠的动画效果

Flutter:实现一个滑动头部折叠的动画效果

作者: 吉原拉面 | 来源:发表于2018-08-17 12:56 被阅读378次

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

    相关文章

      网友评论

      本文标题:Flutter:实现一个滑动头部折叠的动画效果

      本文链接:https://www.haomeiwen.com/subject/hdbzbftx.html