flutter开发实战-轮播Swiper更改Custom_layout样式中Widget层级

flutter开发实战-轮播Swiper更改Custom_layout样式中Widget层级

在之前的开发过程中,需要实现卡片轮播效果,但是卡片轮播需要中间大、两边小一些的效果,这里就使用到了Swiper。具体效果如视频所示
添加链接描述

这里需要的效果是中间大、两边小一些,中间的卡片在最上层,两边的卡片会被中间的卡片挡住一部分。所以需要处理一下Custom_layout样式中Widget层级关系。

一、引入Swiper

在工程的pubspec.yaml中引入swiper

复制代码
  # 轮播图
  flutter_swiper_null_safety: ^1.0.2

二、Swiper使用

Swiper无限轮播

通过Swiper()来构建轮播图控件,可以同步不同的属性搭配不同的效果

默认效果

复制代码
Container(
    height: 200,
    child: new Swiper(
        itemBuilder: (BuildContext context,int index){
            return new Image.network(imgs[index],fit: BoxFit.cover,);
        },
        itemCount: imgs.length,
        pagination: new SwiperPagination(),//如果不填则不显示指示点
        control: new SwiperControl(),//如果不填则不显示左右按钮
    ),
),

3D卡片滚动

复制代码
Container(
    height:  200,
    child: new Swiper(
        itemBuilder: (BuildContext context, int index) {
            return new Image.network(imgs[index],fit: BoxFit.cover,);
        },
        itemCount: imgs.length,
        viewportFraction: 0.8,
        scale: 0.9,
    ),
),

无限卡片堆叠

复制代码
Container(
    height: 200,
    child: new Swiper(
        itemBuilder: (BuildContext context, int index) {
            return new Image.network(imgs[index],fit: BoxFit.cover,);
        },
        itemCount: imgs.length,
        itemWidth: 300.0,
        layout: SwiperLayout.STACK,
    ),
),

无限卡片堆叠2

复制代码
Container(
    height: 200,
    child: new Swiper(
        itemBuilder: (BuildContext context, int index) {
            return new Image.network(imgs[index],fit: BoxFit.cover,);
        },
        itemCount: imgs.length,
        itemWidth: 300.0,
        itemHeight: 300.0,
        layout: SwiperLayout.TINDER,
    ),
),

自定义效果

复制代码
Container(
    height: 200,
    child: new Swiper(
    layout: SwiperLayout.CUSTOM,
    customLayoutOption: new CustomLayoutOption(
        startIndex: -1,
        stateCount: 3
    ).addRotate([
        -45.0/180,
        0.0,
        45.0/180
    ]).addTranslate([
        new Offset(-370.0, -40.0),
        new Offset(0.0, 0.0),
        new Offset(370.0, -40.0)
    ]),
    itemWidth: 300.0,
    itemHeight: 200.0,
    itemBuilder: (context, index) {
        return new Image.network(imgs[index],fit: BoxFit.cover,);
    },
    itemCount: imgs.length),
)

三、更改Custom_layout样式中Widget层级

需要的效果是中间大、两边小一些,中间的卡片在最上层,两边的卡片会被中间的卡片挡住一部分

这里使用的是SwiperLayout.CUSTOM,

这里就需要查看源码,更改Custom_layout样式中Widget层级关系,更改Stack的子Widget层级关系,需要调整中间的卡片在最上层。

找到Custom_layout.dart的源码,找到Widget _buildAnimation(BuildContext context, Widget? w)。

需要更改list,重新排列list

复制代码
if (list.isNotEmpty) {
      int length = list.length;
      int mid = length~/2;

      List<Widget> transList = [];
      for (int i = mid; i >= 0; i--) {
        List<Widget> subList = [];
        for (int index = 0; index < length; index++) {
          int abs = (index - mid).abs();
          if (abs == i) {
            subList.add(list[index]);
          }
        }
        transList.addAll(subList);
      }

      print("transList:${transList}");
      if (transList.isNotEmpty && transList.length == list.length) {
        list = transList;
      }
    }

更改后的_buildAnimation代码如下

复制代码
Widget _buildAnimation(BuildContext context, Widget? w) {
    List<Widget> list = [];

    if (_animationCount != null) {
      double? animationValue = _animation?.value;
      for (int i = 0; i < _animationCount!; ++i) {
        int realIndex = _currentIndex + i + (_startIndex ?? 0);
        realIndex = realIndex % widget.itemCount;
        if (realIndex < 0) {
          realIndex += widget.itemCount;
        }
        if (animationValue != null) {
          list.add(_buildItem(i, realIndex, animationValue));
        }
      }
    }

    if (list.isNotEmpty) {
      int length = list.length;
      int mid = length~/2;

      List<Widget> transList = [];
      for (int i = mid; i >= 0; i--) {
        List<Widget> subList = [];
        for (int index = 0; index < length; index++) {
          int abs = (index - mid).abs();
          if (abs == i) {
            subList.add(list[index]);
          }
        }
        transList.addAll(subList);
      }

      print("transList:${transList}");
      if (transList.isNotEmpty && transList.length == list.length) {
        list = transList;
      }
    }

    return new GestureDetector(
      behavior: HitTestBehavior.opaque,
      onPanStart: _onPanStart,
      onPanEnd: _onPanEnd,
      onPanUpdate: _onPanUpdate,
      child: new ClipRect(
        child: new Center(
          child: _buildContainer(list),
        ),
      ),
    );
  }

