Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發藝術探索——第四章View的工作原理

Android開發藝術探索——第四章View的工作原理

編輯:關於Android編程

Android開發藝術探索——第四章View的工作原理

4.1

(一)初識ViewToot和DecorView

基本概念
  ViewRoot對應於ViewRootImpl類,是連接WindowManager和DecorView的紐帶,View的三大流程均是通過ViewRoot來完成的。在ActivityThread中,當Activity對象被創建完成後,會將DecorView添加到View中。同時,會創建ViewRootImpl對象,並將ViewTootImpl對象和DecorView建立關聯。

源碼如下:
root = new ViewRootImpl(view,getContext(),dispaly);
root.setView(view,panelParentView);

  View的繪制流程是從ViewRoot的performTraversals方法開始的,它經過measure、layout和draw三個過程才能最終將一個View繪制出來,其中measure用來測量View的寬和高,layout用來確定View在父容器中的位置,而draw則負責將View繪制在屏幕上。

  performMeasure ——>  measur  ——> onMeasure      
                        ↓
  preformLayout  ——>  layout  ——> onLayout
                        ↓
  performDraw    ——>   draw   ——> onDraw

4.2

(二)理解MeasureSpec

  MeasureSpec翻譯為”測量規格”。MeasureSpec在很大程度上決定一個View的尺寸規格,之所以說是很大程度上是因為這個過程還受父容器的影響,父容器影響View的MeasureSpec。在測量過程中,系統會將View的LayoutParams根據這個measureSpec來測量出View的寬/高,不一定等於View的最終寬/高。

4.2.1MeasureSpec

  MeasureSpec代表一個32位int值,高2位代表SpecMode,低30位代表SpecSize。SpecMode是指測量模式,SpecSize是指某種測量模式下的規格大小。

  部分源碼:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
public stataic int makeMeasureSpec(int size,int mode){
    if( sUseBrokenMakeMeasureSpec){
          retrurn size + mode ;
    }else{
         return (size & ~Mode_MASK)|(mode & MODE_MASK)
    }
}
public static int getMode(int measureSpec){
    return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec){
    rerurn (measureSpec & ~MODE_MASK);
}

  MeaureSpec通過將SpecMode個SpecSize打包打包一個int值避免過多的對象內存分配,為了方便操作,其提供了打包和解包方法。SpecMode和SpecSize可以打包一個MeasureSpec,而一個MeasureSpec可以通過解包的形式來得出原始的SpecMode和SpecSize,需要注意的是這裡提到的MeasureSpec是指MeaureSpec所代表的int值,而非MeasureSpec本身。

SpecMode

UNSPECIFIED
  父容器不對View有任何限制,要多大給多大,這種情況一般用於系統內部,表示一種測量狀態。

EXACTLY
  父容器已經檢測出View所要的精確的大小,這個時候View的最終大小就是SpecSize所指定的值。對應於LayoutParams中的match_parent和具體的數值這兩種模式。

AT_MOST
  父容器指定了一個可用大小的SpecSize,View的大小不能大於這個值,具體是什麼值要看不同View的具體實現。它對應LayoutParams中的warp_content。

4.2.2MeasureSpec和LayoutParams的對應關系

  系統內部是通過MeasureSpec來進行View的測量,但正常情況下使用View指定MeasureSpec,可以給View設置LayoutParams。在View測量的時候,系統會將LayoutParams在父容器的約束下轉換成對應的MeasureSpec,然後在根據這個MeasureSpec來確定View測量後的寬/高。 注意: MeasureSpec不是唯一由LayoutParams決定的,LayoutParams需要父容器一起才能決定View的MeasureSpec,從而進一步決定View的寬/高。
  另外,對於頂級的View也就是DecorView普通View來說,MeasureSpec的轉換過程略有不同。對於DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams來共同確定;對於普通View,其MeasureSpec一旦確定後,onMeasure中就可以確定View的測量寬/高。
  DecorView在ViewRootImpl中的measureHierarchy方法有如下一段代碼,展示了DecorView的MeasureSpec的創建過程,其中desiredWindowWidth和desiredWindowHeight是屏幕尺寸:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);

  getRootMeasureSpec方法的實現:

private static int getRootMeasureSpec(int windowSize,int rootDimension){
    int measureSpec;
    switch(rootDimension){
        case ViewGroup.LayoutParams.MATCH_PARENT:
             measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);
        break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
             measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);
        break;
        default:
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec ;
}

DecorView的MeasureSpec的產生過程就很明確了,具體遵守如下規則,根據它的LayoutParams中的寬/高的參數劃分。

LayoutParams.MATCH_PARENT:精確模式,大小就是窗口的大小 LayoutParams.WRAP_CONTENT:最大模式,大小為LayoutParams中的指定的大小。 固定大小:精確模式,大小LayoutParams中指定的大小。

4.3

(三)View的工作原理

  View的工作流程主要是指measure、layout、draw這三大流程,即測量、布局和繪制,其中measure確定View的測量寬/高,layout確定View的最高寬/高和四個頂點的位置,而draw則將View繪制到屏幕上。


4.3.1Measure過程

  measure過程要分情況來看,如果只是一個原始的View,那麼通過measure方法就完成了其測量過程,如果是一個ViewGroup,除了完成自己的測量過程外,還會遍歷去請用所有子元素的measure方法,各個子元素
再遞歸去執行這個過程。

1. View的measure過程

   View的measure過程由measure方法來完成,measure方法是一個final類型的方法,這意味著子類不能重寫此方法,在View的measure方法中會調用View的onMeasure方法。

  View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec , int heightMeasureSpec){
    setMearsureDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}

View的最終的大小是在Layout階段確定。

4.3View的工作原理一遍沒理解多看幾遍。

4.3.3draw過程

  Draw過程就是將View繪制到屏幕上。

* (1)繪制背景backgroud。draw(canvas)*

* (2)繪制自己(onDraw)*

* (3)繪制children(dispatchDraw)*

* (4)繪制裝飾(onDrawScrollBars)*
  

4.4

(三)自定義View

4.4.1 自定義View的分類

  
根據個人理解分為了四類:

* 1. 繼承View重寫onDraw*
  這種方法主要用於實現一些不規則的效果,即這種效果不方便通過布局的組合方式來達到,往往需要靜態或者動態地顯示一些不規則的圖形。很顯然這需要通過繪制的方式來實現,及重寫onDraw方法。采用這種方法需要自己支持wrap_content,並且padding也需要自己處理。

* 2. 繼承ViewGroup派生特殊的Layout*
  這種方法主要用於實現自定義的布局,即除了LinearLayout、RelativeLayout、FrameLayout這幾種系統的布局之外,重新定義一中新布局,當某種效果看起來很像幾種View組合在一起的時候,可以采用這種方法來實現。采用這種方式稍微復雜一些。需要合適地處理ViewGroup的測量、布局這兩個過程,並同時處理子元素的測量和布局過程。

* 3. 繼承特定的View(比如TextView)*
  一般是用於擴展某種已有的View功能,比如TextView,這種方法比較容易實現。這種方法不需要自己支持wrap_content和padding。

* 4. 繼承特定的ViewGroup(比如LinearLayout)*
  當某種效果看起來很像幾種View組合在一起的時候,可以采用這種方法來實現。采用這種方法不需要自己處理ViewGroup的測量和布局這兩個過程。需要注意這種方法和方法2的區別,一般來說方法2能實現的效果方法4都能實現,兩者主要的差別在於方法2更接近底層。

4.4.2自定義View須知

* 1.讓View支持wrap_content*
  因為直接繼承View或者ViewGroup的控件,如果不在onMeasure中對wrap_content做特殊處理,當外界在布局中使用wrap_content時就無法達到預期的效果。

* 2.如果有必要,讓你的View支持padding*
  因為直接繼承View的控件,如果不在draw方法中處理padding,那麼padding屬性無法起作用的。另外,直接繼承自ViewGroup的控件需要在onMeasure和onLayout中考慮padding和子元素的margin對其造成的影響不然將導致padding和子元素的margin失效。

* 3.盡量不要在View在中使用Handler,沒必要*
  View內部本身就提供了post系列的方法,完全可以替代Handler的作用,當然除非你明確地要使用Handler來發送消息。

* 4.View中如果有線程或者動畫,需要及時停止,參考View#onDetachedFromWinow*
  如果線程或者動畫需要停止時,onDetachedFromWindow是一個很好的時機。當包含此View的Activity退出或者當前View被remove時,View的onDetachedFromWindow方法會被調用,此方法對應的是onAttachedToWindow,當包含此View的Activity啟動時,View的onAttachedToWindow方法會被調用。同時,當View變得不可見時,我們也需要停止線程和動畫,如果不及時處理這種問題,可能會造成內存洩露。

* 5.View帶有滑動嵌套情形時,需要處理好滑動沖突*

4.4.3自定義View實例

1.繼承View重寫onDraw方法
package view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import com.superdaxue.customviewdemo.R;

/**
 * Created by ZX_CC on 2016/4/26.
 */

