INVISIBLE、GONE、VISIBLE这三个变量,应该是我们最常用的了,有没有思考系统是怎么实现的呢?
首先要明确一点,通常使用的View都是放在ViewGroup以及ViewGroup子类的,大小都是在父控件的onMeasure和onLayout来进行确定。我们从最简单的FrameLayout原来来看看,它是怎么实现View的GONE操作的。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//判断自己是不是EXACTLY,对应的就是判断自己是不是Match_Parent
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//获取最大宽度与高度
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//需要测量所有子view||不为GONE时,才需要测量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
//加上pandding
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
//.....
//设置好FrameLayout的高度和宽度.此时FrameLayout的measureHeight与MeasureWidth已经有值了
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//.....
}
}
上面的onMeasure其实就做一件事,就是把FrameLayout的maxWidth和maxHeight算出来。在计算时过滤掉mMeasureAllChildren = false是的child是gone的情况,那么问题来了,这个mMeasureAllChildren是做什么的呢?看源码的赋值,我们找到方法
/**
* Sets whether to consider all children, or just those in
* the VISIBLE or INVISIBLE state, when measuring. Defaults to false.
*
* @param measureAll true to consider children marked GONE, false otherwise.
* Default value is false.
*
* @attr ref android.R.styleable#FrameLayout_measureAllChildren
*/
@android.view.RemotableViewMethod
public void setMeasureAllChildren(boolean measureAll) {
mMeasureAllChildren = measureAll;
}
看注释的意思是,当为true时,为GONE的所有children也会被测量。所以,也并不是说GONE了,我们就不会去测量这个child。得是FrameLayout这个measureAllChildren的属性为false时,GONE才不会去测量。为了验证我们的阅读的源码。我写出了以下例子:
假设我们有如下xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:measureAllChildren="true"
android:background="@color/black"
tools:context=".MainActivity">
<TextView
android:layout_width="20dp"
android:layout_height="20dp"
android:text="Hello World!"
android:background="@android:color/holo_red_dark"
/>
<TextView
android:layout_width="200dp"
android:visibility="gone"
android:layout_height="200dp"
android:text="Hello World!"
android:background="@android:color/holo_red_dark"
/>
</FrameLayout>
按照前面我们对measureAllChildren的解读,measureAllChildren为true,此时FrameLayout的黑色应该是200dp。看实验结果:
从实验结果看,我们的假设阅读源码得到的结论和实验结果是一致的。默认情况下mMeasureAllChildren是false,所以我们日常使用时,GONE实际上是不会计算实际大小的。
@UnsupportedAppUsage
boolean mMeasureAllChildren = false;
GONE的问题,搞清楚了,现在我们来看看测量里面INVISIBLE
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//需要测量所有子view||不为GONE时,才需要测量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
在上一段代码中,并未有对INVISIBLE做一个拦截,也就是说,INVISIBLE在测量阶段和VISIBLE是一样的。
onLayout我们之前已经讲过,它不仅可以控制子view的位置,实际上,也可以控制子view的大小。翻开FrameLayout的源代码
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//和onMeasure相似的GONE判断
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
上述的代码,是Android SDK30中,FrameLayout的onLayout全部代码,其实上述代码做的事情也很简单,就是计算各种gravity.仅需关注child.getVisibility() != GONE 这一行,表示GONE的时候,child是不参与layout的。不参与layout代表什么呢?不参与layout即表示,子view不会被放到摆放在viewgroup中,即使你已经测量过了。
通过通读FrameLayout的源代码可以给我们提供的思考是什么?
看完了ViewGroup的onMeasure和onLayout,我们也仅能知道为GONE并且mMeasureAllChildren == false时,child不会被layout.但是INVISIBLE还是会Layout的啊,那INVISIBLE这个漏网之鱼,他的控制显然就不是在ViewGroup层面来做的了。那我们就要去看看View的setVisibility
上面已经分析完了GONE,这里我们着重分析一下INVISIBLE。INVISIBLE的控制,一般我们通过
/**
* Set the visibility state of this view.
*
* @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
* @attr ref android.R.styleable#View_visibility
*/
@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
内部调用的其实是
/**
* Set flags controlling behavior of this view.
*
* @param flags Constant indicating the value which should be set
* @param mask Constant indicating the bit range that should be changed
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
void setFlags(int flags, int mask) {
//判断现在的flags和之前的flag是否有变化
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
int changed = mViewFlags ^ old;
if (changed == 0) {
return;
}
int privateFlags = mPrivateFlags;
boolean shouldNotifyFocusableAvailable = false;
final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
if ((changed & VISIBILITY_MASK) != 0) {
//... 省略和焦点相关的代码
mPrivateFlags |= PFLAG_DRAWN;
invalidate(true);
needGlobalAttributesUpdate(true);
}
}
/* Check if the GONE bit has changed */
if ((changed & GONE) != 0) {
needGlobalAttributesUpdate(false);
requestLayout();
if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
destroyDrawingCache();
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
((View) mParent).invalidate(true);
}
// Mark the view drawn to ensure that it gets invalidated properly the next
// time it is visible and gets invalidated
mPrivateFlags |= PFLAG_DRAWN;
}
}
/* Check if the VISIBLE bit has changed */
if ((changed & INVISIBLE) != 0) {
needGlobalAttributesUpdate(false);
/*
* If this view is becoming invisible, set the DRAWN flag so that
* the next invalidate() will not be skipped.
*/
mPrivateFlags |= PFLAG_DRAWN;
if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) {
//... 省略和焦点相关的代码
}
}
if ((changed & VISIBILITY_MASK) != 0) {
// If the view is invisible, cleanup its display list to free up resources
if (newVisibility != VISIBLE && mAttachInfo != null) {
cleanupDraw();
}
if (mParent instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) mParent;
parent.onChildVisibilityChanged(this, (changed & VISIBILITY_MASK),
newVisibility);
parent.invalidate(true);
} else if (mParent != null) {
mParent.invalidateChild(this, null);
}
if (mAttachInfo != null) {
dispatchVisibilityChanged(this, newVisibility);
// Aggregated visibility changes are dispatched to attached views
// in visible windows where the parent is currently shown/drawn
// or the parent is not a ViewGroup (and therefore assumed to be a ViewRoot),
// discounting clipping or overlapping. This makes it a good place
// to change animation states.
if (mParent != null && getWindowVisibility() == VISIBLE &&
((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
dispatchVisibilityAggregated(newVisibility == VISIBLE);
}
}
}
}
首先我们来看一下第一段源码:
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
int changed = mViewFlags ^ old;
if (changed == 0) {
return;
}
哦吼,一堆位运算。头大,我们来,弄个对照表
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个位都为 1 时,结果才为 1 |
I | 或 | 两个位都是 0 时,结果才为 0 |
^ | 异或 | 两个位相同时为 0,相异为 1 |
~ | 取反 | 0 变 1,1 变 0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补 0 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补 0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补 0 (逻辑右移) |
我们知道mask的值是VISIBILITY_MASK = 0x0000000C ,GONG,VISIBLE,INVISIBLE的值:
//二进制 0000
public static final int VISIBLE = 0x00000000;
//二进制 0100
public static final int INVISIBLE = 0x00000004;
//二进制 1000
public static final int GONE = 0x00000008;
//二进制 1100
static final int VISIBILITY_MASK = 0x0000000C;
首先来看这行
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
我们先来分析前半部分 (mViewFlags & ~mask) 这个mask的值是通过setVisibility 传过来的,值为VISIBILITY_MASK,二进制位1100,取反"~“后,0011,神奇的事情来了。此时无论mViewFlags是GONE、VISIBLE、INVISIBLE与”&"上mask的反码,都是0,那证明这段代码的意思就是把前面的标志位数据清除掉。
//推导过程
INVISIBLE & ~ VISIBILITY_MASK
0100 & ~1100
0100 & 0011
0000
VISIBLE & ~VISIBLE_MASK
0000 & ~1100
0000 & 0011
0000
GONE & ~VISIBLE_MASK
1000 & ~1100
1000 & 0011
0000
现在来分析后半部分 (flags & mask) ,上面我们讲到,&~是清除,那&是什么呢,推导一下吧
VISIBLE & ~VISIBLE_MASK
0000 & 1100
0000
INVISIBLE & VISIBILITY_MASK
0100 & 1100
0100
GONE & VISIBILITY_MASK
1000 & 1100
1000
相信你发现了吧,INVISIBLE、VISIBLE、GONE这三个标志位,&上mask之后,都是自己。前面讲了&~mask都是0,现在这个&mask都是自己,那不是相当于直接给mViewFlags赋值为flag就完事儿了吗?事情当然不会这么简单啦,接下来我们继续看
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
int changed = mViewFlags ^ old;
if (changed == 0) {
return;
}
这里面还有一个关键代码:int changed = mViewFlags ^ old;,old表示的是上一轮的flags,mViewFlags表示的是当前设置成的flag,老规矩推导一下吧
INVISIBILE ^ VISIBLE
0100 ^ 0000
0100
GONE ^ VISIBLE
1000 ^ 0000
1000
GONE ^ INVISIBILE
1000 ^ 0100
1100
GONE ^ GONE
1000 ^ 1000
0000
INVISIBLE^INVISIBLE
0100 ^ 0100
0000
从推导可以看出,如果上一轮和这一轮的flag不同,那change >0,如果相同,chang==0。change等于0就直接return了。那么现在可以进行总结一下了,这里的位运算主要是用来计算和上一轮的标志位是否一致。
接下来,我们接着分析
final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
if ((changed & VISIBILITY_MASK) != 0) {
//... 省略和焦点相关的代码
mPrivateFlags |= PFLAG_DRAWN;
invalidate(true);
needGlobalAttributesUpdate(true);
}
}
前面我们讲到了flags&mask的结果最后还是等于flag,changed&mask同理,这里代码就很好理解了,当心的flags为visible,且上一轮不是visible,那这里就执行invalidate刷新界面。
那我还很好奇invalidate里面是怎么写的。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) {
return;
}
//...将view画到屏幕上
}
mGhostView这个是view上面的一层overlay先不用管,通常情况下是为空的。着重看一下skipInvalidate.源码如下
/**
* Do not invalidate views which are not visible and which are not running an animation. They
* will not get drawn and they should not set dirty flags as if they will be drawn
*/
private boolean skipInvalidate() {
return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
(!(mParent instanceof ViewGroup) ||
!((ViewGroup) mParent).isViewTransitioning(this));
}
这段代码就比较简单了。如果不是VISIBLE状态和没有动画并且父view不是viewgroup,就跳过。假设我们将一个viewgroup的子view设置为gone,并且没有动画,那我们就直接skip了,即不会再显示。到此为止,INVISIBLE、GONE、VISIBLE的的实现已经全线完了。