自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

HarmonyOS自定義控件之測量與布局

系統(tǒng) OpenHarmony
這里我們來分析一下測量與布局的用法,并且結(jié)合上一篇文章事件分發(fā)一起實現(xiàn)一個簡單的滾動視差布局ParallaxLayout。

[[417822]]

想了解更多內(nèi)容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

在HarmonyOS中,控件最終展示到用戶界面上,會經(jīng)歷測量(Estimate)、布局(Arrange)、繪制(Draw)等過程。這里我們來分析一下測量與布局的用法,并且結(jié)合上一篇文章事件分發(fā)一起實現(xiàn)一個簡單的滾動視差布局ParallaxLayout。

ParallaxLayout效果圖:

測量Estimate

如何自定義測量過程

首先通過setEstimateSizeListener(Component.EstimateSizeListener listener)來設(shè)置測量的回調(diào):

  1. setEstimateSizeListener(new EstimateSizeListener() { 
  2.     @Override 
  3.     public boolean onEstimateSize(int widthEstimateSpec, int heightEstimateSpec) { 
  4.         return false
  5.     } 
  6. }); 

在EstimateSizeListener 中,第一個參數(shù)為寬度測量參數(shù),第二個為高度測量參數(shù),我們可以通過EstimateSpec來獲取寬度、高度的模式(mode)與大小(size):

  1. int mode = EstimateSpec.getMode(widthEstimateSpec); 
  2. int size = EstimateSpec.getSize(widthEstimateSpec); 

計算了控件最終大小之后,我們可以調(diào)用setEstimatedSize(int estimatedWidth, int estimatedHeight)函數(shù)來設(shè)置最終的測量大小。注意:setEstimatedSize函數(shù)需要的是具體的大小,而非測量參數(shù),即EstimateSpec.getSize后的具體大小

  1. setEstimatedSize(widthSize, heightSize); 

最后,如果需要讓設(shè)置的測量大小生效,我們應(yīng)該在onEstimateSize中返回true:

  1. setEstimateSizeListener(new EstimateSizeListener() { 
  2.     @Override 
  3.     public boolean onEstimateSize(int widthEstimateSpec, int heightEstimateSpec) { 
  4.         int widthSize = EstimateSpec.getSize(widthEstimateSpec); 
  5.         int heightSize = EstimateSpec.getSize(heightEstimateSpec); 
  6.         setEstimatedSize(widthSize, heightSize); 
  7.         return true
  8.     } 
  9. }); 

如果onEstimateSize返回true,那么最終系統(tǒng)不會在native層來測量大小。如果返回了false,系統(tǒng)還是會繼續(xù)測量大小,最終的大小可能與setEstimatedSize設(shè)置的結(jié)果不一致。

在setEstimatedSize后我們就可以通過下面的函數(shù)來獲取我們設(shè)置的大?。?/p>

  1. getEstimatedWidth(); // 獲取測量的寬度 
  2. getEstimatedHeight(); // 獲取測量的高度 

EstimateSpec

EstimateSpec是Component的內(nèi)部類,EstimateSpec提供了一系列的操作測量參數(shù)的方法。

測量參數(shù)

測量參數(shù)是一個int值,它封裝了來自父控件的測量需求。測量參數(shù)由模式與大小兩個int值組合而成,公式如下:

  1. (size & ~EstimateSpec.ESTIMATED_STATE_BIT_MASK) | (mode & EstimateSpec.ESTIMATED_STATE_BIT_MASK) 

其中size為當(dāng)前控件的大小,mode為模式。也可以通過下面的函數(shù)來,通過size和mode來生成一個測量參數(shù):

  1. int spec = EstimateSpec.getSizeWithMode(size, mode); 

模式mode

通過EstimateSpec獲取的mode有三種取值:

  • EstimateSpec.NOT_EXCEED 不超過:該模式表示父控件已經(jīng)規(guī)定了當(dāng)前控件大小的最大值
  • EstimateSpec.PRECISE 精確:該模式表示父控件已經(jīng)規(guī)定了當(dāng)前控件大小的值
  • EstimateSpec.UNCONSTRAINT 無約束:該模式表示父控件對當(dāng)前控件大小沒有約束,控件可以想要任何大小

