自定義View

View是什麼?

View是屏幕善的一塊矩形區域,它負責來顯示一個區域,而且響應這個區域內的事件。能夠說,手機屏幕上任意能夠看得見的地方都是View。html

對於Activity來講,咱們經過setContentView(view)添加的佈局到Activity上,實際上都是添加到了Activity內部的DecorView上面,這個DecorView,其實就是一個FrameLayout,所以咱們的佈局實際上添加到了FrameLayout裏面。java

View是怎麼工做的?

View的工做流程分爲兩部分,第一部分是顯示在屏幕上的過程,第二部分是響應屏幕上的觸摸事件的過程。android

對於顯示在屏幕上的過程:是View從無到有,通過測量大小(Measure)、肯定在屏幕中的位置(Layout)、以及最終繪製在屏幕上(Draw)這一系列的過程。canvas

對於響應屏幕上的觸摸事件的過程:則是Touch事件的分發過程。c#

Measure()、Layout()方法是final修飾的,沒法重寫,Draw()雖然不是final的,可是耶不建議重寫該方法。app

如何自定義View?

View爲咱們提供了onMeasure()、onLayout()、onDraw()這樣的方法,其實所謂的自定義View,也就是對這三個方法的重寫過程。less

measure()

View的onMeasure()方法以下:ide

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

onMeasure經過父View傳遞過來額大小和模式,以及自身的背景圖片的大小得出自身最終的大小,經過setMeasuredDimension()方法設置給mMeasuredWidth和mMeasuredHeight。佈局

普通的View的onMeasure邏輯大同小異,基本都是測量自身內容和背景,而後根據父View傳遞過來的MeasureSpec進行最終的大小斷定,例如TextView會根據文字的長度,文字的大小,文字行高,文字的寬度,顯示方法,背景圖片以及父View傳遞過來的模式和大小最終肯定自身的大小。post

MeasureSpec

這是Android中本身定義的測量規格。由於測量過程當中,系統會把咱們代碼或者佈局中設置的View的LayoutParams轉換成MeasureSpec,而後經過MeasureSpec來測量肯定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的大小作任何限制,能夠是子佈局須要的任意大小 
        * 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;  
  
       /** 已經爲子View給出了確切的值 至關於給View的LayoutParams指定了具體數值,或者match_parent. 
        * 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;  
  
       /**子View能夠跟本身的測量大小同樣大。對應於View的LayoutParams的wrap_content 
        * 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;  
  
       /**根據 size和mode 去生成一個MeasureSpec值 
        * Creates a measure specification based on the supplied size and mode. 
        * 
        * The mode must always be one of the following: 
        *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> 
        *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> 
        *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> 
        * @return the measure specification based on size and mode 
        */  
       public static int makeMeasureSpec(int size, int mode) {  
           if (sUseBrokenMakeMeasureSpec) {  
               return size + mode;  
           } else {  
               return (size & ~MODE_MASK) | (mode & MODE_MASK);  
           }  
       }  
  
       /**獲取測量模式 
        * Extracts the mode from the supplied measure specification. 
        * 
        * @param measureSpec the measure specification to extract the mode from 
        * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, 
        *         {@link android.view.View.MeasureSpec#AT_MOST} or 
        *         {@link android.view.View.MeasureSpec#EXACTLY} 
        */  
       public static int getMode(int measureSpec) {  
           return (measureSpec & MODE_MASK);  
       }  
  
       /**獲取測量值大小 
        * Extracts the size from the supplied measure specification. 
        * 
        * @param measureSpec the measure specification to extract the size from 
        * @return the size in pixels defined in the supplied measure specification 
        */  
       public static int getSize(int measureSpec) {  
           return (measureSpec & ~MODE_MASK);  
       }  
  
       static int adjust(int measureSpec, int delta) {  
           return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));  
       }  
  
   }

MeasureSpec是由父佈局和View自身的LayoutParams來決定的

PS:通過實際使用,父View的佈局爲match_parent或指定具體數字時是EXACTLY;

父View的佈局爲wrap_content時是AT_MOST。

 

通過measure後,咱們就能夠經過getMeasuredWidth/Height獲取View的寬高。

layout()

普通的View對於layout方法直接空實現便可。

draw()

draw()的過程就是繪製View到屏幕上的規程,draw()的執行遵照以下步驟:

/* 
 * 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(繪製children) 
 *      5. If necessary, draw the fading edges and restore layers(畫出褪色的邊緣和恢復層) 
 *      6. Draw decorations (scrollbars for instance)(繪製裝飾 好比scollbar) 
 */

通常2和5是能夠跳過的。

在TextView中在該方法中繪製文字、光標和CompoundDrawable、ImageView中相對簡單,只是繪製了圖片。View的繪製主要經過dispatchDraw()。

View的幾個比較重要的方法

requestLayout - View從新調用一次layout過程。

invalidate - View從新調用一次draw過程。

postInvalidate - 在非UI線程發起invalidate動做。

forceLayout - 標識View在下一次重繪,須要從新調用layout過程。

Demo

一個自定義方塊,循環顯示不一樣顏色

/**
 * Created by xupeng on 16/12/25.
 */

public class MyCustomView extends View {

    private Paint mPaint = null;

    private int[] colors = {Color.BLUE, Color.GREEN, Color.YELLOW};

    public MyCustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, 0, defStyleAttr);
        int color = typedArray.getColor(R.styleable.MyCustomView_custom_color, Color.BLUE);
        mPaint = new Paint();
        mPaint.setColor(color);
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (true){
                    mPaint.setColor(colors[i++ % 3]);
                    postInvalidate();
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getDefaultSize(getMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getMinimumHeight(), heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onDraw(final Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, 200, 300, mPaint);
    }

}

 

參考連接:

View原理簡介

http://blog.csdn.net/u011733020/article/details/50849475

幫你搞定Android自定義View

http://www.jianshu.com/p/84cee705b0d3

Android手把手教您自定義ViewGroup

http://blog.csdn.net/lmj623565791/article/details/38339817/

爲何自定義ViewGroup ondraw方法不會被調用

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1014/1765.html

關於getMeasuredHeight和getHeight區別(getMeasureHeight是xml或代碼中制定的測量高度,setMeasuredHeight; getHeight是實際顯示出來的高度,經過view.layout()方即可以改變其值)

http://blog.csdn.net/qq_29951983/article/details/50571840

相關文章
相關標籤/搜索