这回我们是深入到ViewGroup内部\,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识。以便为以后能灵活的使用自定义空间打更近一步的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android。

一、ViewGroup是什么?

一个ViewGroup是一个可以包含子View的容器,是布局文件和View容器的基类。在这个类里定义了ViewGroup.LayoutParams类,这个类是布局参数的子类。

其实ViewGroup也就是View的容器。通过ViewGroup.LayoutParams来指定子View的参数。

ViewGroup作为一个容器,为了制定这个容器应有的标准所以为其指定了接口

  1. public abstract class ViewGroup extends View implements ViewParent, ViewManager

这两个接口这里不研究,如果涉及到的话会带一下。ViewGroup有小4000行代码,下面我们一个模块一个模块分析。

二、ViewGroup这个容器

ViewGroup是一个容器,其采用一个数组来存储这些子View:

  1. // Child views of this ViewGroup
  2. private View[] mChildren;

由于是通过一个数组来存储View数据的,所以对于ViewGroup来说其必须实现增、删、查的算法。下面我们就来看看其内部实现。

2.1 添加View的算法

  1. protected boolean addViewInLayout(View child, int index, LayoutParams params) {
  2. return addViewInLayout(child, index, params, false);
  3. }
  4. protected boolean addViewInLayout(View child, int index, LayoutParams params,
  5. boolean preventRequestLayout) {
  6. child.mParent = null;
  7. addViewInner(child, index, params, preventRequestLayout);
  8. child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
  9. return true;
  10. }
  11. private void addViewInner(View child, int index, LayoutParams params,
  12. boolean preventRequestLayout) {
  13. ...
  14. addInArray(child, index);
  15. ...
  16. }
  17. private void addInArray(View child, int index) {
  18. ...
  19. }

上面四个方法就是添加View的核心算法的封装,它们是层层调用的关系。而我们通常调用的addView就是最终通过上面那个来最终达到添加到ViewGroup中的。

2.1.1 我们先来分析addViewInner方法:

  1. 首先是对子View是否已经包含到一个父容器中,主要的防止添加一个已经有父容器的View,因为添加一个拥有父容器的View时会碰到各种问题。比如记录本身父容器算法的问题、本身被多个父容器包含时更新的处理等等一系列的问题都会出现。

    1. if (child.getParent() != null) {
    2. throw new IllegalStateException("The specified child already has a parent. " +
    3. "You must call removeView() on the child's parent first.");
    4. }
  2. 然后就是对子View布局参数的处理。

  3. 调用addInArray来添加View

  4. 父View为当前的ViewGroup

  5. 焦点的处理。

  6. 当前View的AttachInfo信息,这个信息是用来在窗口处理中用的。Android的窗口系统就是用过AttachInfo来判断View的所属窗口的,这个了解下就行。详细信息设计到Android框架层的一些东西。

    1. AttachInfo ai = mAttachInfo;
    2. if (ai != null) {
    3. boolean lastKeepOn = ai.mKeepScreenOn;
    4. ai.mKeepScreenOn = false;
    5. child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
    6. if (ai.mKeepScreenOn) {
    7. needGlobalAttributesUpdate(true);
    8. }
    9. ai.mKeepScreenOn = lastKeepOn;
    10. }
  7. View树改变的监听

    1. if (mOnHierarchyChangeListener != null) {
    2. mOnHierarchyChangeListener.onChildViewAdded(this, child);
    3. }
  8. 子View中的mViewFlags的设置:

  1. if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
  2. mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
  3. }

2.1.2 addInArray

这个里面的实现主要是有个知识点,以前也没用过arraycopy,这里具体实现就不多加描述了。

  1. System.arraycopy(children, 0, mChildren, 0, index);
  2. System.arraycopy(children, index, mChildren, index + 1, count - index);

2.2 移除View

移除View的几种方式:

        • 移除指定的View。

        • 移除从指定位置的View

        • 移除从指定位置开始的多个View

        • 移除所有的View

其中具体涉及到的方法就有好多了,不过最终对要删除的子View中所做的无非就是下列的事情:

        • 如果拥有焦点则清楚焦点

        • 将要删除的View从当前的window中解除关系。

        • 设置View树改变的事件监听,我们可以通过监听OnHierarchyChangeListener事件来进行一些相应的处理。

        • 从父容器的子容器数组中删除。

具体的内容这里就不一一贴出来了,大家回头看看源码就哦了。

2.3 查询

这个就简单了,就是直接从数组中取出就可以了:

  1. public View getChildAt(int index) {
  2. try {
  3. return mChildren[index];
  4. } catch (IndexOutOfBoundsException ex) {
  5. return null;
  6. }
  7. }

分析到这儿,其实我们已经相当于分析了ViewGroup四分之一的代码了,呵呵。

三、onFinishInflate

我们一般使用View的流程是在onCreate中使用setContentView来设置要显示Layout文件或直接创建一个View,在当设置了ContentView之后系统会对这个View进行解析,然后回调当前视图View中的onFinishInflate方法。只有解析了这个View我们才能在这个View容器中获取到拥有Id的组件,同样因为系统解析完View之后才会调用onFinishInflate方法,所以我们自定义组件时可以onFinishInflate方法中获取指定子View的引用。

四、测量组件

在ViewGroup中提供了测量子组件的三个方法。

  1. //1、measureChild(View, int, int),为子组件添加Padding
  2. protected void measureChild(View child, int parentWidthMeasureSpec,
  3. int parentHeightMeasureSpec) {
  4. final LayoutParams lp = child.getLayoutParams();
  5. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  6. mPaddingLeft + mPaddingRight, lp.width);
  7. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  8. mPaddingTop + mPaddingBottom, lp.height);
  9. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  10. }
  1. //2、measureChildren(int, int)根据指定的高和宽来测量所有子View中显示参数非GONE的组件。
  2. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
  3. final int size = mChildrenCount;
  4. final View[] children = mChildren;
  5. for (int i = 0; i < size; ++i) {
  6. final View child = children[i];
  7. if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
  8. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  9. }
  10. }
  11. }
  1. 3、measureChildWithMargins(View, int, int, int, int)测量指定的子组件,为子组件添加Padding和Margin。
  2. protected void measureChildWithMargins(View child,
  3. int parentWidthMeasureSpec, int widthUsed,
  4. int parentHeightMeasureSpec, int heightUsed) {
  5. final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  6. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  7. mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
  8. + widthUsed, lp.width);
  9. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  10. mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
  11. + heightUsed, lp.height);
  12. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  13. }

上面三个方法都是为子组件设置了布局参数。最终调用的方法是子组件的measure方法。在View中我们知道这个调用实际上就是设置了子组件的布局参数并且调用onMeasure方法,最终设置了View测量后的高度和宽度。

五、onLayout

这个函数是一个抽象函数,要求实现ViewGroup的函数必须实现这个函数,这也就是ViewGroup是一个抽象函数的原因。因为各种组件实现的布局方式不一样,而onLayout是必须被重载的函数。

  1. @Override
  2. protected abstract void onLayout(boolean changed,
  3. int l, int t, int r, int b);
  4. 来看View中layout方法:
  5. public final void layout(int l, int t, int r, int b) {
  6. boolean changed = setFrame(l, t, r, b);
  7. if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
  8. if (ViewDebug.TRACE_HIERARCHY) {
  9. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
  10. }
  11. onLayout(changed, l, t, r, b);
  12. mPrivateFlags &= ~LAYOUT_REQUIRED;
  13. }
  14. mPrivateFlags &= ~FORCE_LAYOUT;
  15. }

在这个方法中调用了setFrame方法,这个方法是用来设置View中的上下左右边距用的

  1. protected boolean setFrame(int left, int top, int right, int bottom) {
  2. boolean changed = false;
  3. //.......
  4. if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
  5. changed = true;
  6. // Remember our drawn bit
  7. int drawn = mPrivateFlags & DRAWN;
  8. // Invalidate our old position
  9. invalidate();
  10. int oldWidth = mRight - mLeft;
  11. int oldHeight = mBottom - mTop;
  12. mLeft = left;
  13. mTop = top;
  14. mRight = right;
  15. mBottom = bottom;
  16. mPrivateFlags |= HAS_BOUNDS;
  17. int newWidth = right - left;
  18. int newHeight = bottom - top;
  19. if (newWidth != oldWidth || newHeight != oldHeight) {
  20. onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
  21. }
  22. if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
  23. // If we are visible, force the DRAWN bit to on so that
  24. // this invalidate will go through (at least to our parent).
  25. // This is because someone may have invalidated this view
  26. // before this call to setFrame came in, therby clearing
  27. // the DRAWN bit.
  28. mPrivateFlags |= DRAWN;
  29. invalidate();
  30. }
  31. // Reset drawn bit to original value (invalidate turns it off)
  32. mPrivateFlags |= drawn;
  33. mBackgroundSizeChanged = true;
  34. }
  35. return changed;
  36. }
  37. //我们可以看到如果新的高度和宽度改变之后会调用重新设置View的四个参数:
  38. //protected int mLeft;
  39. //protected int mRight;
  40. //protected int mTop;
  41. //protected int mBottom;
  42. //这四个参数指定了View将要布局的位置。而绘制的时候是通过这四个参数来绘制,所以我们在View中调用layout方法可以实现指定子View中布局。

六、ViewGroup的绘制。

ViewGroup的绘制实际上是调用的dispatchDraw,绘制时需要考虑动画问题,而动画的实现实际上就通过dispatchDraw来实现的。

我们不用理会太多的细节,直接看其绘制子组件调用的是drawChild方法,这个里面具体的东西就多了,涉及到动画效果的处理,如果有机会的话再写,我们只要知道这个方法的功能就行。

这里有个demo贴出其中的代码大家可以测试下。

  1. public ViewGroup01(Context context)
  2. {
  3. super(context);
  4. Button mButton = new Button(context);
  5. mButton.setText("测试");
  6. addView(mButton);
  7. }
  8. @Override
  9. protected void onLayout(boolean changed, int l, int t, int r, int b)
  10. {
  11. View v = getChildAt(0);
  12. if(v != null)
  13. {
  14. v.layout(120, 120, 250, 250);
  15. }
  16. }
  17. @Override
  18. protected void dispatchDraw(Canvas canvas)
  19. {
  20. super.dispatchDraw(canvas);
  21. View v = getChildAt(0);
  22. if(v != null)
  23. {
  24. drawChild(canvas, v, getDrawingTime());
  25. }
  26. }

七、效果图片:

本文转自:http://www.incoding.org/admin/archives/199.html

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

  1. 深入理解Android中View

    文章目录   [隐藏] 一.View是什么? 二.View创建的一个概述: 三.View的标志(Flag)系统 四.MeasureSpec 五.几个重要方法简介 5.1 onFinishInflate ...

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

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

  3. 一个demo让你彻底理解Android中触摸事件的分发

    注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOW ...

  4. Android菜单详解(一)——理解android中的Menu

    前言 今天看了pro android 3中menu这一章,对Android的整个menu体系有了进一步的了解,故整理下笔记与大家分享. PS:强烈推荐<Pro Android 3>,是我至 ...

  5. 彻底理解 Android 中的阴影

    如果我们想创造更好的 Android App,我相信我们需要遵循 Material Design 的设计规范.一般而言,Material Design 是一个包含光线,材质和投影的三维环境.如果我们想 ...

  6. 理解android中ListFragment和Loader

    一直以来不知Android中Loader怎么用,今天晚上特意花了时间来研究,算是基本上搞明白了,现在把相关的注释和代码发出来,以便笔记和给网友一个参考,错误之处还望大家给我留言,共同进步,这个例子采用 ...

  7. 绝对让你理解Android中的Context

    这个问题是StackOverFlow上面一个热门的问题What is Context in Android? 整理这篇文章的目的是Context确实是一个非常抽象的东西.我们在项目中随手都会用到它,但 ...

  8. 理解Android中的注解与反射

    反射 Java反射(Reflection)定义 Java反射机制是指在运行状态中 对于任意一个类,都能知道这个类的所有属性和方法:对于任何一个对象,都能够调用它的任何一个方法和属性: 这样动态获取新的 ...

  9. 【转】深入理解Android中的SharedPreferences

    SharedPreferences作为Android中数据存储方式的一种,我们经常会用到,它适合用来保存那些少量的数据,特别是键值对数据,比如配置信息,登录信息等.不过要想做到正确使用SharedPr ...

随机推荐

  1. ORACLE 实验二

    实验二:数据操纵 实验学时:4学时 实验类型:综合型 实验要求:必修 一.实验目的 1.掌握SQL数据查询语句: 2.掌握SQL聚集函数的使用. 3.掌握SQL插入.改动.删除语句的使用. 二.实验内 ...

  2. ubuntu下ssh使用 与 SCP 使用

    1 ssh远程登录服务器 ssh username@remote_ip #将username换成自己的用户名,将remote_ip换成远程服务器的ip地址 2 将文件/文件夹从远程服务器拷至本地(sc ...

  3. eclipse3.1.1汉化版安装

    确认安装好jdk以后,下载eclipse3.1.1及多语言包eclipse3.1.1 下载地址   http://eclipse.areum.biz/downloads/drops/R-3.1.1-2 ...

  4. Lua 与C 交换 第一篇

    编译 windows上编译lua源代码 cl /MD /O2 /W3 /c /DLUA_BUILD_AS_DLL *.c del *.o ren lua.obj lua.o ren luac.obj ...

  5. 数据一致性(consistency)、服务可用性(availability)、分区容错性(partition-tolerance)

    数据一致性(consistency).服务可用性(availability).分区容错性(partition-tolerance) 分布式系统理论基础 - CAP 2016-04-04 18:27 b ...

  6. POJ 1146:ID Codes

    ID Codes Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 6281 Accepted: 3769 Description ...

  7. (23)unity4.6学习Ugui中国文档-------非官方Demo1

    大家好,我是广东太阳.   转载请注明出处:http://write.blog.csdn.net/postedit/38922399 更全的内容请看我的游戏蛮牛地址:http://www.unitym ...

  8. Windows Phone开发(9):关于页面状态

    原文:Windows Phone开发(9):关于页面状态 按照一般做法,刚学会如何导航,还是不够的,因为要知道,手机里面的每个页面,就如同Web页面一样,是无状态的. 啥是无状态?如果我们玩过Web开 ...

  9. Java对于私有变量“反思暴力”技术

    (1)这两个类:(在相同的包装可以是) watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGxnZW4xNTczODc=/font/5a6L5L2T/font ...

  10. 开源Math.NET基础数学类库使用(17)C#计算矩阵条件数

    原文:[原创]开源Math.NET基础数学类库使用(17)C#计算矩阵条件数                本博客所有文章分类的总目录:http://www.cnblogs.com/asxinyu/p ...