前言

Android自定义View的详细步骤是我们每一个Android开发人员都必须掌握的技能,因为在开发中总会遇到自定义View的需求。为了提高自己的技术水平,自己就系统的去研究了一下,在这里写下一点心得,有不足之处希望大家及时指出。

流程

在Android中对于布局的请求绘制是在Android framework层开始处理的。绘制是从根节点开始,对布局树进行measure与draw。在RootViewImpl中的performTraversals展开。它所做的就是对需要的视图进行measure(测量视图大小)、layout(确定视图的位置)与draw(绘制视图)。下面的图能很好的展现视图的绘制流程:

当用户调用requestLayout时,只会触发measure与layout,但系统开始调用时还会触发draw

下面来详细介绍这几个流程。

measure

measure是View中的final型方法不可以进行重写。它是对视图的大小进行测量计算,但它会回调onMeasure方法,所以我们在自定义View的时候可以重写onMeasure方法来对View进行我们所需要的测量。它有两个参数widthMeasureSpec与heightMeasureSpec。其实这两个参数都包含两部分,分别为size与mode。size为测量的大小而mode为视图布局的模式

我们可以通过以下代码分别获取:

intwidthSize = MeasureSpec.getSize(widthMeasureSpec);

intheightSize = MeasureSpec.getSize(heightMeasureSpec);

intwidthMode = MeasureSpec.getMode(widthMeasureSpec);

intheightMode = MeasureSpec.getMode(heightMeasureSpec);

获取到的mode种类分为以下三种:

MODE

EXPLAIN

UNSPECIFiED

父视图不对子视图进行约束,子视图大小可以是任意大小,一般是对ListView、ScrollView等进行自定义,一般用不到

EXACTLY

父视图对子视图设定了一个精确的尺寸,子视图不超过该尺寸,一般为精确的值例如200dp或者使用了match_parent

AT_MOST

父视图对子视图指定了一***的尺寸,确保子视图的所以内容都刚好能在该尺寸中显示出来,一般为wrap_content,这种父视图不能获取子视图的大小,只能由子视图自己去计算尺寸,这也是我们测量要实现的逻辑情况

setMeasuredDimension

通过以上逻辑获取视图的宽高,***要调用setMeasuredDimension方法将测量好的宽高进行传递出去。其实最终是调用setMeasuredDimensionRaw方法对传过来的值进行属性赋值。调用super.onMeasure()的调用逻辑也是一样的。

下面以自定义一个验证码的View为例,它的onMeasure方法如下:

@Override

protected void onMeasure(intwidthMeasureSpec,intheightMeasureSpec) {

intwidthSize = MeasureSpec.getSize(widthMeasureSpec);

intheightSize = MeasureSpec.getSize(heightMeasureSpec);

intwidthMode = MeasureSpec.getMode(widthMeasureSpec);

intheightMode = MeasureSpec.getMode(heightMeasureSpec);

if (widthMode == MeasureSpec.EXACTLY) {

//直接获取精确的宽度

width = widthSize;

} elseif (widthMode == MeasureSpec.AT_MOST) {

//计算出宽度(文本的宽度+padding的大小)

width = bounds.width() + getPaddingLeft() + getPaddingRight();

}

if (heightMode == MeasureSpec.EXACTLY) {

//直接获取精确的高度

height = heightSize;

} elseif (heightMode == MeasureSpec.AT_MOST) {

//计算出高度(文本的高度+padding的大小)

height = bounds.height() + getPaddingBottom() + getPaddingTop();

}

//设置获取的宽高

setMeasuredDimension(width, height);

}

可以对自定义View的layout_width与layout_height进行设置不同的属性,达到不同的mode类型,就可以看到不同的效果

measureChildren

如果你是对继承ViewGroup的自定义View那么在进行测量自身的大小时还要测量子视图的大小。一般通过measureChildren(int widthMeasureSpec, int heightMeasureSpec)方法来测量子视图的大小。

protected void measureChildren(intwidthMeasureSpec,intheightMeasureSpec) {

final intsize= mChildrenCount;

final View[] children = mChildren;

for(inti = 0; i

final Viewchild = children[i];

if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {

measureChild(child, widthMeasureSpec, heightMeasureSpec);

}

}

}

