哈哈,兄弟我终于自己写了一个view,不是网上那种简单的哦,还是有一定技术含量的,

我是通过学习ApiDemo(android自带的sample)里面LabelView实现的,

先谈谈学习过程,觉得一开始不应当盲目的动手做,应对想把原理搞明白,哪怕一个很小的View,也应当将各个细节弄清楚,

等这些搞定了,接下来的工作就是水道渠成了!


自定义一个View那必须继承View,

首先说说我的View是啥,恩,很简单,就是一个椭圆,其中可以设置4个参数,分别是top、left、right、bottom,应该很清楚吧,因为canvas.drawOval时用到了4个值,


首先得定义4个属性值,是我的View专有的,

建立attr.xml放到values下面

<?xml version="1.0" encoding="UTF-8"?>
<resources>
     <declare-styleable name="RocketView">
        <attr name="ovalLeft" format="dimension" />
        <attr name="ovalTop" format="dimension" />
        <attr name="ovalRight" format="dimension" />
        <attr name="ovalBottom" format="dimension" />
    </declare-styleable>
</resources>


然后写个RocketView

package com.myos;

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

public class RocketView extends View{
	private Paint mOvalPaint;
	private int mStrokeWidth = 2;
	private int padding = 3;
	
	//椭圆参数
	private int mOval_l;
	private int mOval_t;
	private int mOval_r;
	private int mOval_b;
	
	
	
	//构造
	public RocketView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initRocketView();
		
		TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.RocketView);

        mOval_l = a.getDimensionPixelOffset(R.styleable.RocketView_ovalLeft, padding);  
        mOval_t = a.getDimensionPixelOffset(R.styleable.RocketView_ovalTop, padding);  
        mOval_r = a.getDimensionPixelOffset(R.styleable.RocketView_ovalRight, 100);  
        mOval_b = a.getDimensionPixelOffset(R.styleable.RocketView_ovalBottom, 100);  

        a.recycle();
	}

    private void initRocketView() {
    	mOvalPaint = new Paint();
    	mOvalPaint.setAntiAlias(true);
    	mOvalPaint.setColor(Color.BLUE);
    	mOvalPaint.setStyle(Paint.Style.STROKE);
    	mOvalPaint.setStrokeWidth(mStrokeWidth);
    	setPadding(padding,padding,padding,padding);
    }
    
    public void setOvalRect(int l, int t, int r, int b){
    	mOval_l = l + padding;
    	mOval_t = t + padding;
    	mOval_r = r;
    	mOval_b = b;
    	requestLayout();
        invalidate();
    }
	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		super.onDraw(canvas);
		canvas.drawColor(Color.WHITE);
		// 绘制椭圆
		RectF re11 = new RectF(mOval_l, mOval_t, mOval_r, mOval_b);
		canvas.drawOval(re11, mOvalPaint);	
		
//		// 绘制圆形
//		canvas.drawCircle(mCircle_x, mCircle_y, mCircle_r, mPaint);
	}
	
	 /**
	  * @see android.view.View#measure(int, int)
	  */
	 @Override
	 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	     setMeasuredDimension(measureWidth(widthMeasureSpec),
	             measureHeight(heightMeasureSpec));
	 }

	 /**
	  * Determines the width of this view
	  * @param measureSpec A measureSpec packed into an int
	  * @return The width of the view, honoring constraints from measureSpec
	  */
	 private int measureWidth(int measureSpec) {
	     int result = 0;
	     int specMode = MeasureSpec.getMode(measureSpec);
	     int specSize = MeasureSpec.getSize(measureSpec);

	     if (specMode == MeasureSpec.EXACTLY) {
	         // We were told how big to be
	         result = specSize;
	     } else {
	         // Measure the text
	         result = mOval_r + getPaddingLeft()
	                 + getPaddingRight();
	         if (specMode == MeasureSpec.AT_MOST) {
	             // Respect AT_MOST value if that was what is called for by measureSpec
	             result = Math.min(result, specSize);
	         }
	     }
		 
	     return result;
	 }

	 /**
	  * Determines the height of this view
	  * @param measureSpec A measureSpec packed into an int
	  * @return The height of the view, honoring constraints from measureSpec
	  */
	 private int measureHeight(int measureSpec) {
	     int result = 0;
	     int specMode = MeasureSpec.getMode(measureSpec);
	     int specSize = MeasureSpec.getSize(measureSpec);

	     if (specMode == MeasureSpec.EXACTLY) {
	         // We were told how big to be
	         result = specSize;
	     } else {
	         // Measure the text (beware: ascent is a negative number)
	         result = mOval_b + getPaddingTop()
	                 + getPaddingBottom();
	         if (specMode == MeasureSpec.AT_MOST) {
	             // Respect AT_MOST value if that was what is called for by measureSpec
	             result = Math.min(result, specSize);
	         }
	     }
	     
	     return result;
	 }
}

在构造中getDimensionPixelOffset检索出一个属性值,没有的话就使用第2个参数做默认值,

可以在布局xml中初始化这个属性值,下面是我main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.myos"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
	
	<TextView  
	    android:layout_width="wrap_content" 
	    android:layout_height="wrap_content" 
	    android:text="@string/hello"
	    />
	
	<com.myos.RocketView
	    app:ovalLeft="0dp"
	    android:id="@+id/rv"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content" />
	