public class CircleView extends View {
    //WRAP_CONTENT 下的默認高度
    private int mWidth = 100;
    private int mHeight = 100;
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    public CircleView(Context context) {
        super(context);
        init();
    }
    public CircleView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }
    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
        a.recycle();
        init();
    }
    private void init() {
        mPaint.setColor(mColor);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        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 && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth,mHeight);
        }else if (widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth,heightSpecSize);
        }else if (heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,mHeight);
        }
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft  = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop   = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(paddingLeft+width/2,paddingTop +height/2,radius,mPaint);
    }
}
2.繼承ViewGroup派生特殊的Layout
package view;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Created by ZX_CC on 2016/4/26.
 */

public class HorizontalScrollViewEx extends ViewGroup {
    private  static final String TAG = "HorizontalScrollViewEx";
    private int mChildrenSize ;
    private int mChildrenWidth ;
    private int mChildrenIndex ;
    private Scroller mScroller ;
    private VelocityTracker mVelocityTracker ;

    //分別記錄上次滑動坐標
    private int mLastX = 0;
    private int mLastY = 0;
    //分別記錄上次滑動的坐標(在onInterceptTouchEvent)
    private int mLastXIntercept = 0;
    private int mLastYIntercept = 0;

    public HorizontalScrollViewEx(Context context) {
        super(context);
        init();
    }

    public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }



    public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        if (mScroller == null){
            mScroller = new Scroller(getContext());
            mVelocityTracker = VelocityTracker.obtain();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                if ( !mScroller.isFinished() ){
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                if (Math.abs(deltaX) > Math.abs(deltaY)){
                    intercepted = true;
                }else{
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
        }
        Log.e(TAG,"--intercepted = " + intercepted);
        mLastX = x;
        mLastY = y;

        mLastXIntercept = x ;
        mLastYIntercept = y ;
        return intercepted;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if ( !mScroller.isFinished()){
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                scrollBy( - deltaX ,0);
                break;
            case MotionEvent.ACTION_UP :
                int scrollX = getScrollX();
                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocity = mVelocityTracker.getXVelocity();
                if (Math.abs(xVelocity) >= 50){
                    mChildrenIndex = xVelocity > 0 ? mChildrenIndex -1 : mChildrenIndex + 1;
                }else {
                    mChildrenIndex  = (scrollX + mChildrenWidth/2) /mChildrenWidth;
                }
                mChildrenIndex = Math.max(0,Math.min(mChildrenIndex,mChildrenIndex-1));
                int dx = mChildrenIndex * mChildrenWidth - scrollX ;
                smoothScrollBy(dx,0);
                mVelocityTracker.clear();
                break;
        }
        mLastX = x;
        mLastY = y;
        return true;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measureWidth = 0;
        int measureHeight = 0;
        final int childCount = getChildCount();

        measureChildren(widthMeasureSpec,heightMeasureSpec);

        int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        if (childCount == 0){
            setMeasuredDimension(0,0);
        }else if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measureWidth = childView.getMeasuredWidth() * childCount;
            measureHeight = childView.getMeasuredHeight();
            setMeasuredDimension(measureWidth,measureHeight);
        }else if (heightSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measureHeight = childView.getMeasuredHeight();
            setMeasuredDimension(widthSpaceSize,measureHeight);
        }else if (widthSpecMode == MeasureSpec.AT_MOST){
            final View childView = getChildAt(0);
            measureWidth = childView.getMeasuredWidth() * childCount;
            setMeasuredDimension(measureWidth,heightSpaceSize);
        }
    }

//    @Override
//    protected void onDraw(Canvas canvas) {
//        super.onDraw(canvas);
//        final int childCount = getChildCount();
//
//    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
              int childLeft = 0;
              final int childCount = getChildCount();
              mChildrenSize = childCount;

              for (int i = 0; i < childCount; i ++){
                  final View childView = getChildAt(i);
                  if (childView.getVisibility() != View.GONE){
                      final int childWidth = childView.getMeasuredWidth();
                      mChildrenWidth = childWidth;
                      //MarginLayoutParams lm = (MarginLayoutParams) childView.getLayoutParams();
                     // int m = min(lm.leftMargin,lm.rightMargin,lm.topMargin,lm.bottomMargin);
                      childView.layout(childLeft,0,childLeft + childWidth  ,childView.getMeasuredHeight());
                      childLeft += childWidth;
                  }
              }
    }

    private int min(int...params){
        int min  = params[0];
        for (int p : params){
            if (p < min){
                min = p;
            }
        }
        return min;
    }

    private void smoothScrollBy(int dx, int dy){
        mScroller.startScroll(getScrollX(),0,dx,0,500);
        invalidate();
    }
    @Override
    public void computeScroll(){
        if (mScroller.computeScrollOffset()){
              scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
              postInvalidate();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mVelocityTracker.recycle();
        super.onDetachedFromWindow();
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved