相信 ViewGroup 的下面两个方法,大家都不陌生:
- removeView(View view)
- addView(View child)
其实在 ViewGroup 内部还有两个类似的方法:
- protected void detachViewFromParent(View child)
- protected void attachViewToParent(View child, int index, LayoutParams params)
问题来了:
- 你知道detachViewFromParent/attachViewToParent 这一组方法在哪些控件中被使用中?
- detachViewFromParent/attachViewToParent 与 removeView/addView 有什么区别呢?
- detachViewFromParent/attachViewToParent在什么场景下非常适合使用?
更多问答 >>
-
每日一问| View 绘制的一个细节,如何修改 View 绘制的顺序?
2020-08-12 10:21 -
每日一问 | apply plugin: 'com.android.application' 背后发生了什么?
2020-08-16 19:56 -
2020-08-23 23:54
-
2020-08-26 21:11
-
2020-09-09 23:54
-
每日一问 | RecyclerView的多级缓存机制,每级缓存到底起到什么样的作用?
2020-07-19 23:56 -
2020-07-08 23:05
-
每日一问 | Android P 上,需要配置 network_security_config ,才能抓包,正确吗?
2020-06-29 21:26 -
每日一问 | 曾经的记忆中“onSaveInstanceState 会在系统意外杀死 Activity 时调用”,正确吗?
2020-07-12 23:49 -
2020-06-09 23:17
先来看下它们各自做了什么操作:attachViewToParent
、detachViewFromParent
与addView
、removeView
的区别?首先是
attachViewToParent
方法:给
child
的LayoutParams赋值(注意:现在是直接赋值而不是通过setLayoutParams
方法来设置,因为setLayoutParams
方法里面是会调用requestLayout
的);将
child
添加到ViewGroup的mChildren
数组(那个addInArray
方法的代码就不贴了,它里面也就是操作mChildren
数组,把参数child
赋值到指定index
上并将mChildrenCount
的数量+1而已);给
child
指定Parent为这个ViewGroup自身;去掉PFLAG_DIRTY_MASK(如果有这个标记,表示该View的内容有变更(相比于上一次draw))和PFLAG_DRAWING_CACHE_VALID(这个标记表示该View的绘制缓存还有效,一般是配合
setDrawingCacheEnabled
一起使用的,不过现在这个方法已经过时了)标识并加上PFLAG_DRAWN(确保调用invalidate
之后会回调draw
方法)和PFLAG_INVALIDATED(强行重建DisplayList),表示在下一次invalidate
之后会重新绘制该View;好,接着来看
addView
方法:先调用了
requestLayout
和invalidate
,然后调用addViewInner
:addViewInner
主要做了8件事:检查
child
是否已经被添加到某个ViewGroup中(限制不能重复添加);播放添加View的过渡动画(可以通过
setLayoutTransition
方法来设置动画);检查LayoutParams是否合法(如果该ViewGroup有自定义的LayoutParams,一般会判断
child
的LayoutParams是否该ViewGroup的自定义LayoutParams对象,如果不是,会根据child
的LayoutParams创建一个自定义的LayoutParams对象);给
child
更新LayoutParams;添加
child
到mChildren
数组中;更新
child
的Parent;回调
child
的onAttachedToWindow
方法;回调自身
onViewAdded
方法(自定义ViewGroup时如果想监听View添加,可以重写该方法);好啦,来对比一下
很明显,attachViewToParent
和addView
方法的差异:addView
方法做的事情比attachViewToParent
要多,比如做了一些安全检测(重复添加、匹配LayoutParams),还会回调目标子View的onAttachedToWindow
以及自身的onViewAdded
方法。还有一开始调用的requestLayout
和invalidate
方法,也就是说,onMeasure
、onLayout
、onDraw
方法都会回调了。要注意:attachViewToParent
方法是不会检查child
的LayoutParams的,所以在调用此方法前就要自己手动做检测了,除非child
是刚在同一个ViewGroup中detach下来的。还有就是,因为attachViewToParent
不会帮你调用requestLayout
,也就是不会重新测量和布局了,如果attach之前,View的尺寸有变更,那就需要手动requestLayout
,否则attach之后看到的还是旧尺寸。现在来看下
detachViewFromParent
和removeView
的区别:
不过还有一个操作,那就是移除对应子View的Parent(置空)。detachViewFromParent
里面只调用了一个removeFromArray
方法,这个removeFromArray
跟刚刚的addInArray
刚好相反,具体操作就是:把mChildren
对应index置空,然后将index后面的子View对象往前移一位,mChildrenCount
数量-1。好,到
removeView
了:如果
removeViewInternal
返回true
就会调用requestLayout
和invalidate
:index >= 0
的话就表示该ViewGroup添加了目标View,只要ViewGroup添加了目标View,就会接着调用removeViewInternal(int index, View view)
方法,然后返回true
,那么上面的removeView
方法就会调用requestLayout
和invalidate
了:removeViewInternal
方法主要做了6件事:播放移除View的过渡动画(如果有设置的话);
清除目标子View焦点;
如果目标子View当前正在被触摸,则分派一个ACTION_CANCEL事件(取消触摸)(如果输入方式是鼠标,也会分派一个HOVER_EXIT事件);
如果目标子View正在播放动画(补间动画),则不会直接回调它的
onDetachedFromWindow
方法,而是等待动画播放完毕之后才回调;从
mChildren
中移除目标子View,并置空其Parent;回调自身
onViewRemoved
方法;总结
addView
和attachViewToParent
的区别:addView
比attachViewToParent
更安全,更全面,但效率会比attachViewToParent
低一倍左右;attachViewToParent
不会检查LayoutParams是否合法,添加到mChildren
之前也不会检查目标View是否已有Parent,所以!用这个方法可以将同一个View对象多次添加到多个ViewGroup中!虽然这样做没什么用,但也算是一个知识点;attachViewToParent
不支持过渡动画,也不会回调onAttachedToWindow
、onViewAdded
;attachViewToParent
不会requestLayout
,也就是被添加的子View的尺寸在下一次measure前是不会改变的;removeView
和detachViewFromParent
的区别:detachViewFromParent
更高效,效率大概比removeView
高15倍左右;一个正在被触摸的View,如果是使用
detachViewFromParent
方法把它移除的话,那么在ACTION_UP之前,依然能收到触摸事件,而通过removeView
方法移除就会打断触摸事件;detachViewFromParent
跟attachViewToParent
一样不支持过渡动画,也不会回调onDetachedFromWindow
和onViewRemoved
方法;detachViewFromParent
方法不会清除目标子View焦点,不会调用requestLayout
;哪些控件有使用到
detachViewFromParent
、attachViewToParent
这两个方法?detachViewFromParent
:ListView,主要是用于处理列表滑动时的快速批量移除Item;
RecyclerView,回收ViewHolder时,如果该ViewHolder没有被标记无效,就会调用
detachViewFromParent
而非removeView
;attachViewToParent
:ListView,列表滚动时的重新布局,如果是从缓存中取出来的View,会通过
attachViewToParent
方法来添加,因为上次已经测量并布局过了,所以这次也不用重新measure
和layout
;RecyclerView,处理滚动时的重新布局,检测到目标VIew是从
mAttachedScrap
或mChangedScrap
中取出来时会使用;detachViewFromParent
、attachViewToParent
的使用场景?批量添加或移除View时(快速高效);
切换子View层级顺序(避免两次
requestLayout
和invalidate
出现的闪烁问题);应对某些变态需求,需要同一个View被多次添加;
临时移除,稍候需要重新添加回去;
小缘大佬无敌!
别这样,我是弟弟
细致!
绘制过程主要是由
CPU 来进行 Measure、Layout、Record、Execute的数据计算工作,GPU 负责 栅格化、渲染。所谓的栅格化就是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作;