文章目录

 

[隐藏]

这回我们是深入到View内部,去研究View,去了解View的工作,抛弃其他因素,以便为以后能灵活的使用自定义空间打下一定的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android。

一、View是什么?

View是什么了,每个人都有自己的理解。在Android的官方文档中是这样描述的:这个类表示了用户界面的基本构建模块。一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理。View是用来构建用户界面组件(Button,Textfields等等)的基类。ViewGroup子类是各种布局的基类,它是个包含其他View(或其他ViewGroups)和定义这些View布局参数的容器。

其实说白了,View就是一个矩形区域,我们可以在这个区域上定义自己的控件。

注明:有对系统回调不太了解的回头看看回调,这样有助于对文章的理解。

二、View创建的一个概述:

在API中对View的回调流程有以个详细的描述,下面给出了原文翻译:(翻译有点仓促,大家多多包涵,有啥错的地方麻烦告知下我,我好改过来)

1.Creation           :创建

----Constructors(构造器)

There is a form of the constructor that arecalled when the view is created from code and a form that is called when theview is inflated from a layout file. The second form should parse and apply anyattributes defined in the layout file.在构造器中有个一个表单当View从代码中创建和从Layout File 文件中创建时。第二个表单应该解析和应用一些在Layout File中定义的属性。

---- onFinishInflate()

Called after a view and all of itschildren has been inflated from XML.当View和他的所有子View从XML中解析完成后调用。

2. Layout            :布局

----onMeasure(int, int)

Called to determine the size requirementsfor this view and all of its children.   确定View和它所有的子View要求的尺寸时调用

---- onLayout(boolean, int, int,int, int)

Calledwhen this view should assign a size and position to all of its children当这个View为其所有的子View指派一个尺寸和位置时调用

---- onSizeChanged(int, int, int,int)

Calledwhen the size of this view has changed.当这个View的尺寸改变后调用

3. Drawing         :绘制

---- onDraw(Canvas)

Calledwhen the view should render its content.当View给定其内容时调用

4.Event processing    :事件流程

----onKeyDown(int, KeyEvent)

Calledwhen a new key event occurs.当一个新的键按下时

---- onKeyUp(int, KeyEvent)

Calledwhen a key up event occurs.当一个键弹起时

----onTrackballEvent(MotionEvent)

Calledwhen a trackball motion event occurs.当滚迹球事件发生时。

----onTouchEvent(MotionEvent)

Calledwhen a touch screen motion event occurs.当一个触摸屏事件发生时。

5. Focus              :焦点

---- onFocusChanged(boolean, int,Rect)

onFocusChanged(boolean,int, Rect)当View得到和失去焦点时调用

---- onWindowFocusChanged(boolean)

Called when the windowcontaining the view gains or loses focus.当Window包含的View得到或失去焦点时调用。

根据View里面方法调用流程的概述,我们来重写其中的几个回调方法来直观的了解下这个调用,具体代码这里就不贴了,代码见测试包:DEMO_View调用流程.rar,调用的log显示:

这样大家就对View的调用有了个大概的认识,下面将针对View的标志系统、View的的布局参数系统等做一个简单的描述。

三、View的标志(Flag)系统

在一个系统中往往使用标志来指示系统中的某些参数,这里对View的标志系统做一些简单的介绍,这样大家可以借鉴下,以后也可以用这种表示方法。

一般而言标志都是成对出现的也就是表示相反两个属性,对于这种属性的表示方法我们使用一位的0和1就可以表示。如果有多个成对属性,如果每对属性都用一个int值来标志是不方便的。这种情况通常是用一个int的各个位来分别表示每个标志,在处理器中有一个标志位就是采用这种方式设计的。

我们先来看看位运算。位运算符包括: 与(&)、非(~)、或(|)、异或(^)

      &:   当两边操作数的位同时为1时,结果为1,否则为0。如1100&1010=1000   

|:   当两边操作数的位有一边为1时,结果为1,否则为0。如1100|1010=1110   

~:   0变1,1变0   

^:   两边的位不同时,结果为1,否则为0.如1100^1010=0110

在View系统使用mViewFlags来表征这些属性,其设置的主要方法如下

  1. void setFlag(int mask, int falg)
  2. {
  3. int old = mViewFlags;①
  4. mViewFlags = (mViewFlags & ~mask) | (mask & falg);②
  5. int changed = mViewFlags ^ old;// 获取改变的位,方法是对改变的位置1③
  6. ... ...
  7. }
void setFlag(int mask, int falg)
{
int old = mViewFlags;①
mViewFlags = (mViewFlags & ~mask) | (mask & falg);②
int changed = mViewFlags ^ old;// 获取改变的位,方法是对改变的位置1③
... ...
}

其中mask指的是标志位所在的位,falg表示的标志位。下面举个例子:

  1. public static final int VISIBLE = 0x00000000;
  2. public static final int INVISIBLE = 0x00000004;
  3. public static final int GONE = 0x00000008;
  4. static final int VISIBILITY_MASK = 0x0000000C;
public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static final int GONE = 0x00000008;
static final int VISIBILITY_MASK = 0x0000000C;

其中VISIBLE和INVISIBLE和GONE就是标志位,VISIBILITY_MASK是标志位所在的位,也就有VISIBLE+INVISIBLE+GON=VISIBILITY_MASK。看不懂的把上面四个转换为二进制就看出来了。

为什么要使用VISIBILITY_MASK?会不会有些多余呢?我们来看View中的计算公式:

  1. mViewFlags = (mViewFlags & ~mask) | (mask & falg);②
mViewFlags = (mViewFlags & ~mask) | (mask & falg);②

其中mViewFlags & ~mask是用来将mViewFlags中表示该标志的位置零。mask & falg是用来获得标志位。举个例子:

假设mViewFlags的二进制表示为110000;flag为INVISIBLE我们将上面的标志位转换为二进制VISIBLE 0000、INVISIBLE 0100、GONE 1000、VISIBILITY_MASK 1100。

mViewFlags & ~mask=110000 & 0011 = 110000(上面所用的标志位占用的是最后四位,我们通过这个运算来将这个标志位置零)。

  1. mask & falg = 1100 & 0100 =0100(获得标志)。
mask & falg = 1100 & 0100 =0100(获得标志)。

110000 | 0100(通过或运算来计算出最后的标志)。

一般而言:在多个同种类型的标志中,通常使用0来作为默认的标志。关于上面的标志系统的其他具体使用我们就不再深入,有兴趣的可以自行深入,有啥好的想法在群里分享下。

四、MeasureSpec

在View系统中,指定宽和高,以及指定布局的属性,是由MeasureSpec来封装的。下面是各个模式的标志位表示。

  1. private static final int MODE_SHIFT = 30;
  2. private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
  3. /**
  4. * Measure specification mode: The parent has not imposed any constraint
  5. * on the child. It can be whatever size it wants.
  6. */
  7. public static final int UNSPECIFIED = 0 << MODE_SHIFT;
  8. /**
  9. * Measure specification mode: The parent has determined an exact size
  10. * for the child. The child is going to be given those bounds regardless
  11. * of how big it wants to be.
  12. */
  13. public static final int EXACTLY     = 1 << MODE_SHIFT;
  14. /**
  15. * Measure specification mode: The child can be as large as it wants up
  16. * to the specified size.
  17. */
  18. public static final int AT_MOST     = 2 << MODE_SHIFT;
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;

在这个解析系统中是通过移位来存放更多的数据,现在每个数据标志位都向左移动了30位。这样表示一个View大小是很方便的,我们来看下面的方法:

  1. public static int makeMeasureSpec(int size, int mode) {
  2. return size + mode;
  3. }
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}

通过这个方法就可以制作一个含有两个参数的int值,这个参数包含一个mode标志和一个宽或高的表示。

我们通过如下方法来获取到mode:

  1. public static int getMode(int measureSpec) {
  2. return (measureSpec & MODE_MASK);
  3. }
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}

我们也可以用下面方法来获取高或宽的数据表示:

  1. public static int getSize(int measureSpec) {
  2. return (measureSpec & ~MODE_MASK);
  3. }
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

五、几个重要方法简介

正如第二节写的那个调用流程一样,这几个重要的方法是系统回调是调用的,同样对于这几个方法也是自定义组件的重要的方法。

在这节里我们主要是了解这些方法的用途,以期在自定义组件时可以对这些方法得心应手。

5.1 onFinishInflate()

这个是当系统解析XML完成,并且将子View全部添加完成之后调用这个方法,我们通常重写这个方法,在这个方法中查找并获得子View引用,当然前提是这个View中有子View所以一般都是继承ViewGroup时用这个方法比较多,比如抽屉效果中:

  1. @Override
  2. protected void onFinishInflate() {
  3. mHandle = findViewById(mHandleId);
  4. if (mHandle == null) {
  5. throw new IllegalArgumentException("The handle attribute is must refer to an"
  6. + " existing child.");
  7. }
  8. mHandle.setOnClickListener(new DrawerToggler());
  9. mContent = findViewById(mContentId);
  10. if (mContent == null) {
  11. throw new IllegalArgumentException("The content attribute is must refer to an"
  12. + " existing child.");
  13. }
  14. mContent.setVisibility(View.GONE);
  15. }
@Override
protected void onFinishInflate() {
mHandle = findViewById(mHandleId);
if (mHandle == null) {
throw new IllegalArgumentException("The handle attribute is must refer to an"
+ " existing child.");
}
mHandle.setOnClickListener(new DrawerToggler()); mContent = findViewById(mContentId);
if (mContent == null) {
throw new IllegalArgumentException("The content attribute is must refer to an"
+ " existing child.");
}
mContent.setVisibility(View.GONE);
}

通过重写这个方法来获取手柄的View和要显示内容的View。

5.2 onMeasure(int, int)

测量这个View的高和宽。通过调用这个方法来设置View的测量后的高和宽,其最终调用的方法是:

  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
  2. mMeasuredWidth = measuredWidth;
  3. mMeasuredHeight = measuredHeight;
  4. mPrivateFlags |= MEASURED_DIMENSION_SET;
  5. }
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight; mPrivateFlags |= MEASURED_DIMENSION_SET;
}

可见其最终是将高和宽保存在mMeasuredWidth、mMeasuredHeight这两个参数中。

其实调用onMeasure(int, int)的方法的不是系统,而是

public final voidmeasure(int widthMeasureSpec, int heightMeasureSpec)

这个才是系统回调的方法,然后通过这个方法调用onMeasure(int, int)方法,个人感觉这种设计就是把系统方法和用户可以重写的方法分离开,这样避免一些不必要的错误。

在这个方法中主要是用来初始化各个子View的布局参数,我们来看看抽屉中的实现:

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  4. int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
  5. int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  6. int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
  7. if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
  8. throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
  9. }
  10. final View handle = mHandle;
  11. measureChild(handle, widthMeasureSpec, heightMeasureSpec);
  12. if (mVertical) {
  13. int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
  14. mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
  15. MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
  16. } else {
  17. int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
  18. mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
  19. MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
  20. }
  21. setMeasuredDimension(widthSpecSize, heightSpecSize);
  22. }
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");
} final View handle = mHandle;
measureChild(handle, widthMeasureSpec, heightMeasureSpec); if (mVertical) {
int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else {
int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;
mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));
} setMeasuredDimension(widthSpecSize, heightSpecSize);
}

刚才我们已经获取到mHandle和mContent的引用,因为onFinishInflate()方法调用在onMeasure(int, int)方法之前,所以这个不会出现nullPoint。我们可以看到在这个方法中主要就是为mHandle和mContent指定了布局参数。这里用到了MeasureSpec。

5.3 onLayout(boolean, int, int,int, int)

onLayout是用来指定各个子View的位置,这个方法和上面方法类似,也不是真正的系统回调函数,真正的回调函数是Layout。这个方法的使用主要在ViewGroup中。这里不再详述。我们在ViewGroup讲解时再去了解这个方法。

5.4 onSizeChanged(int, int, int,int)

这个是当View的大小改变时调用,这个也不再详述,基本上用的也比较少。

5.5 onDraw(android.graphics.Canvas)

这个方法相信大家都不会陌生了,在我以前的博客里也有这个方法的使用。当然那个比较入门,比较肤浅,呵呵。这里我们深入进去,类似于onMeasure(int, int),其实这个方法是由draw(Canvas)方法调用的。在这个方法中有一个对这个方法的描述:

  1. /*
  2. * Draw traversal performs several drawing steps which must be executed
  3. * in the appropriate order:
  4. *
  5. *      1. Draw the background
  6. *      2. If necessary, save the canvas' layers to prepare for fading
  7. *      3. Draw view's content
  8. *      4. Draw children
  9. *      5. If necessary, draw the fading edges and restore layers
  10. *      6. Draw decorations (scrollbars for instance)
  11. */
/*
* 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)
*/

我们可以看到:

               首先是绘制背景

               其次如果需要准备层之间的阴影

               然后绘制内容(这个内容就是调用我们的onDraw方法)

               再绘制children(dispatchDraw(canvas);)这个方法的调用主要实现在ViewGroup中,和继承ViewGroup的组件中。

               如果需要绘制层之间的阴影。

               绘制装饰,也就是scrollbars。

dispatchDraw(canvas);这也是一个重要的方法,用于绘制子组件用的。下面是抽屉中的实现方法。也比较简单,大家自行阅读下也就了解了。

  1. @Override
  2. protected void dispatchDraw(Canvas canvas) {
  3. final long drawingTime = getDrawingTime();
  4. final View handle = mHandle;
  5. final boolean isVertical = mVertical;
  6. drawChild(canvas, handle, drawingTime);
  7. if (mTracking || mAnimating) {
  8. final Bitmap cache = mContent.getDrawingCache();
  9. if (cache != null) {
  10. if (isVertical) {
  11. canvas.drawBitmap(cache, 0, handle.getBottom(), null);
  12. } else {
  13. canvas.drawBitmap(cache, handle.getRight(), 0, null);
  14. }
  15. } else {
  16. canvas.save();
  17. canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,
  18. isVertical ? handle.getTop() - mTopOffset : 0);
  19. drawChild(canvas, mContent, drawingTime);
  20. canvas.restore();
  21. }
  22. } else if (mExpanded) {
  23. drawChild(canvas, mContent, drawingTime);
  24. }
  25. }
@Override
protected void dispatchDraw(Canvas canvas) {
final long drawingTime = getDrawingTime();
final View handle = mHandle;
final boolean isVertical = mVertical; drawChild(canvas, handle, drawingTime); if (mTracking || mAnimating) {
final Bitmap cache = mContent.getDrawingCache();
if (cache != null) {
if (isVertical) {
canvas.drawBitmap(cache, 0, handle.getBottom(), null);
} else {
canvas.drawBitmap(cache, handle.getRight(), 0, null);
}
} else {
canvas.save();
canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,
isVertical ? handle.getTop() - mTopOffset : 0);
drawChild(canvas, mContent, drawingTime);
canvas.restore();
}
} else if (mExpanded) {
drawChild(canvas, mContent, drawingTime);
}
}

好了,这个就是View里面的内容,关于事件监听我们这里就不再详细描述,自定义组件的话,在写完深入ViewGroup中会有一个专门的专题,而ViewGroup中也会去深化View中一些东西。

欢迎大家加入:Android研究交流群:194802363

深入理解Android中View的更多相关文章

  1. 深入理解 Android 之 View 的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  2. Android中View绘制流程以及invalidate()等相关方法分析

    [原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...

  3. 源码解析Android中View的measure量算过程

    Android中的Veiw从内存中到呈现在UI界面上需要依次经历三个阶段:量算 -> 布局 -> 绘图,关于View的量算.布局.绘图的总体机制可参见博文< Android中View ...

  4. 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...

  5. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  6. Android中View绘制流程以及invalidate()等相关方法分析(转载的文章,出处在正文已表明)

    转载请注明出处:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时 ...

  7. Android中View和ViewGroup介绍

    1. 概念Android中的View与我们以前理解的“视图”不同.在Android中,View比视图具有更广的含义,它包含了用户交互和显示,更像Windows操作系统中的window. ViewGro ...

  8. 【转】Android菜单详解——理解android中的Menu--不错

    原文网址:http://www.cnblogs.com/qingblog/archive/2012/06/08/2541709.html 前言 今天看了pro android 3中menu这一章,对A ...

  9. 【转】深入理解Android之View的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

随机推荐

  1. VxWorks嵌入式系统几种常用的延时方法

    1 taskDelay     taskDelay(n)使调用该函数的任务延时n个tick(内核时钟周期).该任务在指定的时间内主动放弃CPU,除了taskDelay(0)专用 于任务调度(将CPU交 ...

  2. Java中的三目运算符

    1.问题背景    以下代码运行的结果是:    A.hai    B.1987    C.1988    D.以上答案都不对 /** * 三目运算符 * A.hai * B.1987 * C.198 ...

  3. 从零一起学Spring Boot之LayIM项目长成记(三) 数据库的简单设计和JPA的简单使用。

    前言 今天是第三篇了,上一篇简单模拟了数据,实现了LayIM页面的数据加载.那么今天呢就要用数据库的数据了.闲言少叙,书归正传,让我们开始吧. 数据库 之前有好多小伙伴问我数据库是怎么设计的.我个人用 ...

  4. [翻译] 比较 Node.js,Python,Java,C# 和 Go 的 AWS Lambda 性能

    [翻译] 比较 Node.js,Python,Java,C# 和 Go 的 AWS Lambda 性能 原文: Comparing AWS Lambda performance of Node.js, ...

  5. Bzoj4237:稻草人

    题面 传送门 Sol \(CDQ\)分治 先对\(x\)排序,对\(y\)在\(CDQ\)分治是从大到小排序 从大到小加入,右边用单调栈维护\(x\)递增,\(y\)递减的序列 左边就是找到\(x\) ...

  6. Vue-框架模板的源代码注释

    请稍等..吃完饭回来写 吃饭回来了~嘿 ----------------正经分割线----------------- 先看我的目录结构:这是配置好node环境和配置好webpack后,生成的原始框架. ...

  7. centos7下搭建 MongoDB -01

    距离上次写的一篇mongoDB搭建已经有一年多的时间了,刚好这次在公司搭建好在centos7下的mongodb搭建,简单的做一个记录吧 mongo 是一个基于分布式文件存储的数据库,数据主要存储在磁盘 ...

  8. Mysql 忘记管理员密码更改

    对管理员设置密码 第一种方式: #mysqladmin -u root password 'new-password'; #mysqladmin -u root -h localhost passwo ...

  9. Maven错误信息:Missing artifact jdk.tools:jdk.tools:jar:1.6

    在pom.xml中添加依赖: <dependency> <groupId>jdk.tools</groupId> <artifactId>jdk.too ...

  10. ssh框架中struts.xml 的配置参数详解

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "- ...