深入理解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. [Linux内核]ctrl-z/fg/bg/nohup/setsid/()与&/disown/screen

    转自:https://my.oschina.net/alphajay/blog/65058 My Tips: Ctrl -z    ->   suspend fg           -> ...

  2. [gpio]Linux GPIO简单使用方式1-sysfs

    转自:http://blog.csdn.net/drivermonkey/article/details/20132241 1.1.References 1.2.GPIO Usage from a L ...

  3. linux 串口 特殊字符

    近日在写一个linux的串口程序,发现大多数情况下数据接收没问题,但是有时却有问题.主要是接收的字符串中包含有0x03这个字符,会造成与它相邻的字符同时也接收不到,搞了好久才发现这个错误.查找资料后发 ...

  4. google cloud之查看任务任务过程

    点击侧边栏的logging

  5. MVC已经是现代Web开发中的一个很重要的部分,下面介绍一下Spring MVC的一些使用心得。

    MVC已经是现代Web开发中的一个很重要的部分,下面介绍一下Spring MVC的一些使用心得. 之前的项目比较简单,多是用JSP .Servlet + JDBC 直接搞定,在项目中尝试用 Strut ...

  6. 【vijos】1006 晴天小猪历险记之Hill(dijkstra)

    https://vijos.org/p/1006 连边后跑点权的最短路 注意连边的时候左端点可以连到下一行的右端点,右端点可以连到下一行的左端点 #include <cstdio> #in ...

  7. Elixir语言

    Elixir是一个基于Erlang VM的函数式元编程语言(类似Ruby),通过动态语言的灵活的语法和宏能够利用Erlang建立一个并发 分布 失败冗余的高质量代码

  8. Android中BroadcastReceiver组件具体解释

    Android系统的4个组件最终还剩一种组件了BroadcastReceiver,这个组件是全局监听器,能够监听系统全局的广播消息,能够方便的实现系统中不同组件之间的通信 BroadcastRecei ...

  9. iOS 模块化

    模块化 1.公共模块 网络层 模型层(基类) 2.mvvm 3.模块化(单元模块,实现单元功能,单元测试) 4.pod 5.路由

  10. ios开发之 -- UIView总结

    如果想调用某个类的某个方法可以写成这样,这个方法来自NSObject类 performSelector: performSelector:withObject: performSelector:wit ...