当前位置: 首页 > 科技观察

HarmonyOS自定义控件的实测布局

时间:2023-03-17 11:48:26 科技观察

更多内容请访问:Harmonyos技术社区https://harmonyos.51cto.com与华为官方合作搭建在HarmonyOS中,控件最终显示在用户界面上,将去通过测量(Estimate)、布局(Arrange)、绘图(Draw)等过程。这里我们分析measurement和layout的用法,结合上一篇的事件分发,实现一个简单的滚动视差布局ParallaxLayout。ParallaxLayout效果图:如何测量Estimate如何自定义测量过程?首先,通过setEstimateSizeListener(Component.EstimateSizeListenerlistener)设置测量回调:第一个参数是宽度测量参数,第二个是高度测量参数。我们可以通过EstimateSpec获取宽高的众数(mode)和大小(size):intmode=EstimateSpec.getMode(widthEstimateSpec);intsize=EstimateSpec。getSize(widthEstimateSpec);在计算出控件的最终尺寸后,我们可以调用setEstimatedSize(intestimatedWidth,intestimatedHeight)函数来设置最终的测量尺寸。注意:setEstimateSize函数需要的是具体尺寸,不是测量参数,即EstimateSpec.getSize后的具体尺寸setEstimatedSize(widthSize,heightSize);最后,如果设置的测量尺寸需要生效,我们应该在onEstimateSize中返回true:;setEstimatedSize(widthSize,heightSize);returntrue;}});如果onEstimateSize返回true,那么最终系统不会在native层测量尺寸。如果返回false,系统会继续测量尺寸,最终的尺寸可能与setEstimatedSize设置的结果不一致。setEstimatedSize后,我们可以使用如下函数获取我们设置的尺寸:getEstimatedWidth();//获取测量宽度getEstimatedHeight();//获取测量高度EstimateSpecEstimateSpec是Component的内部类,EstimateSpec提供了一系列的操纵测量参数的方法。测量参数测量参数是一个int值,它封装了来自父控件的测量要求。测量参数由mode和size两个int值组成,公式如下:(size&~EstimateSpec.ESTIMATED_STATE_BIT_MASK)|(mode&EstimateSpec.ESTIMATED_STATE_BIT_MASK)其中size为当前控件的大小,mode为模式。您还可以使用以下函数通过大小和模式生成测量参数:intspec=EstimateSpec.getSizeWithMode(size,mode);modemode通过EstimateSpec得到的mode有三个值:EstimateSpec.NOT_EXCEED不超过:该模式表示父控件指定了当前控件的最大尺寸EstimateSpec.PRECISEPrecise:该模式表示父控件指定了当前控件的最大尺寸currentcontrolsizeEstimateSpec.UNCONSTRAINTUnconstrained:这种模式表示父控件对当前控件的大小没有约束,控件可以想如何确定任意大小的不同模式下控件的大小?通过下面的代码可以很容易理解://size变量是控件的期望大小,estimateSpec变量是父控件的测量参数finalintspecMode=EstimateSpec.getMode(estimateSpec);finalintspecSize=EstimateSpec.getSize(estimateSpec);finalintresult;switch(specMode){caseEstimateSpec.NOT_EXCEED:result=Math.min(specSize,size);break;caseEstimateSpec.PRECISE:result=specSize;break;caseEstimateSpec.UNCONSTRAINT:default:result=size;}当模式为NOT_EXCEED,控件的预期大小应小于或等于父控件给定的大小。当模式为PRECISE时,控件的大小应等于父控件给定的大小。当mode为UNCONSTRAINT时,控件的大小可以自定义布局为他期望的大小。在自定义布局的时候,我们不仅要测量自己的尺寸,还要测量子控件的尺寸。子控件可以使用estimateSize(intwidthEstimatedConfig,intheightEstimatedConfig)函数设置测量参数:child.estimateSize(widthEstimatedConfig,heightEstimatedConfig);注意:estimateSize的两个参数需要的是测量参数,不是具体尺寸。这两个参数将传递给子控件的onEstimateSize(intwidthEstimateSpec,intheightEstimateSpec)回调。默认情况下,子控件会根据widthEstimatedConfig和heightEstimatedConfig来确定其最终大小。子控件也可以通过setEstimateSizeListener自定义其测量过程。它最后指的测量参数就是我们通过estimateSize函数设置的测量参数。接下来我们只需要遍历所有的子控件,为它们设置测量参数就可以达到测量子控件大小的目的。自定义布局的测量过程基本包括以下两个步骤:为所有子控件设置测量参数和测量自己的尺寸。子控件的测量参数最重要的问题是,我们如何确定子控件的测量参数?也就是说,我们如何生成或获取子控件的测量参数?子控件的度量参数与很多因素有关,比如父控件的度量参数、父控件的padding值、子控件本身的期望大小等。我们可以根据这些参数来确定子控件的测量参数。这里我们使用辅助函数来生成子控件的测量参数。首先,函数的定义应该是这样的:/***根据子组件的spec、padding、预期大小生成子组件的spec*@paramspecofparentcomponentspec*@paramaddingparentcomponentpadding*@paramchildDimension子组件预期大小*@return子组件规范*/publicstaticintgetChildEstimateSpec(intspec,intpadding,intchildDimension);注意:childDimension应该怎么获取呢?其实就是ComponentContainer.LayoutConfig中的width或者height,height是用来衡量高度的,width是用来衡量宽度的。接下来我们要根据父控件的mode和childDimension来确定子控件的mode和size,并生成测量参数。具体参考如下代码:publicstaticintgetChildEstimateSpec(intspec,intpadding,intchildDimension){intspecMode=EstimateSpec.getMode(spec);intspecSize=EstimateSpec.getSize(spec);intsize=Math.max(0,specSize-padding);intresultSize=0;intresultMode=0;switch(specMode){//ParenthasimposedanexactsizeonuscaseEstimateSpec.PRECISE:if(childDimension>=0){resultSize=childDimension;resultMode=EstimateSpec.PRECISE;}elseif(childDimension==ComponentContainer.LayoutConfig.MATCH_PARENT){//Childwantstobeoursize.Sobeit.resultSize=size;resultMode=EstimateSpec.PRECISE;}elseif(childDimension==ComponentContainer.LayoutConfig.MATCH_CONTENT){//Childwantstodeterminiteitsownsize.Itcan'tbe//biggerthanus.resultSize=size;resultMode=EstimateSpec.NOT_EXCEED;if(size==0){//sizewillnotbe0whenresultModeisNOT_EXCEED,don'tknowwhyresultMode=EstimateSpec.PRECISE;}}break;//ParenthasimposedamaximumsizeonuscaseEstimateSpec.NOT_EXCEED:if(childDimension>=0){//Childwantsaspecificsize...sobeitresultSize=childDimension;resultMode=EstimateSpec.PRECISE;}elseif(childDimension==ComponentContainer.LayoutConfig.MATCH_PARENT){//Childwantstobeoursize,butoursizeisnotfixed.//Constrainchildtonotbebiggerthanus.resultSize=size;resultMode=EstimateSpec.NOT_EXCEED;}elseif(childDimension==ConfigComponentContainer.Layout复制代码.MATCH_CONTENT){//Childwantstodeterminesitsownsize.Itcan'tbe//biggerthanus.resultSize=size;resultMode=EstimateSpec.NOT_EXCEED;if(size==0){//sizewillnotbe0whenresultModeisNOT_EXCEED,don’tknowwhyresultMode=EstimateSpec.PRECISE;}}break;//ParentaskedtoseehowbigwewanttobecaseEstimateSpec.UNCONSTRAINT:if(childDimension>=0){//Childwantsaspecificsize...lethimhaveitresultSize=childDimension;resultMode=EstimateSpec.PRECISE;}elseif(childDimension==ComponentContainer.LayoutConfig.MATCH_PARENT){//Childwantstobeoursize...findouthowbigitshould/beresultSize=size;resultMode=EstimateSpec.UNCONSTRAINT;}elseif(childDimension==ComponentContainer.LayoutConfig.MATCH_CONTENT){//Childwantstodeterminitesownsize....findouthow//bigitshouldberesultSize=size;resultMode=EstimateSpec.UNCONSTRAINT;}break;}returnmakeEstimateSpec(resultSize,resultMode);}makeEstimateSpec函数实际上是(size&~EstimateSpec.ESTIMATED_STATE)|BIT_MA&EstimateSpec.ESTIMATED_STATE_BIT_MASK)值的测量过程到这里基本就结束了,我们来看一个稍微简单一点的布局。LayoutArrange如何自定义布局流程首先通过setArrangeListener(ComponentContainer.ArrangeListenerlistener)设置测量回调:和measurement类似,layout也是通过回调函数来自定义的。第一个参数是控件的left值,第二个是top值,第三个是宽度,第四个是高度。setArrangeListener定义在ComponentContainer中,而不是在Component中。与measurement不同的是,控件本身的position、width、height已经由父控件决定了,也就是onArrange回调中的四个参数。在Arrange过程中,我们需要做的就是递归地为每个子控件设置位置。通过调用子控件的arrange(intleft,inttop,intwidth,intheight)函数对子元素进行排列:child.arrange(left,top,child.getEstimatedWidth(),child.getEstimatedHeight());同样,onArrange回调需要返回true,布局才会生效。调用child的arrange函数后,可以通过child.getWidth()和child.getHeight()获取子控件的宽高。简单垂直顺序布局的简化代码如下:@OverridepublicbooleanonArrange(intl,intt,intwidth,intheight){intchildCount=getChildCount();intchildTop=t;for(inti=0;i