天天看點

Android View的22500行源碼分析系列(一)setFlag 方法,setVisibilty,setFoucus,setEnable等方法内部實作

一直想系統地學習Android源碼,最近又計劃寫一個自定義控件系列的部落格,而在自定義控件的過程中,許多技巧和方法需要你在Android View,ViewGroup的源碼中得到靈感。是以,這一個View的22500行源碼分析系列就誕生了

Android View的22500行源碼分析系列(一)setFlag 方法,setVisibilty,setFoucus,setEnable等方法内部實作
Android View的22500行源碼分析系列(一)setFlag 方法,setVisibilty,setFoucus,setEnable等方法内部實作

,本系列基于api23的View的源碼,主要是對源碼的注解作一些翻譯和簡單的總結,目的是讓大家了解整個View的工

作流程,一些主要方法實作裡面的細節問題。說時遲那時快,老司機開車啦,趕緊上車

Android View的22500行源碼分析系列(一)setFlag 方法,setVisibilty,setFoucus,setEnable等方法内部實作

首先我們看一下View常用的幾個方法:

@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {
    setFlags(visibility, VISIBILITY_MASK);
}      
public void setFocusable(boolean focusable) {
    if (!focusable) {
        setFlags(0, FOCUSABLE_IN_TOUCH_MODE);
    }
    setFlags(focusable ? FOCUSABLE : NOT_FOCUSABLE, FOCUSABLE_MASK);
}
      
public void setEnabled(boolean enabled) {
    if (enabled == isEnabled()) return;

    setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);

    /*
     * The View most likely has to change its appearance, so refresh
     * the drawable state.      
視圖大多數可能改變它的樣子,是以重新整理drawable的狀态
     */
    refreshDrawableState();

    // Invalidate too, since the default behavior for views is to be
    // be drawn at 50% alpha rather than to change the drawable.      
//同時也要重新整理,因為預設的View enable為false的行為是透明度為原來的50%而不是改變drawable
    invalidate(true);

    if (!enabled) {
        cancelPendingInputEvents();
    }
}      
兄弟們,看見沒,看見沒!這三個方法裡面都會調用關鍵的setFlags()方法,這方法到底是何方神聖!讓我們慢慢揭開其神秘的面紗吧
        
Android View的22500行源碼分析系列(一)setFlag 方法,setVisibilty,setFoucus,setEnable等方法内部實作
/**
    
    
      * 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  表示應該要被改變的位的常量
    
    
      */
    
    
     該函數在View中多處被調用,例如 View.setEnable()、View.setClickable(),setFocusable()等很多函數都調用到該函數。
    
    
     在View中使用mViewFlags和mPrivateFlags變量儲存大多數的屬性:
    
    
     – mViewFlags:該标記用來儲存和視圖狀态相關的屬性。
    
    
     – mPrivateFlags 該标記用來儲存和内部邏輯相關的屬性
    
    
     如Visible相關的Flag:
    
    
     
            
