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
相关产品推荐
相关产品推荐

