Android顯示框架:Android應用視圖的載體View

關於做者java

郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。android

第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄git

文章目錄程序員

  • 一 View的生命週期
  • 二 View的測量流程
  • 三 View的佈局流程
  • 四 View的繪製流程
  • 五 View事件分發機制

This class represents the basic building block for user interface components. A Viewoccupies a rectangular area on the screen and is
responsible for drawing and event handling.github

View是屏幕上的一塊矩形區域,負責界面的繪製與觸摸事件的處理,它是一種界面層控件的抽象,全部的控件都繼承自View。canvas

View是Android顯示框架中較爲複雜的一環,首先是它的生命週期會隨着Activity的生命週期進行變化,掌握View的生命週期對咱們自定義View有着重要的意義。另外一個方面View從ViewRoot.performTraversals()開始
經歷measure、layout、draw三個流程最終顯示在用戶面前,用戶在點擊屏幕時,點擊事件隨着Activity傳入Window,最終由ViewGroup/View進行分發處理。今天咱們就圍繞着這些主題進行展開分析。數組

一 View生命週期

在View中有諸多回調方法,它們在View的不一樣生命週期階段調用,經常使用的有如下方法。架構

咱們寫一個簡單的自定義View來觀察View與Activity的生命週期變化。app

public class CustomView extends View {

    private static final String TAG = "View";

    public CustomView(Context context) {
        super(context);
        Log.d(TAG, "CustomView()");
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        Log.d(TAG, "CustomView()");
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.d(TAG, "CustomView()");
    }

    /** * View在xml文件里加載完成時調用 */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        Log.d(TAG, "View onFinishInflate()");
    }

    /** * 測量View及其子View大小時調用 */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG, "View onMeasure()");
    }

    /** * 佈局View及其子View大小時調用 */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.d(TAG, "View onLayout() left = " + left + " top = " + top + " right = " + right + " bottom = " + bottom);
    }

    /** * View大小發生改變時調用 */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.d(TAG, "View onSizeChanged() w = " + w + " h = " + h + " oldw = " + oldw + " oldh = " + oldh);
    }

    /** * 繪製View及其子View大小時調用 */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.d(TAG, "View onDraw()");
    }

    /** * 物理按鍵事件發生時調用 */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d(TAG, "View onKeyDown() event = " + event.getAction());
        return super.onKeyDown(keyCode, event);
    }

    /** * 物理按鍵事件發生時調用 */
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        Log.d(TAG, "View onKeyUp() event = " + event.getAction());
        return super.onKeyUp(keyCode, event);
    }

    /** * 觸摸事件發生時調用 */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.d(TAG, "View onTouchEvent() event = " + event.getAction());
        return super.onTouchEvent(event);
    }

    /** * View獲取焦點或者失去焦點時調用 */
    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
        Log.d(TAG, "View onFocusChanged() gainFocus = " + gainFocus);
    }

    /** * View所在窗口獲取焦點或者失去焦點時調用 */
    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        Log.d(TAG, "View onWindowFocusChanged() hasWindowFocus = " + hasWindowFocus);
    }

    /** * View被關聯到窗口時調用 */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Log.d(TAG, "View onAttachedToWindow()");
    }

    /** * View從窗口分離時調用 */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Log.d(TAG, "View onDetachedFromWindow()");
    }

    /** * View的可見性發生變化時調用 */
    @Override
    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
        super.onVisibilityChanged(changedView, visibility);
        Log.d(TAG, "View onVisibilityChanged() visibility = " + visibility);
    }

    /** * View所在窗口的可見性發生變化時調用 */
    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        Log.d(TAG, "View onWindowVisibilityChanged() visibility = " + visibility);
    }
}複製代碼

Activity與View的生命週期變化一目瞭然。框架

Activity create

Activity pause

Activity resume

Activity destory

咱們來總結一下View的聲明週期隨着Activity生命週期變化的狀況。

咱們瞭解這些生命週期方法有什麼做用呢?🤔

其實這些方法在咱們自定義View的時候發揮着很大的做用,咱們來舉幾種應用場景。

場景1:在Activity啓動時獲取View的寬高,可是在onCreate、onStart和onResume均沒法獲取正確的結果。這是由於在Activity的這些方法裏,Viewed繪製可能尚未完成,咱們能夠在View的生命週期方法裏獲取。

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if(hasFocus){
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
    }
}複製代碼

場景2:在Activity生命週期發生變化時,View也要作響應的處理,典型的有VideoView保存進度和恢復進度。

@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
    super.onVisibilityChanged(changedView, visibility);
    //TODO do something if activity lifecycle changed if necessary
    //Activity onResume()
    if(visibility == VISIBLE){

    }
    //Activity onPause()
    else {

    }
}

@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
    super.onWindowFocusChanged(hasWindowFocus);

    //TODO do something if activity lifecycle changed if necessary
    //Activity onResume()
    if (hasWindowFocus) {
    }
    //Activity onPause()
    else {
    }
}複製代碼

場景3:釋放線程、資源

@Override
protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    //TODO release resources, thread, animation
}複製代碼

二 View的測量流程

View是一個矩形區域,它有本身的位置、大小與邊距。

View位置

View位置:有左上角座標(getLeft(), getTop())決定,該座標是以它的父View的左上角爲座標原點,單位是pixels。

View大小

View大小:View的大小有兩對值來表示。getMeasuredWidth()/getMeasuredHeight()這組值表示了該View在它的父View裏指望的大小值,在measure()方法完成後可得到。
getWidth()/getHeight()這組值表示了該View在屏幕上的實際大小,在draw()方法完成後可得到。

View內邊距

View內邊距:View的內邊距用padding來表示,它表示View的內容距離View邊緣的距離。經過getPaddingXXX()方法獲取。須要注意的是咱們在自定義View的時候須要單獨處理
padding,不然它不會生效,這一塊的內容咱們會在View自定義實踐系列的文章中展開。

View外邊距

View內邊距:View的內邊距用margin來表示,它表示View的邊緣離它相鄰的View的距離。

Measure過程決定了View的寬高,該過程完成後,一般均可以經過getMeasuredWith()/getMeasuredHeight()得到寬高。

理解了上面這些概念,咱們接下來來看看詳細的測量流程。

View的測量流程看似複雜,實際遵循着簡單的邏輯。

在作測量的時候,measure()方法被父View調用,在measure()中作一些準備和優化工做後,調用onMeasure()來進行實際的自我測量。對於onMeasure(),View和ViewGroup有所區別:

  • View:View 在 onMeasure() 中會計算出本身的尺寸而後保存;
  • ViewGroup:ViewGroup在onMeasure()中會調用全部子View的measure()讓它們進行自我測量,並根據子View計算出的指望尺寸來計算出它們的實際尺寸和位置而後保存。同時,它也會
    根據子View的尺寸和位置來計算出本身的尺寸而後保存.

在介紹測量流程以前,咱們先來介紹下MeasureSpec,它用來把測量要求從父View傳遞給子View。咱們知道View的大小最終由子View的LayoutParams與父View的測量要求公共決定,測量要求指的
就是這個MeasureSpec,它是一個32位int值。

  • 高2位:SpecMode,測量模式
  • 低30位:SpecSize,在特定測量模式下的大小

測量模式有三種:

public static class MeasureSpec {

    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

    //父View不對子View作任何限制,須要多大給多大,這種狀況通常用於系統內部,表示一種測量的狀態
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    //父View已經檢測出View所須要的精確大小,這個時候View的最終大小就是SpecSize所指定的值,它對應LayoutParams中的match_parent和具體數值這兩種模式
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    //父View給子VIew提供一個最大可用的大小,子View去自適應這個大小。
    public static final int AT_MOST     = 2 << MODE_SHIFT;  
}複製代碼

