鸿蒙开源第三方组件——自定义流式布局组件FlowLayout_ohos 原创 精华

发布于 2021-7-23 18:45
浏览
14收藏

 前言 

           基于安卓平台的自定义流式布局组件FlowLayout(https://blog.csdn.net/fzhhsa/article/details/103003019),实现了鸿蒙的功能化迁移和重构。代码已经开源到(https://gitee.com/isrc_ohos/flow-layout_ohos),欢迎各位开发者下载使用并提出宝贵意见!

背景

        流式布局也叫百分比布局,它具有指定的对齐方式、水平间隙和垂直间隙,特别适用于多标签的展示,可以实现组件中的标签横向对齐,也可以在多个标签的总宽度超过组件宽度时自动换行,是移动端开发中经常使用的布局方式之一。我们可以在很多应用场景下看到流式布局的使用,比如商品分类展示,搜索记录展示等。

组件效果展示

        该组件应用只包含一个显示页面。为了呈现出流式布局的效果,我们在页面布局中添加了多个标签,如“java”、“kotlin”、“ohos”、“Deveco-studio”、“app”等作为布局中的子组件。具体显示效果如图1所示。

鸿蒙开源第三方组件——自定义流式布局组件FlowLayout_ohos-开源基础软件社区图1 组件效果展示

Sample解析

        FlowLayout_ohos在Library中已经封装了组件的主要功能,往FlowLayout_ohos组件中放入标签会自动横向对齐并且在多个标签的总宽度超过组件宽度时自动换行,因此在Sample中我们只需要添加标签内容并使用流式布局将标签内容进行显示即可。

        在标签显示的过程中,我们可以调用一些Library暴露的接口来对子组件的显示特征进行设置,比如组件最多显示的行数等。下面将具体讲解FlowLayout_ohos组件的使用方法,共分为5个步骤:

步骤1. 导入相关类

步骤2. 初始化流式布局和数据容器

步骤3. 添加标签内容到数据容器

步骤4. 将标签内容添加进布局

步骤5. 相关特征设置

        接下来我们来看一下每一个步骤涉及的详细操作。

(1)导入相关类

        在MainAbilitySlice文件中,通过import关键字导入FlowAdapter类和FlowLayout类。FlowLayout类用于组件的显示,FlowAdapter类用于向组件设置标签。

import com.huawei.mylibrary.FlowAdapter;
import com.huawei.mylibrary.FlowLayout;

(2)初始化流式布局和数据容器

         实例化FlowLayout类的对象mFlowLayout ,然后创建元素为String类型的列表mContentList作为添加标签的容器,以下我们称之为数据容器。

private FlowLayout mFlowLayout;
private List<String> mContentList = new ArrayList<>();
@Override 
public void onStart(Intent intent) {
      ...... 
      mFlowLayout = new FlowLayout(this);
}

(3)添加标签内容到数据容器

       通过add()方法向数据容器mContentList中添加想要展示的标签,5个不同的标签通过for循环循环四次逐个放入容器,共形成20个需要在页面展示的标签。

for (int i = 0; i < 4; i++) {
      mContentList.add("java");
      mContentList.add("kotlin");
      mContentList.add("ohos");
      mContentList.add("Deveco-studio");
      mContentList.add("app");  
}

(4)将标签内容添加进布局

      实例化FlowAdapter类的对象adapter,并将数据容器mContentList作为FlowAdapter类构造方法的参数。后通过setAdapter()方法将标签内容添加到组件中。

// 设置 Adapter
FlowAdapter adapter = new FlowAdapter(this, mContentList);
// 将标签内容添加到组件中
mFlowLayout.setAdapter(adapter);

(5) 将标签内容添加到组件中

mFlowLayout.setAdapter(adapter);

(5)相关特征设置

      mFlowLayout可以调用一些Library暴露的接口实现流式布局的特征设置,这里我们设置了组件布局内最多显示的行数。

// 设置最多显示的行数
mFlowLayout.setMaxLines(9);

Library解析

      流式布局应用非常广泛,但鸿蒙官方却并未给出相应的布局方式,因此流式布局只能自定义实现,本节主要介绍自定义布局的步骤。

      想要实现自定义布局,需要完成以下三个步骤:1)流式布局的FlowLayout类需要继承ComponentContainer类,并添加构造方法。2) 实现ComponentContainer.EstimateSizeListener接口,重写onEstimateSize()方法,用于确定FlowLayout_ohos组件宽高。3)实现Component.LayoutRefreshedListener接口,重写onRefreshed()方法用来排列子组件并确定子组件位置。1)步骤的操作较为简单,此处不再赘述,本节主要描述2)、3)步骤的原理。

