INVISIBLE、GONE、VISIBLE這三個變數,應該是我們最常用的了,有沒有思考系統是怎么實作的呢?
ViewGroup
首先要明確一點,通常使用的View都是放在ViewGroup以及ViewGroup子類的,大小都是在父控制元件的onMeasure和onLayout來進行確定,我們從最簡單的FrameLayout原來來看看,它是怎么實作View的GONE操作的,
onMeasure階段:
@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階段
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.在測量階段,我們需要處理View的GONE事件,否則View仍然會進入onMeasure,
- 并非GONE了,就不會被測量了,
- 分析一類問題,要從最簡單的樣本開始,
看完了ViewGroup的onMeasure和onLayout,我們也僅能知道為GONE并且mMeasureAllChildren == false時,child不會被layout.但是INVISIBLE還是會Layout的啊,那INVISIBLE這個漏網之魚,他的控制顯然就不是在ViewGroup層面來做的了,那我們就要去看看View的setVisibility
View
上面已經分析完了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的的實作已經全線完了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/255238.html
標籤:其他