</LinearLayout>

我这里没啥用处,仅仅试一下,待扩展属性时再说,说明一下xmlns:app中app可以随便命名的,这个android的规矩还真多

另外构造函数中onMeasure很重要的,onDraw很明显就是画椭圆了,android在画的时候呢,会去先测量,需要我们来提供View的宽和高,


大家都知道wrap_content、fill_parent,测量时会有3种模式,分别是UNSPECIFIED、EXACTLY、AT_MOST,

当使用

wrap_content时就是AT_MOST模式,fill_parent就是EXACTLY模式,可以看看官方文档:

http://developer.android.com/guide/topics/ui/how-android-draws.html

相信研读一下代码就明白了

RocketView定义好了,接下来就是使用了

package com.myos;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
	RocketView mRocketView;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mRocketView = (RocketView)findViewById(R.id.rv);
        mRocketView.setOvalRect(0, 0, 500, 100);
    }
}

可以通过setOvalRect函数来动态调整椭圆的大小!

下面是运行效果,ko啦!




onMeasure方法在控件的父元素正要放置它的子控件时调用.它会问一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec.

  它们指明控件可获得的空间以及关于这个空间描述的元数据.
  比返回一个结果要好的方法是你传递View的高度和宽度到setMeasuredDimension方法里.
(请发邮件到 fafanqiang@126.com 获得翻强软件,能上youtube哟。)
  接下来的代码片段给出了如何重写onMeasure.注意,调用的本地空方法是来计算高度和宽度的.它们会译解widthHeightSpec和heightMeasureSpec值,并计算出合适的高度和宽度值.

java代码:

1. @Override
2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3. 
4. int measuredHeight = measureHeight(heightMeasureSpec);
5. int measuredWidth = measureWidth(widthMeasureSpec);
6. setMeasuredDimension(measuredHeight, measuredWidth);
7. }
8. 
9. private int measureHeight(int measureSpec) {
10. 
11. 
12. // Return measured widget height.
13. }
14. 
15. private int measureWidth(int measureSpec) {
16. 
17. // Return measured widget width.
18. }

复制代码

边界参数——widthMeasureSpec和heightMeasureSpec ,效率的原因以整数的方式传入。

MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求。一个MeasureSpec由大小和模式组成。

它有三种模式:

UNSPECIFIED(未指定),

EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;

AT_MOST(至多),子元素至多达到指定大小的值。

它常用的三个函数:

   1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一)

  2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小)

  3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式)

这个类的使用呢,通常在view组件的onMeasure方法里面调用但也有少数例外

在它们使用之前,首先要做的是使用MeasureSpec类的静态方法getMode和getSize来译解,如下面的片段所示:

java代码:

1. int specMode = MeasureSpec.getMode(measureSpec);
2. int specSize = MeasureSpec.getSize(measureSpec);

复制代码

依据specMode的值,如果是AT_MOST,specSize 代表的是最大可获得的空间;如果是EXACTLY,specSize 代表的是精确的尺寸;如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。
  当以EXACT方式标记测量尺寸,父元素会坚持在一个指定的精确尺寸区域放置View。在父元素问子元素要多大空间时,AT_MOST指示者会说给我最大的范围。在很多情况下,你得到的值都是相同的。
  在两种情况下,你必须绝对的处理这些限制。在一些情况下,它可能会返回超出这些限制的尺寸,在这种情况下,你可以让父元素选择如何对待超出的View,使用裁剪还是滚动等技术。

  接下来的框架代码给出了处理View测量的典型实现:

java代码:

1. @Override
2. 
3. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
4. 
5. int measuredHeight = measureHeight(heightMeasureSpec);
6. 
7. int measuredWidth = measureWidth(widthMeasureSpec);
8. 
9. setMeasuredDimension(measuredHeight, measuredWidth);
10. 
11. }
12. 
13. private int measureHeight(int measureSpec) {
14. 
15. int specMode = MeasureSpec.getMode(measureSpec);
16. int specSize = MeasureSpec.getSize(measureSpec);
17. 
18. // Default size if no limits are specified.
19. 
20. int result = 500;
21. if (specMode == MeasureSpec.AT_MOST){
22. 
23. // Calculate the ideal size of your
24. // control within this maximum size.
25. // If your control fills the available
26. // space return the outer bound.
27. 
28. result = specSize;
29. } 
30. else if (specMode == MeasureSpec.EXACTLY){
31. 
32. // If your control can fit within these bounds return that value.
33. result = specSize;
34. }
35. 
36. return result;
37. }
38. 
39. private int measureWidth(int measureSpec) {
40. int specMode = MeasureSpec.getMode(measureSpec);
41. int specSize = MeasureSpec.getSize(measureSpec);
42. 
43. // Default size if no limits are specified.
44. int result = 500;
45. if (specMode == MeasureSpec.AT_MOST){
46. // Calculate the ideal size of your control
47. // within this maximum size.
48. // If your control fills the available space
49. // return the outer bound.
50. result = specSize;
51. } 
52. 
53. else if (specMode == MeasureSpec.EXACTLY){
54. // If your control can fit within these bounds return that value.
55. 
56. result = specSize;
57. }
58. 
59. return result;
60. }