`

从源码中浅析Android中如何利用attrs和styles定义控件

阅读更多

一直有个问题就是,Android中是如何通过布局文件,就能实现控件效果的不同呢?比如在布局文件中,我设置了一个TextView,给它设置了textColor,它就能够改变这个TextView的文本的颜色。这是如何做到的呢?我们分3个部分来看这个问题1.attrs.xml  2.styles.xml  3.看组件的源码。

1.attrs.xml:

 我们知道Android的源码中有attrs.xml这个文件,这个文件实际上定义了所有的控件的属性,就是我们在布局文件中设置的各类属性

你可以找到attrs.xml这个文件,打开它,全选,右键->Show In->OutLine。可以看到整个文件的解构

下面是两个截图:

 

 

我们大概可以看出里面是Android中的各种属性的声明,比如textStyle这个属性是这样定义的:

<!-- Default text typeface style. -->
    <attr name="textStyle">
        <flag name="normal" value="0" />
        <flag name="bold" value="1" />
        <flag name="italic" value="2" />
    </attr>

 那么现在你知道,我们在写android:textStyle的时候为什么会出现normal,bold和italic这3个东西了吧,就是定义在这个地方。

再看看textColor:

<!-- Color of text (usually same as colorForeground). -->
    <attr name="textColor" format="reference|color" />

 format的意思是说:这个textColor可以以两种方式设置,要么是关联一个值,要么是直接设置一个颜色的RGB值,这个不难理解,因为我们可以平时也这样做过。

 

也就是说我们平时在布局文件中所使用的各类控件的属性都定义在这里面,那么这个文件,除了定义这些属性外还定义了各种具体的组件,比如TextView,Button,SeekBar等所具有的各种特有的属性

比如SeekBar:

 

<declare-styleable name="SeekBar">
        <!-- Draws the thumb on a seekbar. -->
        <attr name="thumb" format="reference" />
        <!-- An offset for the thumb that allows it to extend out of the range of the track. -->
        <attr name="thumbOffset" format="dimension" />
    </declare-styleable>

也许你会问SeekBar的background,等属性怎么没有看到?这是因为Android中几乎所有的组件都是从View中继承下来的,SeekBar自然也不例外,而background这个属性几乎每个控件都有,因此被定义到了View中,你可以在declare-styleable:View中找到它。 

 

总结下,也就是说attrs.xml这个文件定义了布局文件中的各种属性attr:***,以及每种控件特有的属性declare-styleable:***

 

2.styles.xml:

刚才的attrs.xml定义的是组件的属性,现在要说的style则是针对这些属性所设置的值,一些默认的值。

 

 

 

这个是SeekBar的样式,我们可以看到,这里面设置了一个SeekBar的默认的样式,即为attrs.xml文件中的各种属性设置初始值

<style name="Widget.SeekBar">
        <item name="android:indeterminateOnly">false</item>
        <item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
        <item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
        <item name="android:minHeight">20dip</item>
        <item name="android:maxHeight">20dip</item>
        <item name="android:thumb">@android:drawable/seek_thumb</item>
        <item name="android:thumbOffset">8dip</item>
        <item name="android:focusable">true</item>
    </style>

 这个是Button的样式:

<style name="Widget.Button">
        <item name="android:background">@android:drawable/btn_default</item>
        <item name="android:focusable">true</item>
        <item name="android:clickable">true</item>
        <item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
        <item name="android:textColor">@android:color/primary_text_light</item>
        <item name="android:gravity">center_vertical|center_horizontal</item>
    </style>

 

有了属性和值,但是这些东西是如何关联到一起的呢?它们如何被android的framework层所识别呢?

 

3.组件的源码

我们看下TextView的源码:

public TextView(Context context) {
        this(context, null);
    }//这个构造器用来给用户调用,比如new TextView(this);

    public TextView(Context context,
                    AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.textViewStyle);
    }

    public TextView(Context context,
                    AttributeSet attrs,
                    int defStyle) {
        super(context, attrs, defStyle);//为用户自定义的TextView设置默认的style
        mText = "";

        //设置画笔
        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        mTextPaint.density = getResources().getDisplayMetrics().density;
        mTextPaint.setCompatibilityScaling(
                getResources().getCompatibilityInfo().applicationScale);
       
        mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mHighlightPaint.setCompatibilityScaling(
                getResources().getCompatibilityInfo().applicationScale);

        mMovement = getDefaultMovementMethod();
        mTransformation = null;

        //attrs中包含了这个TextView控件在布局文件中定义的属性,比如android:background,android:layout_width等
        //com.android.internal.R.styleable.TextView中包含了TextView中的针对attrs中的属性的默认的值
        //也就是说这个地方能够将布局文件中设置的属性获取出来,保存到一个TypeArray中,为这个控件初始化各个属性
        TypedArray a =
            context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.TextView, defStyle, 0);

        int textColorHighlight = 0;
        ColorStateList textColor = null;
        ColorStateList textColorHint = null;
        ColorStateList textColorLink = null;
        int textSize = 15;
        int typefaceIndex = -1;
        int styleIndex = -1;

        /*
         * Look the appearance up without checking first if it exists because
         * almost every TextView has one and it greatly simplifies the logic
         * to be able to parse the appearance first and then let specific tags
         * for this View override it.
         */
        TypedArray appearance = null;
        //TextView_textAppearance不太了解为什么要这样做?难道是为了设置TextView的一些默认的属性?
        int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1);
        if (ap != -1) {
            appearance = context.obtainStyledAttributes(ap,
                                com.android.internal.R.styleable.
                                TextAppearance);
        }
        if (appearance != null) {
            int n = appearance.getIndexCount();
            for (int i = 0; i < n; i++) {
                int attr = appearance.getIndex(i);

                switch (attr) {
                case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
                    textColorHighlight = appearance.getColor(attr, textColorHighlight);
                    break;

                case com.android.internal.R.styleable.TextAppearance_textColor:
                    textColor = appearance.getColorStateList(attr);
                    break;

                case com.android.internal.R.styleable.TextAppearance_textColorHint:
                    textColorHint = appearance.getColorStateList(attr);
                    break;

                case com.android.internal.R.styleable.TextAppearance_textColorLink:
                    textColorLink = appearance.getColorStateList(attr);
                    break;

                case com.android.internal.R.styleable.TextAppearance_textSize:
                    textSize = appearance.getDimensionPixelSize(attr, textSize);
                    break;

                case com.android.internal.R.styleable.TextAppearance_typeface:
                    typefaceIndex = appearance.getInt(attr, -1);
                    break;

                case com.android.internal.R.styleable.TextAppearance_textStyle:
                    styleIndex = appearance.getInt(attr, -1);
                    break;
                }
            }

            appearance.recycle();
        }
        //各类属性
 boolean editable = getDefaultEditable();
        CharSequence inputMethod = null;
        int numeric = 0;
        CharSequence digits = null;
        boolean phone = false;
        boolean autotext = false;
        int autocap = -1;
        int buffertype = 0;
        boolean selectallonfocus = false;
        Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
            drawableBottom = null;
        int drawablePadding = 0;
        int ellipsize = -1;
        boolean singleLine = false;
        int maxlength = -1;
        CharSequence text = "";
        CharSequence hint = null;
        int shadowcolor = 0;
        float dx = 0, dy = 0, r = 0;
        boolean password = false;
        int inputType = EditorInfo.TYPE_NULL;

        int n = a.getIndexCount();
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
            //通过switch语句将用户设置的,以及默认的属性读取出来并初始化
            switch (attr) {
            case com.android.internal.R.styleable.TextView_editable:
                editable = a.getBoolean(attr, editable);
                break;

            case com.android.internal.R.styleable.TextView_inputMethod:
                inputMethod = a.getText(attr);
                break;

            case com.android.internal.R.styleable.TextView_numeric:
                numeric = a.getInt(attr, numeric);
                break;

           //更多的case语句...

           case com.android.internal.R.styleable.TextView_textSize:
                textSize = a.getDimensionPixelSize(attr, textSize);//设置当前用户所设置的字体大小
                break;

            case com.android.internal.R.styleable.TextView_typeface:
                typefaceIndex = a.getInt(attr, typefaceIndex);
                break;
           //更多的case语句...
}

 

通过上面的代码大概可以知道,每个组件基本都有3个构造器,其中只传递一个Context上下文的那个构造器一般用来在java代码中实例化使用。

比如你可以

TextView tv = new TextView(context);

 来实例化一个组件。

 

最终调用的是第3个构造器

public TextView(Context context,
                    AttributeSet attrs,
                    int defStyle)

 

 在这个构造器中为你设置了默认的属性attrs和值styles。关键不在这里,而是后面通过使用下面的代码

TypedArray a =
            context.obtainStyledAttributes(
                attrs, com.android.internal.R.styleable.TextView, defStyle, 0);

 来将属性和值获取出来,放到一个TypeArray中,然后再利用一个switch语句将里面的值取出来。再利用这些值来初始化各个属性。这个View最终利用这些属性将这个控件绘制出来。

如果你在布局文件中定义的一个View的话,那么你定义的值,会被传递给构造器中的attrs和styles。也是利用同样的方式来获取出你定义的值,并根据你定义的值来绘制你想要的控件。

再比如其实Button和EditText都是继承自TextView。看上去两个控件似乎差异很大,其实不然。Button的源码其实相比TextView变化的只是style而已:

public class Button extends TextView {
    public Button(Context context) {
        this(context, null);
    }

    public Button(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.buttonStyle);
    }

    public Button(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
}

 再看看EditText:

public class EditText extends TextView {
    public EditText(Context context) {
        this(context, null);
    }

    public EditText(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.editTextStyle);
    }

    public EditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected boolean getDefaultEditable() {
        return true;
    }

    @Override
    protected MovementMethod getDefaultMovementMethod() {
        return ArrowKeyMovementMethod.getInstance();
    }

    @Override
    public Editable getText() {
        return (Editable) super.getText();
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        super.setText(text, BufferType.EDITABLE);
    }

    /**
     * Convenience for {@link Selection#setSelection(Spannable, int, int)}.
     */
    public void setSelection(int start, int stop) {
        Selection.setSelection(getText(), start, stop);
    }

    /**
     * Convenience for {@link Selection#setSelection(Spannable, int)}.
     */
    public void setSelection(int index) {
        Selection.setSelection(getText(), index);
    }

    /**
     * Convenience for {@link Selection#selectAll}.
     */
    public void selectAll() {
        Selection.selectAll(getText());
    }

    /**
     * Convenience for {@link Selection#extendSelection}.
     */
    public void extendSelection(int index) {
        Selection.extendSelection(getText(), index);
    }

    @Override
    public void setEllipsize(TextUtils.TruncateAt ellipsis) {
        if (ellipsis == TextUtils.TruncateAt.MARQUEE) {
            throw new IllegalArgumentException("EditText cannot use the ellipsize mode "
                    + "TextUtils.TruncateAt.MARQUEE");
        }
        super.setEllipsize(ellipsis);
    }
}

 不知道你是不是和我一样感到意外呢?

 

不得不说这种方式非常的好。最大程度地利用了继承,并且可以让控件之间的属性可以很方便的被开发者使用。也利用以后的扩展,实际上,不同的style就可以得到不同的UI,这也是MVC的一种体现。

比如用户想自定义某个控件,只要覆盖父类的style就可以很轻松的实现,可以参考我的一篇博文,就是使用style自定义ProgressBar

Android中的主题theme也是使用的style。当用户在Activity中设置一个style的时候那么会影响到整个Activity,如果为Application设置style的话,则会影响所有的Activity,所以,如果你在开发一个应用的时候

可以考虑将应用的Activity的背景颜色等一类的属性放到一个style中去,在Application中调用,这种做法会比较方便。

themes.xml:

<!-- Variant of the default (dark) theme with no title bar -->
    <style name="Theme.NoTitleBar">
        <item name="android:windowNoTitle">true</item>
    </style>
    
    <!-- Variant of the default (dark) theme that has no title bar and
         fills the entire screen -->
    <style name="Theme.NoTitleBar.Fullscreen">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowContentOverlay">@null</item>
    </style>

 我们平时使用的主题实际上就定义在这个文件中。也是一个style。

 

 

 

  • 大小: 103.7 KB
  • 大小: 127.9 KB
  • 大小: 53.9 KB
分享到:
评论
5 楼 魅离儿 2015-04-05  
魅离儿 写道
前辈!问一下.attrs.xml这个文件在源码的什么位置啊

已解决。。
4 楼 魅离儿 2015-04-05  
前辈!问一下.attrs.xml这个文件在源码的什么位置啊
3 楼 wufengwuhen 2014-11-12  
写的很好,给了我很大启发!
2 楼 js1986 2014-08-02  
楼主再么,问个问题,为啥我自己定义了一个 父类 FatherView 然后在 declare-styleable 中定义了一些属性 ,在 在FatherView 的子类的 xml 中按 alt+/无法提示呢?
1 楼 chishangjin 2013-01-25  
虽不明,但觉厉!

相关推荐

    Android自定义控件使用attrs属性Demo

    Android自定义控件的使用与自定义控件中使用自定义attrs.xml属性标签

    Android中自定义属性attrs.xml、TypedArray的使用

    Android中自定义属性attrs.xml、TypedArray的使用,只是做的一个示例,不喜勿喷

    Android自定义View中attrs.xml的实例详解

    Android自定义View中attrs.xml的实例详解 我们在自定义View的时候通常需要先完成attrs.xml文件 在values中定义一个attrs.xml 然后添加相关属性 这一篇先详细介绍一下attrs.xml的属性。 &lt;?xml version=1.0 ...

    详解Android自定义控件属性TypedArray以及attrs

    主要为大家介绍了android自定义控件属性TypedArray以及attrs,感兴趣的小伙伴们可以参考一下

    Android代码-一个对安卓应用支持多种主题的库

    它支持静态设置控件使用主题元素的方式——layout的xml中定义控件时使用,也支持程序动态设置——主题控件辅助类来动态改变主题元素。 目前已经支持Style中设置attrRes来支持多主题控件属性切换。 可以很容易地在...

    基于Android studio实现的电池控件view,有水平和垂直两个方向,同时根据电池电量更改电池中的电量颜色

    基于Android studio实现的电池控件view,有水平和垂直两个方向,同时根据电池电量更改电池中的电量颜色 1.在values目录下新建attrs,添加所需要的名字啊,包括可以更改的电池排列方向,电池颜色,电池电量。 2.创建...

    Android自定义控件深入学习 Android生成随机验证码

    在上一篇的文章中介绍了自定义控件的属性,详情见《详解Android自定义控件属性TypedArray以及attrs》。那么在这基础上实现随机验证码生成,里面的代码是自定义控件以及涉及到自定义view绘画。 1、先看实现的效果图 ...

    Android控件架构与自定义控件详解(二)——自定义View

    在自定义View时,我们通常会去重写onDraw()方法来绘制View的显示内容。如果该View还需要使用wrap_content属性,那么还必须重写onMeasure()方法。另外,通过自定义attrs属性,还可以设置新的属性配置值。

    asm-attrs.jar

    asm-attrs.jar \asm-attrs.jar

    android 自定义控件 自定义属性详细介绍

    自定义控件在android中无处不见,... 一、控件自定义属性介绍 以下示例中代码均在values/attrs.xml 中定义,属性均可随意命名。 1. reference:参考某一资源ID。 示例: [java] 代码如下: &lt;declare xss=removed&gt; &lt;at

    attrs.xml文件

    解决error: No resource identifier found for attribute 'cardCornerRadius' in package 'com.example.test' android\sdk\extras\android\support\v7\cardview\res\values\attrs.xml 把这个拷贝到工程目录下就有了

    Android自定义控件的步骤

    学习初衷:在工作实际开发过程中,原有的安卓控件已不能满足实际的功能需求,而且有些应用还需要一些独特的展示效果,这时就需要自定义控件来定制控件去满足我们的需求了。 自定义控件的步骤 步骤一:首先要新建一...

    Android UI开发专题

    近期很多网友对Android用户界面的设计表示很感兴趣,对于Android UI开发自绘控件和游戏制作而言掌握好绘图基础是必不可少的。本次专题分10节来讲述,有关OpenGL ES相关的可能将放到以后再透露。本次主要涉及以下四个...

    Vue组件通信$attrs、$listeners实现原理解析

    主要介绍了Vue组件通信$attrs、$listeners实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    详解Android自定义控件属性

    在Android开发中,往往要用到自定义的控件来实现我们的需求或效果。在使用自定义 控件时,难免要用到自定义属性,那怎么使用自定义属性呢? 在文件res/values/下新建attrs.xml属性文件,中定义我们所需要的属性。 ...

    create_enum_from_xml:从 Android attrs.xml 创建 Java 枚举

    从 Android attrs XML 生成 Java 枚举类介绍(和警告) 这是一个黑客。 而已。 我敢肯定,拥有比我更多 Gradle 技能的人可能可以将它变成一个合适的插件,但是如果你需要一些快速而肮脏的东西,试试这个……但如果它...

Global site tag (gtag.js) - Google Analytics