博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Flutter 你想知道的Widget可视区域,相对位置,大小
阅读量:7120 次
发布时间:2019-06-28

本文共 8679 字,大约阅读时间需要 28 分钟。

半夜睡不着觉,把心情写成代码,只好到这里水一篇bug

FlutterCandies QQ群:181398081 说起来这些东西,其实是一个怨念,从一个issue开始。

NestedScrollView里面有2个Scroll Control,一个outer(header),一个是inner(body),当inner里面有PageView/TabBarView,并且每个page被缓存(AutomaticKeepAliveClientMixin or PageStorageKey)的,滑动inner会对全部的列表都有影响

之前通过key的方式来判断哪个一个列表是当前可视区域里面激活的,让NestedScrollView滑动只对它有影响,。

其实我一开始就想知道怎么知道一个widget是不是在可视区域,日夜苦读,终于找到个可行的方案来优美的解决这个问题。

文字图代码会比较多。建议准备好瓜子水。边看边吃。。

我找到的第一个API是getOffsetToReveal

/// The optional `rect` parameter describes which area of that `target` object  /// should be revealed in the viewport. If `rect` is null, the entire  /// `target` [RenderObject] (as defined by its [RenderObject.paintBounds])  /// will be revealed. If `rect` is provided it has to be given in the  /// coordinate system of the `target` object.  ///  /// The `alignment` argument describes where the target should be positioned  /// after applying the returned offset. If `alignment` is 0.0, the child must  /// be positioned as close to the leading edge of the viewport as possible. If  /// `alignment` is 1.0, the child must be positioned as close to the trailing  /// edge of the viewport as possible. If `alignment` is 0.5, the child must be  /// positioned as close to the center of the viewport as possible.  ///  /// The target might not be a direct child of this viewport but it must be a  /// descendant of the viewport and there must not be any other  /// [RenderAbstractViewport] objects between the target and this object.  ///  /// This method assumes that the content of the viewport moves linearly, i.e.  /// when the offset of the viewport is changed by x then `target` also moves  /// by x within the viewport.  ///  /// See also:  ///  ///  * [RevealedOffset], which describes the return value of this method.  RevealedOffset getOffsetToReveal(RenderObject target, double alignment, {Rect rect});复制代码

简单说下,就是获得目标RenderOject跟Viewport的距离,下面是主要用法

RenderAbstractViewport viewport =                RenderAbstractViewport.of(renderObject);            /// Distance between top edge of screen and MyWidget bottom edge            var offsetToRevealLeading =                viewport.getOffsetToReveal(renderObject, 0.0);            /// Distance between bottom edge of screen and MyWidget top edge            var offsetToRevealTrailingEdge =                viewport.getOffsetToReveal(renderObject, 1.0);复制代码

demo地址, demo中展示了怎么判断一个ListView里面一个Widget是否进入可视区域的

这是一个新的发现,吓的我赶快在TabBarView里面试了一下。。结果。。。

这个方法能判断出每个Tab相对于自己PageView/TabBarView可视区域的相对位置。通过判断PageView/TabBarView的position.pixels 与offsetToRevealLeading是否相等,来判断当前激活的Tab,但是当有多个PageView/TabBarView的时候。你就搞不清楚到底是哪个算是激活的,因为你需要先判断父PageView/TabBarView是否激活,然后才是子PageView/TabBarView

因为暂时没发现有什么好的方法区分,只是先暂时放弃,如果你有好的idea,请告诉我,万分感谢

后来我又找到个一个API (localToGlobal)

/// Convert the given point from the local coordinate system for this box to  /// the global coordinate system in logical pixels.  ///  /// If `ancestor` is non-null, this function converts the given point to the  /// coordinate system of `ancestor` (which must be an ancestor of this render  /// object) instead of to the global coordinate system.  ///  /// This method is implemented in terms of [getTransformTo].  Offset localToGlobal(Offset point, { RenderObject ancestor }) {    return MatrixUtils.transformPoint(getTransformTo(ancestor), point);  }复制代码