平常開發中咱們接觸最多的不是MeasureSpec而是LayoutParams,在View測量的時候,LayoutParams會和父View的MeasureSpec相結合被換算成View的MeasureSpec,進而決定View的大小。

View的MeasureSpec計算源碼以下所示:

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

     public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);

            int size = Math.max(0, specSize - padding);

            int resultSize = 0;
            int resultMode = 0;

            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;

            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;

            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = 0;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }

}複製代碼

該方法用來獲取子View的MeasureSpec,由參數咱們就能夠知道子View的MeasureSpec由父容器的spec,父容器中已佔用的的空間大小
padding,以及子View自身大小childDimension共同來決定的。

經過上述方法,咱們能夠總結出普通View的MeasureSpec的建立規則。

  • 當View採用固定寬高的時候,無論父容器的MeasureSpec是什麼,resultSize都是指定的寬高,resultMode都是MeasureSpec.EXACTLY。
  • 當View的寬高是match_parent,當父容器是MeasureSpec.EXACTLY,則View也是MeasureSpec.EXACTLY,而且其大小就是父容器的剩餘空間。當父容器是MeasureSpec.AT_MOST
    則View也是MeasureSpec.AT_MOST,而且大小不會超過父容器的剩餘空間。
  • 當View的寬高是wrap_content時,無論父容器的模式是MeasureSpec.EXACTLY仍是MeasureSpec.AT_MOST,View的模式老是MeasureSpec.AT_MOST,而且大小都不會超過芙蓉的剩餘空間。

瞭解了MeasureSpec的概念以後,我就就能夠開始分析測量流程了。

  • 對於頂級View(DecorView)其MeasureSpec由窗口的尺寸和自身的LayoutParams共同肯定的。
  • 對於普通View其MeasureSpec由父容器的Measure和自身的LayoutParams共同肯定的。

View的繪製會先調用View的measure()方法,measure()方法用來測量View的大小,實際的測量工做是由ziView的onMeasure()來完成的。咱們來看看
onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法的實現。

關鍵點1:View.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                   getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
       }

       //設置View寬高的測量值
       protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);

       //measureSpec指的是View測量後的大小
       public static int getDefaultSize(int size, int measureSpec) {
           int result = size;
           int specMode = MeasureSpec.getMode(measureSpec);
           int specSize =  MeasureSpec.getSize(measureSpec);

           switch (specMode) {
           //MeasureSpec.UNSPECIFIED通常用來系統的內部測量流程
           case MeasureSpec.UNSPECIFIED:
               result = size;
               break;
           //咱們主要關注着兩種狀況,它們返回的是View測量後的大小
           case MeasureSpec.AT_MOST:
           case MeasureSpec.EXACTLY:
               result = specSize;
               break;
           }
           return result;
       }

       //若是View沒有設置背景,那麼返回android:minWidth這個屬性的值,這個值能夠爲0
       //若是View設置了背景,那麼返回android:minWidth和背景最小寬度二者中的最大值。
       protected int getSuggestedMinimumHeight() {
           int suggestedMinHeight = mMinHeight;

           if (mBGDrawable != null) {
               final int bgMinHeight = mBGDrawable.getMinimumHeight();
               if (suggestedMinHeight < bgMinHeight) {
                   suggestedMinHeight = bgMinHeight;
               }
           }

           return suggestedMinHeight;
       }
}複製代碼

View的onMeasure()方法實現比較簡單,它調用setMeasuredDimension()方法來設置View的測量大小,測量的大小經過getDefaultSize()方法來獲取。

若是咱們直接繼承View來自定義View時,須要重寫onMeasure()方法,並設置wrap_content時的大小。爲何呢?🤔

經過上面的描述咱們知道,當LayoutParams爲wrap_content時,SpecMode爲AT_MOST,而在

關於getDefaultSize(int size, int measureSpec) 方法須要說明一下,經過上面的描述咱們知道etDefaultSize()方法中AT_MOST與EXACTLY模式下,返回的
都是specSize,這個specSize是父View當前可使用的大小,若是不處理,那wrap_content就至關於match_parent。

如何處理?🤔

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      Log.d(TAG, "widthMeasureSpec = " + widthMeasureSpec + " heightMeasureSpec = " + heightMeasureSpec);

      //指定一組默認寬高,至於具體的值是多少,這就要看你但願在wrap_cotent模式下
      //控件的大小應該設置多大了
      int mWidth = 200;
      int mHeight = 200;

      int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
      int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);

      int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
      int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

      if (widthSpecMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
          setMeasuredDimension(mWidth, mHeight);
      } else if (widthSpecMode == MeasureSpec.AT_MOST) {
          setMeasuredDimension(mWidth, heightSpecSize);
      } else if (heightSpecMode == MeasureSpec.AT_MOST) {
          setMeasuredDimension(widthSpecSize, mHeight);
      }
  }複製代碼

注:你能夠本身嘗試一下自定義一個View,而後不重寫onMeasure()方法,你會發現只有設置match_parent和wrap_content效果是同樣的,事實上TextView、ImageView
等系統組件都在wrap_content上有本身的處理,能夠去翻一翻源碼。

看完了View的measure過程,咱們再來看看ViewGroup的measure過程。ViewGroup繼承於View,是一個抽象類,它並無重寫onMeasure()方法,由於不一樣佈局類型的測量
流程各不相同,所以onMeasure()方法由它的子類來實現。

咱們來看個FrameLayout的onMeasure()方法的實現。

關鍵點2:FrameLayout.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

View.onMeasure()方法的具體實現通常是由其子類來完成的,對於應用窗口的頂級視圖DecorView來講,它繼承於FrameLayout,咱們來看看FrameLayout.onMeasure()
方法的實現。

public class FrameLayout extends ViewGroup {

       @Override
       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
           final int count = getChildCount();

           int maxHeight = 0;
           int maxWidth = 0;

           // Find rightmost and bottommost child
           for (int i = 0; i < count; i++) {
               final View child = getChildAt(i);
               if (mMeasureAllChildren || child.getVisibility() != GONE) {
                   measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                   maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
                   maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
               }
           }

           // Account for padding too
           maxWidth += mPaddingLeft + mPaddingRight + mForegroundPaddingLeft + mForegroundPaddingRight;
           maxHeight += mPaddingTop + mPaddingBottom + mForegroundPaddingTop + mForegroundPaddingBottom;

           // Check against our minimum height and width
           maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
           maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

           // Check against our foreground's minimum height and width
           final Drawable drawable = getForeground();
           if (drawable != null) {
               maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
               maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
           }

           setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
                   resolveSize(maxHeight, heightMeasureSpec));
       }

      public static int resolveSize(int size, int measureSpec) {
           int result = size;
           int specMode = MeasureSpec.getMode(measureSpec);
           int specSize =  MeasureSpec.getSize(measureSpec);
           switch (specMode) {
           case MeasureSpec.UNSPECIFIED:
               result = size;
               break;
           case MeasureSpec.AT_MOST:
               result = Math.min(size, specSize);
               break;
           case MeasureSpec.EXACTLY:
               result = specSize;
               break;
           }
           return result;
       }
}複製代碼

