文档章节

Android中ViewGroup

董家二少
 董家二少
发布于 2014/03/27 13:31
字数 2040
阅读 103
收藏 0

一、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.       

      6.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,   

      7.                 mPaddingLeft + mPaddingRight, lp.width);   

      8.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,   

      9.                 mPaddingTop + mPaddingBottom, lp.height);   

      10.       

      11.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   

      12.     }  


      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, intintintint)测量指定的子组件,为子组件添加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.       

      7.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,   

      8.                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin   

      9.                         + widthUsed, lp.width);   

      10.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,   

      11.                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin   

      12.                         + heightUsed, lp.height);   

      13.       

      14.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);   

      15.     }  

             上面三个方法都是为子组件设置了布局参数。最终调用的方法是子组件的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.   

      12.         onLayout(changed, l, t, r, b);   

      13.         mPrivateFlags &= ~LAYOUT_REQUIRED;   

      14.     }   

      15.     mPrivateFlags &= ~FORCE_LAYOUT;   

      16. }  

             在这个方法中调用了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.         

      7.             // Remember our drawn bit    

      8.             int drawn = mPrivateFlags & DRAWN;    

      9.         

      10.             // Invalidate our old position    

      11.             invalidate();    

      12.         

      13.         

      14.             int oldWidth = mRight - mLeft;    

      15.             int oldHeight = mBottom - mTop;    

      16.         

      17.             mLeft = left;    

      18.             mTop = top;    

      19.             mRight = right;    

      20.             mBottom = bottom;    

      21.         

      22.             mPrivateFlags |= HAS_BOUNDS;    

      23.         

      24.             int newWidth = right - left;    

      25.             int newHeight = bottom - top;    

      26.         

      27.             if (newWidth != oldWidth || newHeight != oldHeight) {    

      28.                 onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);    

      29.             }    

      30.         

      31.             if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {    

      32.                 // If we are visible, force the DRAWN bit to on so that    

      33.                 // this invalidate will go through (at least to our parent).    

      34.                 // This is because someone may have invalidated this view    

      35.                 // before this call to setFrame came in, therby clearing    

      36.                 // the DRAWN bit.    

      37.                 mPrivateFlags |= DRAWN;    

      38.                 invalidate();    

      39.             }    

      40.         

      41.             // Reset drawn bit to original value (invalidate turns it off)    

      42.             mPrivateFlags |= drawn;    

      43.         

      44.             mBackgroundSizeChanged = true;    

      45.         }    

      46.         return changed;    

      47.     }    

      48. //我们可以看到如果新的高度和宽度改变之后会调用重新设置View的四个参数:    

      49.     //protected int mLeft;    

      50.     //protected int mRight;    

      51.     //protected int mTop;    

      52.     //protected int mBottom;    

      53. //这四个参数指定了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.     

      9. @Override  

      10. protected void onLayout(boolean changed, int l, int t, int r, int b)    

      11. {    

      12.     View v = getChildAt(0);    

      13.     if(v != null)    

      14.         {    

      15.         v.layout(120120250250);    

      16.         }    

      17. }    

      18. @Override  

      19. protected void dispatchDraw(Canvas canvas)    

      20. {    

      21.     super.dispatchDraw(canvas);    

      22.     View v = getChildAt(0);    

      23.     if(v != null)    

      24.         {    

      25.         drawChild(canvas, v, getDrawingTime());    

      26.         }    

      27. }  

      © 著作权归作者所有

      共有 人打赏支持
      董家二少
      粉丝 3
      博文 34
      码字总数 85000
      作品 0
      海淀
      程序员
      私信 提问
      Android应用资源---布局资源类型(Layout)

      布局资源定义了UI的Activity或组件的界面架构。 文件位置(FILE LOCATION): res/layout/filename.xml 文件名被用作资源ID。 被编译资源的数据类型(COMPILED RESOURCE DATATYPE): 资源指...

      长平狐
      2012/10/16
      177
      0
      界面编程与视图(View)组件

      Android应用的绝大部分UI组件都放在android.widget包及其子包、android.view包及其子包中,Android应用的所有UI组件都继承了View类,View组件非常类似于Swing编程的JPanel,它代表一个空白的...

      谷飞
      2012/11/05
      0
      0
      第五讲:用户界面 View(一)

      一、什么是View 我们上节课说,Activity是Android程序的显示层,每一个显示窗口都是一个Activity;可是Activity本身无法显示在屏幕上,我们可以把它理解成是一个抽象层,一个壳子;就譬如一个...

      程序袁_绪龙
      2014/08/25
      0
      0
      Android杂谈--Activity、Window、View的关系

      一、首先说说View和ViewGroup吧   Android系统中的所有UI类都是建立在View和ViewGroup这两个类的基础上的。所有View的子类成为”Widget”,所有ViewGroup的子类成为”Layout”。View和Vie...

      垂盆草
      2012/11/10
      0
      1
      Android Activity---实现一个用户界面

      一个Activity的用户界面是通过View树来提供的---这些View对象继承View类。每个View在Activity的窗口中都控制一个特定的的矩形区域,并且能够响应用户的操作。例如,一个按钮在用户点击它时,...

      长平狐
      2012/10/16
      432
      0

      没有更多内容

      加载失败,请刷新页面

      加载更多

      Vue.js开发环境搭建说明(mac)

      vue开发环境搭建(mac) 投影放大:cmd + + 安装Node 下载Node 官网下载 https://nodejs.org/en/download/ 安装Node 双击安装包,选择安装目录,比如: /usr/local/bin 安装成功后最好记录一...

      Danni3
      15分钟前
      1
      0
      Qt编写自定义控件3-速度仪表盘

      前言 速度仪表盘,写作之初的本意是用来展示当前测试的网速用的,三色圆环+数码管显示当前速度,Qt自带了数码管控件QLCDNumber,直接集成即可,同时还带有动画功能,其实也可以用在汽车+工业...

      飞扬青云
      20分钟前
      0
      0
      【论文阅读】Image Super-Resolution via Deep Recursive Residual Network

      题目:通过深度递归残差网络实现图像的超分辨率 摘要: 近年来,基于卷积神经网络的模型在单张图像的超分辨率上已经取得了巨大的成功。由于深度网络的强大,这些CNN模型学习了从低分辨率输入...

      云烟成雨forever
      22分钟前
      1
      0
      为什么强烈建议大家使用枚举来实现单例

      关于单例模式,我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的...

      群星纪元
      41分钟前
      11
      0
      Confluence 6 超过当前许可证期限进行升级

      这个页面将会对你在进行 Confluence 升级的时候超过了当前许可证的期限进行升级的情况。 许可证警告 在升级的过程中,你将会在 Confluence 的应用程序日志(log file)中看到类似下面的错误提...

      honeymoose
      54分钟前
      2
      0

      没有更多内容

      加载失败,请刷新页面

      加载更多

      返回顶部
      顶部