深入理解Android View(一) View的位置參数信息

二、View的绘制过程

View的绘制过程一共分为三个部分:

- measure(測量View的大小)

- layout(确定View的位置)

- draw(画出View)

通常我们的View都是以这种树结构呈现的。例如以下图



当然我们这里ViewGroup事实上上面事实上是放在DecorView中的。我们能够通过findViewbById(andorid.id.content)获取到顶级ViewGroup。这里的DecorView开发中一般涉及不到,这里我们只是多分析,我们仅仅须要知道我们的绘制过程是从DecorView分发下来的就可以。

我们先来看ViewGroup中的一段源代码

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
} protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

这里我们能够看到在ViewGroup中的measureChildren的方法。首先获取到了全部的子View。然后调用View的measure方法进行子View的測量,这样就完毕了一次測量过程,然后子View会反复父View的操作。如此返回就完毕了整个View树的測量过程。通过源代码我们发现一个View的大小,主要由父View的MeasureSpec和自身的LayoutParams来共同决定。LayoutParams我们应都比較熟悉了,接下来我们就来深入分析MeasureSpec,这是在View类中的一个静态内部类,看名字就能猜到它代表它的作用是一种測量标准或者说測量规格。我们先来看下它的源代码

public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
// 老版api
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
} public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
} public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}

MeasureSpec代表一个32位的int值,高两位代表SpecMode。低30位代表SpecSize,Mode指測量模式,Size则指測量的大小。通过看MeasureSpec的源代码也能看到,事实上这里谷歌project师很巧妙的把Mode和Size封装到了一个int值中。很巧妙的办法(这种设计方式能够借鉴),假设你对Java运算符不太熟的话,你能够这样理解,谷歌project师把一个32位的int分成了两节,一节用来表示Mode,一节用来表示Size。能够看到Mode分为三种模式例如以下所看到的:

  • UNSPECIFIED

    父容器不正确子View做不论什么限制,要多大给多大。一般用于系统内部,这里不用过多考虑
  • EXACTLY

    精准模式。一般View指定了详细的大小(dp/px)或者设置为match_parent则就是这个模式
  • AT_MOST

    父容器制定了一个可用的大小。子View不能大于这个值,对于wrap_content



    这里借用任主席博客中的一张图。UNSPECIFIED 我们不予考虑。

    总结:一个View的绘制主要由父View的MeasureSpec和自身的LayoutParams决定。其关系如上表。说了这么多事实上大多数时候当我自己定义View时,仅仅须要处理AT_MOST就可以。通用代码例如以下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//分别获取宽高的Mode 和size
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 假设是精准模式则直接使用获取到的宽高。假设是AT_MOST,则使用我 们自己測量的宽高
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}

假设当前是View则直接完毕測量,假设当前是ViewGruop除了完毕自身绘制外,还须要遍历调用子View的measure方法。

View的layout方法和Draw方法就比較简单了。layout用于确定View的位置,原理和Measure过程相似,当一个ViewGroup的位置被确定之后,会遍历其全部子View并调用其layout方法。Draw的过程也相似。通过dispatchDraw(Canvas canvas)方法,遍历子View,通过canvas进行Draw过程,接下来我通过一个小Demo流式布局,来运用一下三个过程



