Extended NestedscrollView 相關文章html
爲何想要本身來定義NestedScrollView呢?git
FlutterCandies QQ羣:181398081github
要從我提交的2個issue開始講:緩存
1.當中的Pinned爲true的Sliver組件對body裏面滾動組件的影響app
2.當在裏面放上tabview,而且tab是緩存狀態的時候,會出現滾動會互相影響的問題ide
沒有任何進展,用一個表情表達Flutter小組的意思 svg
不過還好,有源碼,還好我喜歡看源碼。。 這一篇的篇幅估計不少,請先買好瓜子汽水前排坐好,開車了。。 post
NestedScrollView 是一個複雜的組件,它跟Sliver 系列是一夥的,最下層是個CustomScrollView.性能
Sliver系列的東東不少,咱們下面來一一介紹一下。ui
是Sliver組件的老祖宗,所有的Sliver都放在這個裏面。
SliverList, which is a sliver that displays linear list of children.
SliverFixedExtentList, which is a more efficient sliver that displays linear list of children that have the same extent along the scroll axis. 比SliverList多一個就是相同的行高。這樣性能會更好
SliverPrototypeExtentList SliverPrototypeExtentList arranges its children in a line along the main axis starting at offset zero and without gaps. Each child is constrained to the same extent as the prototypeItem along the main axis and the SliverConstraints.crossAxisExtent along the cross axis.
SliverGrid, which is a sliver that displays a 2D array of children. 能夠設置每行的個數的Grid
SliverPadding, which is a sliver that adds blank space around another sliver.
SliverPersistentHeader A sliver whose size varies when the sliver is scrolled to the leading edge of the viewport. This is the layout primitive that SliverAppBar uses for its shrinking/growing effect.
很是好用的組件,SliverAppBar就是用這個實現的。這個組件的特色是能夠建立出隨着滑動變化的能夠Pinned的元素,你們常常用的什麼吸頂組件能夠用這個很方便的構建,後面我會使用這個寫一個自定義效果的SliverAppbar。
SliverAppBar, which is a sliver that displays a header that can expand and float as the scroll view scrolls.
SliverToBoxAdapter 當你想把一個非Sliver的Widget放在CustomScrollview裏面的時候,你須要用這個包裹一下。
SliverSafeArea A sliver that insets another sliver by sufficient padding to avoid intrusions by the operating system. For example, this will indent the sliver by enough to avoid the status bar at the top of the screen.爲了防止各類邊界的越界,好比說越過頂部的狀態欄
SliverFillRemaining sizes its child to fill the viewport in the cross axis and to fill the remaining space in the viewport in the main axis. 使用這個它會填充完剩餘viewport裏面的所有空間
SliverOverlapAbsorber,SliverOverlapAbsorberHandle 這個上面2個是官方專門爲了解決咱們今天主角NestedScrollView中Pinned 組件對Body 裏面Scroll 狀態影響的,但官方作的不夠完美。
看源碼是一件好玩的事情,你們跟我一塊兒來吧。 flutter\packages\flutter\lib\src\widgets\nested_scroll_view.dart
首先咱們看看第一個問題,從官方文檔中的Sample能夠看到NestedScrollView
DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: 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),
child: SliverAppBar(
title: const Text('Books'), // This is the title in the app bar.
pinned: 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 ListTile(
title: Text('Item $index'),
);
},
// 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: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
)
複製代碼
能夠看到官方用一個SliverOverlapAbsorber包裹了SliverAppbar,在下面body裏面,每個list的上面都加了個SliverOverlapInjector。實際效果就是SliverOverlapInjector的高度就等於SliverAppbar的Pinned的高度。 若是不加入這些代碼,當body裏面的list滾動到SliverAppbar下方的時候。。依然能夠繼續向上滾動,也就是說body的滾動最上面點爲0,而不是SliverAppbar的Pinned 高度。
爲何會出現這種狀況呢? 這要從Sliver的老祖宗CustomScrollView提及來。可能不少人發現,這些Sliver widgets(能夠滾動的那種)沒有ScrollController這個東西(CustomScrollview和NestedScrollView除外)。其實當你把Sliver Widgets(能夠滾動的那種)放到CustomScrollView裏面的時候將由CustomScrollView來統一處理各類Sliver Widgets(能夠滾動的那種),每一個Sliver Widgets(能夠滾動的那種)都會attach 各自的ScrollPosition。好比說第一個列表滾動到頭了,第2個列表就會開始處理對應的ScrollPosition,將出如今viewport裏面的元素render出來。
在咱們的主角NestedScrollView當中,有2個ScrollController.
class _NestedScrollController extends ScrollController {
_NestedScrollController(
this.coordinator, {
double initialScrollOffset = 0.0,
String debugLabel,
複製代碼
一個是inner,一個outer。 outer是負責headerSliverBuilder裏面的滾動widgets inner是負責body裏面的滾動widgets 當outer滾動到底了以後,就會看看inner裏面是否有能滾動的東東,開始滾動。
爲了解決1問題,咱們這裏須要來處理outer這個ScrollController裏面控制的_NestedScrollPosition,問題1在於,當header裏面有多個pinned的widget的時候,咱們outer能滾動的extent。應該要去減掉這個pinned的總的高度。這樣當滾動到pinned的組件下方的時候。咱們就會開始滾動inner。
在_NestedScrollPosition 裏面
// The _NestedScrollPosition is used by both the inner and outer viewports of a
// NestedScrollView. It tracks the offset to use for those viewports, and knows
// about the _NestedScrollCoordinator, so that when activities are triggered on
// this class, they can defer, or be influenced by, the coordinator.
class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDelegate {
_NestedScrollPosition({
@required ScrollPhysics physics,
@required ScrollContext context,
double initialPixels = 0.0,
ScrollPosition oldPosition,
String debugLabel,
@required this.coordinator,
}) : super(
複製代碼
我override了applyContentDimensions方法
@override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
if (debugLabel == 'outer' &&
coordinator.pinnedHeaderSliverHeightBuilder != null) {
maxScrollExtent =
maxScrollExtent - coordinator.pinnedHeaderSliverHeightBuilder();
maxScrollExtent = math.max(0.0, maxScrollExtent);
}
return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
}
複製代碼
pinnedHeaderSliverHeightBuilder是我從最外層傳遞進來的用於獲取當時Pinned 爲true的所有Sliver header的高度。。在這裏把outer最大的滾動extent減去了Pinned 的總的高度,這樣咱們就完美解決了問題.1
在個人demo裏面。pinned 的高度 由 status bar + appbar + 1個或者2個tabbar 組成。這裏爲何要用個function而不是直接傳遞個算好的高度呢?由於在個人case裏面這個pinned的高度是會改變的。
var tabBarHeight = primaryTabBar.preferredSize.height;
var pinnedHeaderHeight =
//statusBa height
statusBarHeight +
//pinned SliverAppBar height in header
kToolbarHeight +
//pinned tabbar height in header
(primaryTC.index == 0 ? tabBarHeight * 2 : tabBarHeight);
return NestedScrollViewRefreshIndicator(
onRefresh: onRefresh,
child: extended.NestedScrollView(
headerSliverBuilder: (c, f) {
return _buildSliverHeader(primaryTabBar);
},
//
pinnedHeaderSliverHeightBuilder: () {
return pinnedHeaderHeight;
},
複製代碼
最後放上 Github extended_nested_scroll_view,若是你有更好的方式解決這個問題或者有什麼不明白的地方,都請告訴我,由衷感謝。