手把手写Android自定义控件(三):测量宽高与绘制

您所在的位置:网站首页 叉车宽度测量视频教程 手把手写Android自定义控件(三):测量宽高与绘制

手把手写Android自定义控件(三):测量宽高与绘制

2024-07-16 22:07| 来源: 网络整理| 查看: 265

上一篇讲解了自定义属性的相关操作,本篇来讲解如何测量控件。相比于前面的步骤,测量工作的复杂了许多,在这个阶段建议准备一张草稿纸记录各种思路和计算结果,这样不容易乱。下面是我在设计WaveLoadingView时的草稿。 在这里插入图片描述

认识MeasureSpec

在正式开始写测量代码前,首先需要知道一个重要的参数,MeasureSpec。 它是一个32位的整型数据,由 模式 和 长度 组成,它的结构如下。 在这里插入图片描述 其中0 ~ 29位封装了具体的尺寸值(像素个数),30 ~ 31位封装了模式。

模式有三种:EXACTLY,AT_MOST 和 UNSPECIFIED

EXACTLY:使用具体的尺寸值(如10dp)或match_parent AT_MOST:使用wrap_content UNSPECIFIED:父布局不对本控件尺寸作要求

UNSPECIFIED 是用在布局控件上的,这里不做过多说明。如此一来我们需要关心的就只有EXACTLY 和 AT_MOST 了。

获取尺寸值和模式用到如下两个方法。

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

举两个例子。

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

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

重写onMeasure方法

onMeasure方法是控件用来测量控件本身大小的,做好了前期的准备,现在就来重写这个方法。

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //super.onMeasure(widthMeasureSpec, heightMeasureSpec); //获取xml上的宽高尺寸及模式 int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); //wrap_content int wrapWidth = mRadius * 2 * 2; int wrapHeight = mRadius * 2; //1 if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST){ width = wrapWidth; height = wrapHeight; //2 }else if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.AT_MOST){ height = wrapHeight; //3 }else if(widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.EXACTLY){ width = wrapWidth; //4 }else{ } setMeasuredDimension(MeasureSpec.makeMeasureSpec(width,widthMode),MeasureSpec.makeMeasureSpec(height,heightMode)); getPaddingAttr(); }

首先获取宽高的尺寸值和模式。

接着计算一下wrap_content所需的尺寸。规定最小高度为mRadius的两倍(也就是直径),最小宽度为mRadius的四倍(也就是两倍直径)。 在这里插入图片描述 接着有四个判断分支。分别是 1.宽高都取最小 2.宽取具体值,高取最小值 3.宽取最小值,高取具体值 4.宽高都去具体值

计算好测量结果后调用setMeasuredDimension方法,把宽高尺寸放入。

这里回顾一下,当宽度使用match_parent时,获取的尺寸值为0,模式为EXACTLY。setMeasuredDimension方法传入的宽度虽然为0,但父布局是会把这个控件的宽度设置为和父布局一样宽的。

onMeasure的难度跨度有点大,还没弄明白的朋友可以先在草稿纸上演算一下。

最后是获取Padding(内间距),getPaddingAttr方法如下。

private void getPaddingAttr(){ //获取控件上下左右的内间距 mPaddingRight = getPaddingRight(); mPaddingLeft = getPaddingLeft(); mPaddingTop = getPaddingTop(); mPaddingBottom = getPaddingBottom(); }

如果能把padding也考虑进去,那离优秀的控件就又近了一小步。

完成了测量接下来就是绘制控件了。

重写onDraw方法

在此之前,简单提一下。其实控件从初始化到真正显示出来是要先后调用onMeasure,onLayout 和 onDraw 三个方法的。

onMeasure所测量出来的尺寸只是一个初步的结果。如果现在设计的是布局控件,那么还得考虑子控件之间的位置关系。LinearLayout 和 RelativeLayout 对控件摆放的策略就不一样。摆放的结果就是在onLayout中计算的,这个时候得到的就是布局控件真正的尺寸了。

不过由于我们目前写的Switch控件并不是布局控件,所以可以不用考虑重写onLayout方法。

先写一个init方法,在 所有的 构造函数里调用它。Paint 是画笔类,控件的图像就是通过它画出来的。再定义一个布尔量记录当前的开关状态。

private Paint mPaint; private boolean switchOn; private void init(){ mPaint = new Paint(); mPaint.setAlpha(255); mPaint.setAntiAlias(true); switchOn = false; }

setAntiAlias开启反锯齿,这样绘制出来的图像质量会好很多。当然,会对性能造成细微的损耗。

接下来讲Android坐标系,这个非常重要! 在这里插入图片描述 在Android设备里,从左到右是x轴正方向,从上到下是y轴正方向。 左上角是原点(0,0)

先来做个小练习吧。控件宽度为100,,高度为50,求控件正中央的坐标。红线长度为30,求黑点坐标。 在这里插入图片描述 题解:控件左上角是原点坐标(蓝点),根据刚刚说的Android坐标系,红点坐标为(50,25)。

黑点在原点(0,0)的左边,红线长度为30,所以黑点坐标为(-30,0)。

如果一个点在原点上方,则 y 坐标为负。

做好了必要的准备,接下来重写onDraw方法。

@Override protected void onDraw(Canvas canvas) { //super.onDraw(canvas); drawBack(canvas); drawInside(canvas); if(switchOn){ drawOn(canvas); }else{ drawOff(canvas); } }

默认情况下,后面绘制的东西会叠在最上面。所以这里绘制的顺序是 底部 -> 凹槽 -> 开关。

先来写drawBack方法,这个方法绘制控件底部,其实就是两个圆中间放一个矩形。

protected void drawBack(Canvas canvas){ int width = getWidth(); mPaint.setColor(mBackColor); canvas.drawCircle(mPaddingLeft + mRadius, mPaddingTop + mRadius, mRadius, mPaint); canvas.drawCircle(width - mPaddingRight - mRadius, mPaddingTop + mRadius, mRadius, mPaint); canvas.drawRect(mPaddingLeft + mRadius, mPaddingTop, width - mPaddingRight - mRadius, mPaddingTop + mRadius * 2, mPaint); }

接下来绘制凹槽。

protected void drawInside(Canvas canvas){ int width = getWidth(); mPaint.setColor(mInsideColor); canvas.drawCircle(mPaddingLeft + mRadius, mPaddingTop + mRadius, mRadius - mInsidePadding, mPaint); canvas.drawCircle(width - mPaddingRight - mRadius, mPaddingTop + mRadius, mRadius - mInsidePadding, mPaint); canvas.drawRect(mPaddingLeft + mRadius, mPaddingTop + mInsidePadding, width - mPaddingRight - mRadius, mPaddingTop + mRadius * 2 - mInsidePadding, mPaint); }

最后绘制开关,开关有两种状态,开 和 关,这里分别绘制。

protected void drawOn(Canvas canvas){ mPaint.setColor(mOnColor); canvas.drawCircle(mPaddingLeft + mRadius, mPaddingTop + mRadius, mRadius - mSwitchPadding, mPaint); } protected void drawOff(Canvas canvas){ int width = getWidth(); mPaint.setColor(mOffColor); canvas.drawCircle(width - mPaddingRight - mRadius, mPaddingTop + mRadius, mRadius - mSwitchPadding, mPaint); } 简单测试

控件代码写到这里基本上可以显示出来了。下面我们做个小测试。 现在布局文件里放一个Switch控件,就按照下面这样设置。

com.pyjtlk.widgetlib.Switch 根据项目起的名字不同,这里会有一点小区别注意一下。

如果显示如下,则说明目前的工作算是成功的。 在这里插入图片描述

最后

下一篇将讲解如何给自定义控件添加状态监听器。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3