通过上面的源码会发现,它其实是遍历每一个子视图,如果该子视图不是隐藏的就调用measureChild方法,那么来看下measureChild源码:

protected void measureChild(Viewchild,intparentWidthMeasureSpec,

intparentHeightMeasureSpec) {

final LayoutParams lp = child.getLayoutParams();

final intchildWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight, lp.width);

final intchildHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

会发现它首先调用了getChildMeasureSpec方法来分别获取宽高,***再调用的就是View的measure方法,而通过前面的分析我们已经知道它做的就是对视图大小的计算。而对于measure中的参数是通过getChildMeasureSpec获取,再来看下其源码:

publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension) {

intspecMode = MeasureSpec.getMode(spec);

intspecSize = MeasureSpec.getSize(spec);

intsize= Math.max(0, specSize - padding);

intresultSize = 0;

intresultMode = 0;

switch (specMode) {

// Parent has imposed an exact sizeonus

caseMeasureSpec.EXACTLY:

if (childDimension >= 0) {

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} elseif (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants tobe oursize. So be it.

resultSize = size;

resultMode = MeasureSpec.EXACTLY;

} elseif (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants todetermine its ownsize. It can't be

// bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

// Parent has imposed a maximum sizeonus

caseMeasureSpec.AT_MOST:

if (childDimension >= 0) {

// Child wants a specific size... so be it

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} elseif (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants tobe oursize, but oursizeisnotfixed.

// Constrain child tonotbe bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

} elseif (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants todetermine its ownsize. It can't be

// bigger than us.

resultSize = size;

resultMode = MeasureSpec.AT_MOST;

}

break;

// Parent asked tosee how big we wanttobe

caseMeasureSpec.UNSPECIFIED:

if (childDimension >= 0) {

// Child wants a specific size... let him have it

resultSize = childDimension;

resultMode = MeasureSpec.EXACTLY;

} elseif (childDimension == LayoutParams.MATCH_PARENT) {

// Child wants tobe oursize... findouthow big it should

// be

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 :size;

resultMode = MeasureSpec.UNSPECIFIED;

} elseif (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants todetermine its ownsize.... findouthow

// big it should be

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 :size;

resultMode = MeasureSpec.UNSPECIFIED;

}

break;

}

//noinspection ResourceType

returnMeasureSpec.makeMeasureSpec(resultSize, resultMode);

}

是不是容易理解了点呢。它做的就是前面所说的根据mode的类型,获取相应的size。根据父视图的mode类型与子视图的LayoutParams类型来决定子视图所属的mode,***再将获取的size与mode通过MeasureSpec.makeMeasureSpec方法整合返回。***传递到measure中,这就是前面所说的widthMeasureSpec与heightMeasureSpec中包含的两部分的值。整个过程为measureChildren->measureChild->getChildMeasureSpec->measure->onMeasure->setMeasuredDimension,所以通过measureChildren就可以对子视图进行测量计算。

layout

layout也是一样的内部会回调onLayout方法,该方法是用来确定子视图的绘制位置,但这个方法在ViewGroup中是个抽象方法,所以如果要自定义的View是继承ViewGroup的话就必须实现该方法。但如果是继承View的话就不需要了,View中有一个空实现。而对子视图位置的设置是通过View的layout方法通过传递计算出来的left、top、right与bottom值,而这些值一般都要借助View的宽高来计算,视图的宽高则可以通过getMeasureWidth与getMeasureHeight方法获取,这两个方法获取的值就是上面onMeasure中setMeasuredDimension传递的值,即子视图测量的宽高。

getWidth、getHeight与getMeasureWidth、getMeasureHeight是不同的,前者是在onLayout之后才能获取到的值,分别为left-right与top-bottom;而后者是在onMeasure之后才能获取到的值。只不过这两种获取的值一般都是相同的,所以要注意调用的时机。

下面以定义一个把子视图放置于父视图的四个角的View为例:

@Override

protected void onLayout(boolean changed, intl,intt,intr,intb) {

intcount= getChildCount();

MarginLayoutParams params;

intcl;

intct;

intcr;

intcb;

for(inti = 0; i

Viewchild = getChildAt(i);

params = (MarginLayoutParams) child.getLayoutParams();

if (i == 0) {

//左上角

cl = params.leftMargin;

ct = params.topMargin;

} elseif (i == 1) {

//右上角

cl = getMeasuredWidth() - params.rightMargin - child.getMeasuredWidth();

ct = params.topMargin;

} elseif (i == 2) {

//左下角

cl = params.leftMargin;

ct = getMeasuredHeight() - params.bottomMargin - child.getMeasuredHeight()

- params.topMargin;

} else{

//右下角

cl = getMeasuredWidth() - params.rightMargin - child.getMeasuredWidth();

ct = getMeasuredHeight() - params.bottomMargin - child.getMeasuredHeight()

- params.topMargin;

}

cr = cl + child.getMeasuredWidth();

cb = ct + child.getMeasuredHeight();

//确定子视图在父视图中放置的位置

child.layout(cl, ct, cr, cb);

}

}

至于onMeasure的实现源码我后面会给链接,如果要看效果图的话,我后面也会贴出来,前面的那个验证码的也是一样

draw

draw是由dispatchDraw发动的,dispatchDraw是ViewGroup中的方法,在View是空实现。自定义View时不需要去管理该方法。而draw方法只在View中存在,ViewGoup做的只是在dispatchDraw中调用drawChild方法,而drawChild中调用的就是View的draw方法。那么我们来看下draw的源码:

publicvoid draw(Canvas canvas) {

final intprivateFlags = mPrivateFlags;

final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&

(mAttachInfo == null|| !mAttachInfo.mIgnoreDirtyState);

mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/*

* Draw traversal performs several drawing steps which must be executed

* inthe appropriateorder:

*

*      1. Draw the background

*      2. If necessary, save the canvas' layers toprepareforfading

*      3. Draw view's content

*      4. Draw children

*      5. If necessary, draw the fading edges andrestore layers

*      6. Draw decorations (scrollbars forinstance)

*/

// Step 1, draw the background, if needed

intsaveCount;

if (!dirtyOpaque) {

drawBackground(canvas);

}

// skip step 2 & 5 if possible (common case)

final intviewFlags = mViewFlags;

boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;

boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

if (!verticalEdges && !horizontalEdges) {

// Step 3, draw the content

if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children

dispatchDraw(canvas);

// Overlay ispartofthe contentanddraws beneath Foreground

if (mOverlay != null&& !mOverlay.isEmpty()) {

mOverlay.getOverlayView().dispatchDraw(canvas);

}

// Step 6, draw decorations (foreground, scrollbars)

onDrawForeground(canvas);

// we're done...

return;

}

//省略2&5的情况

....

}

源码已经非常清晰了draw总共分为6步;

绘制背景

如果需要的话,保存layers

绘制自身文本

绘制子视图

如果需要的话,绘制fading edges

绘制scrollbars

其中 第2步与第5步不是必须的。在第3步调用了onDraw方法来绘制自身的内容,在View中是空实现,这就是我们为什么在自定义View时必须要重写该方法。而第4步调用了dispatchDraw对子视图进行绘制。还是以验证码为例:

@Override

protected void onDraw(Canvas canvas) {

//绘制背景

mPaint.setColor(getResources().getColor(R.color.autoCodeBg));

canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

mPaint.getTextBounds(autoText, 0, autoText.length(), bounds);

//绘制文本

for(inti = 0; i

mPaint.setColor(getResources().getColor(colorRes[random.nextInt(6)]));

canvas.drawText(autoText, i, i + 1, getWidth() / 2 - bounds.width() / 2 + i * bounds.width() / autoNum

, bounds.height() + random.nextInt(getHeight() - bounds.height())

, mPaint);

}

//绘制干扰点

for(intj = 0; j

canvas.drawPoint(random.nextInt(getWidth()), random.nextInt(getHeight()), pointPaint);

}

//绘制干扰线

for(intk = 0; k

intstartX = random.nextInt(getWidth());

intstartY = random.nextInt(getHeight());

intstopX = startX + random.nextInt(getWidth() - startX);

intstopY = startY + random.nextInt(getHeight() - startY);

linePaint.setColor(getResources().getColor(colorRes[random.nextInt(6)]));

canvas.drawLine(startX, startY, stopX, stopY, linePaint);

}

}

其实很简单,就是一些绘制的业务逻辑。好了基本就到这里了,下面上传一张示例的效果图,与源码链接

示例图

对了还有自定义属性,这里简单说一下。自定义View时一般都要自定义属性,所以都会在res/values/attr.xml中定义attr与declare-styleable,***在自定义View中通过TypedArray获取。

【编辑推荐】

【责任编辑:枯木 TEL:(010)68476606】

点赞 0

android 自定义 child,Android自定义View相关推荐

  1. android fragment中引入自定义view_厉害了,用Android自定义View实现八大行星绕太阳3D旋转效果...

    作者:史蒂芬诺夫斯基 链接:https://www.jianshu.com/p/2954f2ef8ea5 好久没写View了,最近恰巧遇到一个八大行星绕太阳旋转的假3D效果,写完之后感觉效果还不错.能 ...

  2. android自定义view流布局,Android控件进阶-自定义流式布局和热门标签控件

    一.概述: 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧 类似的自定义换行流式布局控件.下 ...

  3. android炫酷的自定义view,Android自定义View实现炫酷进度条

    本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...

  4. android组件什么时候加载到r文件,Android自定义加载loading view动画组件

    我写写使用步骤 自定义view(CircleProgress )的代码 package com.hysmarthotel.view; import com.hysmarthotel.roomcontr ...

  5. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]

    http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...

  6. android sqlite自定义函数,Android中自定义一个View的方法详解

    本文实例讲述了Android中自定义一个View的方法.分享给大家供大家参考,具体如下: Android中自定义View的实现比较简单,无非就是继承父类,然后重载方法,即便如此,在实际编码中难免会遇到 ...

  7. Android Paint应用之自定义View实现进度条控件

    在上一篇文章<Android神笔之Paint>学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 上图就是本文要 ...

  8. 【Android 应用开发】自定义View 和 ViewGroup

    一. 自定义View介绍 自定义View时, 继承View基类, 并实现其中的一些方法. (1) ~ (2) 方法与构造相关 (3) ~ (5) 方法与组件大小位置相关 (6) ~ (9) 方法与触摸 ...

  9. Android开发-将自定义View布局到Layout中并调用

    写程序的时候,关于布局方面遇到并解决的问题 1.自定义View及其layout属性. 自定义View: [java] view plaincopy public class DrawView exte ...

