滚动卡片效果

标签: 编程学习 Flutter学习

要做一个滚动的卡片效果,当前显示的卡片稍微大一些,不是当前的卡片缩小一部分,效果如下

首先实现滑动卡片的效果,我这里使用的是 PageView,很容易就能实现切换卡片的效果

PageView.builder(
    controller: _pageController,
    itemCount: _getTab(context).length,
    itemBuilder: (context, index) {
      return Card(
        cardData: _getCardData(context, index),
        pageController: _pageController,
      );
    },
);

这里使用 PageView 也可以很容易的实现横向不占满的效果,只需要给 PageController 设置 viewportFraction 属性即可

_pageController = PageController(initialPage: 0, viewportFraction: 0.85);

实现当前卡片稍大的效果首先需要获取拖动卡片的事件(NotificationListener),然后计算滑动的偏移

double _calculateCardPosition(int index) {
    final viewFraction = this.viewPortFraction ?? 1.0;
    final itemWidth = (this.scrollMetrics?.viewportDimension ?? 1.0) * viewFraction;
    final scrollX = this.scrollMetrics?.pixels ?? 0.0;
    final pageX = itemWidth * index;
    var position = (scrollX - pageX) / itemWidth;

    //计算偏移
    if(position > 1.0){
      position = 1.0;
    } else if(position < -1.0){
      position = 1.0;
    }

    //放大比例
    position = (1 - position.abs()) * (1 - 0.9);
    position = 0.9 + position;
    return position;
}

最终根据计算出的比例更改卡片的缩放比例,我这里添加了动画效果

