`

从源码中浅析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`文件来定义自定义属性,以便在布局文件中更灵活地配置和使用这些控件。 首先,我们了解`attrs.xml`文件的作用。这个文件通常位于`res/values`目录...

    android 自定义控件源码实现

    本文将深入探讨如何在Android中实现自定义控件,特别是针对"TestImgTextButton"这个自定义按钮的源码分析。 首先,我们要了解自定义控件的基本流程。自定义控件通常包括以下几个步骤: 1. **创建新类**:自定义...

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

    在这个例子中,`init()`方法从`attrs`参数中获取`TypedArray`对象,然后通过`getColor()`、`getDimension()`和`getString()`方法分别获取`customColor`、`customTextSize`和`customFont`的值。注意,`R.styleable....

    Android自定义组合控件

    本文将深入探讨如何根据【标题】"Android自定义组合控件"和【描述】中的内容,结合【标签】"android 自定义控件 组合控件 自定义属性",来创建一个自己的自定义组合控件。 首先,我们要明白什么是自定义控件。在...

    《Android自定义控件入门到实战》源码

    《Android自定义控件入门到实战》源码提供了一套完整的自定义控件学习资源,涵盖了从基础到高级的各种实例,帮助开发者深入理解和实践Android自定义控件的开发。 自定义控件的核心在于扩展Android内置的View或...

    android 自定义控件 源码

    在Android开发中,自定义控件是提升应用独特性和用户体验的重要手段。自定义控件允许开发者根据需求创建具有特殊功能或独特设计的组件,从而更好地满足应用的个性化需求。本资源提供了一个完整的Android自定义控件...

    android自定义控件以及复用控件

    在Android开发中,自定义控件(Custom View)与复用控件是提升应用界面独特性和性能的关键技术。本文将深入探讨这两个概念,通过实践案例和源码分析,帮助开发者更好地理解和掌握它们。 首先,自定义控件允许开发者...

    android之自定义开关控件

    在Android开发中,自定义控件是提升应用用户体验和界面美观度的重要手段。"android之自定义开关控件"这个主题旨在教你如何在Android平台上创建一个类似苹果iOS Toggle的自定义开关组件。以下是对这个主题的详细讲解...

    Android自定义控件开发入门与实战.zip

    《Android自定义控件开发入门与实战》这本书深入浅出地讲解了如何在Android平台上创建和使用自定义控件,旨在帮助开发者从基础知识到实战技巧,全方位掌握这一核心技术。 一、自定义控件基础 自定义控件在Android中...

    android控件中英对照

    3. **属性定义**:通过XML定义文件,在`res/values/attrs.xml`中定义控件支持的属性,这些属性将供其他应用在布局XML中引用。 4. **布局描述**:可选地,在`res/layout`目录下创建布局XML文件,描述控件的外观和布局...

    mono for android 之 自定义控件

    自定义控件是Android应用开发中的一个重要概念,它允许开发者根据需求创建具有独特功能和外观的用户界面元素。在Mono for Android中,自定义控件同样可以实现,为开发者提供了极大的灵活性和创造力。 首先,我们...

    android 重写控件添加自定义属性

    Android提供了XML资源文件来定义控件的属性。自定义属性需要在res/values/attrs.xml文件中声明: ```xml &lt;!-- 定义自定义属性 --&gt; ``` 这里,我们声明了两个自定义属性:customTextSize(尺寸类型)和...

    Android 编写自定义控件实例

    在Android开发中,自定义控件是提升应用独特性和用户体验的重要手段。本教程将通过一个具体的实例——saRoundProgressBarDemo,来教你如何编写一个自定义的圆形进度条控件。这个自定义控件不仅提供了基本的进度显示...

    Android自定义控件之拖动条

    在Android开发中,自定义控件是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何创建一个美观的自定义拖动条控件,即"Android自定义控件之拖动条"。我们将讨论以下几个关键知识点: 1. **基础知识**:...

    自定义控件实例源码

    通过阅读和分析这些源码,我们可以学习到如何定义控件的布局XML、在Java代码中处理事件、重写onDraw()方法绘制自定义图形,以及如何在属性动画的支持下实现动态效果。 自定义控件的流程一般包括以下几个步骤: 1. ...

    android 自定义控件 博客源码

    在Android开发中,自定义控件是提升应用用户体验和界面独特性的重要手段。本文将深入探讨如何在Android中创建自定义视图,并结合提供的博客源码进行解析。 首先,了解自定义控件的基本流程。创建自定义控件通常涉及...

    android自定义组合控件

    在Android开发中,自定义组合控件是一种常见的需求,它允许开发者根据具体应用的界面设计和功能需求,创建独特且个性化的用户界面组件。本文将深入探讨如何在Android中实现自定义组合控件,以及这一过程中的关键知识...

    Android应用源码数字标识控件.zip

    通过分析和学习这个源码,开发者可以了解到如何在Android中创建一个自定义的数字标识控件,并将其集成到自己的应用中。这将有助于提升开发者对于Android UI编程的理解,尤其是自定义视图的实现技巧,这对于构建独特...

    Android自定义控件视频下载链接

    在Android开发中,自定义控件是提升应用用户体验和界面独特性的重要手段。自定义控件允许开发者根据项目需求创建独特的视图元素,这不仅能够满足个性化设计的需求,还可以优化功能实现,使得代码更加模块化。本篇...

    安卓Android源码——自定义控件入门级demo.rar

    本示例"安卓Android源码——自定义控件入门级demo"提供了一个学习和实践的基础平台,旨在帮助开发者掌握自定义控件的基本步骤和技巧。我们将深入探讨这个入门级demo中涉及的关键知识点。 1. **自定义控件基础** ...

Global site tag (gtag.js) - Google Analytics