创建新的View方法与希望达到的目标有关:

1、如果现有的控件已经可以满足希望实现的基本功能,只需要对现在的控件外观或动作进行修改或扩展即可。通过重写事件处理程序和onDraw方法。但是仍然需要回调超类的方法,可以对控件进行控制,而不必重新实现它的功能。如可以定制一个TextView来显示10进制小数的集合。

2、可以通过组合多个控件来创建不可分割的、可重用的Widget,从而使它可以综合使用多个相互关联的控件的功能。如可以通过组合TextView和一个Buttun来创建一个下拉式文本框,当单击的时候,就显示一个浮动ListView 。

3、当需要一个全新的界面,而通过修改或者组合现有的控件不能实现这个目标的时候,就可以创建一个全新的控件。


一、修改现有的View

工具箱包含了很多创建UI必需的控件,但是这些控件通常都是很通用的。通过定制这些基本的VIew,可以在避免重新实现已有的动作情况下,同时又对应用程序所需要的每一个控件的用户界面和功能作出改善。

要在一个已有控件的基础上创建一个新的Widget,就需要创建一个扩展了原控件的新类;要修改View的外观或动作,只要重写和扩展与希望修改的动作相关的事件处理程序。

二、创建复合控件

复合控件即是指不可分割的,可重用的widget,这样的widget包含了多个布局和捆绑在一起的子控件。当创建复合控件的时候,必须对它的布局,外观和它所包含的View之间的交互进行定义,复合控件是通过扩展一个ViewGroup(通常是一个布局管理器)来创建的,因此创建一个新的复合控件,首先需要选择一个最适合放置子控件的布局类,并按照下面的框架代码来对其进行扩展:

如public class MyCompounView extends LinearLayou{}

看代码,下面例子是用xml文件创建复合组件:

public class CompoundView extends LinearLayout {

	EditText editText;
	Button clearButton;
	
	public CompoundView(Context context) {
		super(context);
		System.out.println("CompoundView:1"+context);
	}

	public CompoundView(Context context, AttributeSet attrs) {//属性的值是通过AttributeSet传递给它的,当用代码创建的时候,就不会了
		super(context, attrs);
		System.out.println("CompoundView:2"+context);
		String infService = Context.LAYOUT_INFLATER_SERVICE;
		LayoutInflater li;
		li = (LayoutInflater) getContext().getSystemService(infService);
		li.inflate(R.layout.clearable_edit_text, this, true);
		
		//获得对子控件的引用
		editText = (EditText) findViewById(R.id.editText);
		clearButton = (Button) findViewById(R.id.clearButton);
		
		//链接这个功能
		hokupButton();
	}

	private void hokupButton(){
		clearButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				editText.setText("");
			}
		});
	}
}



但是这里有一点注意,当我们用代码添加这个复合组件的时候,就需要用public CompoundView(Context context, AttributeSet attrs) {这个构造方法了,不然不会执行,当我们用XML文件 定义的复合组件的时候,也只会执行这个构造方法,所以注意

显示结果:

view 能不能创建索引 新建view_扩展

三、创建定制的Widget和控件:

1、创建新的可视界面。

看实例:

package hb.android.customview;

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

public class CustomView extends View {
	private String namespace = "http://hb.android.customview";
	int bearing;

	public int getBearing() {
		return bearing;
	}

	public void setBearing(int bearing) {
		this.bearing = bearing;
	}

	public CustomView(Context context, AttributeSet attrs) {
		super(context, attrs);
//		下面遍历所有属性
//		System.out.println(attrs.getAttributeCount());
//		for (int i = 0; i < attrs.getAttributeCount(); i++) {
//			System.out.println(attrs.getAttributeName(i)+":"+attrs.getAttributeResourceValue(i, -1));
//		}
//		 在这得到属性;根据对应的类型选择。
		bearing = attrs.getAttributeIntValue(namespace, "bearing", 10);
//		下面这种方法不能获取整形; 
//		bearing = attrs.getAttributeResourceValue(4,10);
		System.out.println(MeasureSpec.getSize(bearing));
	}

	public CustomView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public CustomView(Context context) {
		super(context);
	}

	/**
	 * onMeasure()方法是当前部件和他的父部件之间的一个重要联系。onMeasure()方法必须被有效地重写,并且要精准的报告所含子组件的大小,
	 * 这是由android这种较为严格的向父组件报告自己状态的要求来决定的
	 * ,当部件的宽高需要被计算时,使用测量得到的宽度高度值来调用setMeasureDimension
	 * ()方法传值?。如果重写了onMeasure()方法但你却每调用,那么计算宽高值时会出现异常。 你可以这样实现onMeasure()方法: 
	 * 被覆盖的onMeasure()应该给出边界值,(widthMeasureSpec 和heightMeasureSpec参数都是int型的尺寸)。 @
	 * 你所重写组件的onMeasure
	 * ()方法应该计算宽高值因为在绘制组件时那两个参数是必须的。最好能保留传入参数的值,尽管可以把传入的值改大一些。在这种修改的情况下
	 * ,组件的父组件会选择怎样处理:扔出异常或再次调用onMeasure(),最有可能的是保留原来的数值。 @
	 * 一旦宽高被计算,setMeasuredDimension()方法一定会被调用,不然将会抛出异常。
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		int widthModel = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		int heightModel = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		System.out.println(widthSize + ":" + heightSize);

		// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		/**
		 * 这个方法必须调用,不然,控件绘制不出来。有时候也不会报错。
		 * 当然,直接调用super.onMeasure()也会调用这个方法,但是参数必须是传递进来的参数 源码: protected void
		 * onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		 * 		setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 
		 * }
		 * widthMeasureSpec,heightMeasureSpec它们指定了控件可用的空间和一些描述这些空间的元数据。
		 */
		setMeasuredDimension(widthSize, heightSize);
	}

	/**
	 * 宽度和高度必须在这里取,不能在构造函数里取
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		System.out.println("bearing:"+bearing);
		System.out.println("getWidth:" + getWidth());
		System.out.println("getMeasureWidth:" + getMeasuredWidth());
		Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
		paint.setStyle(Paint.Style.FILL);
		paint.setColor(Color.YELLOW);
		canvas.rotate(bearing);
		canvas.drawText("HelloCustomView", getMeasuredWidth() / 2,
				getMeasuredHeight() / 2, paint);
		super.onDraw(canvas);
	}
}

对应的xml文件定义和命名空间的定义如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:mytest="http://hb.android.customview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <hb.android.customview.CustomView
        android:id="@+id/my_cv"
        android:layout_width="200dip"
        android:layout_height="200dip"
        android:background="#77efefef"
        mytest:bearing="30" />

</LinearLayout>


这样就创建了一个自定义的View 。