能夠看到該方法主要作了如下事情:

  1. 調用measureChildWithMargins()去測量每個子View的大小,找到最大高度和寬度保存在maxWidth/maxHeigth中。
  2. 將上一步計算的maxWidth/maxHeigth加上padding值,mPaddingLeft,mPaddingRight,mPaddingTop ,mPaddingBottom表示當前內容區域的左右上下四條邊分別到當前視圖的左右上下四條邊的距離,
    mForegroundPaddingLeft ,mForegroundPaddingRight,mForegroundPaddingTop ,mForegroundPaddingBottom表示當前視圖的各個子視圖所圍成的區域的左右上下四條邊到當前視圖前景區域的
    左右上下四條邊的距離,通過計算得到最終寬高。
  3. 當前視圖是否設置有最小寬度和高度。若是設置有的話,而且它們比前面計算獲得的寬度maxWidth和高度maxHeight還要大,那麼就將它們做爲當前視圖的寬度和高度值。
  4. 當前視圖是否設置有前景圖。若是設置有的話,而且它們比前面計算獲得的寬度maxWidth和高度maxHeight還要大,那麼就將它們做爲當前視圖的寬度和高度值。
  5. 通過以上的計算,就獲得了正確的寬高,先調用resolveSize()方法,獲取MeasureSpec,接着調用父類的setMeasuredDimension()方法將它們做爲當前視圖的大小。

咱們再來看看resolveSize(int size, int measureSpec)方法是若是獲取MeasureSpec的?

這個方法的兩個參數:int size:前面計算出的最大寬/高,int measureSpec父視圖指定的MeasureSpec,它們按照:

  • MeasureSpec.UNSPECIFIED: 取size
  • MeasureSpec.AT_MOST: 取size, specSize的最小值
  • MeasureSpec.EXACTLY: 取specSize

來生成最後的大小。

以上即是Measure的整個流程,該流程完成之後,咱們能夠經過getMeasuredWidth()與getMeasuredHeight()來得到View的寬高。可是在某些狀況下,系統須要通過屢次Measure才能肯定
最終的寬高,所以在onMeasure()方法中拿到的寬高極可能是不正確的,比較好的作法是在onLayout()方法中獲取View的寬高。

三 View的佈局流程

在進行佈局的時候,layout()方法被父View調用,在layout()中它會保存父View傳進來的本身的位置和尺寸,而且調用onLayout()來進行實際的內部佈局。對於onLayout(),View和ViewGroup有所區別:

  • View:因爲沒有子 View,因此 View 的 onLayout() 什麼也不作。
  • ViewGroup:ViewGroup在onLayout()中會調用本身的全部子View的layout()方法,把它們的尺寸和位置傳給它們,讓它們完成自個人內部佈局。

layout()方法用來肯定View自己的位置,onLayout()方法用來肯定子元素的位置。

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

   public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        //1 調用setFrame()設置View四個頂點ed位置
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

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

            //2 調用onLayout()肯定View子元素的位置
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
}複製代碼

關鍵點1:View.invalidate()

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

    public void invalidate() {
        if (ViewDebug.TRACE_HIERARCHY) {
            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.INVALIDATE);
        }

        //檢查mPrivateFlags的DRAWN位與HAS_BOUNDS是否被置1,說明上一次請求執行的UI繪製已經完成了,這個時候才能執行新的UI繪製操做
        if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
            //將mPrivateFlags的DRAWN位與HAS_BOUNDS是否被置0
            mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
            final ViewParent p = mParent;
            final AttachInfo ai = mAttachInfo;
            if (p != null && ai != null) {
                final Rect r = ai.mTmpInvalRect;
                r.set(0, 0, mRight - mLeft, mBottom - mTop);
                // Don't call invalidate -- we don't want to internally scroll
                // our own bounds
                p.invalidateChild(this, r);
            }
        }
    }
}複製代碼

該方法檢查mPrivateFlags的DRAWN位與HAS_BOUNDS是否被置1,說明上一次請求執行的UI繪製已經完成了,這個時候才能執行新的UI繪製操做,在執行新的UI繪製操做以前,還會將
這兩個標誌位置0,而後調用ViewParent.invalidateChild()方法來完成繪製操做,這個ViewParent指向的是ViewRoot對象。

關鍵點2:FrameLayout.onLayout(boolean changed, int left, int top, int right, int bottom)

onLayout的實現依賴於具體的佈局,因此View/ViewGroup並無實現這個方法,咱們來看看FrameLayout的實現。

public class FrameLayout extends ViewGroup {

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            final int count = getChildCount();

            final int parentLeft = mPaddingLeft + mForegroundPaddingLeft;
            final int parentRight = right - left - mPaddingRight - mForegroundPaddingRight;

            final int parentTop = mPaddingTop + mForegroundPaddingTop;
            final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom;

            mForegroundBoundsChanged = true;

            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                    final int width = child.getMeasuredWidth();
                    final int height = child.getMeasuredHeight();

                    int childLeft = parentLeft;
                    int childTop = parentTop;

                    final int gravity = lp.gravity;

                    if (gravity != -1) {
                        final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                        final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                        switch (horizontalGravity) {
                            case Gravity.LEFT:
                                childLeft = parentLeft + lp.leftMargin;
                                break;
                            case Gravity.CENTER_HORIZONTAL:
                                childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                                        lp.leftMargin - lp.rightMargin;
                                break;
                            case Gravity.RIGHT:
                                childLeft = parentRight - width - lp.rightMargin;
                                break;
                            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);
                }
            }
        }
}複製代碼

咱們先來解釋一下這個函數裏的變量的含義。

  • int left, int top, int right, int bottom: 描述的是當前視圖的外邊距,即它與父窗口的邊距。
  • mPaddingLeft,mPaddingTop,mPaddingRight,mPaddingBottom: 描述的當前視圖的內邊距。

經過這些參數,咱們就能夠獲得當前視圖的子視圖所能佈局在的區域。

接着,該方法就會遍歷它的每個子View,並獲取它的左上角的座標位置:childLeft,childTop。這兩個位置信息會根據gravity來進行計算。
最後會調用子View的layout()方法循環佈局操做,直到全部的佈局都完成爲止。

View的繪製流程

Draw過程最終將View繪製在屏幕上。

繪製從ViewRoot.draw()開始,它首先會建立一塊畫布,接着再在畫布上繪製Android上的UI,再把畫布的內容交給SurfaceFlinger服務來渲染。

關鍵點1:ViewRoot.draw(boolean fullRedrawNeeded)