在不同模式下,控件的大小是如何確定的呢?可以簡單的通過下面的代碼來理解:

  1. // size變量為控件期望的大小,estimateSpec變量為父控件的測量參數(shù) 
  2. final int specMode = EstimateSpec.getMode(estimateSpec); 
  3. final int specSize = EstimateSpec.getSize(estimateSpec); 
  4. final int result; 
  5. switch (specMode) { 
  6.     case EstimateSpec.NOT_EXCEED: 
  7.         result = Math.min(specSize, size); 
  8.         break; 
  9.     case EstimateSpec.PRECISE: 
  10.         result = specSize; 
  11.         break; 
  12.     case EstimateSpec.UNCONSTRAINT: 
  13.     default
  14.         result = size
  • 當(dāng)mode為NOT_EXCEED時,控件的期望大小應(yīng)該小于等于父控件給定的size
  • 當(dāng)mode為PRECISE時,控件的大小應(yīng)該等于父控件給定的size
  • 當(dāng)mode為UNCONSTRAINT時,控件的大小可以為他期望的size

自定義布局

在自定義布局時,我們不僅僅要測量自己的大小,還需要測量子控件的大小。子控件可以通過estimateSize(int widthEstimatedConfig, int heightEstimatedConfig)函數(shù)來設(shè)置測量參數(shù):

  1. child.estimateSize(widthEstimatedConfig, heightEstimatedConfig); 

注意:estimateSize的兩個參數(shù)需要的是測量參數(shù),而非具體的大小。這兩個參數(shù)會傳遞到子控件的onEstimateSize(int widthEstimateSpec, int heightEstimateSpec)回調(diào)中。

默認(rèn)情況下,子控件會根據(jù)widthEstimatedConfig與heightEstimatedConfig來確認(rèn)自己的最終大小,子控件也可以通過setEstimateSizeListener來自定義其測量過程,最終其參考的測量參數(shù)就是我們通過estimateSize函數(shù)設(shè)置的測量參數(shù)。

接下來我們只需要遍歷所有子控件來為他們設(shè)置測量參數(shù)就達(dá)到了測量子控件的大小的目的。自定義布局的測量過程基本就是包含了這兩個步驟:為所有子控件設(shè)置測量參數(shù)以及測量自己的大小。

子控件的測量參數(shù)

那么,最重要的問題是,我們?nèi)绾未_定子控件的測量參數(shù)到底應(yīng)該是多少,換句話說我們?nèi)绾紊苫蛘攉@取子控件的測量參數(shù)呢?子控件的測量參數(shù)與很多因素有關(guān),如父控件的測量參數(shù)、父控件的padding值、子控件自己的期望大小。我們可以根據(jù)這幾個參數(shù)來確定子控件的測量參數(shù)。

這里我們通過一個幫助函數(shù)來生成子控件的測量參數(shù),首先函數(shù)的定義應(yīng)該如下:

  1. /** 
  2.  * 根據(jù)父component的spec、padding以及子component的期望大小,生成子component的spec 
  3.  * @param spec 父component的spec 
  4.  * @param padding 父component的padding 
  5.  * @param childDimension 子component的期望大小 
  6.  * @return 子component的spec 
  7. */ 
  8. public static int getChildEstimateSpec(int spec, int padding, int childDimension); 

注意:childDimension應(yīng)該怎么獲取呢?實際上就是ComponentContainer.LayoutConfig中的width或者h(yuǎn)eight,測量高度就取height、寬度就取width。

