Android为自定义View添加属性

(You can suggest changes to this post.)

Android 自定义View 己经不是什么新鲜话题,Android Api提供了一大堆基础组件给我们,需要什么特定功能还需要我们继承它们然后定制更加丰富的功能。那么如何给自定义的View添加一些自定义xml属性呢,如one:textTitle=”“,不仅如此,我们知道xml中有一个android:onClick=”onClickMethod”,这样在Activity中就不需要给该View设置监听器了,那么有没有类似的自定义listener的属性呢?答案是肯定的。

先来看下我们最后想要定义的格式:

<com.boohee.view.Navbar
    android:id="@+id/navbar"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    one:textTitle="@string/tab_more_text"
    one:onAction="onAction" />

接着便会有如下的效果:

其中textTitle是定义navbar的标题,onAction是navbar上“保存”按钮的事件。好了,下面就来看下代码实现:

定义attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="Navbar">
        <attr name="textTitle" format="string|reference" />
        <attr name="onAction" format="string" />
    </declare-styleable>

</resources>

上面attrs.xml文件中定义了两个属性,关于自定义属性格式,见这篇blogAndroid中自定义属性格式详解

自定义View的初始化下添加代码

public class Navbar extends FrameLayout {
	private Context ctx;
	private Button left_btn, right_btn;
	private TextView title;

	public Navbar(Context context) {
		super(context);
		setUp();
	}

	public Navbar(Context context, AttributeSet attrs) {
		super(context, attrs);
		setUp();
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Navbar);
		final int N = a.getIndexCount();
		for (int i = 0; i < N; ++i) {
			int attr = a.getIndex(i);
			switch (attr) {
			case R.styleable.Navbar_textTitle:
				title.setText(a.getString(attr));
				break;
			case R.styleable.Navbar_onAction:
				...
				break;
			}
		}
		a.recycle();
	}

	void setUp() {
		ctx = getContext();
		addView(LayoutInflater.from(this.getContext()).inflate(R.layout.navbar, null));
		left_btn = (Button) findViewById(R.id.left_btn);
		title = (TextView) findViewById(R.id.title);
		right_btn = (Button) findViewById(R.id.right_btn);
	}
}

在XML布局文件中使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:one="http://schemas.android.com/apk/res/com.boohee.one"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/main_bg"
    android:orientation="vertical" >

    <com.boohee.myview.Navbar
        android:id="@+id/navbar"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        one:textTitle="@string/tab_more_text" />

</LinearLayout>

需要注意的是根布局要加上命名空间xmlns:one=”http://schemas.android.com/apk/res/com.boohee.one”

添加一个回调属性

上面说明了如何自定义一些基础属性,那么如何像android:onClick属性一样自定义一个方法回调属性呢,这个一开始实在不晓得如何下手,还好android是开源的,通过看源码后终于有了方法,废话不多说,看代码:

case R.styleable.Navbar_onAction:
	if (context.isRestricted()) {
		throw new IllegalStateException();
	}

	right_btn.setVisibility(View.VISIBLE);
	final String handlerName = a.getString(attr);
	if (handlerName != null) {
		right_btn.setOnClickListener(new OnClickListener() {
			private Method mHandler;

			public void onClick(View v) {
				if (mHandler == null) {
					try {
						mHandler = getContext().getClass().getMethod(handlerName,
								View.class);
					} catch (NoSuchMethodException e) {
						throw new IllegalStateException("NoSuchMethodException");
					}
				}

				try {
					mHandler.invoke(getContext(), right_btn);
				} catch (IllegalAccessException e) {
					throw new IllegalStateException();
				} catch (InvocationTargetException e) {
					throw new IllegalStateException();
				}
			}
		});
	}
	break;

代码倒是不难理解,只是上述代码用到了java的反射机制,这篇blog则讲述了java的反射机制