Flutter 锚点之改官方源码

335 阅读1分钟

前言

extended_scroll是我上传到pub.dev的flutter组件,扩展 ScrollController的ensureVisible方法,支持设置offsetTop

为什么要扩展

image
  void scroll2PositionByTabIndex(int index) {
    RenderSliverToBoxAdapter? keyRenderObject = cardKeys[index].currentContext?.findAncestorRenderObjectOfType<RenderSliverToBoxAdapter>();
    if (keyRenderObject != null) {
      _scrollController.position
          .ensureVisible(keyRenderObject, duration: const Duration(milliseconds: 300), curve: Curves.linear)
          .then((value) => isTabClicked = false);
    }
  }

痛点:当点击浮动header中tab(评价、详情)时,调用ScrollController的ensureVisible方法时,总是存在被浮动header挡住的情况

解决这个问题

1、页面初始化缓存各个模块的位置信息,再调用scrollController.animateTo去滚动页面

//缓存商品、评论、详情、同店好货4个模块的y坐标
void cacheChildrenOffset() {
  for (int i = 0; i < cardKeys.length; i++) {
    RenderObject? keyRenderObject = cardKeys[i].currentContext?.findRenderObject();
    if (keyRenderObject != null) {
      double offsetY = keyRenderObject.getTransformTo(null).getTranslation().y;
      itemsOffsetMap[i] = offsetY;
    }
  }
}
Widget floatingHeader = Positioned(
  top: 0,
  left: 0,
  child: tabHeader(
    context,
    onChange: (index) {
      isTabClicked = true;
      store.dispatch(ChangeTopTabIndexAction(index));
      scroll2PositionByTabIndex(index);
    },
  ),
);
//根据index滚动页面至相应模块位置
void scroll2PositionByTabIndex(int index) {
  if (itemsOffsetMap[index] != null) {
    double offsetY = itemsOffsetMap[index]! - 42 - getStatusHeight(context);
    if (offsetY < 0) offsetY = 0;
    _scrollController.animateTo(offsetY, duration: const Duration(milliseconds: 300), curve: Curves.linear).then((value) => isTabClicked = false);
  }
}

缺点:存在偶尔失灵,由于各个设备的性能不一样,存在没缓存好位置信息就要滚动的情况,有点不稳定

2、利用ensureVisible使锚点可见,并能设置距离顶部一定距离,可是查看定义并不支持

iShot_2023-07-28_16.50.30.png

iShot_2023-07-28_16.52.02.png 发现有个alignment,原本认为可以满足需求

void scroll2PositionByTabIndex(int index) {
  RenderSliverToBoxAdapter? keyRenderObject = cardKeys[index].currentContext?.findAncestorRenderObjectOfType<RenderSliverToBoxAdapter>();
  if (keyRenderObject != null) {
    _scrollController.position
        .ensureVisible(keyRenderObject,
            alignment: (42 + getStatusHeight(context)) / getScreenHeight(context), duration: const Duration(milliseconds: 300), curve: Curves.linear)
        .then((value) => isTabClicked = false);
  }
}
image

经过测试并不能满足需求, 到这里怒改官方代码,(flutter/lib/src/widgets/scroll_position.dart#line686)

iShot_2023-07-28_17.02.44.png

//根据index滚动页面至相应模块位置
void scroll2PositionByTabIndex(int index) {
  RenderSliverToBoxAdapter? keyRenderObject = cardKeys[index].currentContext?.findAncestorRenderObjectOfType<RenderSliverToBoxAdapter>();
  if (keyRenderObject != null) {
    _scrollController.position
        .ensureVisible(keyRenderObject, offsetTop: 42 + getStatusHeight(context), duration: const Duration(milliseconds: 300), curve: Curves.linear)
        .then((value) => isTabClicked = false);
  }
}

到这里,完美满足需求,效果如下

image

结语

这个ensureVisible的扩充,已经抽成独立的包,pub.dev,欢迎使用,详细example见 github.com/GuoguoDad/j… , 如果对你有帮助,请不要吝啬你的赞👍🏻