public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks {

    private void draw(boolean fullRedrawNeeded) {
            //surface用來操做應用窗口的繪圖表面
            Surface surface = mSurface;
            if (surface == null || !surface.isValid()) {
                return;
            }

            if (!sFirstDrawComplete) {
                synchronized (sFirstDrawHandlers) {
                    sFirstDrawComplete = true;
                    for (int i=0; i<sFirstDrawHandlers.size(); i++) {
                        post(sFirstDrawHandlers.get(i));
                    }
                }
            }

            scrollToRectOrFocus(null, false);

            if (mAttachInfo.mViewScrollChanged) {
                mAttachInfo.mViewScrollChanged = false;
                mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
            }

            int yoff;
            //計算窗口是否處於滾動狀態
            final boolean scrolling = mScroller != null && mScroller.computeScrollOffset();
            if (scrolling) {
                yoff = mScroller.getCurrY();
            } else {
                yoff = mScrollY;
            }
            if (mCurScrollY != yoff) {
                mCurScrollY = yoff;
                fullRedrawNeeded = true;
            }
            //描述窗口是否正在請求大小縮放
            float appScale = mAttachInfo.mApplicationScale;
            boolean scalingRequired = mAttachInfo.mScalingRequired;

            //描述窗口的髒區域,即須要從新繪製的區域
            Rect dirty = mDirty;
            if (mSurfaceHolder != null) {
                // The app owns the surface, we won't draw.
                dirty.setEmpty();
                return;
            }

            //用來描述是否須要用OpenGL接口來繪製UI,當應用窗口flag等於WindowManager.LayoutParams.MEMORY_TYPE_GPU
            //則表示須要用OpenGL接口來繪製UI
            if (mUseGL) {
                if (!dirty.isEmpty()) {
                    Canvas canvas = mGlCanvas;
                    if (mGL != null && canvas != null) {
                        mGL.glDisable(GL_SCISSOR_TEST);
                        mGL.glClearColor(0, 0, 0, 0);
                        mGL.glClear(GL_COLOR_BUFFER_BIT);
                        mGL.glEnable(GL_SCISSOR_TEST);

                        mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                        mAttachInfo.mIgnoreDirtyState = true;
                        mView.mPrivateFlags |= View.DRAWN;

                        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                        try {
                            canvas.translate(0, -yoff);
                            if (mTranslator != null) {
                                mTranslator.translateCanvas(canvas);
                            }
                            canvas.setScreenDensity(scalingRequired
                                    ? DisplayMetrics.DENSITY_DEVICE : 0);
                            mView.draw(canvas);
                            if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                                mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                            }
                        } finally {
                            canvas.restoreToCount(saveCount);
                        }

                        mAttachInfo.mIgnoreDirtyState = false;

                        mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
                        checkEglErrors();

                        if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                            int now = (int)SystemClock.elapsedRealtime();
                            if (sDrawTime != 0) {
                                nativeShowFPS(canvas, now - sDrawTime);
                            }
                            sDrawTime = now;
                        }
                    }
                }

                //若是窗口處於滾動狀態,則應用窗口須要立刻進行下一次所有重繪,調用scheduleTraversals()方法
                if (scrolling) {
                    mFullRedrawNeeded = true;
                    scheduleTraversals();
                }
                return;
            }

            //是否須要所有重繪,若是是則將窗口的髒區域設置爲整個窗口區域,表示整個窗口曲雲都須要重繪
            if (fullRedrawNeeded) {
                mAttachInfo.mIgnoreDirtyState = true;
                dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
            }

            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(TAG, "Draw " + mView + "/"
                        + mWindowAttributes.getTitle()
                        + ": dirty={" + dirty.left + "," + dirty.top
                        + "," + dirty.right + "," + dirty.bottom + "} surface="
                        + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
                        appScale + ", width=" + mWidth + ", height=" + mHeight);
            }

            if (!dirty.isEmpty() || mIsAnimating) {
                Canvas canvas;
                try {
                    int left = dirty.left;
                    int top = dirty.top;
                    int right = dirty.right;
                    int bottom = dirty.bottom;
                    //調用Surface.lockCanvas()來建立畫布
                    canvas = surface.lockCanvas(dirty);

                    if (left != dirty.left || top != dirty.top || right != dirty.right ||
                            bottom != dirty.bottom) {
                        mAttachInfo.mIgnoreDirtyState = true;
                    }

                    // TODO: Do this in native
                    canvas.setDensity(mDensity);
                } catch (Surface.OutOfResourcesException e) {
                    Log.e(TAG, "OutOfResourcesException locking surface", e);
                    // TODO: we should ask the window manager to do something!
                    // for now we just do nothing
                    return;
                } catch (IllegalArgumentException e) {
                    Log.e(TAG, "IllegalArgumentException locking surface", e);
                    // TODO: we should ask the window manager to do something!
                    // for now we just do nothing
                    return;
                }

                try {
                    if (!dirty.isEmpty() || mIsAnimating) {
                        long startTime = 0L;

                        if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                            Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
                                    + canvas.getWidth() + ", h=" + canvas.getHeight());
                            //canvas.drawARGB(255, 255, 0, 0);
                        }

                        if (Config.DEBUG && ViewDebug.profileDrawing) {
                            startTime = SystemClock.elapsedRealtime();
                        }

                        // If this bitmap's format includes an alpha channel, we
                        // need to clear it before drawing so that the child will
                        // properly re-composite its drawing on a transparent
                        // background. This automatically respects the clip/dirty region
                        // or
                        // If we are applying an offset, we need to clear the area
                        // where the offset doesn't appear to avoid having garbage
                        // left in the blank areas.
                        if (!canvas.isOpaque() || yoff != 0) {
                            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                        }

                        dirty.setEmpty();
                        mIsAnimating = false;
                        mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
                        mView.mPrivateFlags |= View.DRAWN;

                        if (DEBUG_DRAW) {
                            Context cxt = mView.getContext();
                            Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
                                    ", metrics=" + cxt.getResources().getDisplayMetrics() +
                                    ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
                        }
                        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
                        try {
                            canvas.translate(0, -yoff);
                            if (mTranslator != null) {
                                mTranslator.translateCanvas(canvas);
                            }
                            canvas.setScreenDensity(scalingRequired
                                    ? DisplayMetrics.DENSITY_DEVICE : 0);
                            mView.draw(canvas);
                        } finally {
                            mAttachInfo.mIgnoreDirtyState = false;
                            canvas.restoreToCount(saveCount);
                        }

                        if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
                            mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
                        }

                        if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
                            int now = (int)SystemClock.elapsedRealtime();
                            if (sDrawTime != 0) {
                                nativeShowFPS(canvas, now - sDrawTime);
                            }
                            sDrawTime = now;
                        }

                        if (Config.DEBUG && ViewDebug.profileDrawing) {
                            EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
                        }
                    }

                } finally {
                    //UI繪製完成後,調用urface.unlockCanvasAndPost(canvas)S來請求SurfaceFlinger進行UI的渲染
                    surface.unlockCanvasAndPost(canvas);
                }
            }

            if (LOCAL_LOGV) {
                Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
            }

            if (scrolling) {
                mFullRedrawNeeded = true;
                scheduleTraversals();
            }
        }
}複製代碼

這個函數主要作了如下事情:

  1. 調用Scroller.computeScrollOffset()方法計算應用是否處於滑動狀態,並得到應用窗口在Y軸上的即時滑動位置yoff。
  2. 根據AttachInfo裏描述的數據,判斷窗口是否須要縮放。
  3. 根據成員變量React mDirty的描述來判斷窗口髒區域的大小,髒區域指的是須要所有重繪的窗口區域。
  4. 根據成員變量boolean mUserGL判斷是否須要用OpenGL接口來繪製UI,當應用窗口flag等於WindowManager.LayoutParams.MEMORY_TYPE_GPU則表示須要用OpenGL接口來繪製UI.
  5. 若是不是用OpenGL來繪製,則用Surface來繪製,先調用Surface.lockCanvas()來建立畫布,UI繪製完成後,再調用urface.unlockCanvasAndPost(canvas)S來請求SurfaceFlinger進行UI的渲染

注:這裏的Surface對象對應了C++層裏的Surface對象,真正的功能在C++層,關於C++層的實現,咱們會在後續的文章進一步分析。

