如何在PageView.builder中实现AppBar滚动隐藏与显示?
问题描述
尝试在PageView.builder滚动时隐藏AppBar,但滚动时AppBar无响应。相同的SliverAppBar逻辑在ListView.builder中可正常工作,需求为向下滚动隐藏AppBar、向上滚动重新显示,询问能否在PageView.builder中实现该功能。
原代码:
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../Controller/NewsController.dart'; import 'Export.dart'; class ShowNewsPage extends StatefulWidget { const ShowNewsPage({Key? key}) : super(key: key); @override State<ShowNewsPage> createState() => _ShowNewsPageState(); } class _ShowNewsPageState extends State<ShowNewsPage> { bool _init = true; bool _isLoadingNews = false; @override void didChangeDependencies() async { if (_init) { _isLoadingNews = await Provider.of<NewsController>(context, listen: false).getNews(); } _init = false; setState(() {}); super.didChangeDependencies(); } @override Widget build(BuildContext context) { final news = Provider.of<NewsController>(context).allNews; if (!_isLoadingNews) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } else { return Scaffold( extendBodyBehindAppBar: true, body: NestedScrollView( floatHeaderSlivers: true, headerSliverBuilder: (context, innerBoxIsScrolled) => [ SliverAppBar( floating: true, title: Text('Next'), centerTitle: true, toolbarHeight: 40, ), ], body: PageView.builder( physics: const ScrollPhysics(), itemCount: news.length, scrollDirection: Axis.vertical, // shrinkWrap: true, itemBuilder: (context, index) { return NewNewscardWidget( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, title: news[index].title!.rendered!, content: news[index].content!.rendered!, image: news[index].ogImage ?? [], id: news[index].id!, // authorName: news[index].author!, date: 'November 5, 2022', ); }, ), ), ); } } }
解决方案
NestedScrollView的联动逻辑仅支持内部是ListView、GridView这类实现了嵌套滚动协议的组件,PageView不在其支持范围内,因此直接嵌套无法触发SliverAppBar的显示/隐藏。可以通过以下方法实现需求:
方法:用NotificationListener监听滚动,手动控制AppBar状态
通过监听ScrollUpdateNotification判断滚动方向,手动切换AppBar的显示状态,同时可保留动画过渡效果:
class _ShowNewsPageState extends State<ShowNewsPage> { bool _init = true; bool _isLoadingNews = false; bool _showAppBar = true; // 可选:添加滚动阈值,避免微小滚动触发状态切换 final double _scrollThreshold = 20; double _lastScrollPosition = 0; @override void didChangeDependencies() async { if (_init) { _isLoadingNews = await Provider.of<NewsController>(context, listen: false).getNews(); } _init = false; setState(() {}); super.didChangeDependencies(); } @override Widget build(BuildContext context) { final news = Provider.of<NewsController>(context).allNews; if (!_isLoadingNews) { return const Scaffold( body: Center(child: CircularProgressIndicator()), ); } else { return Scaffold( extendBodyBehindAppBar: true, appBar: AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: _showAppBar ? AppBar( key: const ValueKey('appbar'), title: const Text('Next'), centerTitle: true, toolbarHeight: 40, ) : null, ), body: NotificationListener<ScrollUpdateNotification>( onNotification: (notification) { final currentPosition = notification.metrics.pixels; final scrollDelta = currentPosition - _lastScrollPosition; // 向下滚动超过阈值,隐藏AppBar if (scrollDelta > _scrollThreshold && _showAppBar) { setState(() => _showAppBar = false); } // 向上滚动超过阈值,显示AppBar else if (scrollDelta < -_scrollThreshold && !_showAppBar) { setState(() => _showAppBar = true); } _lastScrollPosition = currentPosition; return true; }, child: PageView.builder( physics: const ScrollPhysics(), itemCount: news.length, scrollDirection: Axis.vertical, itemBuilder: (context, index) { return NewNewscardWidget( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, title: news[index].title!.rendered!, content: news[index].content!.rendered!, image: news[index].ogImage ?? [], id: news[index].id!, date: 'November 5, 2022', ); }, ), ), ); } } }
关键说明
- 使用
AnimatedSwitcher给AppBar添加显示/隐藏的过渡动画,提升体验 - 增加
_scrollThreshold阈值,避免微小滚动导致AppBar频繁切换状态 - 通过
ScrollUpdateNotification监听PageView的滚动偏移变化,判断滚动方向
内容的提问来源于stack exchange,提问作者Faiz Ahamed
相关产品推荐
相关产品推荐

