美文网首页ViewviewAndroid知识
Android学习笔记---自定义View#01

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

作者: Kenny_Ho | 来源:发表于2016-09-12 23:20 被阅读213次

    最近发现自己对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构造函数详解

    相关文章

      网友评论

        本文标题:Android学习笔记---自定义View#01

        本文链接:https://www.haomeiwen.com/subject/mhqmettx.html