Android学习笔记---自定义View#01

最近发现自己对Android的学习只在表面,并没有深入的理解,我不喜欢这种感觉,而且没有自己的理解,学习到的内容也很难为我所用.

所以从本次开始,我要写点自己理解的东西,但要对知识有自己的理解,那就必须深入了解它的原理.而我觉得Android的自定义View是一个很好的入口.

学会如何自定义View,能够了解Android系统中View使如何创建和维护的.这有助于我们学习Android的View的基本机制,也能解决我们日常开发的需求.

接下来我们一起来对自定义View中的各个部分做详细的研究,现在我们先从View的构造函数入手.

自定义View的构造函数

自定义View最基础的方式就是创建一个继承于View或其子类的类,并且最少重写父类的一个构造函数.

/**
 */
public class MyView extends View {
    /**
     * 在代码中使用new关键字创建View时会调用
     * @param context
     */
    public MyView(Context context) {
        super(context);
    }

    /**
     * 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
     * @param context
     * @param attrs 存有View在xml布局文件中的自定义的属性
     */
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 与第二个构造函数不同的地方在于,此构造函数可以指定View的默认样式
     * @param context
     * @param attrs
     * @param defStyleAttr 是当前theme中包含的指向View的样式资源的属性(0代表此参数无效)
     */
    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}

第一个构造函数比较简单,我们从第二个构造函数开始研究.
当我们需要在xml布局文件中使用我们的View时,我们就必须实现第二个构造函数.第二个构造函数的参数列表为MyView(Context context, AttributeSet attrs),这里的context不用多说,就是View所在的上下文,而第二个参数就是AttributeSet类型的一个Set集合.它是一个保存了View的自定义属性的集合,即我们在xml布局文件中为View所设置的属性可以通过这个参数获取.
通常我们会在res/values/文件下创建一个attrs.xml文件来声明我们的自定义属性.该文件是一个资源文件.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="myview_text" format="string"/>
    </declare-styleable>
</resources>

上面我们在attrs.xml中定义了我们的自定义属性,其中<declare-styleable>标签表示一组自定义的属性,对应着一个View的自定义属性.其中的name可以是任何值,但为了方便和规范,建议name与对应的View同名.
<attr>标签就是一个具体的属性了,它的name代表该属性的名字.format代表该属性的值的格式.者两个是必须要有的.<attr>标签支持的formatstring,enum,boolean,dimension,color,float...等多种格式.

有了自定义的属性后,在xml布局文件中使用View时就能使用我们的自定义属性了.但我们需要在使用自定义属性前,指定自定义属性的命名空间,这样系统才能准确的找到你的自定义属性.
而它们的命名空间为http://schemas.android.com/apk/res/[your package name],这里不同的View可能会有不同的package name,这样一来就比较麻烦.但是在Android Studio中我们只需指定一个统一的命名空间即可http://schemas.android.com/apk/res-auto.剩下的AS会帮我们完成.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <com.source.kevin.costomviewlib.costomview.MyView
        app:myview_text="Hello"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</RelativeLayout>

为了验证第二个参数AttributeSet能否得到我们的自定义属性,我们在第二个构造函数中测试一下.

    /**
     * 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
     * @param context
     * @param attrs 存有View在xml布局文件中的自定义的属性
     */
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
        try{
            String string = a.getString(R.styleable.MyView_myview_text);
            Log.e("RESULT",string);
        }finally {
            a.recycle();//回收资源
        }
    }

先运行一下应用,然后在控制台的Log中我们看到了输出


我们很简单的就得到了自定义的属性.代码中我们使用了context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)这个函数获取我们的自定义属性集合.返回的是一个TypeArray类型的对象,这个对象包含了View的自定义属性.第1个参数便是要解析的AttributeSet,第2个参数int[] attrs就是我们attrs.xml文件中定义的一组属性资源.其实我们可以打开自动生成的R.java文件,在文件中搜索MyView关键字,定位到相应的行数,我们可以看到下面的代码:

....
public static final class attr {
....
    public static final int myview_text=0x7f0100a7;
....
}
....
public static final class styleable {
....
    public static final int[] MyView = {
            0x7f0100a7
    };
    public static final int MyView_myview_text = 0;
....
}

