其他分享
首页 > 其他分享> > 2021-05-26

2021-05-26

作者:互联网

鸿蒙组件-自定义listContainer 遇到的问题

前言

鸿蒙的ListContainer是一个类似recyclerview的组件,提供在屏幕上向上或向下滚动时显示的列表容器。这个类继承自ComponentContainer,使用BaseItemProvider或RecycleItemProvider来存储对象。

 

正文

最近在移植一个安卓的三方库------一个RecyclerView列表,通过自定义LayoutManager来实现子view弧形排布的效果,并且随着半径和镜像距离的改变子view的排布也不断变化,效果如下:

安卓效果

最初的设想是按照安卓的逻辑,自定义一个LayoutManager继承DirectionalLayoutManager,然后重写相关方法来重新计算子view位置,但是却发现该类中只有设置方向的方法,,类似android中的scrollVerticallyBy,scrollHorizontallyBy,onLayoutChildren,removeAndRecycleAllViews()方法统统都没有。

鸿蒙LayoutManager

这显然是无法满足需求,再加上ListContainer不像RecyclerView,不设置布局管理器子view就显示不出来,于是决定从ListContainer本身入手,新建TurnListContainer类继承ListContainer,虽然实现方式和安卓略有不同,但是思路要保持一致:

1.实现ComponentContainer.ArrangeListener接口,重写onArrange()方法,在该方法中计算圆心,及x,y坐标偏移量(列表是垂直方向时计算x轴偏移量,水平方向时计算y轴偏移量)

2.调用child.arrange()方法修改子view位置,因为本文是排坑指南,因此圆心、view的坐标计算过程就不赘述了,熟悉三角函数就很容易看懂,有兴趣的可以下载代码了解一下

3.在修改半径、镜像距离、方向、文字旋转角度时,调用Component的postLayout()方法请求重新进行测量、布局、绘制这三个流程来更新位置,因为我的子view是provider提供的,不牵扯测量、和绘制过程,调用postLayout()的目的只是触发onArrange回调对子view位置修改

准备工作告一段落,开始测试, what? 结果并不像预期那样,除了设置文字旋转有效果,修改半径,镜像距离,水平方向都没效果,。。。。。。无情的现实浇灭了我所有的热情。

 

陷入沉思中。。。。。。,

尝试了N多种方法后,最终经过不懈努力终于发现了问题,设置半径、镜像距离时不用调用postLayout()来请求重新布局,直接调用child.arrange()更新子View位置即可,(好坑啊,这个问题一度让我放弃了继承listContainer,采用继承ComponentContainer实现,走了好多弯路),至于为什么会这样,说实话我也不知道,后面有时间了深入研究下,代码修改后效果如下:

怎么样,效果还可以吧,修改半径、镜像距离,方向都能达到预期效果,但是细心的小伙伴一定观察到了异常,。。。对,静止状态下是没有问题的,一旦开始滚动就出现原始位置和修改后位置交替出现的情况,为什么呢??,因为看不到源码不知道listContainer滚动中的刷新逻辑,只能推测滚动事件过程中肯定是触发了重新布局的方法,导致view位置被反复重置,那就从滚动事件开始入手吧,我的思路是监听滚动状态,如果已经开始滑动了,改变滚动状态跳过惯性滚动直接停止滚动:

方法1:ListContainer.ScrolledListener监听滚动,惯性滚动时设置setEnabled(false)

方法2:Component.TouchEventListener监听滚动,手指抬起时设置listContainer.setEnabled(false)

但是经过测试,两种方法都没法立即停止惯性滚动,也就是说一旦listContainer开始滚动了就只能看着他慢慢滚动直到结束,至少目前我没有找到阻止惯性滚动的方法,好吧,只能再尝试其他方法了,。。。。。。。。。。。。。。在我尝试了各种方法都以失败告终后,最后请教大佬后才得以解决,这是这个项目中我遇到的最大的坑没有之一,耗费好太多时间和精力,我太难了。。。。。。

说话的方式简单点,递进的情绪就省略了,直接看正解吧:

对,没有错,就只是修改了一行代码,用child.setTranslationX()替换child.arrange(),就这么简单,不管你相不相信他就是这么神奇,不管怎么滚动位置都不会被重置了:

以上的努力后,效果基本和安卓差不多了,但是你以为这就完了吗,并没有,还记得安卓的效果吗,我们对比一下效果:

打开旋转开关后,安卓的效果是不光文字由垂直方向变成了绕圆弧方向显示,而且列表两端的圆消失了,而我的效果只是文字发生了旋转;

研究了原项目代码并没有发现隐藏view的代码,就连设置透明度的方法也没有,并且查看日志发现,打开和关闭旋转状态下的子view位置是一样的,并没有发生改变,所以问题只能出在这:child.setRotation(radius),这是设置view绕y轴旋转角度的方法,默认锚点是view中心,测试发现安卓的child.setRotation(radius)存在问题,如果传入的角度是NaN,这个view就消失了,网上搜了一下发现这个方法确实有问题,跟机型或系统版本有关,有兴趣可以研究下,鸿蒙的child.setRotation(radius)这个方法不会出现这样的问题,应该是内部做了判断

if(Float.isNaN(angle)){
    angle=0;
}

 

总结

到此,经过许多波折,项目最终达到了预期的效果,其实项目本身并不复杂,计算量也不大,主要是分享下我遇到的坑,避免后面再有人误入歧途,浪费宝贵的时间

 

下面是技术总结:

 

1. child.arrange()会触发listContainer的滚动刷新机制,反复重置位置

鸿蒙调用child.arrange()修改子view位置一切正常,但是listContainer滚动中位置会被频繁重置,如果涉及到修改子view位置的,出现滚动中位置被反复重置的,可以尝试用child.setTranslationX(x)和child.setTranslationX(y)来代替;

 

2.安卓的child.setRotation()与鸿蒙的child.setRotation()

android的setRotation(degree)方法传入NaN会导致view消失,

而鸿蒙的setRotation(degree)内部应该做了判断,如果传入的值是NaN会把值处理成0,并不会导致view消失

 

3. 监听滚动事件

android中有scrollVerticallyBy和scrollHorizontallyBy回调来监听横向滚动和垂直滚动,鸿蒙可以实现ListContainer.ScrolledListener接口或者Component.TouchEventListener接口监听,我这里只所以选择实现ListContainer.ScrolledListener是因为可以重写它的两个方法,onContentScrolled监听滚动中变化和scrolledStageUpdate监听滚动状态变化,会比TouchEventListener方便些

 

4.setEnable(false)

这个方法可以禁止listContainer滚动,但是如果listContainer已经开始滚动了再设置setEnable(false)并不会阻止listContainer惯性滚动,禁止惯性滚动的方法目前还没有找到

 

传送门:代码地址

标签:26,滚动,05,listContainer,安卓,2021,child,方法,view
来源: https://blog.csdn.net/maosizouguo/article/details/117292734