您的位置:首页 > 其它

自定义View

2016-12-27 00:00 323 查看

View是什么?

View是屏幕善的一块矩形区域,它负责来显示一个区域,并且响应这个区域内的事件。可以说,手机屏幕上任意可以看得见的地方都是View。

对于Activity来说,我们通过setContentView(view)添加的布局到Activity上,实际上都是添加到了Activity内部的DecorView上面,这个DecorView,其实就是一个FrameLayout,因此我们的布局实际上添加到了FrameLayout里面。

View是怎么工作的?

View的工作流程分为两部分,第一部分是显示在屏幕上的过程,第二部分是响应屏幕上的触摸事件的过程。

对于显示在屏幕上的过程:是View从无到有,经过测量大小(Measure)、确定在屏幕中的位置(Layout)、以及最终绘制在屏幕上(Draw)这一系列的过程。

对于响应屏幕上的触摸事件的过程:则是Touch事件的分发过程。

Measure()、Layout()方法是final修饰的,无法重写,Draw()虽然不是final的,但是耶不建议重写该方法。

如何自定义View?

View为我们提供了onMeasure()、onLayout()、onDraw()这样的方法,其实所谓的自定义View,也就是对这三个方法的重写过程。

measure()

View的onMeasure()方法如下:

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传递过来的模式和大小最终确定自身的大小。

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: