android 自定义 child,Android自定义View
前言
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相关推荐
- android fragment中引入自定义view_厉害了,用Android自定义View实现八大行星绕太阳3D旋转效果...
作者:史蒂芬诺夫斯基 链接:https://www.jianshu.com/p/2954f2ef8ea5 好久没写View了,最近恰巧遇到一个八大行星绕太阳旋转的假3D效果,写完之后感觉效果还不错.能 ...
- android自定义view流布局,Android控件进阶-自定义流式布局和热门标签控件
一.概述: 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧 类似的自定义换行流式布局控件.下 ...
- android炫酷的自定义view,Android自定义View实现炫酷进度条
本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...
- android组件什么时候加载到r文件,Android自定义加载loading view动画组件
我写写使用步骤 自定义view(CircleProgress )的代码 package com.hysmarthotel.view; import com.hysmarthotel.roomcontr ...
- android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
http://blog.csdn.net/jj120522/article/details/8095852 示意图就不展示了,和上一节的一样,滑动菜单SlidingMenu效果如何大家都比较熟悉,在这 ...
- android sqlite自定义函数,Android中自定义一个View的方法详解
本文实例讲述了Android中自定义一个View的方法.分享给大家供大家参考,具体如下: Android中自定义View的实现比较简单,无非就是继承父类,然后重载方法,即便如此,在实际编码中难免会遇到 ...
- Android Paint应用之自定义View实现进度条控件
在上一篇文章<Android神笔之Paint>学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 上图就是本文要 ...
- 【Android 应用开发】自定义View 和 ViewGroup
一. 自定义View介绍 自定义View时, 继承View基类, 并实现其中的一些方法. (1) ~ (2) 方法与构造相关 (3) ~ (5) 方法与组件大小位置相关 (6) ~ (9) 方法与触摸 ...
- Android开发-将自定义View布局到Layout中并调用
写程序的时候,关于布局方面遇到并解决的问题 1.自定义View及其layout属性. 自定义View: [java] view plaincopy public class DrawView exte ...
最新文章
- 关于XAMPP环境配置
- vue生命周期探究(一)
- ARM(IMX6U)裸机按键输入实验(BSP+SDK、GPIO输入与输出、按键消抖)
- OC-封装、继承、多态
- HiveQL: 数据操作
- 安装sql server 2000
- mongodb 3.0版本安装
- 董明珠上榜中国杰出商界女性100
- 如何基于MySQL及Redis搭建统一的KV存储服务
- ISO27001标准的起源和发展
- item_search - 按关键字搜索alibaba商品
- TypeScript代理模式/委托模式
- 深入理解Android之Java Security第一部分
- Element UI修改message控件显示的时间
- Cobbler实现系统自动安装和cobbler的web管理实现
- 网页短信平台国际通道搭建|后台定制-移讯云短信系统
- i59300h处理器能带动matlab,i5-9300h相当于什么层次 在做选择
- CSDN VIP年卡大放送!中国大数据技术大会超值福利,等你来拿!
- 聚苯乙烯微球为成孔模板制备多孔PI/HMSNs复合膜/交联氨基聚苯乙烯微球/羧基聚苯乙烯微球
- V4L2视频驱动框架---meida_device管理模块简述
热门文章
- 如何修改被编译后DLL文件
- liferay 导入源码问题
- 【SpringBoot】在普通类中获取spring容器中的bean
- Ivan Fedorov:用已知无法想象未来 - Mixin Network开发者访谈
- 一名拿到阿里offer的Java程序员分享三轮面试经验
- Notepad++ 设置执行 lua 和 python
- 如何集成Spring和Struts(实例说明)
- svn Error:Wrong committed revision number: -1。
- Good Technology 产品特色
- T-SQL:流程控制 4,Case 语句