自定义View属性

QuibblerQuibbler 2020-08-25 约 19 分钟 1673 次阅读

自定义View属性

在布局中使用自定义的View时系统会自动调用自定义View的第二个构造函数View(Context context, AttributeSet attrs),生成自定义View实例:

<?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
        
        <.MyView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:myColor="#ff6600"
            app:myGravity="bottom"
            app:mySize="200dp" />
            
    </LinearLayout>

1、自定义View属性

其中自定义的属性myColor、myGravity、mySize都是res/values/attrs.xml文件中定义的。

<declare-styleable name="MyView">
        <attr name="mySize" format="dimension" />
        <attr name="myColor" format="color" />
        <attr name="myOrientation">
            <flag name="vertical" value="1" />
            <flag name="horizontal" value="0" />
        </attr>
        <attr name="myGravity">
            <enum name="left" value="0" />
            <enum name="right" value="1" />
            <enum name="top" value="2" />
            <enum name="bottom" value="3" />
        </attr>
    </declare-styleable>

1.1、<declare-styleable/>自定义属性类型

在res/values文件夹下新建attrs.xml文件,支持定义各种属性,<declare-styleable/>标签表示为自定义的属性,name表示自定义View的类名。

<declare-styleable name="MyView">
        ...
    </declare-styleable>

<attr/>标签中的name表示自定义的属性名。

<attr name="myGravity">
            ...
        </attr>

内部定义该属性所对应的类型,主要有一下几种数据类型:

①integer:int整型

②boolean:boolean

③color:颜色

④dimension:尺寸

⑤enum:枚举

⑥flag:位或运算,常量

⑦float:float型

⑧fraction:百分数

⑨reference:资源文件

⑩string:字符串

1.2、抽取公共属性

如果多个自定义View的属性有重复的怎么办,只需要将重复的单独提取出来,重复定义公共Attribute。在每个需要使用的地方引用即可:

<!-- 公共属性 -->
    <attr name="common" format="dimension" />
    
    <declare-styleable name="View1">
        <attr name="a" format="color" />
        <attr name="common" />
    </declare-styleable>
    
    <declare-styleable name="View2">
        <attr name="b" format="boolean" />
        <attr name="common" />
    </declare-styleable>

1.3、使用自定义属性

使用起来很简单,直接在布局中用app:引用即可,别忘了额外的命名空间xmlns:app="http://schemas.android.com/apk/res-auto,自动查找自定义的属性,否则找不到应用自定义的View属性。

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

2、获取xml布局局中属性的值

解铃还须系铃人,自定义的View还得自己写代码去用,不然没什么用。在布局中使用自定义的View,并且设定自定义的属性,还需要在代码中借助TypedArray解析xml布局获取添加的属性:

public MyView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
        int color = typedArray.getColor(R.styleable.MyView_myColor, Color.BLACK);
        float length = typedArray.getDimension(R.styleable.MyView_mySize, 0.0f);
        int gravity = typedArray.getInt(R.styleable.MyView_myGravity, 0);
        typedArray.recycle();//使用完要回收
        ... ...
    }

2.1、获取自定义属性值

之前已经介绍过了,自定义的属性在res/values/attrs.xml文件中,可以通过R.资源获取到:

TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyView);
    //获取自定义的属性myColor,给个默认值
    int color = typedArray.getColor(R.styleable.MyView_myColor, Color.BLACK);

2.2、获取android预定义属性值

当然除了自定义的,我们更像获取到在xml布局文件中设置的各种属性,诸如:layout_height、layout_width等:

①定义一个int[]数组,里面是我们想要获取到的资源id。必须按照升序排列,否则将不能正确获取到对应属性的值。

//属性资源id数组
    int[] array = new int[]{android.R.attr.layout_width, android.R.attr.layout_height}

②传入int[]数组。

//和第一种一样,传入int[]数组。
    TypedArray typedArray = context.obtainStyledAttributes(attrs, array);

前面用到的自定义属性R.styleable.MyView其实也是一个int[]类型的数组

