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

一、ViewGroup是什么?

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

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

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

publicabstractclassViewGroupextendsViewimplementsViewParent, ViewManager

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

二、ViewGroup这个容器

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

// Child views of this ViewGroup

privateView[] mChildren;

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

2.1 添加View的算法

protectedbooleanaddViewInLayout(View child,intindex, LayoutParams params) {

returnaddViewInLayout(child, index, params,false);

}

protectedbooleanaddViewInLayout(View child,intindex, LayoutParams params,

booleanpreventRequestLayout) {

child.mParent = null;

addViewInner(child, index, params, preventRequestLayout);

child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;

returntrue;

}

privatevoidaddViewInner(View child,intindex, LayoutParams params,

booleanpreventRequestLayout) {

...

addInArray(child, index);

...

}

privatevoidaddInArray(View child,intindex) {

...

}

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

2.1.1

我们先来分析addViewInner方法:

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

if(child.getParent() !=null) {

thrownewIllegalStateException("The specified child already has a parent. "+

"You must call removeView() on the child's parent first.");

}

然后就是对子View布局参数的处理。

调用addInArray来添加View

父View为当前的ViewGroup

焦点的处理。

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

AttachInfo ai = mAttachInfo;

if(ai !=null) {

booleanlastKeepOn = ai.mKeepScreenOn;

ai.mKeepScreenOn = false;

child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));

if(ai.mKeepScreenOn) {

needGlobalAttributesUpdate(true);

}

ai.mKeepScreenOn = lastKeepOn;

}

View树改变的监听

if(mOnHierarchyChangeListener !=null) {

mOnHierarchyChangeListener.onChildViewAdded(this, child);

}

子View中的mViewFlags的设置:

if((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {

mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;

}

2.1.2 addInArray

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

System.arraycopy(children,0, mChildren,0, index);

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

2.2 移除View

移除View的几种方式:

移除指定的View。

移除从指定位置的View

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

移除所有的View

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

如果拥有焦点则清楚焦点

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

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

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

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

2.3 查询

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

publicView getChildAt(intindex) {

try{

returnmChildren[index];

} catch(IndexOutOfBoundsException ex) {

returnnull;

}

}

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

三、onFinishInflate

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

四、测量组件

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

//1、measureChild(View, int, int),为子组件添加Padding

protectedvoidmeasureChild(View child,intparentWidthMeasureSpec,

intparentHeightMeasureSpec) {

finalLayoutParams lp = child.getLayoutParams();

finalintchildWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight, lp.width);

finalintchildHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

//2、measureChildren(int, int)根据指定的高和宽来测量所有子View中显示参数非GONE的组件。

protectedvoidmeasureChildren(intwidthMeasureSpec,intheightMeasureSpec) {

finalintsize = mChildrenCount;

finalView[] children = mChildren;

for(inti =0; i

finalView child = children[i];

if((child.mViewFlags & VISIBILITY_MASK) != GONE) {

measureChild(child, widthMeasureSpec, heightMeasureSpec);

}

}

}

3、measureChildWithMargins(View,int,int,int,int)测量指定的子组件,为子组件添加Padding和Margin。

protectedvoidmeasureChildWithMargins(View child,

intparentWidthMeasureSpec,intwidthUsed,

intparentHeightMeasureSpec,intheightUsed) {

finalMarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

finalintchildWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin

+ widthUsed, lp.width);

finalintchildHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin

+ heightUsed, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

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

五、onLayout

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

@Override

protectedabstractvoidonLayout(booleanchanged,

intl,intt,intr,intb);

来看View中layout方法:

publicfinalvoidlayout(intl,intt,intr,intb) {

booleanchanged = setFrame(l, t, r, b);

if(changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {

if(ViewDebug.TRACE_HIERARCHY) {

ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);

}

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

mPrivateFlags &= ~LAYOUT_REQUIRED;

}

mPrivateFlags &= ~FORCE_LAYOUT;

}

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

protectedbooleansetFrame(intleft,inttop,intright,intbottom) {

booleanchanged =false;

//.......

if(mLeft != left || mRight != right || mTop != top || mBottom != bottom) {

changed = true;

// Remember our drawn bit

intdrawn = mPrivateFlags & DRAWN;

// Invalidate our old position

invalidate();

intoldWidth = mRight - mLeft;

intoldHeight = mBottom - mTop;

mLeft = left;

mTop = top;

mRight = right;

mBottom = bottom;

mPrivateFlags |= HAS_BOUNDS;

intnewWidth = right - left;

intnewHeight = bottom - top;

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

onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);

}

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

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

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

// This is because someone may have invalidated this view

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

// the DRAWN bit.

mPrivateFlags |= DRAWN;

invalidate();

}

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

mPrivateFlags |= drawn;

mBackgroundSizeChanged = true;

}

returnchanged;

}

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

//protected int mLeft;

//protected int mRight;

//protected int mTop;

//protected int mBottom;

//这四个参数指定了View将要布局的位置。而绘制的时候是通过这四个参数来绘制,所以我们在View中调用layout方法可以实现指定子View中布局。

六、ViewGroup的绘制。

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

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

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

publicViewGroup01(Context context)

{

super(context);

Button mButton = newButton(context);

mButton.setText("测试");

addView(mButton);

}

@Override

protectedvoidonLayout(booleanchanged,intl,intt,intr,intb)

