自定义控件常见的几种方式:

Android View类是所有view的超类。
* 自定义类直接继承View或者ViewGroup。
* 这种方式需要自己去处理AT_MOST的情况。
* 自定义类继承已有的View,例如Button。
* 系统已经帮你处理好AT_MOST的情况,开发人员直接在其基础上去添加新功能即可。
* 自定义组合控件。
* 将需要的布局转化为一个ViewGroup去管理
*自定义类去继承该布局的根元素,例如LinearLayout,RelativeLayout等ViewGroup

自定义的View属性(建立View的内置属性)

步骤

  • 在values/attrs.xml建立创建自定义属性。
  • 在XML文件中指定属性的值。
  • 在自定义类中获取属性的值。
  • 将获取到的属性值应用到自定义View中。

创建自定义属性

创建values/attrs.xml文件,添加如下代码即可。

<?xml version="1.0" encoding="utf-8"?>
<!-- string,color,dimension,integer,enum,reference,float,boolean,fraction,flag; -->
<resources>
    <declare-styleable name="CustomView">
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="name" format="string" />
        <attr name="isShow" format="boolean" />
        <attr name="Oriental" format="enum">
            <enum name="Horizontal" value="1"></enum>
            <enum name="Vertical" value="0"></enum>
        </attr>
    </declare-styleable>
</resources>

可以将一些公共的属性抽取到外面,那么多个自定义控件都可公用这个属性了

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="textColor" format="color" />
    <declare-styleable name="CustomView">
            <attr name="textColor" />
    </declare-styleable>
</resources>

使用自定义属性

  • 自定义的属性与系统提供的属性用法有一点不一样,那就是命名空间不一样。
  • 系统属性的namespace:
xmlns:android="http://schemas.android.com/apk/res/android"
  • 我们需要给自定义的View也加上namespace
xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews"
或者是
xmlns:custom="http://schemas.android.com/apk/res-auto"(推荐使用)

完整例子

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
   <com.example.view.CustomView
        android:layout_width="160dip"
        android:layout_height="wrap_content"
        custom:name="123456789"
        custom:Oriental="Horizontal"
        custom:textSize="15sp" />
</RelativeLayout>

在自定义类中获取属性值

  • 在自定义类中必须有带有AttributeSet参数的构造函数,这样是为了方便获取通过布局文件实例化的自定义View对象。
  • 一般情况下的模版写法(相互调用,最终都由第三个构造去处理)
public CustomView(Context context) {
        this(context, null);
}
public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
}

从AttributeSet中直接获取属性值

int attributeCount = attrs.getAttributeCount();
for(int i = 0; i<attributeCount;i++) {
    String name = attrs.getAttributeName(i);
    String value = attrs.getAttributeValue(i);
    Log.d("CustomView", name+"..."+value);
}
04-12 10:37:45.357: D/CustomView(27360): paddingLeft...10.0dip
04-12 10:37:45.358: D/CustomView(27360): paddingRight...10.0dip
04-12 10:37:45.358: D/CustomView(27360): layout_width...160.0dip
04-12 10:37:45.358: D/CustomView(27360): layout_height...-2
04-12 10:37:45.358: D/CustomView(27360): textSize...15.0sp
04-12 10:37:45.358: D/CustomView(27360): name...123456789
04-12 10:37:45.358: D/CustomView(27360): Oriental...1

结果可以看到获取到的是所有的属性
* 根据官方文档介绍,虽然可以直接从AttributeSet中直接获取到属性值,但是有两个弊端
* Resource references within attribute values are not resolved
* Styles are not applied

google建议将将AttributeSet传递给obtainStyledAttributes()中

* 该方法会返回一个TypeArray对象。
* 已经dereferenced and styled。
* 系统在之前就已经做了很多准备工作,只等你去调用obtainStyledAttributes()方法
    * 在R.java文件中已经定义自定义属性的ids值。
    * 在Array中为每一个属性定义了index去表示指定的定义属性。
    * 开发者只需要拿预定义的常量值去获取属性即可。
public CustomView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    // 得到自定义属性的值
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
            R.styleable.CustomView, defStyle, 0);
    int count = a.getIndexCount();
    for (int i = 0; i < count; i++) {
        int index = a.getIndex(i);//得到表示属性值的常量标记
        switch (index) {
        case R.styleable.CustomView_name:
            mName = a.getString(index);
            break;
        case R.styleable.CustomView_textSize:
            mTextSize = a.getDimensionPixelSize(index, 16);
            break;
        default:
            break;
        }
    }
    // 回收
    a.recycle();
}
  • 注意:TypeArray是一个share resource使用后必须记得关闭回收。

提供可供外界触发的事件

  • 为什么要暴露属性给外界?
  • 为了让view具备动态的效果,就得通过修改View中的某一些属性,从而从新去绘制和布局View达到动态的效果。也就是去调用invalidate或者postInvalidate方法去进行view的重绘。
  • 怎么暴露?
  • getter/setter方法。
  • 暴露什么属性比较适合?
  • 规则就是暴露影响自定义View外观和行为的属性。
  • getter/setter方法一般会通过某些事件去触发执行,例如监控手势滑动事件不断修改View属性。