flutter 水平滚动的卡片,在抖动时具有Snap效果

a9wyjsp7  于 2023-02-20  发布在  Flutter
关注(0)|答案(6)|浏览(170)

我想创建一个卡滚动水平与捕捉到适合的效果时,无论是从左或右滑动列表。
每张卡之间都有一定的间距,适合屏幕,类似于下图

除此之外,这些水平滚动列表元素应该包含在垂直滚动列表中。
我所有我能够实现的是只显示一个水平滚动卡列表后,在Flutter文档的例子。

class SnapCarousel extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'Horizontal List';

    return MaterialApp(
      title: title,
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Container(
          margin: EdgeInsets.symmetric(vertical: 20.0),
          height: 200.0,
          child: ListView(
            scrollDirection: Axis.horizontal,
            children: <Widget>[
              Container(
                width: 160.0,
                color: Colors.red,
              ),
              Container(
                width: 160.0,
                color: Colors.blue,
              ),
              Container(
                width: 160.0,
                color: Colors.green,
              ),
              Container(
                width: 160.0,
                color: Colors.yellow,
              ),
              Container(
                width: 160.0,
                color: Colors.orange,
              ),
            ],
          ),
        ),
      ),
    );
  }
}
r8xiu3jd

r8xiu3jd1#

使用PageViewListView

import 'package:flutter/material.dart';

main() => runApp(MaterialApp(home: MyHomePage()));

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Carousel in vertical scrollable'),
      ),
      body: ListView.builder(
        padding: EdgeInsets.symmetric(vertical: 16.0),
        itemBuilder: (BuildContext context, int index) {
          if(index % 2 == 0) {
            return _buildCarousel(context, index ~/ 2);
          }
          else {
            return Divider();
          }
        },
      ),
    );
  }

  Widget _buildCarousel(BuildContext context, int carouselIndex) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Text('Carousel $carouselIndex'),
        SizedBox(
          // you may want to use an aspect ratio here for tablet support
          height: 200.0,
          child: PageView.builder(
            // store this controller in a State to save the carousel scroll position
            controller: PageController(viewportFraction: 0.8),
            itemBuilder: (BuildContext context, int itemIndex) {
              return _buildCarouselItem(context, carouselIndex, itemIndex);
            },
          ),
        )
      ],
    );
  }

  Widget _buildCarouselItem(BuildContext context, int carouselIndex, int itemIndex) {
    return Padding(
      padding: EdgeInsets.symmetric(horizontal: 4.0),
      child: Container(
        decoration: BoxDecoration(
          color: Colors.grey,
          borderRadius: BorderRadius.all(Radius.circular(4.0)),
        ),
      ),
    );
  }
}
au9on6nz

au9on6nz2#

截图:

如果您不想使用任何第三方软件包,您可以简单地尝试以下操作:

class _HomePageState extends State<HomePage> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: SizedBox(
          height: 200, // card height
          child: PageView.builder(
            itemCount: 10,
            controller: PageController(viewportFraction: 0.7),
            onPageChanged: (int index) => setState(() => _index = index),
            itemBuilder: (_, i) {
              return Transform.scale(
                scale: i == _index ? 1 : 0.9,
                child: Card(
                  elevation: 6,
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
                  child: Center(
                    child: Text(
                      "Card ${i + 1}",
                      style: TextStyle(fontSize: 32),
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}
cgh8pdjw

cgh8pdjw3#

这是一个老问题,我来这里是为了寻找别的东西;- ),但WitVault所期待的是很容易做这个包:https://pub.dev/packages/flutter_swiper

执行情况:

将依赖项放入pubsec.yaml中:

dependencies:
   flutter_swiper: ^1.1.6

将其导入到需要它的页面中:

import 'package:flutter_swiper/flutter_swiper.dart';

在布局中:

new Swiper(
  itemBuilder: (BuildContext context, int index) {
    return new Image.network(
      "http://via.placeholder.com/288x188",
      fit: BoxFit.fill,
    );
  },
  itemCount: 10,
  viewportFraction: 0.8,
  scale: 0.9,
)
bxjv4tth

bxjv4tth4#

要通过ListView实现捕捉效果,只需将物理设置为PageScrollPhysics

const List<Widget> children = [
  ContainerCard(),
  ContainerCard(),
  ContainerCard(),
];
ListView.builder(
    scrollDirection: Axis.horizontal,
    physics: const PageScrollPhysics(), // this for snapping
    itemCount: children.length,
    itemBuilder: (_, index) => children[index],
  )
knsnq2tg

knsnq2tg5#

高级快照列表

如果您正在寻找高级用途,例如动态项目大小、可配置的捕捉点、项目的可视化和基本控制(例如scrollToIndex、animate),则应该使用具有更多功能的基于本机的SnappyListView

SnappyListView(
  itemCount: Colors.accents.length,
  itemBuilder: (context, index) {
    return Container(
        height: 100,
        color: Colors.accents.elementAt(index),
        child: Text("Index: $index"),
    ),
);
qq24tv8q

qq24tv8q6#

对于不想使用第三方库的用户,我认为solution from CopsOnRoad的答案更好、更简单。但是,由于没有动画,因此我在查看卡片时添加了缩放动画(展开)且上一张卡已刷卡(收缩)使用索引。所以发生的情况是,每当第一次加载页面时,第一张和第二张卡不会有任何动画,而当刷卡时,只有前一张和当前的卡片有缩放动画。这是我的实现:

class MyHomePage extends StatefulWidget {

const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  int currentIndex = -1, previousIndex = 0;

  double getAnimationValue(int currentIndex, int widgetIndex, int previousIndex,
      {bool begin = true}) {
    if (widgetIndex == currentIndex) {
      return begin ? 0.9 : 1;
    } else {
      return begin ? 1 : 0.9;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          SizedBox(
            height: 200, // card height
            child: PageView.builder(
              itemCount: 10,
              controller: PageController(viewportFraction: 0.7),
              onPageChanged: (int index) {
                setState(() {
                  if (currentIndex != -1) {
                    previousIndex = currentIndex;
                  }
                  currentIndex = index;
                });
              },
              itemBuilder: (_, widgetIndex) {
                return (currentIndex != -1 &&
                        (previousIndex == widgetIndex ||
                            widgetIndex == currentIndex))
                    ? TweenAnimationBuilder(
                        duration: const Duration(milliseconds: 400),
                        tween: Tween<double>(
                          begin: getAnimationValue(
                            currentIndex,
                            widgetIndex,
                            previousIndex,
                          ),
                          end: getAnimationValue(
                            currentIndex,
                            widgetIndex,
                            previousIndex,
                            begin: false,
                          ),
                        ),
                        builder: (context, value, child) {
                          return Transform.scale(
                            scale: value,
                            child: Card(
                              elevation: 6,
                              shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(20)),
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  Text(
                                    "Card${widgetIndex + 1}",
                                    style: const TextStyle(fontSize: 30),
                                  ),
                                  Text(
                                    "$widgetIndex >> Widget Index << $widgetIndex",
                                    style: const TextStyle(fontSize: 22),
                                  ),
                                  Text(
                                    "$currentIndex >> Current Index << $currentIndex",
                                    style: const TextStyle(fontSize: 22),
                                  ),
                                  Text(
                                    "$previousIndex >> Previous Index << $previousIndex",
                                    style: const TextStyle(fontSize: 22),
                                  ),
                                ],
                              ),
                            ),
                          );
                        },
                      )
                    : Transform.scale(
                        // this is used when you want to disable animation when initialized the page
                        scale:
                            (widgetIndex == 0 && currentIndex == -1) ? 1 : 0.9,
                        child: Card(
                          elevation: 6,
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(20)),
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              Text(
                                "Card${widgetIndex + 1}",
                                style: const TextStyle(fontSize: 30),
                              ),
                              Text(
                                "$widgetIndex >> Widget Index << $widgetIndex",
                                style: const TextStyle(fontSize: 22),
                              ),
                              Text(
                                "$currentIndex >> Init Index << $currentIndex",
                                style: const TextStyle(fontSize: 22),
                              ),
                              Text(
                                "$previousIndex >> Previous Index << $previousIndex",
                                style: const TextStyle(fontSize: 22),
                              ),
                            ],
                          ),
                        ),
                      );
              },
            ),
          ),
        ],
      ),
    );
  }
}

我用TweenAnimationBuilder制作了这个动画,并对小部件进行了硬编码,你可以在需要的时候为你的小部件使用方法,或者使用flutter_animate包来制作动画。

相关问题