{

View v = getChildAt(0);

if(v !=null)

{

v.layout(120,120,250,250);

}

}

@Override

protectedvoiddispatchDraw(Canvas canvas)

{

super.dispatchDraw(canvas);

View v = getChildAt(0);

if(v !=null)

{

drawChild(canvas, v, getDrawingTime());

}

}

七、效果图片:

深入理解android 博客,深入理解Android中ViewGroup相关推荐

  1. 关于我的Android 博客

    我是曹新雨,我为自己代言.现在的菜鸟,3年以后我就是大神.为自己加油.微信:aycaoxinyu 关于我的Android博客,都是我当初遇到困难,克服之后,写上去的.后来,有人加我微信,问我一些问题, ...

  2. Android 博客周刊13期

    摘要 收录最近一周国内外热门技术博客以及优秀的类库.每周一准时更新. Android博客周刊 新闻 1.Android Studio 2.0 稳定版 Android Studio 2.0 是 Andr ...

  3. 今天开通android博客 该认真学习了

    今天开通android博客 该认真学习了  大家都支持我啊!!!!哈哈哈 转载于:https://www.cnblogs.com/tqj-zyy/archive/2011/08/23/tqj-zyy. ...

  4. android博客排行榜,新浪博文排行_新浪博客 新浪博客Android客户端

    新浪博客 新浪博客Android客户端 JPG,330x294,231KB,281_250 醉 是春花烂漫时 JPG,480x800,191KB,250_417 新浪博客 JPG,472x216,23 ...

  5. android博客导航总结,以及个人常用android免费学习干货(文章,视频,矢量图,字体等)资源分享?

    android博客导航总结与资源分享 1.博客总结 1.mob平台的使用 2.QMUI部分功能实现 3.XUI部分功能实现 4.其他功能 5.Flutter博客总结: 2.资源分享 1.文章类型资源分 ...

  6. 优秀Android博客大全,整理了国内外大神博客/Github地址,是学习Android进阶的首选[转]

    [转]国内外优秀Android博客大全,覆盖了国内外大神博客地址,Github地址,是学习Android进阶的首选 国内: 昵称 Github 博客 介绍 邓凡平   http://blog.csdn ...

  7. 收集优质的中文前端博客(不定期更新中)

    收集优质的中文前端博客(不定期更新中) 注:博主的公司信息来自网上公开资料,仅供参考,不保证准确性. 个人(控制在42个以内) 阮一峰的网络日志(蚂蚁金服) <读懂 ECMAScript 规格& ...

  8. php插入音乐代码,如何修改Wordpress博客代码在文章中插入音乐 | 垃圾站

    垃圾站博客昨天在网上看到有一篇通过修改Wordpress博客代码在文章中插入音乐的教程,特此整理编辑后分享给大家,教程如下: 1.下载swf播放器(点击下载:player.swf )上传到Wordpr ...

  9. Android博客文章整理

             闭关了四个多月,整理了博客上所有的Android文章,一共划分为三大篇,十六个章节,820页,系统地分析了Android系统的源代码,从Linux内核层.HAL层.运行时库层到应用程 ...

  10. Android博客大汇总

    目录介绍 链接:https://juejin.im/post/5d60ad5df265da03c42899f1 01.基础组件(9篇) 02.IPC机制(7篇) 03.View原理(9篇) 04.动画 ...

最新文章

  1. (附视频) | AI奠基人、美国AI科学家特伦斯谈深度学习​
  2. Java正则表达式实现计算器_用java编写win7计算器
  3. 第14、15教学周作业
  4. 前端学习(555):margin与容器的尺寸
  5. Thread类中的join方法
  6. 微信小程序:页面跳转时传递数据到另一个页面
  7. java 停止线程播放音频_Notification 播放 关闭 声音----转载
  8. 初始JAVA--约定俗成的命名规则
  9. java中workbook_java使用Workbook进行excel读取和创建
  10. 透露一个未来3到5年的巨大商机
  11. 用visio制作机柜服务器,visio 绘制机柜接线图 实例教程
  12. c++ 11 中显式默认设置的函数和已删除的函数 总结
  13. ROS学习|Behavoir Tree(BT树)--c++实现
  14. 备忘录模式(设计模式_20)
  15. 对技术的态度/编程修养(上)
  16. 9月28日科技资讯|华为发布全容器化 5G 核心网;余承东评小米 MIX Aphla 手机无实用价值;PHP 新版本更新
  17. 一文读懂电源缓启动原理
  18. 海外IT工程师工作福利揭秘
  19. 黑苹果alc269声卡仿冒id_10.10中我的ALC269VC依旧无声?学习并尝试制作了仿冒声卡,依然无声。...
  20. 户口本翻译,户口本在哪翻译好?

热门文章

  1. java并发编程 Lock
  2. VMware虚拟机安装WinXP出现错误output error file to the following location A:\GHOSTERR.TXT
  3. 深度优先遍历(Depth First Search)
  4. Spring中的@Autowired,@Qualifier和@Primary注解
  5. 记一次去掉中间的某次merge代码
  6. jsp页面中插入css样式的三种方法总结
  7. Ubuntu学习 mkdir
  8. 别被IBM抛出的“认知商业”搞晕 这里为你详解
  9. JavaScript面向对象---原型链继承
  10. KeyShot中该怎么添加反射地平面