接下來我們應(yīng)該根據(jù)父控件的mode以及childDimension來確定子控件的mode與size,并生成測量參數(shù)。具體參考如下代碼:

  1. public static int getChildEstimateSpec(int spec, int padding, int childDimension) { 
  2.         int specMode = EstimateSpec.getMode(spec); 
  3.         int specSize = EstimateSpec.getSize(spec); 
  4.  
  5.         int size = Math.max(0, specSize - padding); 
  6.  
  7.         int resultSize = 0; 
  8.         int resultMode = 0; 
  9.  
  10.         switch (specMode) { 
  11.             // Parent has imposed an exact size on us 
  12.             case EstimateSpec.PRECISE: 
  13.                 if (childDimension >= 0) { 
  14.                     resultSize = childDimension; 
  15.                     resultMode = EstimateSpec.PRECISE; 
  16.                 } else if (childDimension == ComponentContainer.LayoutConfig.MATCH_PARENT) { 
  17.                     // Child wants to be our size. So be it. 
  18.                     resultSize = size
  19.                     resultMode = EstimateSpec.PRECISE; 
  20.                 } else if (childDimension == ComponentContainer.LayoutConfig.MATCH_CONTENT) { 
  21.                     // Child wants to determine its own size. It can't be 
  22.                     // bigger than us. 
  23.                     resultSize = size
  24.                     resultMode = EstimateSpec.NOT_EXCEED; 
  25.                     if (size == 0) { 
  26.                         // size will not be 0 when resultMode is NOT_EXCEED, don't know why 
  27.                         resultMode = EstimateSpec.PRECISE; 
  28.                     } 
  29.                 } 
  30.                 break; 
  31.  
  32.             // Parent has imposed a maximum size on us 
  33.             case EstimateSpec.NOT_EXCEED: 
  34.                 if (childDimension >= 0) { 
  35.                     // Child wants a specific size... so be it 
  36.                     resultSize = childDimension; 
  37.                     resultMode = EstimateSpec.PRECISE; 
  38.                 } else if (childDimension == ComponentContainer.LayoutConfig.MATCH_PARENT) { 
  39.                     // Child wants to be our size, but our size is not fixed. 
  40.                     // Constrain child to not be bigger than us. 
  41.                     resultSize = size
  42.                     resultMode = EstimateSpec.NOT_EXCEED; 
  43.                 } else if (childDimension == ComponentContainer.LayoutConfig.MATCH_CONTENT) { 
  44.                     // Child wants to determine its own size. It can't be 
  45.                     // bigger than us. 
  46.                     resultSize = size
  47.                     resultMode = EstimateSpec.NOT_EXCEED; 
  48.                     if (size == 0) { 
  49.                         // size will not be 0 when resultMode is NOT_EXCEED, don't know why 
  50.                         resultMode = EstimateSpec.PRECISE; 
  51.                     } 
  52.                 } 
  53.                 break; 
  54.  
  55.             // Parent asked to see how big we want to be 
  56.             case EstimateSpec.UNCONSTRAINT: 
  57.                 if (childDimension >= 0) { 
  58.                     // Child wants a specific size... let him have it 
  59.                     resultSize = childDimension; 
  60.                     resultMode = EstimateSpec.PRECISE; 
  61.                 } else if (childDimension == ComponentContainer.LayoutConfig.MATCH_PARENT) { 
  62.                     // Child wants to be our size... find out how big it should 
  63.                     // be 
  64.                     resultSize = size
  65.                     resultMode = EstimateSpec.UNCONSTRAINT; 
  66.                 } else if (childDimension == ComponentContainer.LayoutConfig.MATCH_CONTENT) { 
  67.                     // Child wants to determine its own size.... find out how 
  68.                     // big it should be 
  69.                     resultSize = size
  70.                     resultMode = EstimateSpec.UNCONSTRAINT; 
  71.                 } 
  72.                 break; 
  73.         } 
  74.  
  75.         return makeEstimateSpec(resultSize, resultMode); 
  76.     } 

makeEstimateSpec函數(shù)實際就是(size & ~EstimateSpec.ESTIMATED_STATE_BIT_MASK) | (mode & EstimateSpec.ESTIMATED_STATE_BIT_MASK)的值。

測量過程到此就基本結(jié)束,接下來看看稍微簡單一點的布局。

布局Arrange

如何自定義布局過程

首先通過setArrangeListener(ComponentContainer.ArrangeListener listener)來設(shè)置測量的回調(diào):

  1. setArrangeListener(new ArrangeListener() { 
  2.     @Override 
  3.     public boolean onArrange(int l, int t, int width, int height) { 
  4.         return false
  5.     } 
  6. }); 

與測量類似,布局也是通過回調(diào)函數(shù)的方式來自定義。其中第一個參數(shù)為該控件的left值,第二個為top值,第三個為寬度,第四個為高度。

setArrangeListener是在ComponentContainer中定義的,Component中沒有。

與測量不同的是,控件本身的位置與寬高已經(jīng)由父控件確定了,即為onArrange回調(diào)中的四個參數(shù)。

