Flutter 擴充套件NestedScrollView (二)列表滾動同步解決
接著上篇,沒看上篇的小夥伴建議先看下上篇,免得斷片中。。
我繼續講下第2個問題的解決方案。
ofollow,noindex">當在裡面放上tabview,並且tab是快取狀態的時候,會出現滾動會互相影響的問題
上篇 我們說到 在我們的主角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裡面是否有能滾動的東東,開始滾動
Tabview是在body裡面,這裡我們肯定需要對inner進行處理。 首先我們要明白,NestedScrollView是怎麼處理outer和inner的關係的。
找到這個_NestedScrollCoordinator 的applyUserOffset方法中處理了整個NestedScrollView的滑動處理
@override void applyUserOffset(double delta) { updateUserScrollDirection(delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse); assert(delta != 0.0); if (_innerPositions.isEmpty) { _outerPosition.applyFullDragUpdate(delta); } else if (delta < 0.0) { // dragging "up" // TODO(ianh): prioritize first getting rid of overscroll, and then the // outer view, so that the app bar will scroll out of the way asap. // Right now we ignore overscroll. This works fine on Android but looks // weird on iOS if you fling down then up. The problem is it's not at all // clear what this should do when you have multiple inner positions at // different levels of overscroll. final double innerDelta = _outerPosition.applyClampedDragUpdate(delta); if (innerDelta != 0.0) { for (_NestedScrollPosition position in _innerPositions) position.applyFullDragUpdate(innerDelta); } } else { // dragging "down" - delta is positive // prioritize the inner views, so that the inner content will move before the app bar grows double outerDelta = 0.0; // it will go positive if it changes final List<double> overscrolls = <double>[]; final List<_NestedScrollPosition> innerPositions = _innerPositions.toList(); for (_NestedScrollPosition position in innerPositions) { final double overscroll = position.applyClampedDragUpdate(delta); outerDelta = math.max(outerDelta, overscroll); overscrolls.add(overscroll); } if (outerDelta != 0.0) outerDelta -= _outerPosition.applyClampedDragUpdate(outerDelta); // now deal with any overscroll for (int i = 0; i < innerPositions.length; ++i) { final double remainingDelta = overscrolls[i] - outerDelta; if (remainingDelta > 0.0) innerPositions[i].applyFullDragUpdate(remainingDelta); } } } 複製程式碼
Iterable<_NestedScrollPosition> get _innerPositions { return _innerController.nestedPositions; } 複製程式碼
看到_innerPositions是我們要關注的東西,通過debug,我發現,如果tabview的每個tab做了快取,那麼每個tab裡面列表的ScrollPosition將一直快取在這個ScrollController裡面。 當tab到tabview的某個tab的時候,ScrollController將會將這ScrollPosition attach 上,如果沒有快取,將會在離開的時候detach掉。
@override void attach(ScrollPosition position) { assert(position is _NestedScrollPosition); super.attach(position); coordinator.updateParent(); coordinator.updateCanDrag(); position.addListener(_scheduleUpdateShadow); _scheduleUpdateShadow(); } @override void detach(ScrollPosition position) { assert(position is _NestedScrollPosition); position.removeListener(_scheduleUpdateShadow); super.detach(position); _scheduleUpdateShadow(); } 複製程式碼

真相只有一個。。是的。。可以說。。造成快取tabview的各個tab裡面的列表互相影響的原因,是因為歪果仁說: as design(我就是這樣設計的,不服嗎).
按照我的思想啊,我滾動的時候。當然只想影響當前顯示的這個列表啊。這不科學啊。。
找到原因找到原理,一切就都好解決了。現在的關鍵點在於,我怎麼能知道顯示對應的是哪個列表的?!
這個問題問了很多人。。也查找了好久都沒找到好的方式去獲取當前 啟用的 列表對應的 ScrollPosition。。終於我只能想到一個 workaround。暫時解決這個問題。
提供一個容器,把inner裡面的滾動列表包裹起來,並且設定它的tab 的唯一key
//pack your inner scrollables which are inNestedScrollView body //so that it can find the active scrollable //compare with NestedScrollViewInnerScrollPositionKeyBuilder class NestedScrollViewInnerScrollPositionKeyWidget extends StatefulWidget { final Key scrollPositionKey; final Widget child; NestedScrollViewInnerScrollPositionKeyWidget( this.scrollPositionKey, this.child); @override _NestedScrollViewInnerScrollPositionKeyWidgetState createState() => _NestedScrollViewInnerScrollPositionKeyWidgetState(); } class _NestedScrollViewInnerScrollPositionKeyWidgetState extends State<NestedScrollViewInnerScrollPositionKeyWidget> { @override Widget build(BuildContext context) { return widget.child; } //@override //void didChangeDependencies() { //// TODO: implement didChangeDependencies ////print("didChangeDependencies"+widget.scrollPositionKey.toString()); //super.didChangeDependencies(); //} // //@override //void didUpdateWidget(NestedScrollViewInnerScrollPositionKeyWidget oldWidget) { //// TODO: implement didUpdateWidget ////print("didUpdateWidget"+widget.scrollPositionKey.toString()+oldWidget.scrollPositionKey.toString()); //super.didUpdateWidget(oldWidget); //} } 複製程式碼
然後在剛才attach方法中通過先祖NestedScrollViewInnerScrollPositionKeyWidget
@override void attach(ScrollPosition position) { assert(position is _NestedScrollPosition); super.attach(position); attachScrollPositionKey(position as _NestedScrollPosition); coordinator.updateParent(); coordinator.updateCanDrag(); position.addListener(_scheduleUpdateShadow); _scheduleUpdateShadow(); } @override void detach(ScrollPosition position) { assert(position is _NestedScrollPosition); position.removeListener(_scheduleUpdateShadow); super.detach(position); detachScrollPositionKey(position as _NestedScrollPosition); _scheduleUpdateShadow(); } void attachScrollPositionKey(_NestedScrollPosition position) { if (position != null && scrollPositionKeyMap != null) { var key = position.setScrollPositionKey(); if (key != null) { if (!scrollPositionKeyMap.containsKey(key)) { scrollPositionKeyMap[key] = position; } else if (scrollPositionKeyMap[key] != position) { //in demo ,when tab to tab03, the tab02 key will be tab00 at first //then it become tab02. //this is not a good solution position.clearScrollPositionKey(); Future.delayed(Duration(milliseconds: 500), () { attachScrollPositionKey(position); }); } } } } void detachScrollPositionKey(_NestedScrollPosition position) { if (position != null && scrollPositionKeyMap != null && position.key != null && scrollPositionKeyMap.containsKey(position.key)) { scrollPositionKeyMap.remove(position.key); position.clearScrollPositionKey(); } } 複製程式碼
獲取先祖NestedScrollViewInnerScrollPositionKeyWidget方法
Key setScrollPositionKey() { //if (haveDimensions) { final type = _typeOf<NestedScrollViewInnerScrollPositionKeyWidget>(); NestedScrollViewInnerScrollPositionKeyWidget keyWidget = (this.context as ScrollableState) ?.context ?.ancestorWidgetOfExactType(type); _key = keyWidget?.scrollPositionKey; return _key; } 複製程式碼
找到這個_NestedScrollCoordinator 的applyUserOffset方法中我們現在要替換掉 _innerPositions為_currentInnerPositions
Iterable<_NestedScrollPosition> get _innerPositions { //return _currentPositions; return _innerController.nestedPositions; } Iterable<_NestedScrollPosition> get _currentInnerPositions { return _innerController .getCurrentNestedPositions(innerScrollPositionKeyBuilder); } 複製程式碼
getCurrentNestedPositions裡面的程式碼
Iterable<_NestedScrollPosition> getCurrentNestedPositions( NestedScrollViewInnerScrollPositionKeyBuilder innerScrollPositionKeyBuilder) { if (innerScrollPositionKeyBuilder != null && scrollPositionKeyMap.length > 1) { var key = innerScrollPositionKeyBuilder(); if (scrollPositionKeyMap.containsKey(key)) { return <_NestedScrollPosition>[scrollPositionKeyMap[key]]; } else { return nestedPositions; } } return nestedPositions; } 複製程式碼
SampeCode
extended.NestedScrollView( headerSliverBuilder: (c, f) { return _buildSliverHeader(primaryTabBar); }, // pinnedHeaderSliverHeightBuilder: () { return pinnedHeaderHeight; }, innerScrollPositionKeyBuilder: () { var index = "Tab"; if (primaryTC.index == 0) { index += (primaryTC.index.toString() + secondaryTC.index.toString()); } else { index += primaryTC.index.toString(); } return Key(index); }, 複製程式碼
這裡由你自己協定tab key。。我這裡是一級tab+二級tab的index。。比如 Tab00代表一級tab第一個下面的二級tab的第一個。
定義tab裡面的列表的時候如下,比如第一個tab下面的二級tab的第一個列表,那麼它的key 為Tab00.
return extended.NestedScrollViewInnerScrollPositionKeyWidget( Key("Tab00"), // myRefresh.RefreshIndicator( // child: ListView.builder( itemBuilder: (c, i) { return Container( //decoration: BoxDecoration(border: Border.all(color: Colors.orange,width: 1.0)), alignment: Alignment.center, height: 60.0, child: Text(widget.tabKey.toString() + ": List$i"), ); }, itemCount: 100) //, //onRefresh: onRefresh, // ) ); 複製程式碼
最後放上 Flutter%2Ftree%2Fmaster%2Fextended_nested_scroll_view" rel="nofollow,noindex"> Github extended_nested_scroll_view ,如果你有更好的方式解決這個問題或者有什麼不明白的地方,都請告訴我,由衷感謝。
