我需要一个带有 TabBarView 的浮动 SliverAppBar。每个选项卡都有一个 CustomScrollView 来滚动它的子项。如果我将 PageView 作为子项并且快速滚动,则 PageView 从最后一页而不是第一页开始。我在 flutter 文档中找到了示例代码:
https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html
我刚刚将
ListTile
内的 SliverChildBuilderDelegate
更改为 PageView
。但是当我测试这段代码并快速滚动时,pageView 从最后一页开始。
这是一个演示
https://gyazo.com/79709286236fa3dbf1f88b1e92f5cee3
这是我的代码:
// This example shows a [NestedScrollView] whose header is the combination of a
// [TabBar] in a [SliverAppBar] and whose body is a [TabBarView]. It uses a
// [SliverOverlapAbsorber]/[SliverOverlapInjector] pair to make the inner lists
// align correctly, and it uses [SafeArea] to avoid any horizontal disturbances
// (e.g. the "notch" on iOS when the phone is horizontal). In addition,
// [PageStorageKey]s are used to remember the scroll position of each tab's
// list.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final List<String> _tabs = <String>['Tab 1', 'Tab 2'];
return DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
// These are the slivers that show up in the "outer" scroll view.
return <Widget>[
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
// and redirects it to the SliverOverlapInjector below. If it is
// missing, then it is possible for the nested "inner" scroll view
// below to end up under the SliverAppBar even when the inner
// scroll view thinks it has not been scrolled.
// This is not necessary if the "headerSliverBuilder" only builds
// widgets that do not overlap the next sliver.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title:
const Text('Books'), // This is the title in the app bar.
pinned: true,
floating: true,
expandedHeight: 150.0,
// The "forceElevated" property causes the SliverAppBar to show
// a shadow. The "innerBoxIsScrolled" parameter is true when the
// inner scroll view is scrolled beyond its "zero" point, i.e.
// when it appears to be scrolled below the SliverAppBar.
// Without this, there are cases where the shadow would appear
// or not appear inappropriately, because the SliverAppBar is
// not actually aware of the precise position of the inner
// scroll views.
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
// These are the widgets to put in each tab in the tab bar.
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
];
},
body: TabBarView(
// These are the contents of the tab views, below the tabs.
children: _tabs.map((String name) {
return SafeArea(
top: false,
bottom: false,
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
// In this example, the inner scroll view has
// fixed-height list items, hence the use of
// SliverFixedExtentList. However, one could use any
// sliver widget here, e.g. SliverList or SliverGrid.
sliver: SliverFixedExtentList(
// The items in this example are fixed to 48 pixels
// high. This matches the Material Design spec for
// ListTile widgets.
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
// This builder is called for each child.
// In this example, we just number each list item.
return PageView(
/// [PageView.scrollDirection] defaults to [Axis.horizontal].
/// Use [Axis.vertical] to scroll vertically.
scrollDirection: Axis.horizontal,
children: const <Widget>[
Center(
child: Text('First Page'),
),
Center(
child: Text('Second Page'),
),
Center(
child: Text('Third Page'),
)
],
);
},
// The childCount of the SliverChildBuilderDelegate
// specifies how many children this inner list
// has. In this example, each tab has a list of
// exactly 30 items, but this is arbitrary.
childCount: 100,
),
),
),
],
);
},
),
);
}).toList(),
),
),
),
);
}
}
如何使 pageView 从第一页开始?我尝试将其设置为 pageView
controller: PageController(initialPage: 0),
但它不起作用。
我有同样的问题,我找到了解决方案。您只需将
PageStorageKey
添加到所有滚动视图即可。对我来说效果很好
CustomScrollView(
key: PageStorageKey<String>(S.current.news),
controller: _scrollController,
里面ff
PageView.builder(
key: PageStorageKey<String>('feed-post-${widget.feedItem.id}'),
scrollDirection: Axis.horizontal,
controller: _pageController,
itemCount: widget.feedItem.imageLinks.length,
onPageChanged: (index) => _currentIndex.value = index,
itemBuilder: (ctx, i) => BaseImage(
key: Key(widget.feedItem.imageLinks[i]),
width: double.infinity,
img: widget.feedItem.imageLinks[i],
fit: BoxFit.fitHeight,
),
),
ListView.builder(
key: PageStorageKey<String>('post-position-${widget.feedItem.id}'),
scrollDirection: Axis.horizontal,
controller: _scrollController,
itemCount: widget.feedItem.imageLinks.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => Padding(
padding: const EdgeInsets.only(right: 3),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.slowMiddle,
width: 7,
height: 7,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color:
value == index ? _layout.theme.primary : _layout.theme.lightText,
),
),
),
),