鴻蒙開源第三方元件——自定義流式佈局元件FlowLayout_ohos

前言

基於安卓平臺的自定義流式佈局元件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 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()方法將標籤內容新增到元件中。

// 設定 AdapterFlowAdapter 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中的元素資訊,並將其賦給各子元件即可。

@Overridepublic 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);

專案貢獻人

陳叢笑 鄭森文 朱偉 陳美汝 蔡志傑