波形自定义控件(三):原理解析之测量宽高

上一篇讲解了自定义控件初始化的相关步骤,本篇来讲解自定义控件的宽高测量。

重写onMeasure方法

我们从布局文件上获取到了相关的参数,现在需要测量这个控件的宽高,重写如下方法。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    }

先来讲讲widthMeasureSpec 和 heightMeasureSpec。

我们在布局文件设置的layout_width 和 layout_height尺寸数据分别被封装到widthMeasureSpec 和 heightMeasureSpec里面了。它由两部分组成。
在这里插入图片描述
int是32的,其中0 ~ 29位封装了具体的尺寸值(像素个数),30 ~ 31位封装了模式。

模式分为3种,EXACTLY,AT_MOST 和 UNSPECIFIED

EXACTLY:使用具体的尺寸值(如10dp)或match_parent
AT_MOST:使用wrap_content
UNSPECIFIED:父布局不对本控件尺寸作要求,可以参考这篇文章

举两个例子。

<TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="hello world"/>

<Button
        android:layout_width="100px"
        android:layout_height="50px"/>

在onMeasure里,TextView的宽度为0,宽度模式为EXACTLY,高度为某个固定值,高度模式为AT_MOST。

而Button的宽度为100,宽度模式为EXACTLY,高度为50,高度模式也为EXACTLY。

通过这两个例子,我们能明白EXACTLY和AT_MOST的区别了。那么该怎么获取尺寸和模式呢?可以通过MeasureSpec.getSize方法 和 MeasureSpec.getMode方法获得

int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);

然后根据需求来决定控件宽高,如下是一个常用的模板。

int widht;
int height;
if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
	   //宽高都为wrap_content
	   width = ...
	   height = ...
       ...
}else if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.AT_MOST){
       //宽为具体尺寸或match_parent,高为wrap_content
       width = ...
	   height = ...
       ...
}else if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.EXACTLY){
       //宽高为具体尺寸或match_parent
       width = ...
	   height = ...
       ...
}else{
    width = ...
	height = ...
	...
}

计算好了宽高值后把他们调用setMeasuredDimension方法就可以完成尺寸的初步测量了,如下。

setMeasuredDimension(MeasureSpec.makeMeasureSpec(width,widthMode),MeasureSpec.makeMeasureSpec(height,heightMode));

为什么说是初步测量呢?因为onMeasure只是量出控件自己的宽高,如果是在写自定义容器的话还得考虑子控件的宽高,以及它们的在容器里的摆放位置(这里需要重写onLayout方法),不过由于WaveLoadingView不是容器所以可以不用考虑这么多。

最后来看看WaveLoadingView的onMeasure做了什么吧。

WaveLoadingView的onMeasure方法

	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int elementWidth = 0;
        int elementHeight= 0;

        switch(mType){
            case IMAGE_TYPE_TEXT:
                elementWidth = mTextWidth;
                elementHeight = mTextHeight;
                break;

            case IMAGE_TYPE_CIRCLE:
            case IMAGE_TYPE_SQUARE:
            case IMAGE_TYPE_RECT:
            case IMAGE_TYPE_NOISE:
                elementWidth = elementHeight = mImageSize;
                break;

            case IMAGE_TYPE_CUSTOM:
                elementWidth = elementHeight = mImageSize;
                break;
        }

        onMeasureSelf(widthMeasureSpec,heightMeasureSpec,elementWidth,elementHeight);
    }

首先,这里获取了WaveLoadingView的显示模式,根据模式决定元素的宽高。这里的元素指小方块或字符,每一个小方块或字符就是一个元素。
在这里插入图片描述
接着看看onMeasureSelf方法做了什么。

protected void onMeasureSelf(int widthMeasureSpec, int heightMeasureSpec,int elementWidth,int elemwntHeight){
		//1
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

		//2
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

		//3
        int wrapWidth = mLength * elementWidth +
                (mLength - 1) * mInterval +
                paddingLeft + paddingRight;

		//4
        int waveHeight = (mWaveLength % 2) != 0 ?
                (int) (elemwntHeight + (mWaveLength / 2 + 1) * elemwntHeight * mWaveOffset) :
                (int) (elemwntHeight + mWaveLength / 2 * elemwntHeight * mWaveOffset);

        int wrapHeight = waveHeight + paddingTop + paddingBottom;

        if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){
            width = wrapWidth;
            height = wrapHeight;
        }else if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.AT_MOST){
            height = wrapHeight;
        }else if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.EXACTLY){
            width = wrapWidth;
        }
		
		//5
        setMeasuredDimension(width,height);
    }

WaveLoadingView在使用的时候推荐宽高都写wrap_content。

注释1下面的四行代码获取宽高值及它们的模式,上文刚刚讲过。

注释2下面的四行代码获取到控件的内间距,也就是布局文件里的android:paddingRight,android:paddingLeft,android:paddingTop及android:paddingBottom,单位是像素。

代码3计算wrap_content下的宽度,mLength是指元素个数,计算的各尺寸可以看看下面的示意图。
在这里插入图片描述
代码4计算wrap_content下的高度,这里解释一下,mWaveLength指波的占用的元素个数,mLength指显示的元素总数,下图mWaveLength=5,mLength=11。
在这里插入图片描述
完成了计算最后在代码5设置计算结果onMeasure就完成了。

最后

限于文章篇幅,文章只贴了关键代码,想深入了解此控件测量流程的朋友可以到Github项目上阅读完整代码。

下一篇讲解控件的绘制及动画效果的实现原理,感兴趣的朋友可以看看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值