自定义View
来源:互联网 发布:js alert 编辑:程序博客网 时间:2024/06/06 03:43
前言
我们知道安卓中已经给我们提供了很多的控件,我们可以直接使用,但是很多时候我们也需要自己定义一个View来满足我们项目的需求。那我们怎么来自定义一个View呢,自定义一个View要经历那些过程,接下来,我们一点一点揭秘。
实现自定义View的基本步骤
首先,从大方向来讲,自定义View一共只需要以下三个步骤:
- 测量(onMesure)
- 布局(onLayout)
- 绘制(onDraw)
然后就是实现自定义View的方法一共有三种:
- 继承View
- 继承ViewGroup
- 继承已经定义好的View控件
在了解了这些东西以后,我们就要从源码的角度来揭开自定义View的面纱。内容不算多,跟着我的思路一步一步走就好了,大方向就是刚才说的三个步骤。
源码分析
首先,并不是所有的View都需要重新测量,所以,自定义view的绘制流程是从ViewRootImpl类(ViewRoot的实现类)中的performTraversals()方法开始的,这个方面主要是判断一下rootview本身是否需要重新测量,布局以及绘制,假设需要就就执行相应的动作。假设重新测量会有一个MeasureSpec,通常这个测量控件有的specMode为EXACTLY(已经精确测量),specSize为windowSize,所以我们的根视图通常是全屏的。
测量流程
这里有几个重要的概念:
- 1.ViewGroup.LayoutParams :用来指定视图高度和宽度的参数,不包括Padding。
- 2.MeasureSpec :表示测量规格。32位的Int值,高二位表示模式,后三十位表示测量规格的大小。
- 测量模式有如下三种:
- UNSPECIFIED:不限定子视图尺寸大小。
- EXACTLY:父容器会为子视图确定一个尺寸大小,无论子视图要求多大,都要在父容器的限制内。
- AT_MOST:父容器会为子视图指定一个最大的尺寸,子视图所有的大小都必须在这个尺寸范围内。对应wrap_content。此时父容器无法获取子视图的大小,只能子视图自己根据需求设定。
- 测量模式有如下三种:
ViewGroup.LayoutParams最终会被封装成MeasureSpec。
- ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
- 设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth) ;
onMeasure()方法:该方法会调用setMeasuredDimension()方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
setMeasuredDimension():该方法完成整个的测量过程。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight);}
- 如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。 对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。整个measure调用流程就是个树形的递归过程。
- Android中还有一个特殊的机制,就是当父视图认为子视图给它传递的宽高有异常,它会再次请求子视图去进行测量,如果子视图传递的宽高超过了父视图的约束范围,则父视图会使用一个确切的大小,给子视图设置成AT_MOST或者EXACTLY的形式,再次对子视图进行测量。
布局流程
- layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局);
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
- 如果该View是个ViewGroup类型,需要遍历每个子视图childView,调用该子视图的layout()方法去设置它的坐标值。
@Overrideprotected abstract void onLayout(boolean changed, int l, int t, int r, int b);
绘制过程
源码中一共分了六步:
- 第一步的作用是对视图的背景进行绘制。这里会先得到一个mBGDrawable对象,然后根据layout过程确定的视图位置来设置背景的绘制区域,之后再调用Drawable的draw()方法来完成背景的绘制工作。那么这个mBGDrawable对象是从哪里来的呢?其实就是在XML中通过android:background属性设置的图片或颜色。当然你也可以在代码中通过setBackgroundColor()、setBackgroundResource()等方法进行赋值。
- 第二步 一般跳过
- 第一步的作用是对视图的内容进行绘制。可以看到,这里去调用了一下onDraw()方法,那么onDraw()方法里又写了什么代码呢?进去一看你会发现,原来又是个空方法啊。其实也可以理解,因为每个视图的内容部分肯定都是各不相同的,这部分的功能交给子类来去实现也是理所当然的
- 第四步,这一步的作用是对当前视图的所有子视图进行绘制。但如果当前的视图没有子视图,那么也就不需要进行绘制了。因此你会发现View中的dispatchDraw()方法又是一个空方法,而ViewGroup的dispatchDraw()方法中就会有具体的绘制代码
- 第五步 一般跳过
- 第六步,这一步的作用是对视图的滚动条进行绘制。那么你可能会奇怪,当前的视图又不一定是ListView或者ScrollView,为什么要绘制滚动条呢?其实不管是Button也好,TextView也好,任何一个视图都是有滚动条的,只是一般情况下我们都没有让它显示出来而已。绘制滚动条的代码逻辑也比较复杂,这里就不再贴出来了,因为我们的重点是第三步过程。
public void draw(Canvas canvas) { final int privateFlags = 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 * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = 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 is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // we're done... return; } ...
OK,以上就是自定义View的三大过程,面试的时候最好根据自己的项目来谈一谈。
阅读全文
1 0
- 自定义view
- 自定义View
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义View
- 自定义view
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义view
- 自定义view
- 自定义view
- 自定义view
- 自定义View
- 自定义View
- Java 网络编程 简单Socket(UDP)
- 在Eclipse中测试MySQL-JDBC(13)Apache的DBCP连接池和c3p0连接池
- NB-IoT介绍
- 一个人的旅行 HDU2066
- Android自定义数字键盘
- 自定义View
- 栈溢出笔记1.4 黑掉example_2
- 关于apache配置虚拟主机后仍打开默认Apache目录的问题
- caffe python 图片训练识别 实例
- One 1 基础
- 编程题——替换空格
- 机房收费系统(三)---TRIM函数
- 栈溢出笔记1.5 换一个汇编工具
- iOS 内存管理~深浅拷贝~引用计数器