最新文章

  1. 关于XAMPP环境配置
  2. vue生命周期探究(一)
  3. ARM(IMX6U)裸机按键输入实验(BSP+SDK、GPIO输入与输出、按键消抖)
  4. OC-封装、继承、多态
  5. HiveQL: 数据操作
  6. 安装sql server 2000
  7. mongodb 3.0版本安装
  8. 董明珠上榜中国杰出商界女性100
  9. 如何基于MySQL及Redis搭建统一的KV存储服务
  10. ISO27001标准的起源和发展
  11. item_search - 按关键字搜索alibaba商品
  12. TypeScript代理模式/委托模式
  13. 深入理解Android之Java Security第一部分
  14. Element UI修改message控件显示的时间
  15. Cobbler实现系统自动安装和cobbler的web管理实现
  16. 网页短信平台国际通道搭建|后台定制-移讯云短信系统
  17. i59300h处理器能带动matlab,i5-9300h相当于什么层次 在做选择
  18. CSDN VIP年卡大放送!中国大数据技术大会超值福利,等你来拿!
  19. 聚苯乙烯微球为成孔模板制备多孔PI/HMSNs复合膜/交联氨基聚苯乙烯微球/羧基聚苯乙烯微球
  20. V4L2视频驱动框架---meida_device管理模块简述

热门文章

  1. 如何修改被编译后DLL文件
  2. liferay 导入源码问题
  3. 【SpringBoot】在普通类中获取spring容器中的bean
  4. Ivan Fedorov:用已知无法想象未来 - Mixin Network开发者访谈
  5. 一名拿到阿里offer的Java程序员分享三轮面试经验
  6. Notepad++ 设置执行 lua 和 python
  7. 如何集成Spring和Struts(实例说明)
  8. svn Error:Wrong committed revision number: -1。
  9. Good Technology 产品特色
  10. T-SQL:流程控制 4,Case 语句