四、实现中间大、两边小一些,中间的卡片在最上层,两边的卡片会被中间的卡片挡住的效果

需要实现效果的时候,我们需要使用Swiper的custom,使用CustomLayoutOption添加addScale和addOpacity以及addTranslate来确定不同的卡片的缩放大小、透明度、以及offset

代码如下

复制代码
Swiper(
              autoplay: true,
              layout: SwiperLayout.CUSTOM,
              customLayoutOption:
                  CustomLayoutOption(startIndex: 0, stateCount: 5)
                    ..addScale([
                      0.6,
                      0.8,
                      1.0,
                      0.8,
                      0.6,
                    ], Alignment.center)
                    ..addOpacity([
                      1.0,
                      1.0,
                      1.0,
                      1.0,
                      1.0,
                    ])
                    ..addTranslate([
                      Offset(-180.0, 0),
                      Offset(-80.0, 0),
                      Offset(0.0, 0.0),
                      Offset(80.0, 0),
                      Offset(180.0, 0),
                    ]),
              itemWidth: 230.0,
              itemHeight: 230.0,
              itemBuilder: (context, index) {
                return SwiperCard(imageUrl: imageUrls[index]);
              },
              itemCount: imageUrls.length,
            )

页面的完整代码如下

复制代码
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart';

class SwiperPage extends StatefulWidget {
  const SwiperPage({super.key});

  @override
  State<SwiperPage> createState() => _SwiperPageState();
}

class _SwiperPageState extends State<SwiperPage> {
  List<String> imageUrls = [];

  @override
  void initState() {
    // TODO: implement initState
    imageUrls = [
      "https://d-ssl.dtstatic.com/uploads/blog/202301/08/20230108192142_ff632.thumb.1000_0.jpeg_webp",
      "https://d-ssl.dtstatic.com/uploads/blog/202301/08/20230108192143_f4355.thumb.1000_0.jpeg_webp",
      "https://d-ssl.dtstatic.com/uploads/blog/202301/08/20230108192146_0aaf2.thumb.1000_0.jpeg_webp",
      "https://d-ssl.dtstatic.com/uploads/blog/202301/08/20230108192148_357ff.thumb.1000_0.jpeg_webp",
      "https://d-ssl.dtstatic.com/uploads/blog/202301/08/20230108192149_92c71.thumb.1000_0.jpeg_webp"
    ];
    super.initState();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Size screenSize = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text('SwiperPage'),
      ),
      body: Container(
        width: screenSize.width,
        height: screenSize.height,
        child: Stack(
          alignment: Alignment.center,
          children: [
            Swiper(
              autoplay: true,
              layout: SwiperLayout.CUSTOM,
              customLayoutOption:
                  CustomLayoutOption(startIndex: 0, stateCount: 5)
                    ..addScale([
                      0.6,
                      0.8,
                      1.0,
                      0.8,
                      0.6,
                    ], Alignment.center)
                    ..addOpacity([
                      1.0,
                      1.0,
                      1.0,
                      1.0,
                      1.0,
                    ])
                    ..addTranslate([
                      Offset(-180.0, 0),
                      Offset(-80.0, 0),
                      Offset(0.0, 0.0),
                      Offset(80.0, 0),
                      Offset(180.0, 0),
                    ]),
              itemWidth: 230.0,
              itemHeight: 230.0,
              itemBuilder: (context, index) {
                return SwiperCard(imageUrl: imageUrls[index]);
              },
              itemCount: imageUrls.length,
            )
          ],
        ),
      ),
    );
  }
}

class SwiperCard extends StatelessWidget {
  const SwiperCard({
    super.key,
    required this.imageUrl,
  });

  final String imageUrl;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 230,
      height: 230,
      clipBehavior: Clip.hardEdge,
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.all(
          Radius.circular(10),
        ),
        border: Border.all(
          color: Color(0xFF5C6BC0),
          style: BorderStyle.solid,
          width: 3,
        ),
        boxShadow: [
          BoxShadow(
            color: Color(0xFFE8EAF6),
            offset: Offset(0, -5),
            blurRadius: 10,
          )
        ],
      ),
      child: Stack(alignment: Alignment.center, children: [
        Positioned(
          top: 0,
          child: Image.network(
            imageUrl,
            width: 230,
            height: 230,
          ),
        ),
      ]),
    );
  }
}

最终实现了效果。

五、小结

flutter开发实战-轮播Swiper更改Custom_layout样式中Widget层级

学习记录,每天不停进步。

相关推荐
明君879971 小时前
Flutter 如何给图片添加多行文字水印
前端·flutter
四眼肥鱼9 小时前
flutter 利用flutter_libserialport 实现SQ800 串口通信
前端·flutter
tingshuo291720 小时前
S001 【模板】从前缀函数到KMP应用 字符串匹配 字符串周期
笔记
火柴就是我1 天前
让我们实现一个更好看的内部阴影按钮
android·flutter
王晓枫1 天前
flutter接入三方库运行报错:Error running pod install
前端·flutter
shankss1 天前
Flutter 下拉刷新库 pull_to_refresh_plus 设计与实现分析
flutter
忆江南2 天前
iOS 深度解析
flutter·ios
明君879972 天前
Flutter 实现 AI 聊天页面 —— 记一次 Markdown 数学公式显示的踩坑之旅
前端·flutter
恋猫de小郭2 天前
移动端开发稳了?AI 目前还无法取代客户端开发,小红书的论文告诉你数据
前端·flutter·ai编程
MakeZero2 天前
Flutter那些事-交互式组件
flutter