import java.util.ArrayList;
import java.util.List; import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup; /**
*
*/
public class FlowLayout extends ViewGroup { /**
* 存储全部的子View
*/
private List<List<View>> mAllChildViews = new ArrayList<List<View>>(); /**
* 存储每一行的高度
*/
private List<Integer> mLineHeight = new ArrayList<Integer>(); public FlowLayout(Context context) {
this(context, null);
} public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mAllChildViews.clear();
mLineHeight.clear();
// 获取当前ViewGroup的宽度
int width = getWidth(); int lineWidth = 0;
int lineHeight = 0;
// 记录当前行的view
List<View> lineViews = new ArrayList<View>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight(); // 假设须要换行
if (childWidth + lineWidth + lp.leftMargin + lp.rightMargin > width) {
// 记录LineHeight
mLineHeight.add(lineHeight);
// 记录当前行的Views
mAllChildViews.add(lineViews);
// 重置行的宽高
lineWidth = 0;
lineHeight = childHeight + lp.topMargin + lp.bottomMargin;
// 重置view的集合
lineViews = new ArrayList();
}
lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
+ lp.bottomMargin);
lineViews.add(child);
}
// 处理最后一行
mLineHeight.add(lineHeight);
mAllChildViews.add(lineViews); MarginLayoutParams params = (MarginLayoutParams) this.getLayoutParams(); // 设置子View的位置
int left = 0;
// 加入marginTop
int top = 0 + params.topMargin;
// 获取行数
int lineCount = mAllChildViews.size();
for (int i = 0; i < lineCount; i++) {
// 当前行的views和高度
lineViews = mAllChildViews.get(i);
lineHeight = mLineHeight.get(i);
for (int j = 0; j < lineViews.size(); j++) {
// 为每一列设置marginLeft
if (j == 0) {
left = 0 + params.leftMargin;
}
View child = lineViews.get(j);
// 推断是否显示
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child
.getLayoutParams();
int cLeft = left + lp.leftMargin;
int cTop = top + lp.topMargin;
int cRight = cLeft + child.getMeasuredWidth();
int cBottom = cTop + child.getMeasuredHeight();
// 进行子View进行布局
child.layout(cLeft, cTop, cRight, cBottom);
left += child.getMeasuredWidth() + lp.leftMargin
+ lp.rightMargin;
}
left = 0;
top += lineHeight;
}
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 父控件传进来的宽度和高度以及相应的測量模式
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // 假设当前ViewGroup的宽高为wrap_content的情况
int width = 0;// 自己測量的 宽度
int height = 0;// 自己測量的高度
// 记录每一行的宽度和高度
int lineWidth = 0;
int lineHeight = 0; // 获取子view的个数
int childCount = getChildCount(); for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 測量子View的宽和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到LayoutParams MarginLayoutParams params = (MarginLayoutParams) child
.getLayoutParams();
// 子View占领的宽度
int childWidth = child.getMeasuredWidth() + params.leftMargin
+ params.rightMargin;
// 子View占领的高度
int childHeight = child.getMeasuredHeight() + params.bottomMargin
+ params.topMargin;
// 换行时候
if (lineWidth + childWidth > sizeWidth) {
// 对照得到最大的宽度
width = Math.max(width, lineWidth);
// 重置lineWidth
lineWidth = childWidth;
// 记录行高
height += lineHeight;
lineHeight = childHeight;
} else {
// 不换行情况
// 叠加行宽
lineWidth += childWidth;
// 得到最大行高
lineHeight = Math.max(lineHeight, childHeight);
}
// 处理最后一个子View的情况
if (i == childCount - 1) {
width = Math.max(width, lineWidth);
height += lineHeight;
}
}
// 这里就是关键代码
setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth
: width, modeHeight == MeasureSpec.EXACTLY ? sizeHeight
: height); } /**
* 与当前ViewGroup相应的LayoutParams
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}

浅析Android View(二)的更多相关文章

  1. Android开发艺术探索笔记——View(二)

    Android开发艺术探索笔记--View(二) View的事件分发机制 学习资料: 1.Understanding Android Input Touch Events System Framewo ...

  2. Android View框架总结(二)View焦点

    请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52263256 前言:View框架写到第六篇,发现前面第二篇竟然没有, ...

  3. Android View 的添加绘制流程 (二)

    概述 上一篇 Android DecorView 与 Activity 绑定原理分析 分析了在调用 setContentView 之后,DecorView 是如何与 activity 关联在一起的,最 ...

  4. Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

    View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇  ...

  5. 简单研究Android View绘制二 LayoutParams

    2015-07-28 17:23:20 本篇是关于LayoutParams相关 ViewGroup.LayoutParams文档解释如下: LayoutParams are used by views ...

  6. Android View的绘制机制流程深入详解(二)

    本系列文章主要着重深入介绍Android View的绘制机制及流程,第二篇主要介绍并分析Android视图的绘制的原理和流程.主要从 onMeasure().onLayout()和onDraw()这三 ...

  7. 浅析Android中的消息机制-解决:Only the original thread that created a view hierarchy can touch its views.

    在分析Android消息机制之前,我们先来看一段代码: public class MainActivity extends Activity implements View.OnClickListen ...

  8. 浅析 Android 的窗口

    来源:http://bugly.qq.com/bbs/forum.php?mod=viewthread&tid=555&fromuid=6   一.窗口的概念 在开发过程中,我们经常会 ...

  9. 简单研究Android View绘制三 布局过程

    2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...

随机推荐

  1. love2d--glsl02变量和语句

    Shader分为顶点着色器和片段着色器,GPU先处理顶点再处理片段,大概可以这么理解, 顶点着色器处理模型里的点,输出处理后的数据,这些数据经过GPU其它模块处理后传入 片段着色器,经片段着色器综合后 ...

  2. Andriod——setContentView( )方法

    setContentView( )方法 setContentView(R.layout.main)在Android里面,这句话是什么意思? R.layout.main是个布局文件即控件都是如何摆放如何 ...

  3. CSS3 实现的一个简单的"动态主菜单" 示例

    其实这个示例蛮无聊的 很简单 也没什么实际的用处. 主要是展示了 CSS3 如何实现动画效果. 写这个主要是想看一看 完成这样的效果 我到底要写多少代码. 同时和我熟悉的java做个比较. 比较结果不 ...

  4. jQuery补充,模拟图片放大镜

    jQuery补充,模拟图片放大镜 html <!DOCTYPE html> <html lang="en"> <head> <meta c ...

  5. OAuth2.0 介绍

    一.基本协议流程: (1) Client请求RO(Resource Owner)的授权:请求中一般包含:要访问的资源路径,操作类型,Client的身份等信息.(2) RO批准授权:并将“授权证据”发送 ...

  6. 多媒体开发之分场图像和交错图像interlacing---一个破解版的迅雷云点播网站

    [-] 目录 编辑描述 编辑去交错方法 编辑去交错源自电影的影像 编辑去交错交错式影像 编辑单一场去交错intra-field deinterlacing 编辑场间去交错inter-field dei ...

  7. AJAX解惑篇(转)

    这篇文章会使你对AJAX有一个基本了解,并给出两个容易上手的例子. 什么是AJAX AJAX是一个新的合成术语,隐含了两个已经存在多年的JavaScript特性,但是直到最近,随着一些诸如Gmail. ...

  8. linux下解压 tar.bz2

    tar xvfj xxx.tar.bz2 转自: http://www.360doc.com/content/12/0907/16/8006573_234845810.shtml

  9. 将数据写入TXT文件中,file_put_contents与fwrite

    <?php header("content-type:text/html;charset=utf-8"); $file = './aa.txt'; ###判断是不是文件 if ...

  10. win7下cmake编译opencv2.3.1生成opencv—createsamples.exe和opencv_haartrainingd.exe

    第一步:下载安装cmake,之后进行默认安装即可,这步略过. 第二步:配置cmake ,使cmake找到opencv进行编译安装 watermark/2/text/aHR0cDovL2Jsb2cuY3 ...