【android】自定义View

Android的framework中提供了大量的视图类,如Button,TextView,ListView等,如果要使用这些视图类,需要在res/layout目录下对应的xml文件写上需要的控件,并且可以设置相应的属性值,就能控制布局;但有时候你的app有特定的需求,但android提供的控件都不能支持其需求功能时,就需要创建自定义的view;

1.继承一个android已有的View

所有定义在framework里的view控件都是继承自View这个类,自定义的View也可以直接继承View,或者你可以继承已经存在的View子类,比如Button,这样能更节省时间;为了让Android Developer Tools能和你的view控件交互,你至少要提供一个构造方法,该构造方法需有Context和AttributeSet作为参数。这个构造方法能让layout editor创建和编辑你自定义View的实例;

public class LabelView extends View {
    private Paint mTextPaint;
    private String mText;
    private int mAscent;
    
    /**
     * Constructor.  This version is only needed if you will be instantiating
     * the object manually (not from a layout XML file).
     * @param context
     */
    public LabelView(Context context) {
        super(context);
        initLabelView();
    }

    /**
     * Construct object, initializing with any attributes we understand from a
     * layout file. These attributes are defined in
     * SDK/assets/res/any/classes.xml.
     */
    public LabelView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
2. 自定义属性

当你在添加android自有的view控件到界面时,通常都是在xml文件中通过属性来控制view的特性和行为,而自定义的view也可以同通过xml来添加和改变特性,自定义view要能在xml中受控制,就需要:

  • 在资源文件res/values/attrs.xml中的一个<declare-styleable>元素中添加属性;
  • 在xml文件中指定自定义view的属性值;
  • 在运行代码中返回在xml指定的属性值;
  • 把返回的属性值应用到view控件中;

这个部分主要说明了怎样定义自定义属性并为自定义属性指定赋值,下个部分将会说明要怎样获得属性值并使用获得的属性值;为了定义属性值,就要添加一个<declare-styleable>元素到你的工程中,通常的做法都是把该元素定义在res/values/attrs.xml文件下,这就是apidemos的一个例子:

<resources>
     <declare-styleable name="LabelView">
        <attr name="text" format="string" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
    </declare-styleable>
<resources>

 定义了这些属性后,就可以像android自有的view控件一样在layout中的xml文件中添加自定义view,唯一的不同是自定义属性属于不同的命名空间,android自有的view控件的命名空间是 
http://schemas.android.com/apk/res/android,而自定义view的命名空间为:http://schemas.android.com/apk/res/[your package name]以下代码展示了怎样使用自定义属性: 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
    <com.example.android.apis.view.LabelView
            android:background="@drawable/red"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" 
            app:text="Red"/>
    
    <com.example.android.apis.view.LabelView
            android:background="@drawable/blue"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" 
            app:text="Blue" app:textSize="20dp"/>
    
    <com.example.android.apis.view.LabelView
            android:background="@drawable/green"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" 
            app:text="Green" app:textColor="#ffffffff" />

</LinearLayout>
为了避免重复岑长的URI命名空间,这个例子用了xmlns指示器,这个指示器指派custom作为 http://schemas.android.com/apk/res/com.example.android.apis.view.LabelView的别名,当然你也可以选择任何别名来指示该命名空间;

注意到添加到layout中自定义view的xml标签名,可以看到标签名是自定义view这个类的全名,如果自定义view是一个子类的话,在标签命中也必须展现它的外部类名,举个例子,如果LableView有一个子类叫LableButton,如果要为这个内部类指定属性值,则标签名为:com.example.android.apis.view.LabelView&LableButton.

3.应用自定义属性

当一个view在layout中被创建后,在该view的xml标签中所有属性值就会被读取出来打包成一个资源bundle,这个资源bundle为AttributeSet类型,然后作为view构造方法的一个参数穿进去;虽然你可以直接从读取出来的AttributeSet获得各个属性值,但这样做会有一些缺点:
(1).属性值的资源引用不能被解析;
(2).Styles没有被应用;
相反,应该把AttributeSet传进obtainStyledAttributes()方法,这个方法会返回一个TypedArray数组,这个数组已经被间接引用并styled。android资源编译器做了大量工作才让调用obtainStyledAttributes()看起来很简单,对于在资源目录下的每个<declare-styleable>,系统产生的R文件定义了该属性数组里所有属性值的id和关于该数组里所有属性值的索引常量。所以就可以通过这些常量和id从TypedArray获得具体的属性值:

    public LabelView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLabelView();

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.LabelView);

        CharSequence s = a.getString(R.styleable.LabelView_text);
        if (s != null) {
            setText(s.toString());
        }

        // Retrieve the color(s) to be used for this view and apply them.
        // Note, if you only care about supporting a single color, that you
        // can instead call a.getColor() and pass that to setTextColor().
        setTextColor(a.getColor(R.styleable.LabelView_textColor, 0xFF000000));

        int textSize = a.getDimensionPixelOffset(R.styleable.LabelView_textSize, 0);
        if (textSize > 0) {
            setTextSize(textSize);
        }

        a.recycle();
    }

注意:TypedArray是一个共享资源,在每次使用完后必须回收,即每次使用完后要调用recycle()方法;

4. 添加事件和外观

属性是一个很有用的方法来控制view的行为和外观,但这些属性只有这个view被初始化后才能被读出来,为了提供动态的读写操作,应该为每个自定义属性值提供一对getter和setter方法,以下代码展示了属性mText的setter方法:

    /**
     * Sets the text to display in this label
     * @param text The text to display. This will be drawn as one line.
     */
    public void setText(String text) {
        mText = text;
        requestLayout();
        invalidate();
    }
注意到mText调用了invalidata()和requestLayout()方法,这些调用方法很关键,能确保view视图更稳定展现出来。在对能对view的外观造成影响的属性改变之后,就需要让该view无效化,因为这样能通知系统这个view之后可以再被重画。同样,当一个view的大小和外观变化后,也需要向系统请求一个新的layout.如果没有调用上述两个方法,将会出现很多隐藏的bug。自定义view应该要支持时间监听器来处理交互事件。在写自定义view时很容易忘记暴露出属性和时间的借口,尤其只有一个app要调用这个view时,一定要花一些时间来定义自定义view的属性和时间接口,这样才能让之后的维护更容易。一个公认的规则就是:把能够改变view外观和行为的属性暴露出来。











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值