關鍵點2:View.draw(Canvas canvas)

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

    public void draw(Canvas canvas) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
            }

            final int privateFlags = mPrivateFlags;
            //dirtyOpaque用來描述當前繪製,它有兩種狀況:1 檢查DIRTY_OPAQUE爲是否爲1,若是是則說明當前視圖某個子視圖請求了一個不透明的UI繪製操做,此時當前
            //視圖會被子視圖覆蓋 2 若是mAttachInfo.mIgnoreDirtyState = true則表示忽略該標誌位
            final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
                    (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

            //將DIRTY_MASK與DRAWN置爲1,表示開始繪製
            mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;


            /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */

            // Step 1, draw the background, if needed
            int saveCount;

            if (!dirtyOpaque) {
                //繪製當前視圖的背景
                final Drawable background = mBGDrawable;
                if (background != null) {
                    final int scrollX = mScrollX;
                    final int scrollY = mScrollY;

                    if (mBackgroundSizeChanged) {
                        background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
                        mBackgroundSizeChanged = false;
                    }

                    if ((scrollX | scrollY) == 0) {
                        background.draw(canvas);
                    } else {
                        canvas.translate(scrollX, scrollY);
                        background.draw(canvas);
                        canvas.translate(-scrollX, -scrollY);
                    }
                }
            }

            //檢查是否能夠跳過第2步和第5步,也就是繪製變量,FADING_EDGE_HORIZONTAL == 1表示處於水平
            //滑動狀態,則須要繪製水平邊框漸變效果,FADING_EDGE_VERTICAL == 1表示處於垂直滑動狀態,則
            //須要繪製垂直邊框漸變效果。
            // skip step 2 & 5 if possible (common case)
            final int viewFlags = mViewFlags;
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
            if (!verticalEdges && !horizontalEdges) {

                //窗口內容不透明纔開始繪製,透明的時候就無需繪製了
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);

                // Step 4, draw the children
                dispatchDraw(canvas);

                // Step 6, draw decorations (scrollbars)
                onDrawScrollBars(canvas);

                // we're done...
                return;
            }

            /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */
            //檢查失修須要保存參數canvas所描述的一塊畫布的堆棧狀態,而且建立額外的圖層來繪製當前視圖
            //在滑動時的邊框漸變效果
            boolean drawTop = false;
            boolean drawBottom = false;
            boolean drawLeft = false;
            boolean drawRight = false;

            float topFadeStrength = 0.0f;
            float bottomFadeStrength = 0.0f;
            float leftFadeStrength = 0.0f;
            float rightFadeStrength = 0.0f;

            // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
            int paddingTop = mPaddingTop;

            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
                paddingTop += getTopPaddingOffset();
            }

            //表示當前視圖能夠用來繪製的內容區域,這個區域已經將內置的和擴展的內邊距排除以外
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + paddingTop;
            int bottom = top + mBottom - mTop - mPaddingBottom - paddingTop;

            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }

            final ScrollabilityCache scrollabilityCache = mScrollCache;
            int length = scrollabilityCache.fadingEdgeLength;

            // clip the fade length if top and bottom fades overlap
            // overlapping fades produce odd-looking artifacts
            if (verticalEdges && (top + length > bottom - length)) {
                length = (bottom - top) / 2;
            }

            // also clip horizontal fades if necessary
            if (horizontalEdges && (left + length > right - length)) {
                length = (right - left) / 2;
            }

            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength >= 0.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength >= 0.0f;
            }

            if (horizontalEdges) {
                leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
                drawLeft = leftFadeStrength >= 0.0f;
                rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
                drawRight = rightFadeStrength >= 0.0f;
            }

            saveCount = canvas.getSaveCount();

            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }

                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }

                if (drawLeft) {
                    canvas.saveLayer(left, top, left + length, bottom, null, flags);
                }

                if (drawRight) {
                    canvas.saveLayer(right - length, top, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }

            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            //繪製當前視圖的上下左右邊框的漸變效果
            // Step 5, draw the fade effect and restore layers
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;

            if (drawTop) {
                matrix.setScale(1, fadeHeight * topFadeStrength);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                canvas.drawRect(left, top, right, top + length, p);
            }

            if (drawBottom) {
                matrix.setScale(1, fadeHeight * bottomFadeStrength);
                matrix.postRotate(180);
                matrix.postTranslate(left, bottom);
                fade.setLocalMatrix(matrix);
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }

            if (drawLeft) {
                matrix.setScale(1, fadeHeight * leftFadeStrength);
                matrix.postRotate(-90);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                canvas.drawRect(left, top, left + length, bottom, p);
            }

            if (drawRight) {
                matrix.setScale(1, fadeHeight * rightFadeStrength);
                matrix.postRotate(90);
                matrix.postTranslate(right, top);
                fade.setLocalMatrix(matrix);
                canvas.drawRect(right - length, top, right, bottom, p);
            }

            canvas.restoreToCount(saveCount);

            //繪製當前視圖的滾動條
            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);
        }
}複製代碼

該方法主要完成了如下事情:

  1. 繪製當前視圖的背景
  2. 保存當前畫布的狀態,而且在當前畫布建立額外的突出,以便接下來能夠繪製視圖在滑動時的邊框漸變效果。
  3. 繪製當前視圖的內容
  4. 繪製當前視圖的子視圖
  5. 繪製當前視圖在滑動時的邊框漸變效果
  6. 繪製當前視圖的滾動條

關鍵點2:ViewGroup.dispatchDraw(Canvas canvas)

dispatchDraw用來循環繪製子View視圖。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    protected void dispatchDraw(Canvas canvas) {
            //當前視圖的子視圖個數
            final int count = mChildrenCount;
            final View[] children = mChildren;
            int flags = mGroupFlags;

            if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
                final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;

                for (int i = 0; i < count; i++) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                        final LayoutParams params = child.getLayoutParams();
                        attachLayoutAnimationParameters(child, params, i, count);
                        bindLayoutAnimation(child);
                        if (cache) {
                            child.setDrawingCacheEnabled(true);
                            child.buildDrawingCache(true);
                        }
                    }
                }

                final LayoutAnimationController controller = mLayoutAnimationController;
                if (controller.willOverlap()) {
                    mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
                }

                controller.start();

                //檢查是否須要顯示動畫,即FLAG_RUN_ANIMATION == 1
                mGroupFlags &= ~FLAG_RUN_ANIMATION;
                mGroupFlags &= ~FLAG_ANIMATION_DONE;

                if (cache) {
                    mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
                }

                //通知動畫監聽者動畫開始顯示了
                if (mAnimationListener != null) {
                    mAnimationListener.onAnimationStart(controller.getAnimation());
                }
            }

            int saveCount = 0;
            //若是CLIP_TO_PADDING_MASK != 1,則說明參數canvas描述的是畫布的剪裁區域,該剪裁區域不包含當前視圖組的內邊距
            final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
            if (clipToPadding) {
                saveCount = canvas.save();
                //裁剪畫布
                canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                        mScrollX + mRight - mLeft - mPaddingRight,
                        mScrollY + mBottom - mTop - mPaddingBottom);

            }

            // We will draw our child's animation, let's reset the flag
            mPrivateFlags &= ~DRAW_ANIMATION;
            mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;

            boolean more = false;
            final long drawingTime = getDrawingTime();

            //若是FLAG_USE_CHILD_DRAWING_ORDER == 0,則說明子視圖按照它們在children數組裏順序進行繪製
            //不然須要調用getChildDrawingOrder來判斷繪製順序
            if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
                for (int i = 0; i < count; i++) {
                    final View child = children[i];
                    //若是子視圖可見,則開始繪製子視圖
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                        more |= drawChild(canvas, child, drawingTime);
                    }
                }
            } else {
                for (int i = 0; i < count; i++) {
                    final View child = children[getChildDrawingOrder(count, i)];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                        more |= drawChild(canvas, child, drawingTime);
                    }
                }
            }

            //mDisappearingChildren用來保存哪些正在消失的子視圖,正在消失的子視圖也是須要繪製的
            // Draw any disappearing views that have animations
            if (mDisappearingChildren != null) {
                final ArrayList<View> disappearingChildren = mDisappearingChildren;
                final int disappearingCount = disappearingChildren.size() - 1;
                // Go backwards -- we may delete as animations finish
                for (int i = disappearingCount; i >= 0; i--) {
                    final View child = disappearingChildren.get(i);
                    more |= drawChild(canvas, child, drawingTime);
                }
            }

            if (clipToPadding) {
                canvas.restoreToCount(saveCount);
            }

            // mGroupFlags might have been updated by drawChild()
            flags = mGroupFlags;

            //若是FLAG_INVALIDATE_REQUIRED == 1,則說明須要進行從新繪製
            if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
                invalidate();
            }

            //通知動畫監聽者,動畫已經結束
            if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                    mLayoutAnimationController.isDone() && !more) {
                // We want to erase the drawing cache and notify the listener after the
                // next frame is drawn because one extra invalidate() is caused by
                // drawChild() after the animation is over
                mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
                final Runnable end = new Runnable() {
                   public void run() {
                       notifyAnimationListener();
                   }
                };
                post(end);
            }
        }
}複製代碼