Transform.scale(
  scale: this.visibility.position,
  child: Stack(
    fit: StackFit.expand,
    children: <Widget>[
      Container(
        child: Image.asset('assets/images/member_bg.png',
          fit: BoxFit.fill,
        ),
      ),

      Positioned(
        top: 80,
        left: 0,
        right: 0,
        child: Container(
          alignment: Alignment.center,
          child: Text(cardData.title,
            style: TextStyle(
              color: Color(0xFF333333),
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ),
    ],
  ),
);

来看一下最终的代码

/// CardData
class CardData {
  final int index;  //索引
  final String title;  //标题

  CardData({this.index, this.title});
}

/// CardVisibilityResolver
class CardVisibilityResolver {
  final ScrollMetrics scrollMetrics;
  final double viewPortFraction;
  CardVisibilityResolver({this.scrollMetrics, this.viewPortFraction});

  double _calculateCardPosition(int index) {
    final viewFraction = this.viewPortFraction ?? 1.0;
    final itemWidth = (this.scrollMetrics?.viewportDimension ?? 1.0) * viewFraction;
    final scrollX = this.scrollMetrics?.pixels ?? 0.0;
    final pageX = itemWidth * index;
    var position = (scrollX - pageX) / itemWidth;

    if(position > 1.0){
      position = 1.0;
    } else if(position < -1.0){
      position = 1.0;
    }

    position = (1 - position.abs()) * (1 - 0.9);
    position = 0.9 + position;
    return position;
  }

  CardVisibility resolveCardVisibility(int index) {
    final position = _calculateCardPosition(index);
    return CardVisibility(position: position);
  }
}

/// CardTransformer
typedef PageView CardPageViewBuilder(BuildContext context, CardVisibilityResolver visibilityResolver);

class CardTransformer extends StatefulWidget {
  final CardPageViewBuilder pageViewBuilder;
  CardTransformer({this.pageViewBuilder});

  @override
  _CardTransformerState createState() => _CardTransformerState();
}

class _CardTransformerState extends State<CardTransformer> {
  CardVisibilityResolver _visibilityResolver;

  @override
  Widget build(BuildContext context) {
    final pageView = this.widget.pageViewBuilder(
      context,
      _visibilityResolver ?? CardVisibilityResolver(),
    );

    return NotificationListener<ScrollNotification>(
      child: pageView,
      onNotification: (ScrollNotification notification) {
        setState(() {
          _visibilityResolver = CardVisibilityResolver(
            scrollMetrics: notification.metrics,
            viewPortFraction: pageView.controller.viewportFraction,
          );
        });
        return null;
      },
    );
  }
}

/// Card
class Card extends StatelessWidget {
  final CardData cardData;
  final PageController pageController;
  final CardVisibility visibility;
  Card({this.cardData, this.pageController, this.visibility});

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: this.pageController,
      builder: (BuildContext c, Widget w) {
        return Transform.scale(
          scale: this.visibility.position,
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              Container(
                child: Image.asset('assets/images/member_bg.png',
                  fit: BoxFit.fill,
                ),
              ),

              Positioned(
                top: 80,
                left: 0,
                right: 0,
                child: Container(
                  alignment: Alignment.center,
                  child: Text(cardData.title,
                    style: TextStyle(
                      color: Color(0xFF333333),
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

/// CardPage
class CardPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _CardState();
  }
}

class _CardState extends State<CardPage> with SingleTickerProviderStateMixin  {
  PageController _pageController;
  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 5, vsync: this, initialIndex: 0);
    _pageController = PageController(initialPage: 0, viewportFraction: 0.85);

    WidgetsBinding.instance.addPostFrameCallback((cb) {
      ///把数据的初始化滞后
      _pageController.animateToPage(0, duration: Duration(milliseconds: 10), curve: Curves.linear);
    });
  }

  @override
  Widget build(BuildContext context) {
    final double bottom = MediaQuery.of(context).padding.bottom;
    return Scaffold(
      appBar: AppBar(
        title: Text('卡片',
          style: TextStyle(
            color: Color(0xFFFFFFFF)
          ),
        ),
        centerTitle: true,
        backgroundColor: Color(0xFF2E2E2F),
      ),
      body: Container(
        color: Color(0xFF040404),
        child: Column(
          children: <Widget>[
            Container(
              child: TabBar(
                isScrollable: true,
                controller: _tabController,
                unselectedLabelColor: Color(0xFF997E50),
                labelColor: Color(0xFFF6E3BE),
                labelStyle: TextStyle(fontSize: 20),
                // labelPadding: EdgeInsets.all(0),
                tabs: _getTab(context),
                onTap: (index){
                  setState(() {
                    _pageController.animateToPage(index, duration: Duration(milliseconds: 300), curve: Curves.linear);
                  });
                },
              ),
            ),

            SizedBox(height: 15,),

            Expanded(
              child: CardTransformer(
                pageViewBuilder: (context, visibilityResolver) {
                  return PageView.builder(
                    controller: _pageController,
                    itemCount: _getTab(context).length,
                    itemBuilder: (context, index) {
                      final pageVisibility = visibilityResolver.resolveCardVisibility(index);
                      return Card(
                        cardData: _getCardData(context, index),
                        pageController: _pageController,
                        visibility: pageVisibility,
                      );
                    },
                    onPageChanged: (index) {
                      setState(() {
                        _tabController.index = index;
                      });
                    },
                  );
                },
              ),
            ),

            SizedBox(
              height: 15 + bottom,
            )
          ],
        ),
      ),
    );
  }

  List<Tab> _getTab(BuildContext context) {
    List<Tab> tabs = [
                    Tab(text: '1'),
                    Tab(text: '2'),
                    Tab(text: '3'),
                    Tab(text: '4'),
                    Tab(text: '5'),
                  ];
    return tabs;
  }

  CardData _getCardData(BuildContext context, int index) {
    List<CardData> dataList = [
      CardData(
        index: 0,
        title: '1',
      ),
      CardData(
        index: 1,
        title: '2',
      ),
      CardData(
        index: 2,
        title: '3',
      ),
      CardData(
        index: 3,
        title: '4',
      ),
      CardData(
        index: 4,
        title: '5',
      ),
    ];
    return dataList[index];
  }
}

当然也可以使用别人的轮子去实现该效果:

https://github.com/best-flutter/flutter_swiper