HarmonyOS列表组件-ListContainer

开发 OpenHarmony
我们在app开发中,列表组件绝对是使用场景最高的组件之一,鸿蒙为我们提供了ListContainer列表组件,它是一个是用来呈现连续、多行数据的组件。

[[417156]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

前言

我们在app开发中,列表组件绝对是使用场景最高的组件之一,鸿蒙为我们提供了ListContainer列表组件,它是一个是用来呈现连续、多行数据的组件,继承自ComponentContainer,因此它是一个容器组件,使用BaseItemProvider来存储对象。

正文

这里先简单介绍下ListContainer的基本用法:

1.在layout文件中声明ListContainer控件;

2.定义列表控件的适配器ListItemProvider;

3.在Ability中给ListContainer设置数据;

只需要三步就可以实现最基本的列表效果,这里就不贴代码了,官方文档有比较详细的说明,本文重点分析下如何通过自定义ListContainer来

实现子组件弧形排布的效果,并且随着半径和镜像距离的改变子组件的排布也不断变化,效果如下:

因为ListContainer的子组件默认是直线排列,可以通过设置LayoutManager(布局管理器)来改变子组件排列方式,但是官方只提供了TableLayoutManager(网格)和DirectionalLayoutManager(线性)两种布局管理器,很显然无法满足需求,于是设想自定义一个TurnLayoutManager继承DirectionalLayoutManager,然后重写相关方法对子组件重新排列:

然而事情并非如预想一般简单,DirectionalLayoutManager并没有对应的方法,它的父类LayoutManager也没有,惊不惊喜,意不意外?!

  1. public abstract class LayoutManager { 
  2.     public LayoutManager() { 
  3.         throw new RuntimeException("Stub!"); 
  4.     } 
  5.  
  6.     public void setOrientation(int orientation) { 
  7.         throw new RuntimeException("Stub!"); 
  8.     } 
  9.  
  10.     public int getOrientation() { 
  11.         throw new RuntimeException("Stub!"); 
  12.     } 

但是令人欣慰的是ListContainer并不是必须设置布局管理器子组件才能显示出来,于是一个大胆的念头在我的脑海中闪现:何不从ListContainer本身入手,自定义TurnListContainer类继承ListContainer,因为ListContainer继承自ComponentContainer,可以在onArrange()回调方法中修改子组件的位置以达到预期效果,事不宜迟,说干就干:

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

  1. @Override 
  2. public void onArrange() { 
  3.     //计算圆心 
  4.     this.center = deriveCenter(gravity, getOrientation(), radius, peekDistance, center); 
  5.     //设置子组件偏移     
  6.     setChildOffsets(); 

2.调用child.arrange()方法修改子组件位置(因为本文重点讲解自定义ListContainer中遇到的问题,因此圆心、子组件的坐标计算过程就不赘述了,熟悉三角函数就很容易看懂)

  1. public void setChildOffsetsVertical() { 
  2.     //遍利修改每一个子组件的位置 
  3.     for (int ii = 0; ii < getChildCount(); ii++) { 
  4.         Component child = getComponentAt(ii); 
  5.         if (child == null) { 
  6.             continue
  7.         } 
  8.         LayoutConfig layoutParams = child.getLayoutConfig(); 
  9.         //计算x轴偏移量 
  10.         final int offsetX = (int) resolveOffsetX(radius, child.getContentPositionY() +child.getHeight() / 2.0f, 
  11.                 center, peekDistance); 
  12.         final int x = gravity == Gravity.START ? offsetX + layoutParams.getMarginLeft() 
  13.                 : getWidth() - offsetX - child.getWidth() - getMarginStart(layoutParams); 
  14.         //调用子组件的arrange方法修改自身位置 
  15.         child.arrange(x, child.getTop(), child.getWidth(), child.getHeight()); 
  16.     } 

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

  1. /** 
  2.     * 设置半径 
  3.     * 
  4.     * @param radius 半径 
  5.     */ 
  6.    public void setRadius(int radius) { 
  7.        this.radius = Math.max(radius, MIN_RADIUS); 
  8.        postLayout(); 
  9.    } 
  10.  
  11.    /** 
  12.     * 设置镜像距离 
  13.     * 
  14.     * @param peekDistance 镜像距离 
  15.     */ 
  16.    public void setPeekDistance(int peekDistance) { 
  17.        this.peekDistance = Math.min(Math.max(peekDistance, MIN_PEEK), radius); 
  18.        postLayout(); 
  19.    } 
  20.  
  21.    /** 
  22.     * 设置水平方向 
  23.     * 
  24.     * @param gravity 水平方向 
  25.     */ 
  26.    public void setGravity(@Gravity int gravity) { 
  27.        this.gravity = gravity; 
  28.        postLayout(); 
  29.    } 
  30.  
  31.    /** 
  32.     * 设置文字旋转 
  33.     * 
  34.     * @param isRotate 文字是否旋转 
  35.     */ 
  36.    public void setRotate(boolean isRotate) { 
  37.        this.isRotate = isRotate; 
  38.        postLayout(); 
  39.    } 

准备工作告一段落,开始测试, what? 满心期待的结果并没有出现,除了设置文字旋转有效果,修改半径,镜像距离,水平方向都没效果。。。。。。这翻车来得太快就像龙卷风.

我开始陷入漫长的沉思中。。。。。。,尝试了N多种方法后依然无果,最后分析认为:我是在ListContainer的onArrange()回调中调用了子组件的onArrange()方法,有可能这两个onArrange()方法存在冲突导致子组件本身的onArrange()失效,带着些许疑问我修改了代码,设置半径、镜像距离时不用调用postLayout()来请求重新布局,直接调用child.arrange()更新子组件位置,代码修改后效果如下:

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

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

  1. @Override 
  2. public void scrolledStageUpdate(Component component, int newStage) { 
  3.     switch (newStage) { 
  4.         case Component.SCROLL_IDLE_STAGE: 
  5.             //触摸滚动 
  6.             break; 
  7.         case Component.SCROLL_AUTO_STAGE: 
  8.             //惯性滚动 
  9.             break; 
  10.         case Component.SCROLL_NORMAL_STAGE: 
  11.             //停止滚动 
  12.             break; 
  13.     } 

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

  1. @Override 
  2. public boolean onTouchEvent(Component component, TouchEvent touchEvent) { 
  3.     switch (touchEvent.getAction()){ 
  4.         case TouchEvent.PRIMARY_POINT_DOWN: 
  5.             //按下时设置禁止滑动 
  6.             setEnabled(false); 
  7.             break; 
  8.         case TouchEvent.PRIMARY_POINT_UP: 
  9.             //抬起时设置可以滑动 
  10.             setEnabled(true); 
  11.             break; 
  12.         default
  13.             return true
  14.     } 
  15.     return false

但是经过测试,两种方法都没法立即停止惯性滚动,也就是说没有办法来干预ListContainer的滚动状态,至少目前我没有找到阻止惯性滚动的相关API,那么,只能再尝试其他方法了,。。。。。。。。。。。。。。又一次我陷入漫长的沉思中。。。。。。,在尝试了各种方法都以失败告终后,最终在我锲而不舍的努力下终于得以解决,这是这个项目中我遇到的最大的坑没有之一,耗费了太多时间和精力,鸭梨好大呀,罢了罢了。。。,话不多说,直接看正解吧:

  1. public void setChildOffsetsVertical() { 
  2.     for (int ii = 0; ii < getChildCount(); ii++) { 
  3.         Component child = getComponentAt(ii); 
  4.         if (child == null) { 
  5.             continue
  6.         } 
  7.         LayoutConfig layoutParams = child.getLayoutConfig(); 
  8.         //计算x轴偏移量 
  9.         final int offsetX = (int) resolveOffsetX(radius, child.getContentPositionY() + child.getHeight() / 2.0f, 
  10.                 center, peekDistance); 
  11.         final int xx = gravity == Gravity.START ? offsetX + layoutParams.getMarginLeft() 
  12.                 : getWidth() - offsetX - child.getWidth() - getMarginStart(layoutParams); 
  13.        //调用子组件的setTranslationX方法修改自身x轴偏移量 
  14.         child.setTranslationX(xx); 
  15.         //设置子组件旋转 
  16.         setChildRotationVertical(gravity, child, radius, center); 
  17.     } 

对,没有错,就只是修改了一行代码,用child.setTranslationX()替换child.arrange(),就这么简单,不管你相不相信它就是这么神奇,之所以说神奇是因为看不到源码不知道ListContainer的内部滚动机制:

经过许多波折最终达到了预期的效果,肝都要爆了, 其实一开始并不觉得项目本身有多复杂,计算量也不大,直到开始做的时候问题才一一显现出来,不得不感慨,人生路上哪有那么多的顺风顺水顺心事,总会有一些波折和苦难不合时宜的出现,磕磕绊绊的人生才是完整的。。。。。。,写这个文章主要是分享下开发中我遇到的坑(主要还是想抒发下被代码虐了千百遍的爆炸心态),避免后面再有人误入歧途,浪费宝贵的时间。

结束

下面是技术总结:

1.使用postLayout()请求重新布局后再调用child.arrange(),会导致child.arrange()失效;

  1. @Override 
  2. public boolean onArrange(int i, int i1, int i2, int i3) { 
  3.     child.arrange();//此时设置子组件位置无效 
  4.     return false

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

3.监听滚动事件

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

4.setEnable(false)

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

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2020-12-28 10:15:18

鸿蒙HarmonyOSListContain

2021-08-25 09:49:48

鸿蒙HarmonyOS应用

2022-04-24 15:17:56

鸿蒙操作系统

2021-12-20 20:51:44

鸿蒙HarmonyOS应用

2021-06-22 09:44:56

鸿蒙HarmonyOS应用

2021-01-08 09:55:17

鸿蒙HarmonyOS组装列表

2021-08-02 14:54:50

鸿蒙HarmonyOS应用

2021-11-01 10:21:36

鸿蒙HarmonyOS应用

2021-01-21 09:45:36

鸿蒙HarmonyOS分布式

2021-03-17 09:35:09

鸿蒙HarmonyOS应用开发

2021-03-30 09:45:07

鸿蒙HarmonyOS应用开发

2021-10-26 15:22:52

鸿蒙HarmonyOS应用

2021-08-24 14:57:27

鸿蒙HarmonyOS应用

2022-05-19 15:59:23

组件焦点鸿蒙

2021-03-26 09:35:35

鸿蒙HarmonyOS应用开发

2021-03-31 15:49:34

鸿蒙HarmonyOS应用

2022-10-26 15:54:46

canvas组件鸿蒙

2022-10-25 15:12:24

自定义组件鸿蒙

2021-03-03 09:42:26

鸿蒙HarmonyOS图片裁剪

2022-07-06 20:24:08

ArkUI计时组件
点赞
收藏

51CTO技术栈公众号