dispatchDraw用來循環繪製子View視圖,它主要作了如下事情:

  1. 檢查是否須要顯示動畫,即FLAG_RUN_ANIMATION == 1,則開始顯示動畫,並通知動畫監聽者動畫已經開始。
  2. 若是FLAG_USE_CHILD_DRAWING_ORDER == 0,則說明子視圖按照它們在children數組裏順序進行繪製不然須要調用getChildDrawingOrder來判斷繪製順序,最終調用drawChild()來完成
    子視圖的繪製。
  3. 判斷是否須要進行重繪以及通知動畫監聽者動畫已經結束。

關鍵點3:ViewGroup.drawChild(Canvas canvas, View child, long drawingTime)

ViewGroup.drawChild(Canvas canvas, View child, long drawingTime)用來完成子視圖的繪製。

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            //表示子視圖child是否還在顯示動畫
            boolean more = false;

            //獲取子視圖的繪製區域以及標誌位
            final int cl = child.mLeft;
            final int ct = child.mTop;
            final int cr = child.mRight;
            final int cb = child.mBottom;

            final int flags = mGroupFlags;

            if ((flags & FLAG_CLEAR_TRANSFORMATION) == FLAG_CLEAR_TRANSFORMATION) {
                if (mChildTransformation != null) {
                    mChildTransformation.clear();
                }
                mGroupFlags &= ~FLAG_CLEAR_TRANSFORMATION;
            }

            //獲取子視圖的變換矩陣transformToApply
            Transformation transformToApply = null;
            //獲取子視圖的動畫
            final Animation a = child.getAnimation();
            boolean concatMatrix = false;

            if (a != null) {
                if (mInvalidateRegion == null) {
                    mInvalidateRegion = new RectF();
                }
                final RectF region = mInvalidateRegion;

                final boolean initialized = a.isInitialized();
                if (!initialized) {
                    a.initialize(cr - cl, cb - ct, getWidth(), getHeight());
                    a.initializeInvalidateRegion(0, 0, cr - cl, cb - ct);
                    child.onAnimationStart();
                }

                if (mChildTransformation == null) {
                    mChildTransformation = new Transformation();
                }
                //若是子視圖須要播放動畫,則調用getTransformation開始執行動畫,若是動畫還須要繼續執行,則more == true,而且返回子視圖的
                //變化矩陣mChildTransformation
                more = a.getTransformation(drawingTime, mChildTransformation);
                transformToApply = mChildTransformation;

                concatMatrix = a.willChangeTransformationMatrix();

                if (more) {
                    if (!a.willChangeBounds()) {
                        if ((flags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) ==
                                FLAG_OPTIMIZE_INVALIDATE) {
                            mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
                        } else if ((flags & FLAG_INVALIDATE_REQUIRED) == 0) {
                            // The child need to draw an animation, potentially offscreen, so
                            // make sure we do not cancel invalidate requests
                            mPrivateFlags |= DRAW_ANIMATION;
                            invalidate(cl, ct, cr, cb);
                        }
                    } else {
                        a.getInvalidateRegion(0, 0, cr - cl, cb - ct, region, transformToApply);

                        // The child need to draw an animation, potentially offscreen, so
                        // make sure we do not cancel invalidate requests
                        mPrivateFlags |= DRAW_ANIMATION;

                        final int left = cl + (int) region.left;
                        final int top = ct + (int) region.top;
                        invalidate(left, top, left + (int) region.width(), top + (int) region.height());
                    }
                }
            } 
            //若是FLAG_SUPPORT_STATIC_TRANSFORMATIONS == 1,調用getChildStaticTransformation()方法檢查子視圖是否被設置一個
            //變換矩陣,若是設置了,即hasTransform == true,則mChildTransformation就是子視圖須要的變換矩陣
            else if ((flags & FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
                    FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
                if (mChildTransformation == null) {
                    mChildTransformation = new Transformation();
                }
                final boolean hasTransform = getChildStaticTransformation(child, mChildTransformation);
                if (hasTransform) {
                    final int transformType = mChildTransformation.getTransformationType();
                    transformToApply = transformType != Transformation.TYPE_IDENTITY ?
                            mChildTransformation : null;
                    concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
                }
            }

            //設置mPrivateFlags的DRAWN標誌位爲1,標明它要開始繪製了。
            // Sets the flag as early as possible to allow draw() implementations
            // to call invalidate() successfully when doing animations
            child.mPrivateFlags |= DRAWN;

            if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
                    (child.mPrivateFlags & DRAW_ANIMATION) == 0) {
                return more;
            }

            //調用computeScroll()計算子視圖的滑動位置
            child.computeScroll();

            final int sx = child.mScrollX;
            final int sy = child.mScrollY;

            boolean scalingRequired = false;
            Bitmap cache = null;
            //若是FLAG_CHILDREN_DRAWN_WITH_CACHE或者FLAG_CHILDREN_DRAWN_WITH_CACHE爲1,則表示它採用緩衝的方式進行
            //繪製,它將本身的UI緩衝在一個Bitmap裏,能夠調用getDrawingCache()方法來得到這個Bitmap。
            if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
                    (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
                cache = child.getDrawingCache(true);
                if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
            }

            final boolean hasNoCache = cache == null;

            //設置子視圖child的偏移、Alpha通道以及裁剪區域
            final int restoreTo = canvas.save();
            if (hasNoCache) {
                canvas.translate(cl - sx, ct - sy);
            } else {
                canvas.translate(cl, ct);
                if (scalingRequired) {
                    // mAttachInfo cannot be null, otherwise scalingRequired == false
                    final float scale = 1.0f / mAttachInfo.mApplicationScale;
                    canvas.scale(scale, scale);
                }
            }

            float alpha = 1.0f;

            if (transformToApply != null) {
                if (concatMatrix) {
                    int transX = 0;
                    int transY = 0;
                    if (hasNoCache) {
                        transX = -sx;
                        transY = -sy;
                    }
                    // Undo the scroll translation, apply the transformation matrix,
                    // then redo the scroll translate to get the correct result.
                    canvas.translate(-transX, -transY);
                    canvas.concat(transformToApply.getMatrix());
                    canvas.translate(transX, transY);
                    mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
                }

                alpha = transformToApply.getAlpha();
                if (alpha < 1.0f) {
                    mGroupFlags |= FLAG_CLEAR_TRANSFORMATION;
                }

                if (alpha < 1.0f && hasNoCache) {
                    final int multipliedAlpha = (int) (255 * alpha);
                    if (!child.onSetAlpha(multipliedAlpha)) {
                        canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct, multipliedAlpha,
                                Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
                    } else {
                        child.mPrivateFlags |= ALPHA_SET;
                    }
                }
            } else if ((child.mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
                child.onSetAlpha(255);
            }

            //若是FLAG_CLIP_CHILDREN == 1,則須要設置子視圖的裁剪區域
            if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                if (hasNoCache) {
                    canvas.clipRect(sx, sy, sx + (cr - cl), sy + (cb - ct));
                } else {
                    if (!scalingRequired) {
                        canvas.clipRect(0, 0, cr - cl, cb - ct);
                    } else {
                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                    }
                }
            }

            //繪製子視圖的UI
            if (hasNoCache) {
                // Fast path for layouts with no backgrounds
                if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
                    if (ViewDebug.TRACE_HIERARCHY) {
                        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
                    }
                    child.mPrivateFlags &= ~DIRTY_MASK;
                    child.dispatchDraw(canvas);
                } else {
                    child.draw(canvas);
                }
            } else {
                final Paint cachePaint = mCachePaint;
                if (alpha < 1.0f) {
                    cachePaint.setAlpha((int) (alpha * 255));
                    mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE;
                } else if  ((flags & FLAG_ALPHA_LOWER_THAN_ONE) == FLAG_ALPHA_LOWER_THAN_ONE) {
                    cachePaint.setAlpha(255);
                    mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
                }
                if (Config.DEBUG && ViewDebug.profileDrawing) {
                    EventLog.writeEvent(60003, hashCode());
                }
                canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
            }

            //恢復畫布的堆棧狀態,以便在繪製完當前子視圖的UI後,能夠繼續繪製其餘子視圖的UI
            canvas.restoreToCount(restoreTo);

            if (a != null && !more) {
                child.onSetAlpha(255);
                finishAnimatingView(child, a);
            }

            return more;
        }
}複製代碼

