自定义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

相关文章
相关标签/搜索