Android自定义控件

转自Android–自定义控件解析(一)

背景:

在这个看脸的时代,好的UI效果能让人更容易记住你。有不少人说,不懂自定义控件的Android 工程师,不是合格的Android工程师。本文旨在说明自定义控件的总体步骤,以便对自定义控件有个大致的了解。

使用情景:

当遇到下面几点要求的时候,就需要自定义控件了:

1.特定的显示风格 :比如我们想要实现一个效果但系统并没有提供给我们这样一个控件,那我们就可以自己设计显示风格

2.处理特有的用户交互 :比如Touch Remove这样做一些其它的操作,于是为了实现对应的交互,我们选择自定义控件

3.优化布局 :这个可以体现在我们的 ListView 中,大家都知道 ListView 是通过调用 getView() 方法来渲染item,那么当用户快速滑动我们的ListView的时候,getView() 方法就会被频繁地调用。这时如果我们的item的布局比较复杂的话,那么渲染的速度可能就会变得缓慢,用户在快滑的时候就会感觉到卡顿。所以我们可以通过自定义控件去实现复杂的item布局,极高地提升渲染的效率。

4.封装 : 就是为了封装和复用

自定义控件的流程:

1.自定义属性的声明与获取
2.测量 onMeasure
3.[布局onLayout(ViewGroup)]
4.绘制onDraw
5.onTouchEvent
6.[onInterceptTouchEvent(ViewGroup)]
7.状态的恢复和保存

带了[ ]的,第3个因为是自定义ViewGroup时需要对子View的位置进行设置,而第6个则是对子View的Touch事件,如ScrollView,需要监听子View上是不是触发move事件,如果用户有上下移动的手势,ScrollView就会将其拦截,然后调用自己的onIntercepTouchEvent方法,实现上下滑动的效果。所以这俩是针对于ViewGroup的,如果你要自定义的是View控件,这两步就可以不做。

状态的恢复和保存,拿进度条来举例,当用户正在下载的时候,如果下载时间比较久的话,用户就可能不会在页面等待,也许会切到别的界面去玩会儿游戏,聊会儿天,做别的事情,一会儿之后再回到我们的app。但是因为内存的原因,我们的在后台可能会被系统杀死,但用户第二次进入我们的app时,Activity可能已经被重建了,于是我们的View也会被销毁和重建,如果下载到75%,这个时候我们的进度条即使被重建也应该显示为75%,否则用户就会疑惑下载的内容去了哪里,所以就需要就涉及到状态的恢复和保存。

这些步骤只是总结,在实际开发中,可以根据自己的需要选择步骤去处理,这些步骤并不是一定都是必须的。

自定义属性

在自定义属性之前,我们首先应该先了解Android中是如何定义一个属性的,就拿我们最熟悉的LinearLayout来举例:

我们在xml文件中引用了LinearLayout之后,就要设置它的layout_width和layout_height属性,我们可以在SDK目录下的路径platforms\android-25\data\res\values 找到attrs.xml,Android控件中的属性都在里面。

<declare-styleable name="LinearLayout_Layout">
    <attr name="layout_width" />
    <attr name="layout_height" />
    <attr name="layout_weight" format="float" />
    <attr name="layout_gravity" />
</declare-styleable>

这就是Android定义的LinearLayout的几个属性,我们应该对它们很熟悉。谷歌自定义属性的方法十分清晰,我们要定义一个attrs.xml的文件用来配置我们需要的属性。

其实大部分的标题栏可以分为三部分,即返回按钮,标题和更多按钮
在这里插入图片描述
我们可以知道是把标题栏TopBar分成了三部分,左边的Button,中间的文字,还有右边的Button,看到这个可以知道我们要自定义的属性分别是左边右边Button的文字内容,文字尺寸和背景色,中间文字的内容,尺寸,字体颜色共九个属性。

首先要在我们res/values下创建一个attrs.xml,我们自定义属性要用到的就是declare-styleable标签,我们通过declare-styleable标签来告诉系统这是我们自定义的属性。然后我们在declare-styleable下通过attr这个标签为我们的自定义属性制定名字,类型等。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="Topbar">
        <attr name="title" format="string"/>
        <attr name="titleTextSize" format="dimension"/>
        <attr name="titleTextColor" format="color"/>
        <attr name="leftText" format="string"/>
        <attr name="leftTextColor" format="color"/>
        <attr name="leftBackground" format="reference|color"/>
        <attr name="rightText" format="string"/>
        <attr name="rightTextColor" format="color"/>
        <attr name="rightBackground" format="reference|color"/>
    </declare-styleable>
</resources>

declare-styleable包围所有属性,它的name为该属性集的名字,主要用途是标识该属性集。这个对于我们引用里面的属性是必要的。

attr标签有几个需要我们注意的地方:

format是我们最常用的属性,format是格式的意思,所以这个属性是申明我们自定义的属性是什么类型,使用的类型如下:

reference:参考指定Theme中资源ID。也就是从资源文件中获取值。

 //属性定义
 <declare-styleable name = "Topbar">
     <attr name = "background" format = "reference" />
 </declare-styleable>

 //属性使用
 <ImageView
     android:layout_width = "42dp"
     android:layout_height = "42dp"
     lht:background = "@drawable/..." />

color:颜色值
boolean:布尔值
dimension:尺寸值。这里如果是dp那就会做像素转换
float:浮点值
integer:整型值
string:字符串
fraction:百分数
reference|color:颜色的资源文件
reference|boolean:布尔值的资源文件
enum:枚举值,一般是用作子标签。
flag:是自己定义的,类似于 android:gravity=”top”,就是里面对应了自己的属性值。一般是用作子标签。
enum:

