You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何实现PageView选中项放大、两侧项缩小效果

Flutter PageView 选中项放大效果实现

核心逻辑是监听PageController的实时滑动偏移,计算每个卡片和当前选中位置的距离,根据距离动态计算卡片缩放比例,距离选中位置越近缩放比例越大,实现中间大两侧小的轮播效果。


步骤1:初始化PageController并添加滑动监听

首先在对应State类中定义必要变量,在initState中初始化控制器,注意要设置viewportFraction参数(值小于1时可以露出两侧相邻卡片),同时添加监听实时获取当前滑动的偏移值:

// 放在State类的属性定义区
late PageController pageController;
double currentPage = 0.0;

@override
void initState() {
  super.initState();
  // viewportFraction设为0.8表示单张卡片占视口宽度的80%,两侧会露出相邻卡片
  pageController = PageController(viewportFraction: 0.8, initialPage: 0);
  pageController.addListener(() {
    setState(() {
      currentPage = pageController.page!;
    });
  });
}

// 记得在dispose时释放控制器
@override
void dispose() {
  pageController.dispose();
  super.dispose();
}

原有build方法不需要大改,保持现有结构即可。


步骤2:修改卡片构建方法,添加缩放逻辑

修改_bulidPageItem方法,根据卡片index和当前滑动偏移的差值计算缩放比例,用Transform.scale包裹原有卡片内容实现缩放,同时修复原有代码里三目运算符的语法错误(颜色判断处误把:写成了?):

Widget _bulidPageItem(int index) {
  // 计算当前卡片和选中位置的距离差
  double distance = (index - currentPage).abs();
  // 按距离计算缩放值:选中项缩放1.0,相邻项缩放0.9,更远项最小缩放0.8
  double scale = (1 - distance * 0.1).clamp(0.8, 1.0);

  return Transform.scale(
    scale: scale,
    alignment: Alignment.center,
    child: Stack(
      alignment: Alignment.topCenter,
      children: [
        Container(
          margin: const EdgeInsets.only(left: 5, right: 5),
          height: 220,
          width: 350,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(20),
            // 修复原代码三目运算符语法错误
            color: index.isEven ? const Color(0xFFffd28d) : const Color(0xFF89dad0),
            image: const DecorationImage(
                image: AssetImage('images/chineseFood.jpg'), fit: BoxFit.cover),
          ),
        ),
        Align(
          alignment: Alignment.bottomCenter,
          child: Container(
            margin: const EdgeInsets.only(left: 30, right: 30, bottom: 15),
            height: 130,
            decoration: BoxDecoration(
              boxShadow: [
                BoxShadow(
                    color: Colors.grey.withOpacity(0.3),
                    blurRadius: 6,
                    spreadRadius: 0.7,
                    offset: const Offset(1, 4))
              ],
              borderRadius: BorderRadius.circular(25),
              color: Colors.white,
            ),
            child: Padding(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const BigText(text: 'Chinese side'),
                  const SizedBox(height: 10),
                  Row(
                    children: [
                      Wrap(
                        children: List.generate(
                          5,
                          (index) => const Icon(Icons.star,
                              color: AppColor.mainColor, size: 12),
                        ),
                      ),
                      const SizedBox(width: 10),
                      SmallText(text: 4.5.toString()),
                      const SizedBox(width: 10),
                      const SmallText(text: '1287 comments'),
                    ],
                  ),
                  const SizedBox(height: 20),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: const [
                      SliderIcons(
                          color: AppColor.iconColor1,
                          text: 'Normal',
                          icon: Icons.circle),
                      SliderIcons(
                          color: AppColor.mainColor,
                          text: '1.7km',
                          icon: Icons.location_pin),
                      SliderIcons(
                          color: AppColor.iconColor2,
                          text: '32min',
                          icon: FontAwesomeIcons.clock),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    ),
  );
}

参数调整说明

  • 缩放幅度:修改1 - distance * 0.1里的0.1即可,数值越大选中项和非选中项的尺寸差越明显
  • 卡片占比:调整viewportFraction的值,范围0.7~0.9视觉效果最佳,值越小两侧露出的相邻卡片越多
  • 最小缩放:clamp(0.8, 1.0)里的0.8是最远卡片的最小缩放值,不要低于0.7避免卡片过小看不清

可选优化

如果想要层级感更强,可以在Transform.scale外层再包一层Opacity,给非选中项加一点透明度:

return Transform.scale(
  scale: scale,
  alignment: Alignment.center,
  child: Opacity(
    opacity: scale < 0.9 ? 0.85 : 1.0,
    child: // 原有Stack内容
  )
);

效果参考:
轮播选中放大效果示意图

内容的提问来源于stack exchange,提问作者AHMED Gamal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:03:28