Flutter 擴展NestedScrollView (二)列表滾動同步解決

Extended NestedscrollView 相關文章git

接着上篇,沒看上篇的小夥伴建議先看下上篇,省得斷片中。。github

FlutterCandies QQ羣:181398081 緩存

pub package

我繼續講下第2個問題的解決方案。bash

當在裏面放上tabview,而且tab是緩存狀態的時候,會出現滾動會互相影響的問題app

上篇咱們說到 在咱們的主角NestedScrollView當中,有2個ScrollController.ide

class _NestedScrollController extends ScrollController {
  _NestedScrollController(
      this.coordinator, {
        double initialScrollOffset = 0.0,
        String debugLabel,
複製代碼

一個是inner,一個outer。 outer是負責headerSliverBuilder裏面的滾動widgets inner是負責body裏面的滾動widgets 當outer滾動到底了以後,就會看看inner裏面是否有能滾動的東東,開始滾動svg

Tabview是在body裏面,這裏咱們確定須要對inner進行處理。 首先咱們要明白,NestedScrollView是怎麼處理outer和inner的關係的。post

找到這個_NestedScrollCoordinator 的applyUserOffset方法中處理了整個NestedScrollView的滑動處理ui

@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掉。this

@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 in NestedScrollView 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,
        // )
        );
複製代碼

最後放上 Github extended_nested_scroll_view,若是你有更好的方式解決這個問題或者有什麼不明白的地方,都請告訴我,由衷感謝。

pub package

相關文章
相關標籤/搜索