(1)重写onEstimateSize方法

         根据onEstimateSize(int widthMeasureSpec, int heightMeasureSpec)方法传入的参数,选择测量组件宽度和高度的方式,并得到组件宽度和高度的具体值,通过setEstimatedSize()方法设置给组件。下面介绍具体的步骤:

1、得到组件的测量模式和父组件的宽度、高度

  • 调用EstimateSpec.getMode(widthMeasureSpec)方法,传入widthMeasureSpec参数,得到组件宽度的测量模式。
  • 调用EstimateSpec.getMode(heightMeasureSpec)方法,传入heightMeasureSpec参数,得到组件高度的测量模式。
  • 调用EstimateSpec.getSize(widthMeasureSpec)方法,传入widthMeasureSpec参数,得到父组件的宽度。
  • 调用EstimateSpec.getSize(heightMeasureSpec)方法,传入heightMeasureSpec参数,得到父组件的高度。
int widthSize = EstimateSpec.getSize(widthMeasureSpec);//父组件的宽度
int widthMode = EstimateSpec.getMode(widthMeasureSpec); //组件宽度的测量模式
int heightSize =EstimateSpec.getSize(heightMeasureSpec);//父组件的高度
int heightMode = EstimateSpec.getMode(heightMeasureSpec);//组件高度的测量模式

2、确定组件宽度和高度的具体值

       widthMode /heightMode 可能存在两种不同的模式,在不同的模式下组件的宽度和高度的值也会有不同的计算方式。

  •  PRECISE 模式:在这种模式下,组件设置其宽、高为MATCH_PARENT。
  • NOT_EXCEED 模式:在这种模式下,组件设置其宽、高为MATCH_CONTENT 。

        在PRECISE 模式下,组件的宽度和高度与父组件一致,这种计算方式较为简单。但是在NOT_EXCEED 模式下,组件的宽度和高度是根据子组件的宽度和高度来决定的,此时需要遍历各子组件,对每个子组件进行测量,并在宽度和高度上求和,才能计算出最终的组件的宽高。子组件的遍历过程是通过helper()方法来实现的。 

int[] a = helper(widthSize); 
int measuredHeight = 0;   //组件的高度值
if (heightMode == EstimateSpec.PRECISE) {  // PRECISE 模式
	measuredHeight = heightSize;
}
else if (heightMode == EstimateSpec.NOT_EXCEED) {  // NOT_EXCEED 模式
	measuredHeight = a[0]; //遍历各子组件后得到的组件高度
}
int measuredWidth = 0;   //组件的宽度值
if (widthMode == EstimateSpec.PRECISE) {        // PRECISE 模式
	measuredWidth = widthSize;   
}else if (widthMode == EstimateSpec.NOT_EXCEED) { // NOT_EXCEED 模式
	measuredWidth = a[1];  //遍历各子组件后得到的组件宽度
}

3、将测量得到的高度和宽度值设置给组件。

        通过setEstimatedSize()方法,将步骤2中得到的组件宽度和高度值设置给组件。

setEstimatedSize(measuredWidth, measuredHeight);