ViewGroup.drawChild(Canvas canvas, View child, long drawingTime)用來完成子視圖的繪製,它主要完成了如下事情:

1 獲取子視圖的繪製區域以及標誌位
2 獲取子視圖的變換矩陣transformToApply,這個分兩種狀況:

  • 若是子視圖須要播放動畫,則調用getTransformation開始執行動畫,若是動畫還須要繼續執行,則more == true,而且返回子視圖的變化矩陣mChildTransformation
  • 若是FLAG_SUPPORT_STATIC_TRANSFORMATIONS == 1,調用getChildStaticTransformation()方法檢查子視圖是否被設置一個變換矩陣,若是設置了,即hasTransform == true,則mChildTransformation就是子視圖須要的變換矩陣

3 若是FLAG_CHILDREN_DRAWN_WITH_CACHE或者FLAG_CHILDREN_DRAWN_WITH_CACHE爲1,則表示它採用緩衝的方式進行繪製,它將本身的UI緩衝在一個Bitmap裏,能夠調用getDrawingCache()方法來得到這個Bitmap。
4 設置子視圖child的偏移、Alpha通道以及裁剪區域。

5 繪製子視圖的UI,這分爲兩種狀況:

  • 若是以非緩衝的方式來繪製,若是SKIP_DRAW == 1,則說明須要跳過當前子視圖而去繪製它本身的子視圖,不然先繪製它的視圖,再繪製它的子視圖。繪製自身經過draw()函數來
    完成,繪製它的子視圖則經過dispatchDraw()來完成的。
  • 若是是以緩衝的方式來繪製,這種狀況只須要將上一次的緩衝的Bitmap對象cache繪製到畫布canvas上

6 恢復畫布的堆棧狀態,以便在繪製完當前子視圖的UI後,能夠繼續繪製其餘子視圖的UI。

總結

至此,Android應用程序窗口的渲染流程就分析完了,咱們再來總結一下。

  1. 渲染Android應用視圖的渲染流程,測量流程用來肯定視圖的大小、佈局流程用來肯定視圖的位置、繪製流程最終將視圖繪製在應用窗口上。
  2. Android應用程序窗口UI首先是使用Skia圖形庫API來繪製在一塊畫布上,實際地是繪製在這塊畫布裏面的一個圖形緩衝區中,這個圖形緩衝區最終會被交給SurfaceFlinger服
    務,而SurfaceFlinger服務再使用OpenGL圖形庫API來將這個圖形緩衝區渲染到硬件幀緩衝區中。

五 View事件分發機制

在介紹View的事件分發機制以前,咱們要先了解兩個概念。

  • MotionEvent:Android中用來表示各類事件的對象,例如ACTION_DOWN、ACTION_MOVE等,咱們還能夠經過它獲取事件發生的座標,getX/getY獲取相對於當前View左上角的座標,getRawX/getRawY獲取相對於屏幕左上角的座標。
  • TouchSlop:系統所能識別的最小滑動距離,經過ViewConfiguration.get(context).getScaledTouchSlop()方法獲取。

如今咱們再來看看View裏的事件分發機制,歸納來講,能夠用下面代碼表示:

public boolean dispatchTouchEvent(MotionEvent event){
    boolean consume = false;
    //父View決定是否攔截事件
    if(onInterceptTouchEvent(event)){
        //父View調用onTouchEvent(event)消費事件
        consume = onTouchEvent(event);
    }else{
        //調用子View的dispatchTouchEvent(event)方法繼續分發事件
        consume = child.dispatchTouchEvent(event);
    }
    return consume;
}複製代碼

咱們再來具體看看各個場景中的事件分發。

5.1 Activity的事件分發

當點擊事件發生時,事件最早傳遞給Activity,Activity會首先將事件將誒所屬的Window進行處理,即調用superDispatchTouchEvent()方法。

經過觀察superDispatchTouchEvent()方法的調用鏈,咱們能夠發現事件的傳遞順序:

  • PhoneWinodw.superDispatchTouchEvent()
  • DecorView.dispatchTouchEvent(event)
  • ViewGroup.dispatchTouchEvent(event)

事件一層層傳遞到了ViewGroup裏,關於ViewGroup對事件的處理,咱們下面會說,若是superDispatchTouchEvent()方法返回false,即沒有
處理該事件,則會繼續調用Activity的onTouchEvent(ev)方法來處理該事件。可見Activity的onTouchEvent(ev)在事件處理的優先級是最低的。

public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback, WindowControllerCallback {

        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
}複製代碼

5.2 ViewGroup的事件分發

