成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

系統 OpenHarmony
這里我們來分析一下測量與布局的用法,并且結合上一篇文章事件分發一起實現一個簡單的滾動視差布局ParallaxLayout。

[[417822]]

想了解更多內容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術社區

https://harmonyos.51cto.com

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

ParallaxLayout效果圖:

測量Estimate

如何自定義測量過程

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

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

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

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

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

  1. setEstimatedSize(widthSize, heightSize); 

最后,如果需要讓設置的測量大小生效,我們應該在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,那么最終系統不會在native層來測量大小。如果返回了false,系統還是會繼續測量大小,最終的大小可能與setEstimatedSize設置的結果不一致。

在setEstimatedSize后我們就可以通過下面的函數來獲取我們設置的大小:

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

EstimateSpec

EstimateSpec是Component的內部類,EstimateSpec提供了一系列的操作測量參數的方法。

測量參數

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

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

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

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

模式mode

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

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

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

  1. // size變量為控件期望的大小,estimateSpec變量為父控件的測量參數 
  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
  • 當mode為NOT_EXCEED時,控件的期望大小應該小于等于父控件給定的size
  • 當mode為PRECISE時,控件的大小應該等于父控件給定的size
  • 當mode為UNCONSTRAINT時,控件的大小可以為他期望的size

自定義布局

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

  1. child.estimateSize(widthEstimatedConfig, heightEstimatedConfig); 

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

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

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

子控件的測量參數

那么,最重要的問題是,我們如何確定子控件的測量參數到底應該是多少,換句話說我們如何生成或者獲取子控件的測量參數呢?子控件的測量參數與很多因素有關,如父控件的測量參數、父控件的padding值、子控件自己的期望大小。我們可以根據這幾個參數來確定子控件的測量參數。

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

  1. /** 
  2.  * 根據父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應該怎么獲取呢?實際上就是ComponentContainer.LayoutConfig中的width或者height,測量高度就取height、寬度就取width。

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

  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函數實際就是(size & ~EstimateSpec.ESTIMATED_STATE_BIT_MASK) | (mode & EstimateSpec.ESTIMATED_STATE_BIT_MASK)的值。

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

布局Arrange

如何自定義布局過程

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

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

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

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

與測量不同的是,控件本身的位置與寬高已經由父控件確定了,即為onArrange回調中的四個參數。

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

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

同樣的,onArrange回調需要返回true,才會使布局生效。

在調用了child的arrange函數后,就能通過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回調還是子控件的arrange函數,最后兩個參數都是寬與高,而不是right與bottom。

綜合

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

  1. ParallaxLayout包含有兩個子控件,第一個固定150vp的Image。第二個是高度為match_parent的Text控件
  2. 在onEstimateSize中,主要是遍歷子控件為其設置測量參數,并為自己設置測量結果。Image的測量高度為固定150vp,Text的高度與布局一致,我們需要通過測量參數與LayoutConfig計算出所有子控件的高度與自己的高度。
  3. 在onArrange中,按順序垂直排列子控件。由于Image+Text的高度已經超出了自己的高度,因此Text的底部會有一部分顯示不出來。
  4. 在onTouchEvent中,通過計算手指的移動距離,為每個子控件setTranslateY,來實現位移的效果。最大位移距離為Image的高度。
  5. 通過為Image設置一半的translateY,為Text設置全部的translateY來實現滾動視差效果,關鍵代碼如下:
  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

想了解更多內容,請訪問:

51CTO和華為官方合作共建的鴻蒙技術社區

https://harmonyos.51cto.com

 

責任編輯:jianghua 來源: 鴻蒙社區
相關推薦

2021-08-11 14:29:20

鴻蒙HarmonyOS應用

2021-08-25 10:14:51

鴻蒙HarmonyOS應用

2021-09-06 14:58:23

鴻蒙HarmonyOS應用

2015-02-11 17:49:35

Android源碼自定義控件

2021-09-02 10:00:42

鴻蒙HarmonyOS應用

2009-06-08 20:13:36

Eclipse自定義控

2009-08-06 09:18:01

ASP.NET自定義控ASP.NET控件開發

2013-04-19 10:14:24

2009-07-31 10:23:09

ASP.NET源碼DateTimePic

2017-02-17 09:37:12

Android自定義控件方法總結

2011-08-18 09:44:33

iPhone SDK儀表控件UIDialView

2009-08-06 17:52:45

ASP.NET控件開發自定義控件

2021-10-26 10:07:02

鴻蒙HarmonyOS應用

2022-06-30 14:02:07

鴻蒙開發消息彈窗組件

2022-07-15 16:45:35

slider滑塊組件鴻蒙

2010-08-11 09:01:41

Flex4布局

2009-09-03 13:34:03

.NET自定義控件

2009-08-03 13:34:06

自定義C#控件

2009-08-03 13:39:46

C#自定義用戶控件

2014-12-10 10:37:45

Android自定義布局
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产高清在线观看 | 国产精品美女 | 国产亚洲一区二区三区 | 久久国产精品72免费观看 | 成人av免费在线观看 | 日韩视频在线一区 | 国产午夜久久久 | 亚洲三级在线观看 | 国产成人高清视频 | 国产精品呻吟久久av凹凸 | www.久久 | 久久国产精品99久久久大便 | 色综合欧美| 99精品久久 | 午夜影院在线 | 国产精品成人国产乱一区 | 国产精品免费av | www.久久99| 日韩国产中文字幕 | 久久久不卡网国产精品一区 | 国产日韩欧美在线 | 欧美综合国产精品久久丁香 | 国产乱码高清区二区三区在线 | 国产婷婷| 久久久久久毛片免费观看 | 夜久久 | 羞羞的视频免费观看 | 中文字幕久久精品 | 亚洲精品专区 | 91成人免费观看 | 国产精品久久久久无码av | 91国内在线观看 | a级免费视频| 亚洲视频不卡 | 欧美一区二区三区在线观看 | 亚洲日本乱码在线观看 | 一级二级三级在线观看 | 欧美一区二区在线 | www.日韩欧美 | 欧美日韩一区二区三区四区 | 91免费福利在线 |