在Arrange過程中,我們需要做的就是遞歸為每個子控件設(shè)置位置。通過調(diào)用子控件的arrange(int left, int top, int width, int height)函數(shù)來排列子元素:

  1. child.arrange(lefttop, child.getEstimatedWidth(), child.getEstimatedHeight()); 

同樣的,onArrange回調(diào)需要返回true,才會使布局生效。

在調(diào)用了child的arrange函數(shù)后,就能通過child.getWidth()與child.getHeight()來獲取子控件的寬高了。

一個簡單的垂直順序排列布局的簡化代碼如下:

  1. @Override 
  2. public boolean onArrange(int l, int t, int width, int height) { 
  3.     int childCount = getChildCount(); 
  4.     int childTop = t; 
  5.     for(int i = 0; i < childCount; i++) { 
  6.         Component child = getComponentAt(i); 
  7.         int childHeight = child.getEstimatedHeight(); 
  8.         child.arrange(l, childTop, child.getEstimatedWidth(), childHeight); 
  9.         childTop += childHeight; 
  10.     } 
  11.      
  12.     return true

注意:不管是onArrange回調(diào)還是子控件的arrange函數(shù),最后兩個參數(shù)都是寬與高,而不是right與bottom。

綜合

接下來我們結(jié)合我們前一篇自定義控件之觸摸事件,與測量、布局一起,來自定義一個簡單的滾動視差布局ParallaxLayout。

  1. ParallaxLayout包含有兩個子控件,第一個固定150vp的Image。第二個是高度為match_parent的Text控件
  2. 在onEstimateSize中,主要是遍歷子控件為其設(shè)置測量參數(shù),并為自己設(shè)置測量結(jié)果。Image的測量高度為固定150vp,Text的高度與布局一致,我們需要通過測量參數(shù)與LayoutConfig計算出所有子控件的高度與自己的高度。
  3. 在onArrange中,按順序垂直排列子控件。由于Image+Text的高度已經(jīng)超出了自己的高度,因此Text的底部會有一部分顯示不出來。
  4. 在onTouchEvent中,通過計算手指的移動距離,為每個子控件setTranslateY,來實現(xiàn)位移的效果。最大位移距離為Image的高度。
  5. 通過為Image設(shè)置一半的translateY,為Text設(shè)置全部的translateY來實現(xiàn)滾動視差效果,關(guān)鍵代碼如下:
  1. for (int i = 0; i < childCount; i++) { 
  2.             Component child = getComponentAt(i); 
  3.             if (i == 0) { 
  4.                 child.setTranslationY(deltaY / 2); 
  5.             } else { 
  6.                 child.setTranslationY(deltaY); 
  7.             } 
  8.         } 

具體代碼參考:parallax-layout

想了解更多內(nèi)容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

https://harmonyos.51cto.com

 

責(zé)任編輯:jianghua 來源: 鴻蒙社區(qū)
相關(guān)推薦

2021-08-11 14:29:20

鴻蒙HarmonyOS應(yīng)用

2021-08-25 10:14:51

鴻蒙HarmonyOS應(yīng)用

2021-09-06 14:58:23

鴻蒙HarmonyOS應(yīng)用

2015-02-11 17:49:35

Android源碼自定義控件

2021-09-02 10:00:42

鴻蒙HarmonyOS應(yīng)用

2009-06-08 20:13:36

Eclipse自定義控

2009-08-06 09:18:01

ASP.NET自定義控ASP.NET控件開發(fā)

2013-04-19 10:14:24

2009-07-31 10:23:09

ASP.NET源碼DateTimePic

2017-02-17 09:37:12

Android自定義控件方法總結(jié)

2011-08-18 09:44:33

iPhone SDK儀表控件UIDialView

2009-08-06 17:52:45

ASP.NET控件開發(fā)自定義控件

2010-08-11 09:01:41

Flex4布局

2021-10-26 10:07:02

鴻蒙HarmonyOS應(yīng)用

2022-06-30 14:02:07

鴻蒙開發(fā)消息彈窗組件

2022-07-15 16:45:35

slider滑塊組件鴻蒙

2022-06-20 15:43:45

switch開關(guān)鴻蒙

2009-09-03 13:34:03

.NET自定義控件

2014-12-10 10:37:45

Android自定義布局

2009-08-03 13:34:06

自定義C#控件
點贊
收藏

51CTO技術(shù)棧公眾號