public static final int VISIBLE = 0x00000000;
public static final int INVISIBLE = 0x00000004;
public static final int GONE = 0x00000008;
static final int VISIBILITY_MASK = 0x0000000C;      
void setFlags(int flags, int mask) { final boolean accessibilityEnabled = AccessibilityManager.getInstance(mContext).isEnabled(); final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility(); int old = mViewFlags;//記錄原來的視圖狀态标記 mViewFlags = (mViewFlags & ~mask) | (flags & mask);//更新視圖狀态标記 int changed = mViewFlags ^ old;//異或判斷狀态是否發生改變 if (changed == 0) {//如果沒有改變,立刻傳回 return; } int privateFlags = mPrivateFlags;//記錄目前邏輯屬性标記 檢查Focusable位有沒有改變 if (((changed & FOCUSABLE_MASK) != 0) && ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) { if (((old & FOCUSABLE_MASK) == FOCUSABLE) && ((privateFlags & PFLAG_FOCUSED) != 0)) { 如果不再是focusable清空焦點。 clearFocus(); } else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE) && ((privateFlags & PFLAG_FOCUSED) == 0)) { if (mParent != null) mParent.focusableViewAvailable(this); } } final int newVisibility = flags & VISIBILITY_MASK; if (newVisibility == VISIBLE) { if ((changed & VISIBILITY_MASK) != 0) { mPrivateFlags |= PFLAG_DRAWN; invalidate(true); needGlobalAttributesUpdate(true); // a view becoming visible is worth notifying the parent // about in case nothing has focus. even if this specific view // isn't focusable, it may contain something that is, so let // the root view try to give this focus if nothing else does. 一個視圖将要變成可見的值得通知父控件假設沒有東西有焦點。即使指定的視圖不是 可擷取焦點的,它可能包含了一些能擷取焦點的,是以讓根視圖嘗試讓其擷取焦點。 if ((mParent != null) && (mBottom > mTop) && (mRight > mLeft)) {//可見需要擷取焦點 mParent.focusableViewAvailable(this); } } } 檢查GONE标志位是否發生改變 if ((changed & GONE) != 0) { needGlobalAttributesUpdate(false); requestLayout();//如果Gone的标志位發生了改變,必然會請求重新Layout if (((mViewFlags & VISIBILITY_MASK) == GONE)) { if (hasFocus()) clearFocus();//如果變為gone,且目前是有焦點的,清除焦點 clearAccessibilityFocus();//清除可達的焦點 destroyDrawingCache(); if (mParent instanceof View) { // GONE views noop invalidation, so invalidate the parent ((View) mParent).invalidate(true);//Gone的View不需要重新整理,是以重新整理它的父控件。 } // Mark the view drawn to ensure that it gets invalidated properly the next // time it is visible and gets invalidated 設定DRAWN标記保證其正确地得到重新整理下次為可見并且重新整理的時候 mPrivateFlags |= PFLAG_DRAWN; } if (mAttachInfo != null) { mAttachInfo.mViewVisibilityChanged = true; } } if ((changed & INVISIBLE) != 0) { needGlobalAttributesUpdate(false); mPrivateFlags |= PFLAG_DRAWN; = 0x00000020; if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE)) { // root view becoming invisible shouldn't clear focus and accessibility focus 如果這是根視圖的話,不應該清除焦點和可達的焦點。 if (getRootView() != this) { if (hasFocus()) clearFocus(); clearAccessibilityFocus(); } } if (mAttachInfo != null) { mAttachInfo.mViewVisibilityChanged = true; } } 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) mParent).onChildVisibilityChanged(this, (changed & VISIBILITY_MASK), newVisibility);//回調可見性發生變化 ((View) mParent).invalidate(true); } else if (mParent != null) { mParent.invalidateChild(this, null); } if (mAttachInfo != null) { dispatchVisibilityChanged(this, newVisibility);//分發可見性發生變化 notifySubtreeAccessibilityStateChangedIfNeeded(); } } if ((changed & WILL_NOT_CACHE_DRAWING) != 0) { destroyDrawingCache(); } if ((changed & DRAWING_CACHE_ENABLED) != 0) { destroyDrawingCache(); mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; invalidateParentCaches(); } if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) { destroyDrawingCache(); mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } static final int DRAW_MASK = 0x00000080;16*8=128---10000000 if ((changed & DRAW_MASK) != 0) {//檢查DRAW_MASK标志位是否發生改變 if ((mViewFlags & WILL_NOT_DRAW) != 0) { if (mBackground != null || (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } else { mPrivateFlags |= PFLAG_SKIP_DRAW; } } else { mPrivateFlags &= ~PFLAG_SKIP_DRAW; } requestLayout();//這裡也會重新請求Layout,如果draw_mask标志位發生了改變,也會重新Layout invalidate(true); } if ((changed & KEEP_SCREEN_ON) != 0) { if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) { mParent.recomputeViewAttributes(this); } } if (accessibilityEnabled) { //focus,visibilty,click,longclick,contextClick if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0 || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 || (changed & CONTEXT_CLICKABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { notifySubtreeAccessibilityStateChangedIfNeeded();// Notifies that the accessibility state of this view changed. } else { notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } else if ((changed & ENABLED_MASK) != 0) { notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } } 總結一下: ①View的許多關于屬性的set方法,内部都會調用setFlag方法,來通過Flag标志控制View的行為。其中: mViewFlags:該标記用來儲存和視圖狀态相關的屬性。 mPrivateFlags 該标記用來儲存和内部邏輯相關的屬性 ②View的可見性變為Gone的時候,都會調用requestLayout方法,請求parent重新measure和Layout。 ③此外,如果DRAW_MASK标志位發生了改變,也會調用requestLayout方法和invalidate方法來重新整理。 ④可見性發生變化的時候,會設定PFLAG_DRAWN标志來確定重新整理的時候得到重繪。 ⑤如果視圖不可見,清除它的顯示清單以釋放資源,調用cleanupDraw()方法。 今天,關于setFlag方法的一些了解就到這裡啦,期待下一篇博文吧
Android View的22500行源碼分析系列(一)setFlag 方法,setVisibilty,setFoucus,setEnable等方法内部實作

繼續閱讀