//属性定义
<declare-styleable name="My">
    <attr name="language">
        <enum name="English" value="1"/>
    </attr>
</declare-styleable>
//属性使用
<Button lht:language="English"/>

flag: 与 enum不一样的就是flag还能做位或运算。

//属性定义
<declare-styleable name="My">
   <attr name="math">
       <flag name="one" value="1" />
       <flag name="two" value = "0x30" />
   </attr>
</declare-styleable>
//属性使用
<activity lht:math="one|two"/>

像上面那样就把我们的自定义属性设计好了,我们就可以在控件中调用它们了。

自定义属性获取

属性已经定义好了,因为我们并不是要在已有的控件中添加属性,所以我们不是在xml直接使用,而是先写一个继承系统提供的控件的类,这里我们的TopBar就用RelativeLayout来实现。

public class Topbar extends RelativeLayout {

    public Topbar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

如果没有自定义属性,选择的构造方法只要有上下文参数就可以了,但我们这里有,所以就使用这一个构造方法。

既然要获取我们在xml中的属性,自然要先在Java中声明对应的变量,如下:

public class Topbar extends RelativeLayout {

    private Button leftButton, rightButton;
    private TextView tvTitle;

    private int leftTextColor;
    private String leftText;
    private Drawable leftBackground;

    private int rightTextColor;
    private String rightText;
    private Drawable rightBackground;

    private float titleTextSize;
    private int titleTextColor;
    private String title;

    public Topbar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
        setBackgroundColor(0xFFF59563);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Topbar);

        leftText = ta.getString(R.styleable.Topbar_leftText);
        leftTextColor = ta.getColor(R.styleable.Topbar_leftTextColor, 0);
        leftBackground = ta.getDrawable(R.styleable.Topbar_leftBackground);

        rightText = ta.getString(R.styleable.Topbar_rightText);
        rightTextColor = ta.getColor(R.styleable.Topbar_rightTextColor, 0);
        rightBackground = ta.getDrawable(R.styleable.Topbar_rightBackground);

        title = ta.getString(R.styleable.Topbar_title);
        titleTextColor = ta.getColor(R.styleable.Topbar_titleTextColor, 0);
        titleTextSize = ta.getDimension(R.styleable.Topbar_titleTextSize, 0);

        ta.recycle();

        leftButton = new Button(context);
        rightButton = new Button(context);
        tvTitle = new TextView(context);

        leftButton.setText(leftText);
        leftButton.setTextColor(leftTextColor);
        leftButton.setBackground(leftBackground);

        rightButton.setText(rightText);
        rightButton.setTextColor(rightTextColor);
        rightButton.setBackground(rightBackground);

        tvTitle.setText(title);
        tvTitle.setTextSize(titleTextSize);
        tvTitle.setTextColor(titleTextColor);
        tvTitle.setGravity(Gravity.CENTER);
    }
}

控件和变量的声明我相信大家都能明白的,因为我们的TopBar由两个Button和一个TextView组成,所以这里做的工作就是把我们的自定义属性和控件组合。

要把我们刚才自定义的属性和变量关联,Android给我们提供了很好的API,我们可以通过TypedArray来存储我们在xml中获取到的那些自定义属性,方式就是通过上下文调用obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)方法,set是我们构造方法里的attrs,int数组就是我们用declare-styleable定义的属性集的ID,通过R.styleable.NAME获取。

把属性集拿到后就是从这个TypedArray中取对应的属性了,方法像键值对一样用get方法取之前定义的format类型的值。拿getColor(@StyleableRes int index, @ColorInt int defValue)方法来说,index就是对应属性的ID,这里的写法是属性集名加下划线加属性名,defValue顾名思义就是默认值。还有要注意的一点是我们在用完TypedArray这个变量后,要用recycle()将它回收,既是为了避免浪费资源,也是为了防止因为缓存出现一些错误。

把属性与我们的变量关联后,就是为我们的控件赋值了,这比较简单,相信大家都对setText()、setTextColor()这样的方法十分熟悉了,只要把变量添加进去即可,顺便把TextView设置为居中。

以上,就完成了我们自定义属性的获取。

子View布局参数

接下来就为我们的Button和TextView设置LayoutParams,把它们添加到布局中来:

private LayoutParams leftParams, rightParams, titleParams;

private void layout() {
        leftParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        leftParams.addRule(ALIGN_PARENT_LEFT);

        this.addView(leftButton, leftParams);

        rightParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        rightParams.addRule(ALIGN_PARENT_RIGHT);

        this.addView(rightButton, rightParams);

        titleParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        titleParams.addRule(CENTER_IN_PARENT, TRUE);

        this.addView(tvTitle, titleParams);
    }

只是用了LayoutParams里的几个很简单的方法,相信大家都能看懂。这样就把我们的控件完成了,最后就把这个TopBar添加到布局文件中:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:lht="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_top_bar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.ht.animator.TopBarActivity">

    <com.ht.custom.Topbar
        android:id="@+id/topbar"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        lht:leftBackground="@android:color/holo_blue_bright"
        lht:leftTextColor="#FFFFFF"
        lht:leftText="Back"
        lht:rightBackground="@android:color/holo_blue_bright"
        lht:rightTextColor="#FFFFFF"
        lht:rightText="More"
        lht:title="自定义标题"
        lht:titleTextColor="#000000"
        lht:titleTextSize="10sp">

    </com.ht.animator.Topbar>

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值