(2)重写onRefreshed方法

         onRefreshed()方法主要用来确定子组件的摆放位置。该位置在helper()方法中已经得到,并保存在mChildrenPositionList中。mChildrenPositionList是一个元素类型为Rect的列表,每一个元素代表一个子组件的位置信息。因此,在确定子组件的摆放位置时,只需要调用mChildrenPositionList中的元素信息,并将其赋给各子组件即可。

@Override
public void onRefreshed(Component component) {
            int n = Math.min(getChildCount(), mChildrenPositionList.size());
            for (int i = 0; i < n; i++) {
                Component child = getComponentAt(i);  //获取各组件
                Rect rect = mChildrenPositionList.get(i); //组件信息
                child.setLeft(rect.left);  //组件位置设置
                child.setRight(rect.right);
                child.setBottom(rect.bottom);
                child.setTop( rect.top);
            }
            mVisibleItemCount = n;
    }

(3)helper()方法

        helper()方法是一个“工具”方法,在onEstimateSize()和onRefreshed()的重写中都提供了“帮助”。helper()方法对外提供的功能,主要为以下三个方面:

1)在组件的布局方式为MATCH_CONTENT情况下,遍历各子组件,对每个子组件的宽度和高度进行测量,并在宽度和高度上求和,计算出最终组件的宽度和高度。

2)判断换行条件,实现流动布局的效果。

3)保存子组件的位置信息。

       下面我们将围绕上述内容展开讲解。

   1)计算组件宽度和高度

  • 组件的宽度

       组件的宽度取决于子组件的排布是否存在换行的情况,若是子组件排布存在换行的情况,组件宽度等于父组件的宽度。若是子组件排布不存在换行的情况,组件宽度等于当前行的宽度。代码中isOneRow表示是否存在换行的情况,width 表示当前行的宽度,widthSize表示父组件的宽度,各变量的示意如图2所示。

鸿蒙开源第三方组件——自定义流式布局组件FlowLayout_ohos-开源基础软件社区图2 代码变量示意图

int childWidth =  child.getMarginLeft() + child.getEstimatedWidth() + child.getMarginRight(); //每个子组件的宽度
width += childWidth;  //每行的宽度
...
res[1] = isOneRow? width + getPaddingRight() : widthSize; //组件的宽度
  • 组件的高度

      组件的高度是每一行子组件高度的总和,而每一行的高度则是取该行中所有子组件中最高的值。

int childHeight =child.getMarginTop() + child.getEstimatedHeight() + child.getMarginBottom();
maxHeight = Math.max(maxHeight, childHeight); //取最大值
...
res[0] = height + maxHeight + getPaddingBottom(); //组件的高度 

2)判断换行条件

      从效果图中可以看到,FlowLayout_ohos组件的布局是一行行的,如果当前行的剩余宽度已经放不了下一个子组件,那就把这个子组件移到下一行显示。

      所以我们需要计算当前行已经占据的宽度加上下一个子组件的宽度是否超过组件的最大宽度,以判断下一个子组件是否需要换行显示。

if (width + childWidth + getPaddingRight() > widthSize) { //需要换行
   height += maxHeight; // 增加一行的高度
    width = getPaddingLeft(); // 获取新一行已经占据的宽度
    maxHeight = childHeight; 
   isOneRow = false;
   currLine++;  //行数+1
    if (currLine > mMaxLines) {  //超过设定的最大显示行数,退出
        break;
   }
}

3)保存子组件的位置信息

      根据当前已有的宽高,确定子组件的位置,并将位置信息作为参数传入Rect 类实例化对象的过程中,用Rect 类对象标识子组件的位置信息,并将这些信息逐个放入List中,在onRefreshed()方法中被使用到。

Rect rect = new Rect(width +child.getMarginLeft(),
        height + child.getMarginTop(),
        width + childWidth - child.getMarginRight(),
        height + childHeight - child.getMarginBottom());
mChildrenPositionList.add(rect);

 项目贡献人

陈丛笑 郑森文 朱伟 陈美汝 蔡志杰 

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2021-7-26 09:41:49修改
15
收藏 14
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