常用但忽略的anroid知識6-深入理解Android中View

這回我們是深入到View內部,去研究View,去了解View的工作,拋棄其他因素,以便爲以後能靈活的使用自定義空間打下一定的基礎。希望有志同道合的朋友一起來探討,深入Android內部,深入理解Android。

一、View是什麼?

       View是什麼了,每個人都有自己的理解。在Android的官方文檔中是這樣描述的:這個類表示了用戶界面的基本構建模塊。一個View佔用了屏幕上的一個矩形區域並且負責界面繪製和事件處理。View是用來構建用戶界面組件(Button,Textfields等等)的基類。ViewGroup子類是各種佈局的基類,它是個包含其他View(或其他ViewGroups)和定義這些View佈局參數的容器。

       其實說白了,View就是一個矩形區域,我們可以在這個區域上定義自己的控件。

       註明:有對系統回調不太瞭解的回頭看看回調,這樣有助於對文章的理解。

二、View創建的一個概述:

       在API中對View的回調流程有以個詳細的描述,下面給出了原文翻譯:(翻譯有點倉促,大家多多包涵,有啥錯的地方麻煩告知下我,我好改過來)

       1.Creation           :創建

         ----Constructors(構造器)    

       There is a form of the constructor that arecalled when the view is created from code and a form that is called when theview is inflated from a layout file. The second form should parse and apply anyattributes defined in the layout file.在構造器中有個一個表單當View從代碼中創建和從Layout File 文件中創建時。第二個表單應該解析和應用一些在Layout File中定義的屬性。

         ---- onFinishInflate()

        Called after a view and all of itschildren has been inflated from XML.當View和他的所有子View從XML中解析完成後調用。

       2. Layout            :佈局

         ----onMeasure(int, int)

        Called to determine the size requirementsfor this view and all of its children.   確定View和它所有的子View要求的尺寸時調用

         ---- onLayout(boolean, int, int,int, int)

        Calledwhen this view should assign a size and position to all of its children當這個View爲其所有的子View指派一個尺寸和位置時調用

         ---- onSizeChanged(int, int, int,int)

        Calledwhen the size of this view has changed.當這個View的尺寸改變後調用

       3. Drawing         :繪製

         ---- onDraw(Canvas)

        Calledwhen the view should render its content.當View給定其內容時調用

       4.Event processing    :事件流程

         ----onKeyDown(int, KeyEvent)

        Calledwhen a new key event occurs.當一個新的鍵按下時

         ---- onKeyUp(int, KeyEvent)  

        Calledwhen a key up event occurs.當一個鍵彈起時

         ----onTrackballEvent(MotionEvent)

        Calledwhen a trackball motion event occurs.當滾跡球事件發生時。

         ----onTouchEvent(MotionEvent)

        Calledwhen a touch screen motion event occurs.當一個觸摸屏事件發生時。

       5. Focus              :焦點

         ---- onFocusChanged(boolean, int,Rect)

        onFocusChanged(boolean,int, Rect)當View得到和失去焦點時調用

        ---- onWindowFocusChanged(boolean)

        Called when the windowcontaining the view gains or loses focus.當Window包含的View得到或失去焦點時調用。

       根據View裏面方法調用流程的概述,我們來重寫其中的幾個回調方法來直觀的瞭解下這個調用,具體代碼這裏就不貼了,代碼見測試包:DEMO_View調用流程.rar,調用的log顯示:

       這樣大家就對View的調用有了個大概的認識,下面將針對View的標誌系統、View的的佈局參數系統等做一個簡單的描述。

三、View的標誌(Flag)系統

       在一個系統中往往使用標誌來指示系統中的某些參數,這裏對View的標誌系統做一些簡單的介紹,這樣大家可以借鑑下,以後也可以用這種表示方法。

       一般而言標誌都是成對出現的也就是表示相反兩個屬性,對於這種屬性的表示方法我們使用一位的0和1就可以表示。如果有多個成對屬性,如果每對屬性都用一個int值來標誌是不方便的。這種情況通常是用一個int的各個位來分別表示每個標誌,在處理器中有一個標誌位就是採用這種方式設計的。

我們先來看看位運算。位運算符包括: 與(&)、非(~)、或(|)、異或(^)

      &:   當兩邊操作數的位同時爲1時,結果爲1,否則爲0。如1100&1010=1000   

       |:   當兩邊操作數的位有一邊爲1時,結果爲1,否則爲0。如1100|1010=1110   

       ~:   0變1,1變0   

       ^:   兩邊的位不同時,結果爲1,否則爲0.如1100^1010=0110

       在View系統使用mViewFlags來表徵這些屬性,其設置的主要方法如下

    void setFlag(int mask, int falg)   
        {   
            int old = mViewFlags;①   
            mViewFlags = (mViewFlags & ~mask) | (mask & falg);②   
        int changed = mViewFlags ^ old;// 獲取改變的位,方法是對改變的位置1③   
        ... ...   
        }  

