Flutter Stack叠加线性渐变容器时保持PageView可滚动
问题根因
- Stack上层覆盖的渐变Container默认会拦截命中区域的所有手势,即使区域是完全透明的,触摸事件也不会透传给下层的PageView,直接导致滚动失效。
Expanded仅可在Column/Row/Flex这类弹性布局组件内使用,直接放入Stack的子节点列表属于语法错误,会触发布局异常。- 现有代码重复实现了两次完全一致的线性渐变,属于冗余逻辑。
修复方案
核心逻辑是将纯展示用的渐变遮罩设置为不参与手势命中,让触摸事件直接穿透到下层的PageView,同时将需要响应点击的顶部按钮单独放在最上层保证交互可用:
- 移除Stack内所有误用的Expanded组件,Stack子节点按层级从下到上排列:最底层放PageView视频流,中间层放渐变遮罩,最顶层放可交互的顶部按钮。
- 渐变遮罩层用
IgnorePointer包裹,该组件不会拦截任何手势事件,所有触摸操作都会直接透传给下层的PageView,完全不影响纵向滚动。 - 删除PageView外层冗余的渐变Container,渐变效果仅需在遮罩层实现一次即可。
修正后的核心页面代码如下:
class FeedScreen extends StatefulWidget { static const String id = 'FeedScreen'; const FeedScreen({Key? key}) : super(key: key); @override State<FeedScreen> createState() => _FeedScreenState(); } class _FeedScreenState extends State<FeedScreen> { @override Widget build(BuildContext context) { return Scaffold( endDrawer: CategoriesDrawer(), extendBodyBehindAppBar: true, bottomNavigationBar: BottomBar(selectedIndex: 0), appBar: AppBar( actions: [ Builder( builder: (context) => IconButton( icon: const Icon(Icons.grid_view_outlined, color: Colors.black, size: 24), onPressed: () => Scaffold.of(context).openEndDrawer(), tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, ), ), ], leading: const Image( image: AssetImage( 'assets/images/logo.png', ), height: 28, ), toolbarHeight: 36, elevation: 0, backgroundColor: Colors.white, ), body: SafeArea( child: Stack( alignment: Alignment.topCenter, children: [ // 最底层:纵向滚动视频流 PageView( scrollDirection: Axis.vertical, children: const [ Feed( video_url: 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), Feed( video_url: 'https://citjos3.s3.ap-south-1.amazonaws.com/videos/1605781113_1369820376withSound.mp4'), Feed( video_url: 'https://citjos3.s3.ap-south-1.amazonaws.com/videos/1606913499_1670916107withSound.mp4'), ], ), // 中间层:渐变遮罩,不响应手势,不阻塞滚动 IgnorePointer( child: Container( width: double.infinity, height: double.infinity, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0xFF000000), Colors.transparent, Colors.transparent, Color(0xFF000000), ], // 可自行调整stops值控制上下黑色渐变的高度占比 stops: [0.0, 0.15, 0.85, 1.0], ), ), ), ), // 最上层:顶部可交互按钮栏 Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: () {}, child: const Text( 'Subscribed', style: TextStyle( color: Colors.white, fontSize: 18, ), ), ), TextButton( onPressed: () {}, child: const Text( 'Global', style: TextStyle( color: Colors.white, fontSize: 18, ), ), ), TextButton( onPressed: () {}, child: const Text( 'Local', style: TextStyle( color: Colors.white, fontSize: 18, ), ), ), ], ), ), ], ), ), ); } }
补充说明:渐变配置里的stops参数可以根据UI需求自行调整,示例中[0.0, 0.15, 0.85, 1.0]代表顶部0%-15%高度从纯黑过渡到全透明,底部85%-100%高度从全透明过渡到纯黑,数值越大上下黑色遮罩的覆盖范围越高。

内容的提问来源于stack exchange,提问作者Prerak
相关产品推荐
相关产品推荐

