天天看点

Button源码解析Button源码解析(API 26)

Button源码解析(API 26)

概述

说到Button,实在是再熟悉不过了,那就追追它的源码吧。

啥啥啥?代码这么简单,4个构造函数和2个重写函数就完啦?Button的秘密究竟在哪里,别着急,且听细细道来。

预热

总结一下Button的特性吧:

  • 可以设置文字,所以继承TextView
  • 有Focused,Pressed,Normal等状态,对应不同状态呈现不同UI,这个View里面都有,所以间接继承View
  • 可以监听点击事件,这个也是View里的,所以间接继承View

这样看,实现一个Button好像很简单嘛(的确很简单),继承个TextView,然后对应不同状态设置不同的显示UI就好了,再简单点就用个selector,so esay。快去源码看看是不是这么回事吧,what?比我说的还简单,核心代码就一句话。然而真的这么简单吗?简单的东西用起来很爽,但是把问题简单化却很难,这背后的智慧才是我想要的,那么就来看看吧。

源码跟踪

以下代码取自Android API-26。这里仅列出主要代码(其实重要的就那么一句话):

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 defStyleAttr) {
    this(context, attrs, defStyleAttr, );
}

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

前3个构造函数都会去调用第4个构造函数,自定过View的小伙伴对这些构造函数肯定不会陌生,还不太懂的就再听我唠叨一下吧。

我们自定义一个View的同时也会自定义它的一些属性,属性的定义一般是放在

<resource>

下的

<declare-styleable>

标签中的,但属性值又从哪里获取呢?一般有这么几种途径:

  • 布局文件中定义,比如在

    <Button>

    中定义android:text=”hahaha”,这些属性值会在构造Button时传递给attrs参数(构造函数中的第二个参数)
  • 主题中定义,我们可以在AndroidManifest中指定主题,在构造Button时传递给defStyleAttr参数(构造函数中的第三个参数)
  • 样式资源中定义,一般在

    <resource>

    标签下的

    <style>

    中定义样式,在构造Button时传递给defStyleRes参数(构造函数的第四个参数)

所以当我们在代码中new Button(context)时,会调用第1个构造函数,传递到第4个构造函数时,attrs为null,defStyleAttr为com.android.internal.R.attr.buttonStyle,deyStyleRes为0.

当我们在布局文件中定义Button时,会调用第2个构造函数,此时attrs为从布局文件中获取的值,defStyleAttr为com.android.internal.R.attr.buttonStyle,deyStyleRes为0.

com.android.internal.R.attr.buttonStyle非常重要,Button的默认UI效果必定是在这里面指定的,它的定义可以在Android SDK目录下的platforms/android-xx/data/res/values/attrs.xml中找到,具体内容一会再详细说明。

先看Button的第4个构造函数,它会调用父类(TextView)的构造函数,TextView的代码这里就不多做介绍了,直接划重点:

...
TypedArray a = theme.obtainStyledAttributes(attrs,
    com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
...
           

这里会通过obtainStyleAttributes方法获取对应的属性值,这个方法很重要,得说一下。

前面说过,Button的属性值可以在好几个地方指定,但是如何获取呢,这就得靠obtainStyleAttributes方法了。它有好几个重载方法,这里直接看4个参数的,代码如下:

public TypedArray obtainStyledAttributes(AttributeSet set,
        @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
    return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
}
           

这个函数用来获取指定属性集的值,什么意思呢?比方说,我们想知道View的width,height和background,这些值的定义可以在布局文件中(对应参数set),主题中(对应参数defStyleAttr)或样式资源中(对应参数defStyleRes)。而attrs则表示想要获取哪些属性的值,如[width, height, background]。

总结一下:

  • set,布局文件中的属性值
  • attrs,指明想要获取哪些属性的值
  • defStyleAttr,主题中的属性值
  • defStyleRes,样式资源中的属性值

加入同一个属性值在多个地方被定义了咋办?优先级set

>

defStyleAttr,当defStyleAttr为0时,defStyleRes才有效。

所以再来看前面的代码,把TextView中的obainStyledAttributes的参数替换一下:

...
TypedArray a = theme.obtainStyledAttributes(attrs,
    com.android.internal.R.styleable.TextViewAppearance, com.android.internal.R.attr.buttonStyle, );
...
           

这样就清楚了,TextView获取了TextViewAppearance指定的属性的值,这些值从attrs和buttonStyle中查找。TextViewAppearance必定定义了一个属性集合,而这个集合里的属性顾名思义,必定和TextView的外观相关,如text color,typeface,size等等。

为了验证想法,来做一个小实验吧。

在默认主题上添点东西:

<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="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>

        <item name="buttonStyle">@style/MyButtonStyle</item>
    </style>

    <style name="MyButtonStyle">
        <item name="android:background">@color/colorPrimary</item>
    </style>

</resources>
           

把buttonStyle的风格指定为MyButtonStyle,在代码中或者布局文件中定义一个button看看是什么样子:

Button源码解析Button源码解析(API 26)

看吧,一点button的样子也没有了,点击也没有UI的变换了。那么Android默认主题中的Button样式是怎样定义的呢?从AppTheme沿着parent路径往前追吧,能被绕的晕死,不管了,我的直觉告诉我在platforms/android-xx/data/res/values/styles.xml文件中搜索Button肯定能找到,果不其然(看来还是有点程序猿直觉的)。

看代码:

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

这些应该就是Button的默认属性值了,都是些熟悉的属性,看看background吧,是个drawable,去drawable目录找找看,有了:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:state_enabled="true"
        android:drawable="@drawable/btn_default_normal" />
    <item android:state_window_focused="false" android:state_enabled="false"
        android:drawable="@drawable/btn_default_normal_disable" />
    <item android:state_pressed="true" 
        android:drawable="@drawable/btn_default_pressed" />
    <item android:state_focused="true" android:state_enabled="true"
        android:drawable="@drawable/btn_default_selected" />
    <item android:state_enabled="true"
        android:drawable="@drawable/btn_default_normal" />
    <item android:state_focused="true"
        android:drawable="@drawable/btn_default_normal_disable_focused" />
    <item
         android:drawable="@drawable/btn_default_normal_disable" />
</selector>
           

哈哈,还是用了个selector嘛,到此Android提供的默认Button组件就扒完了。嗯,用了主题的方式去设置Button的默认样式。

总结

配置文件被玩出花了,我们不仅能够通过布局文件去初始化View的样式,还能通过Theme去定义整个应用的样式,而且这种配置还有继承能力,果然是万事尽在配配配呀。

利用配置文件去配置组件的样式是现在十分常用的一种方式,其易用性、易读性、易扩展性和易维护性不言而喻,但这种方式的背后定是一套十分完善的框架体系。身为程序猿的我被它散发的无穷魅力所吸引,对其研究的必然会是困难重重,愿我能常保现在的求知欲,一步一个脚印地探索下去。