大概的意思是。。可以算出目标跟指定对象(ancestor)的相对位置。。结果如下

你能看出来什么吗? 哇塞,跟我想的一样,完美,用一个图表示为

这看起来是一条路。。

现在我们回到最上面那个,想解决这个issue我们还将遇到以下问题:

1.我们需要知道什么时候TabBarView/PageView的Page改变了。

为此我再次使用了熟悉的好东西 我的当中大量使用到它

if (widget.keepOnlyOneInnerNestedScrollPositionActive) {      ///get notifications and compute active one in _innerController.nestedPositions      return NotificationListener
( onNotification: (ScrollNotification notification) { if (notification is ScrollEndNotification && notification.metrics is PageMetrics && notification.metrics.axis == Axis.horizontal) { final PageMetrics metrics = notification.metrics; var depth = notification.depth; final int currentPage = metrics.page.round(); var page = _pageMetricsList[depth]; //ComputeActivatedNestedPosition only when page changed if (page != currentPage) { print("Page changed ${currentPage}"); _coordinator._innerController ._computeActivatedNestedPosition(notification); } _pageMetricsList[depth] = currentPage; } return false; }, child: child);复制代码

使用监听PageMetrics,并且在Page changed时候通知去计算当前在可视区域的NestedPosition.

2.只用localToGlobal 这个玩意就足够了吗??

答案是不够的,因为ScrollEndNotification的时机还是不足够精确,导致会出现0.4,0.9之类的误差。。

解决方法:

1.加了一个100 milliseconds的延迟来执行计算

2.最后在结算与0的相比的值的时候做了个误差计算(因为不同Page的差至少为一个屏幕的差距,所以1的误差是可以忍受的)

void _computeActivatedNestedPosition(ScrollNotification notification,      {
Duration delay: const Duration(milliseconds: 100)}) { ///if layout is not completed, the data will has some gap. ///need more accurate time to compute ///delay it in case. ///to do Future.delayed(delay, () { /// this is the page changed of PageView's renderBox, /// it maybe not the renderBox of [nestedPositions] /// because it maybe has more one tabbarview or pageview in NestedScrollView body final RenderBox pageChangedRenderBox = notification.context.findRenderObject(); int activeCount = 0; nestedPositions.forEach((item) { item._computeActived(pageChangedRenderBox); if (item._isActived) activeCount++; }); if (activeCount > 1) { print( "activeCount more than 1, please report to zmtzawqlp@live.com and show your case."); } coordinator.updateCanDrag(); }); }复制代码

3.你以为这样就可以搞定了吗?

错了,我们忘记考虑padding和margin.

比如我给TabBarView的每个页面的List加了个一个PaddingEdgeInsets.only(left: 190.0),,让我们看看会有什么效果。

那我们怎么处理这个问题呢?从原因上面看通过_NestedScrollPosition的context得到的RenderBox只是这个List的RenderBox的区域,它跟PageView/TabBarView的RenderBox的相对位置不一定总会存在offset.x为0的状况,就像上面加了padding和margin一样

解决方式如下: position 是List跟PageView/TabBarView的相对位置 size 是List跟PageView/TabBarView 大小的差距

通过这样的计算就能抵消padding和margin的影响,当然我这里没有再考虑transform这种东西了。。放过我吧。。

顺手送个Size的获取方式,RenderBox 有个Size属性

final Offset position = child.localToGlobal(Offset.zero, ancestor: parent);    ///remove the margin/padding    final Offset size = Offset(parentSize.width - child.size.width,        parentSize.height - child.size.height);    ///if layout is not completed, the data will has some gap.    ///need more accurate time to compute    ///to do    bool childIsActivedInViewport = ((position.dx - size.dx).abs() < 1 &&        (position.dy - size.dy).abs() < 1);复制代码

4.完美,perfect,beautiful??

忘记考虑多个TabBarView/PageView对结果的影响

为啥会出现这种情况呢? 因为开始我是使用的从ScrollEndNotification的Context计算出来的RenderBox,注意这个是不管你是哪个TabBarView/PageView的Page发生变化的,

但是其实上,比如Tab0切换到Tab1的时候。你应该关心的是Tab1 下面的Tab10,Tab11,Tab12,Tab13的状态,Tab0下面应该都是不激活的.

其实我们应该还要找到_NestedScrollPosition所对应的PageView/TabBarView,计算_NestedScrollPosition和PageView/TabBarView的相对位置。

所以判断_NestedScrollPosition是否为当前可视区域的激活的条件应该如下:

1.ScrollEndNotification的RenderBox和_NestedScrollPosition的RenderBox的相对位置符合

2._NestedScrollPosition对应的PageView/TabBarView的RenderBox跟_NestedScrollPosition的RenderBox的相对位置符合

打印结果也证明了这点:

5.结束了??

没有,localToGlobal这个方法,在一种情况下会报错。

进入localToGlobal中,再进去getTransformTo

Matrix4 getTransformTo(RenderObject ancestor) {    assert(attached);    if (ancestor == null) {      final AbstractNode rootNode = owner.rootNode;      if (rootNode is RenderObject)        ancestor = rootNode;    }    final List
renderers =
[]; for (RenderObject renderer = this; renderer != ancestor; renderer = renderer.parent) { assert(renderer != null); // Failed to find ancestor in parent chain. renderers.add(renderer); } final Matrix4 transform = Matrix4.identity(); for (int index = renderers.length - 1; index > 0; index -= 1) renderers[index].applyPaintTransform(renderers[index - 1], transform); return transform; }复制代码

这里可能会触发

assert(renderer != null); // Failed to find ancestor in parent

分析:说明你提供的ancestor 跟_NestedScrollPosition 没有关联,这时候我们直接try catch, 设置为不激活状态就好了。。

6.应该可以睡觉了吧

可以,但是我还想说2点。

1.如果当计算之后,有超过2个的nestedPositions,请告诉我一下,看看你那个复杂的case是啥(实际上,demo里面栗子已经是很复杂的了)

int activeCount = 0;    nestedPositions.forEach((item) {      item._computeActived(pageChangedRenderBox);      if (item._isActived) activeCount++;    });    if (activeCount > 1) {      print(          "activeCount more than 1, please report to zmtzawqlp@live.com and show your case.");    }复制代码

2.

我只考虑了NestedScrollView滚动方向是垂直而且PageView/TabBarView是水平滚动的情况.

如果你有啥子妖魔鬼怪的布局,你可以试试老的

最后放上 Github ,如果你有什么不明白的地方,请告诉我。

转载地址:http://keiel.baihongyu.com/

你可能感兴趣的文章
.NET深入 c#数据类型2
查看>>
小程序(一)
查看>>
notepad++实用快捷键
查看>>
word实现转换成图片的实现
查看>>
Xamarin 2.0编译报错缺少Google Maps Library
查看>>
新手用WPF山寨QQ管家7.6(二)
查看>>
15条常用的视频音频编辑脚本命令
查看>>
转:算法合集之《从一道题目的解法试谈网络流的构造与算法》
查看>>
迷你MVVM框架 avalonjs v6.1发布,性能大幅提高
查看>>
effective c++ 条款12 赋值对象时勿忘其每一个成员
查看>>
Oracle Class7. 异常和游标管理
查看>>
常用工具小方法
查看>>
Java 中this的用法和静态方法的注意事项
查看>>
程序算法与人生选择
查看>>
修改Hadoop作业调度算法过程解析
查看>>
字符串循环移位
查看>>
java date相关
查看>>
XCode与Git的完美融合,不再依赖其它Git客户端
查看>>
What's the difference between a stub and mock?
查看>>
Revit中如何自定义快捷键
查看>>