int[] arrays = R.styleable.MyView;

③按照int[]数组中对应的索引读取数据

//数组中第0个索引,对应的属性就是android.R.attr.layout_width
    float width = typedArray.getDimension(0, 0.0f);
    float height = typedArray.getDimension(1, 0.0f);

④别忘了回收资源

typedArray.recycle();

2.3、坑点

前面说过,获取属性是传入的int[]数组中的元素需要按照降序排列,否则无法获取到对应属性的值。

int[] array = new int[]{android.R.attr.layout_width, android.R.attr.layout_height}

这个让我足足折腾了一下午时间,自己脑洞大开,又是bug终结者!想能不能把int[]数组中的顺序颠倒一下再获取,结果获取到的值发生错乱,有些属性则获取不到值。

int[] array = new int[]{android.R.attr.layout_height,android.R.attr.layout_width}

查看官方文档说明才发现obtainStyledAttributes (int resId,int[] attrs),属性数组中id的顺序一定要升序才可以!!!天坑啊。

Return a TypedArray holding the values defined by the style resource resid which are listed in attrs.

Be sure to call TypedArray#recycle() when you are done with the array.

Parameters
resIdint: The desired style resource.

attrsint: The desired attributes in the style. These attribute IDs must be sorted in ascending order. This value must never be null.

3、TextView属性源码阅读

Android已有View的示例TextView,是如何定义和使用属性的:

public TextView(Context context) {
        this(context, null);
    }
    public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.textViewStyle);
    }
    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

参考TextView的构造方法,defStyleAttr入参使用了R.attr.textViewStyle。在系统源码的attrs.xml中找到textViewStyle的定义:

... ... 
        <!-- Default TabWidget style. -->
        <attr name="tabWidgetStyle" format="reference" />
        <!-- Default TextView style. -->
        <attr name="textViewStyle" format="reference" />
        <!-- Default WebTextView style. -->
        <attr name="webTextViewStyle" format="reference" />
        ... ...

不难继续跟踪,在系统的values.xml中定义了对应的资源:

<item name="android:textViewStyle">@style/Widget.AppCompat.TextView</item>

而上面这个就是TextView默认的theme主题:

<style name="Widget.AppCompat.TextView" parent="Base.Widget.AppCompat.TextView"/>

经过层层向上追踪到TextView系统默认主题的完整定义:

<style name="Widget.TextView">
        <item name="textAppearance">?attr/textAppearanceSmall</item>
        <item name="textSelectHandleLeft">?attr/textSelectHandleLeft</item>
        <item name="textSelectHandleRight">?attr/textSelectHandleRight</item>
        <item name="textSelectHandle">?attr/textSelectHandle</item>
        <item name="textEditPasteWindowLayout">?attr/textEditPasteWindowLayout</item>
        <item name="textEditNoPasteWindowLayout">?attr/textEditNoPasteWindowLayout</item>
        <item name="textEditSidePasteWindowLayout">?attr/textEditSidePasteWindowLayout</item>
        <item name="textEditSideNoPasteWindowLayout">?attr/textEditSideNoPasteWindowLayout</item>
        <item name="textEditSuggestionItemLayout">?attr/textEditSuggestionItemLayout</item>
        <item name="textEditSuggestionContainerLayout">?attr/textEditSuggestionContainerLayout</item>
        <item name="textEditSuggestionHighlightStyle">?attr/textEditSuggestionHighlightStyle</item>
        <item name="textCursorDrawable">?attr/textCursorDrawable</item>
        <item name="breakStrategy">high_quality</item>
        <item name="hyphenationFrequency">@dimen/config_preferredHyphenationFrequency</item>
    </style>

通过类似TextView的方法,Android系统为很多原生控件提供了默认属性值。相信大家也可以理解,defStyleAttr参数是当前theme下定义的一个style资源的属性,并为最后生成的TypedArray资源数组提供了默认值的功能。

相关资料:

        Android attrs文件(自定义)属性详解

        Android中attr自定义属性详解

        从AttributeSet 中取出特定的值

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w