其中mask指的是標誌位所在的位,falg表示的標誌位。下面舉個例子:

    public static final int VISIBLE = 0x00000000;   
        public static final int INVISIBLE = 0x00000004;   
        public static final int GONE = 0x00000008;   
    static final int VISIBILITY_MASK = 0x0000000C;  

 其中VISIBLE和INVISIBLE和GONE就是標誌位,VISIBILITY_MASK是標誌位所在的位,也就有VISIBLE+INVISIBLE+GON=VISIBILITY_MASK。看不懂的把上面四個轉換爲二進制就看出來了。

       爲什麼要使用VISIBILITY_MASK?會不會有些多餘呢?我們來看View中的計算公式:

mViewFlags = (mViewFlags & ~mask) | (mask & falg);

  其中mViewFlags & ~mask是用來將mViewFlags中表示該標誌的位置零。mask & falg是用來獲得標誌位。舉個例子:

       假設mViewFlags的二進制表示爲110000;flag爲INVISIBLE我們將上面的標誌位轉換爲二進制VISIBLE 0000、INVISIBLE 0100、GONE 1000、VISIBILITY_MASK 1100。

mViewFlags & ~mask=110000 & 0011 = 110000(上面所用的標誌位佔用的是最後四位,我們通過這個運算來將這個標誌位置零)。

mask & falg = 1100 & 0100 =0100(獲得標誌)。 
 110000 | 0100(通過或運算來計算出最後的標誌)。

       一般而言:在多個同種類型的標誌中,通常使用0來作爲默認的標誌。關於上面的標誌系統的其他具體使用我們就不再深入,有興趣的可以自行深入,有啥好的想法在羣裏分享下。

四、MeasureSpec

       在View系統中,指定寬和高,以及指定佈局的屬性,是由MeasureSpec來封裝的。下面是各個模式的標誌位表示。

    private static final int MODE_SHIFT = 30;   
            private static final int MODE_MASK  = 0x3 << MODE_SHIFT;   
            /**  
             * Measure specification mode: The parent has not imposed any constraint  
             * on the child. It can be whatever size it wants.  
             */  
            public static final int UNSPECIFIED = 0 << MODE_SHIFT;   
            /**  
             * Measure specification mode: The parent has determined an exact size  
             * for the child. The child is going to be given those bounds regardless  
             * of how big it wants to be.  
             */  
            public static final int EXACTLY     = 1 << MODE_SHIFT;   
            /**  
             * Measure specification mode: The child can be as large as it wants up  
             * to the specified size.  
             */  
        public static final int AT_MOST     = 2 << MODE_SHIFT;  

     在這個解析系統中是通過移位來存放更多的數據,現在每個數據標誌位都向左移動了30位。這樣表示一個View大小是很方便的,我們來看下面的方法:

    public static int makeMeasureSpec(int size, int mode) {   
                return size + mode;   
            }  

       通過這個方法就可以製作一個含有兩個參數的int值,這個參數包含一個mode標誌和一個寬或高的表示。

       我們通過如下方法來獲取到mode:

    public static int getMode(int measureSpec) {   
                return (measureSpec & MODE_MASK);   
            }  

 我們也可以用下面方法來獲取高或寬的數據表示:
    public static int getSize(int measureSpec) {   
                return (measureSpec & ~MODE_MASK);   
            }  

五、幾個重要方法簡介

       正如第二節寫的那個調用流程一樣,這幾個重要的方法是系統回調是調用的,同樣對於這幾個方法也是自定義組件的重要的方法。

       在這節裏我們主要是瞭解這些方法的用途,以期在自定義組件時可以對這些方法得心應手。

5.1 onFinishInflate()

       這個是當系統解析XML完成,並且將子View全部添加完成之後調用這個方法,我們通常重寫這個方法,在這個方法中查找並獲得子View引用,當然前提是這個View中有子View所以一般都是繼承ViewGroup時用這個方法比較多,比如抽屜效果中:

protected void onFinishInflate() {   
    mHandle = findViewById(mHandleId);   
    if (mHandle == null) {   
        throw new IllegalArgumentException("The handle attribute is must refer to an"  
                + " existing child.");   
    }   
    mHandle.setOnClickListener(new DrawerToggler());   
         
    mContent = findViewById(mContentId);   
    if (mContent == null) {   
        throw new IllegalArgumentException("The content attribute is must refer to an"  
                + " existing child.");   
    }   
    mContent.setVisibility(View.GONE);   
}  