ViewGroup做爲View容器,它須要考慮本身的子View是否處理了該事件,具體說來:

  • 若是ViewGroup攔截了事件,即它的onInterceptTouchEvent()返回true,則該事件由ViewGroup處理,若是ViewGroup調用了setOnTouchListener()則該接口的onTouch()方法會被調用
    不然會調用onTouchEvent()方法。
  • 若是ViewGroup沒有攔截事件,則該事件會傳遞給它的子View,子View的dispatchTouchEvent()會被調用,View.dispatchTouchEvent()的處理流程前面咱們已經分析過。
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    public boolean dispatchTouchEvent(MotionEvent ev) {
            ...
            boolean handled = false;
            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;

                //每當有ACTION_DOWN事件進來的時候,都重置成初始狀態
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // Throw away all previous state when starting a new touch gesture.
                    // The framework may have dropped the up or cancel event for the previous gesture
                    // due to an app switch, ANR, or some other state change.
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }

                // Check for interception.
                final boolean intercepted;
                //MotionEvent.ACTION_DOWN事件老是會被ViewGroup攔截
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    //1. 判斷是否容許ViewGroup攔截除了ACTION_DOWN之外的其餘事件,經過requestDisallowInterceptTouchEvent()方法設置
                    //FLAG_DISALLOW_INTERCEPT標誌位來完成的
                    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                    if (!disallowIntercept) {
                        //2. 經過onInterceptTouchEvent(ev)方法判斷是否攔截該事件
                        intercepted = onInterceptTouchEvent(ev);
                        ev.setAction(action); // restore action in case it was changed
                    } else {
                        intercepted = false;
                    }
                } else {
                    //若是mFirstTouchTarget == null,即沒有接受
                    intercepted = true;
                }

                //ViewGroup以鏈表的形式存儲它的子View,mFirstTouchTarget表示鏈表中第一個
                //被點擊的子View
                if (intercepted || mFirstTouchTarget != null) {
                    ev.setTargetAccessibilityFocus(false);
                }

                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;

                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = false;
                if (!canceled && !intercepted) {

                    ...

                    if (actionMasked == MotionEvent.ACTION_DOWN
                            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        final int actionIndex = ev.getActionIndex(); // always 0 for down
                        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                                : TouchTarget.ALL_POINTER_IDS;

                        // Clean up earlier touch targets for this pointer id in case they
                        // have become out of sync.
                        removePointersFromTouchTargets(idBitsToAssign);

                        final int childrenCount = mChildrenCount;
                        if (newTouchTarget == null && childrenCount != 0) {
                            final float x = ev.getX(actionIndex);
                            final float y = ev.getY(actionIndex);
                            // Find a child that can receive the event.
                            // Scan children from front to back.
                            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();

                            //3. 當ViewGroup再也不攔截事件時,事件會向下分發給它的子View進行處理。
                            final View[] children = mChildren;
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                final int childIndex = getAndVerifyPreorderedIndex(
                                        childrenCount, i, customOrder);
                                final View child = getAndVerifyPreorderedView(
                                        preorderedList, children, childIndex);

                                 //4. 判斷子View是否可以接受點擊事件,判斷標準有兩點:① 子View是否能夠獲取焦點
                                 //② 點擊的座標是否落在了子View的區域內
                                if (childWithAccessibilityFocus != null) {
                                    if (childWithAccessibilityFocus != child) {
                                        continue;
                                    }
                                    childWithAccessibilityFocus = null;
                                    i = childrenCount - 1;
                                }

                                if (!canViewReceivePointerEvents(child)
                                        || !isTransformedTouchPointInView(x, y, child, null)) {
                                    ev.setTargetAccessibilityFocus(false);
                                    continue;
                                }

                                newTouchTarget = getTouchTarget(child);
                                if (newTouchTarget != null) {
                                    // Child is already receiving touch within its bounds.
                                    // Give it the new pointer in addition to the ones it is handling.
                                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                                    break;
                                }

                                resetCancelNextUpFlag(child);
                                //5. dispatchTransformedTouchEvent()方法會去調用子View的dispatchTouchEvent()方法來處理事件
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                    // Child wants to receive touch within its bounds.
                                    mLastTouchDownTime = ev.getDownTime();
                                    if (preorderedList != null) {
                                        // childIndex points into presorted list, find original index
                                        for (int j = 0; j < childrenCount; j++) {
                                            if (children[childIndex] == mChildren[j]) {
                                                mLastTouchDownIndex = j;
                                                break;
                                            }
                                        }
                                    } else {
                                        mLastTouchDownIndex = childIndex;
                                    }
                                    mLastTouchDownX = ev.getX();
                                    mLastTouchDownY = ev.getY();
                                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                    alreadyDispatchedToNewTouchTarget = true;
                                    break;
                                }

                                // The accessibility focus didn't handle the event, so clear
                                // the flag and do a normal dispatch to all children.
                                ev.setTargetAccessibilityFocus(false);
                            }
                            if (preorderedList != null) preorderedList.clear();
                        }

                        ...
                    }
                }
    、           
            ...
            return handled;
        }
}複製代碼

5.3 View的事件分發

View沒有子元素,沒法向下傳遞事件,它只能本身處理事件,因此View的事件傳遞比較簡單。

若是外界設置了OnTouchListener且OnTouchListener.onTouch(this, event)返回true,則表示該方法消費了該事件,則onTouchEvent(event)再也不被調用。
可見OnTouchListener的優先級高於onTouchEvent(event),這樣是爲了便於外界處理事件。

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

     public boolean dispatchTouchEvent(MotionEvent event) {
            ...
            if (onFilterTouchEventForSecurity(event)) {
                if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                    result = true;
                }
                //若是外界設置了OnTouchListener且OnTouchListener.onTouch(this, event)返回true,則
                //表示該方法消費了該事件,則onTouchEvent(event)再也不被調用
                ListenerInfo li = mListenerInfo;
                //若是外界調用了setOnTouchListener()方法且
                if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }

                if (!result && onTouchEvent(event)) {
                    result = true;
                }
            }
            ...
            return result;
        }    
}複製代碼

咱們再來看看View裏的onTouchEvent(event)方法的處理。

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

     public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();

            //1. View的disable屬性不會影響onTouchEvent()方法的返回值,哪怕View是disable的,只要
            //View的clickable或者longClickable爲true,onTouchEvent()方法仍是會返回true
            if ((viewFlags & ENABLED_MASK) == DISABLED) {
                if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                }
                // A disabled view that is clickable still consumes the touch
                // events, it just doesn't respond to them.
                return (((viewFlags & CLICKABLE) == CLICKABLE
                        || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
            }
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
            //2. 只要clickable或者longClickable爲true,onTouchEvent()方法就會消費這個事件
            if (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                    (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                            // take focus if we don't have it already and we should in
                            // touch mode.
                            boolean focusTaken = false;
                            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                                focusTaken = requestFocus();
                            }

                            if (prepressed) {
                                // The button is being released before we actually
                                // showed it as pressed. Make it show the pressed
                                // state now (before scheduling the click) to ensure
                                // the user sees it.
                                setPressed(true, x, y);
                           }

                            if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                                // This is a tap, so remove the longpress check
                                removeLongPressCallback();

                                // Only perform take click actions if we were in the pressed state
                                if (!focusTaken) {
                                    // Use a Runnable and post this rather than calling
                                    // performClick directly. This lets other visual state
                                    // of the view update before click actions start.
                                    if (mPerformClick == null) {
                                        mPerformClick = new PerformClick();
                                    }
                                    //3. 若是View設置了OnClickListener,則performClick()會調用它的onClick方法
                                    if (!post(mPerformClick)) {
                                        performClick();
                                    }
                                }
                            }

                            if (mUnsetPressedState == null) {
                                mUnsetPressedState = new UnsetPressedState();
                            }

                            if (prepressed) {
                                postDelayed(mUnsetPressedState,
                                        ViewConfiguration.getPressedStateDuration());
                            } else if (!post(mUnsetPressedState)) {
                                // If the post failed, unpress right now
                                mUnsetPressedState.run();
                            }

                            removeTapCallback();
                        }
                        mIgnoreNextUpEvent = false;
                        break;

                    case MotionEvent.ACTION_DOWN:
                        ...
                        break;

                    case MotionEvent.ACTION_CANCEL:
                        ...
                        break;

                    case MotionEvent.ACTION_MOVE:
                        ...
                        break;
                }

                return true;
            }

            return false;
        }    
}複製代碼

關於onTouchEvent(MotionEvent event),有兩點須要說明一下:

  1. View的disable屬性不會影響onTouchEvent()方法的返回值,哪怕View是disable的,只要
    View的clickable或者longClickable爲true,onTouchEvent()方法仍是會返回true。
  2. 只要clickable或者longClickable爲true,onTouchEvent()方法就會消費這個事件
  3. 若是View設置了OnClickListener,則performClick()會調用它的onClick方法。

上面咱們提到了viewFlags裏的CLICKABLE與LONG_CLICKABLE,也就是xml或者代碼裏能夠設置的clickable與longClickable,View的LONG_CLICKABLE默認爲
true,CLICKABLE默認爲false,值得一提的是setOnClickListener()方法和setOnLongClickListener()會將這兩個值設置爲true。

經過對源碼的分析,咱們已經掌握了各類場景下事件分發的規律,咱們再來總結一下View事件分發的相關結論。

  • 事件的傳遞是按照Activity -> Window -> View的順序進行的
  • 通常狀況下,一個事件序列只能由一個View攔截並消耗,一旦一個View攔截了該事件,則該事件序列的後續事件都會交由該View來處理。
  • ViewGroup默認不攔截任何事件
  • View沒有onInterceptTouchEvent()方法,一但有點擊事件傳遞給它,它的ouTouchEvent()方法就會被調用。
相關文章
相關標籤/搜索