可以看出R.styleable.MyView是一个int型数组,里面的内容是与R.attr.myview_text相对应的.这就表明了R.style.MyView是一个存放了attrs.xml文件中声明的一组自定义属性的id集合.而R.styleable.MyView_myview_text则是一个属性在数组中对应的下标索引.
如果还是存在疑惑,可以在attrs.xmlMyView属性节点下多添加几个自定义的属性,然后重新编译代码,按照我上面的方法查看相应的代码,我相信你能够明白其中的关系.

我们可以点到context.obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs)这个函数的源码中去,发现这个函数调用的是context.getTheme().obtainStyledAttributes(set, attrs, 0, 0)这个函数.可以看到这是一个4个参数的函数,它的函数原型如下:

/**
*
* @param set 需要解析的属性集合
* @param attrs 属性集合对应的id资源数组
* @param defStyleAttr 当前Theme中包含的一个指向style样式的引用.
*                     当我们没有设置自定义属性时,默认会从该集合中查找布局文件的属性配置值(0代表不向defStyleAttr查找属性默认值)
* @param defStyleRes 也是一个指向Style的资源ID
*                    当defStyleAttr==0 或 defStyleAttr!=0 但Theme中没有为defStyleAttr赋值,该参数才起作用.
* @return 
*/
public TypedArray obtainStyledAttributes(AttributeSet set,@StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)

代码中的注释已经说的很清楚,其中第3个参数和第4个参数分别与View的3参数的构造函数中的第3个参数和4参数构造函数中的第4个参数的意义是一样的.最后当我们使用完了TypeArray,我们需要将它回收,因为它是一个共享的资源.
由上面的函数就可以看出属性可以在很多的地方进行赋值,包括: XML布局文件中、decalare-styleable、theme中等,它们之间是有优先级次序的,按照优先级从高到低排序如下:

在布局xml中直接定义 > 在布局xml中通过style定义 > 自定义View所在的Activity的Theme中指定style引用 > 构造函数中defStyleRes指定的默认值

下面我们来尝试一下如何从第三个参数获取属性值.首先我们在attr.xml文件中添加一个单独的属性MyViewDefStyleAttr,格式为reference,就是资源引用类型.并在MyView属性组下添加一个属性.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyView">
        <attr name="myview_text" format="string"/>
        <attr name="myview_attr" format="string"/>
    </declare-styleable>
    <attr name="MyViewDefStyleAttr" format="reference"/>
</resources>

接下来我们修改style.xml文件,添加一个自定义的style,作为MyViewDefStyleAttr的实现,并在AppTheme style下引用它.

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="MyViewDefStyleAttr">@style/MyViewDefStyleaAttrImpl</item>
    </style>

    <style name="MyViewDefStyleaAttrImpl">
        <item name="myview_attr">attr</item>
    </style>

</resources>

然后我们就可以在第三个构造函数中获取自定义属性.

    /**
     * 在layout文件声明View时会调用,只有实现了这个构造函数才能在布局文件中声明此自定义View
     * @param context
     * @param attrs 存有View在xml布局文件中的自定义的属性
     */
    public MyView(Context context, AttributeSet attrs) {
        this(context, attrs,R.attr.MyViewDefStyleAttr);

    }

    /**
     * 与第二个构造函数不同的地方在于,此构造函数可以指定View的默认样式
     * @param context
     * @param attrs
     * @param defStyleAttr 是当前theme中包含的指向View的样式资源的属性(0代表此参数无效)
     */
    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0);
        try {
            String s = a.getString(R.styleable.MyView_myview_attr);
            Log.e("RESULT",s);
        }finally {
            a.recycle();
        }

    }

这里我们通过两个参数的构造函数调用3参数的构造函数,并传入R.attr.MyViewDefStyleAttr作为默认样式属性值资源,在第三个构造函数中,若要获取第3个参数的默认属性值,必须通过显式的调用context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyView,defStyleAttr, 0)这个4个参数的obtainStyledAttributes()函数.我们运行代码后得到的相应的结果:

通过对View的构造函数的研究,基本了解了View在创建时是通过什么方式获取自定义属性的,并且也知道了该如何实现View的自定义属性.

参考

Android View构造函数详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容