通過重寫這個方法來獲取手柄的View和要顯示內容的View。

5.2 onMeasure(int, int)

       測量這個View的高和寬。通過調用這個方法來設置View的測量後的高和寬,其最終調用的方法是:

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {   
            mMeasuredWidth = measuredWidth;   
            mMeasuredHeight = measuredHeight;   
              
            mPrivateFlags |= MEASURED_DIMENSION_SET;   
        }  

   可見其最終是將高和寬保存在mMeasuredWidth、mMeasuredHeight這兩個參數中。

       其實調用onMeasure(int, int)的方法的不是系統,而是

           public final voidmeasure(int widthMeasureSpec, int heightMeasureSpec)

       這個纔是系統回調的方法,然後通過這個方法調用onMeasure(int, int)方法,個人感覺這種設計就是把系統方法和用戶可以重寫的方法分離開,這樣避免一些不必要的錯誤。

       在這個方法中主要是用來初始化各個子View的佈局參數,我們來看看抽屜中的實現:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {   
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);   
    int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);   
         
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);   
    int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);   
         
    if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {   
        throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions");   
    }   
         
    final View handle = mHandle;   
    measureChild(handle, widthMeasureSpec, heightMeasureSpec);   
         
    if (mVertical) {   
        int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset;   
        mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY),   
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));   
    } else {   
        int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset;   
        mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),   
                MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY));   
    }   
         
    setMeasuredDimension(widthSpecSize, heightSpecSize);   
}  

  剛纔我們已經獲取到mHandle和mContent的引用,因爲onFinishInflate()方法調用在onMeasure(int, int)方法之前,所以這個不會出現nullPoint。我們可以看到在這個方法中主要就是爲mHandle和mContent指定了佈局參數。這裏用到了MeasureSpec。

5.3 onLayout(boolean, int, int,int, int)

       onLayout是用來指定各個子View的位置,這個方法和上面方法類似,也不是真正的系統回調函數,真正的回調函數是Layout。這個方法的使用主要在ViewGroup中。這裏不再詳述。我們在ViewGroup講解時再去了解這個方法。

5.4 onSizeChanged(int, int, int,int)

       這個是當View的大小改變時調用,這個也不再詳述,基本上用的也比較少。

5.5 onDraw(android.graphics.Canvas)

       這個方法相信大家都不會陌生了,在我以前的博客裏也有這個方法的使用。當然那個比較入門,比較膚淺,呵呵。這裏我們深入進去,類似於onMeasure(int, int),其實這個方法是由draw(Canvas)方法調用的。在這個方法中有一個對這個方法的描述:

    /*  
            * 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)  
            */  

   我們可以看到:

               首先是繪製背景

               其次如果需要準備層之間的陰影

               然後繪製內容(這個內容就是調用我們的onDraw方法)

               再繪製children(dispatchDraw(canvas);)這個方法的調用主要實現在ViewGroup中,和繼承ViewGroup的組件中。

               如果需要繪製層之間的陰影。

               繪製裝飾,也就是scrollbars。

       dispatchDraw(canvas);這也是一個重要的方法,用於繪製子組件用的。下面是抽屜中的實現方法。也比較簡單,大家自行閱讀下也就瞭解了。

 protected void dispatchDraw(Canvas canvas) {   
      final long drawingTime = getDrawingTime();   
      final View handle = mHandle;   
      final boolean isVertical = mVertical;   
         
      drawChild(canvas, handle, drawingTime);   
         
      if (mTracking || mAnimating) {   
          final Bitmap cache = mContent.getDrawingCache();   
          if (cache != null) {   
              if (isVertical) {   
                  canvas.drawBitmap(cache, 0, handle.getBottom(), null);   
              } else {   
                  canvas.drawBitmap(cache, handle.getRight(), 0, null);                       
              }   
          } else {   
              canvas.save();   
              canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset,   
                      isVertical ? handle.getTop() - mTopOffset : 0);   
              drawChild(canvas, mContent, drawingTime);   
              canvas.restore();   
          }   
      } else if (mExpanded) {   
          drawChild(canvas, mContent, drawingTime);   
      }   
  }  

  好了,這個就是View裏面的內容,關於事件監聽我們這裏就不再詳細描述,自定義組件的話,在寫完深入ViewGroup中會有一個專門的專題,而ViewGroup中也會去深化View中一些東西。

轉自:http://www.incoding.org/admin/archives/179.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章