原文地址:
http://li53262182.blog.163.com/blog/static/12839338720091113382422/
在Android中Button是非常常用的一個View控件,原本以爲Button實現的代碼肯定很多,但是看了原來着實吃了一驚.Button的源碼幾乎僅僅對繼承的TextView類做了一個小小的修改,僅僅是加了一個Style. 一個Style就能夠實現Button的顯示效果樣式麼?Android的Style機制真的很強大.
首先來看一下ButtonView的實現代碼:
- * <p><strong>XML attributes</strong></p>
- * <p>
- * See {@link android.R.styleable#Button Button Attributes},
- * {@link android.R.styleable#TextView TextView Attributes},
- * {@link android.R.styleable#View View Attributes}
- * </p>
- */
- ublic 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);
- }
可以看到,Button繼承了TextView之後,僅僅是添加了一個默認的Style —
com.android.internal.R.attr.buttonStyle
我們知道,button其實在TextView的基礎之上增加了按鈕的背景效果以及按鈕按下去的press效果。這麼一個Style文件可以搞定這件事情麼?順着這個style找下去,在Android的源碼中找到style.xml,並找到相關的定義:
- <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:textSize">20sp</item>
- <item name="android:textStyle">normal</item>
- <item name="android:textColor">@android:color/button_text</item>
- <item name="android:gravity">center_vertical|center_horizontal</item>
- </style>
這裏定義了好多style相關的屬性,其他的屬性都好理解,這個backgroud屬性難道僅僅是一個drawable圖片?如果僅僅是一個圖片的化,怎麼能夠實現button各種狀態下表現出不同背景的功能呢?還是來看看這個drawable到底是什麼東西。
到drwable目錄中發現這個btn_default原來也是一個xml文件,內容如下:
- <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>
其實drawable在Android中有很多種,最普通的就是一個圖片。而這裏用到的是StateListDrawable。當Android的解析器解析到上面的xml時,會自動轉化成一個StateListDrawable類的實例。這個類的一些核心代碼如下:
- public class StateListDrawable extends DrawableContainer {
- public StateListDrawable()
- {
- this(null);
- }
- /**
- * Add a new image/string ID to the set of images.
- * @param stateSet - An array of resource Ids to associate with the image.
- * Switch to this image by calling setState().
- * @param drawable -The image to show.
- */
- public void addState(int[] stateSet, Drawable drawable) {
- if (drawable != null) {
- mStateListState.addStateSet(stateSet, drawable);
- // in case the new state matches our current state...
- onStateChange(getState());
- }
- }
- ...
- }
xml裏面每一個Item就對應一種狀態,而每一個有state_的屬性就是一個狀態的描述,drawable則是真正的drawable圖片(這個其實也可以是其他類型的Drawable實例)。
當把這個實例付給View作爲Background的時候,View會根據不同的state來切換不同狀態的圖片,從而實現了Press等諸多效果。簡單看一下View中有關狀態切換的代碼如下:
各種View的狀態列表:
- /**
- * The order here is very important to {@link #getDrawableState()}
- */
- private static final int[][] VIEW_STATE_SETS = {
- EMPTY_STATE_SET, // 0 0 0 0 0
- WINDOW_FOCUSED_STATE_SET, // 0 0 0 0 1
- SELECTED_STATE_SET, // 0 0 0 1 0
- SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 0 0 1 1
- FOCUSED_STATE_SET, // 0 0 1 0 0
- FOCUSED_WINDOW_FOCUSED_STATE_SET, // 0 0 1 0 1
- FOCUSED_SELECTED_STATE_SET, // 0 0 1 1 0
- FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 0 1 1 1
- ENABLED_STATE_SET, // 0 1 0 0 0
- ENABLED_WINDOW_FOCUSED_STATE_SET, // 0 1 0 0 1
- ENABLED_SELECTED_STATE_SET, // 0 1 0 1 0
- ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 1 0 1 1
- ENABLED_FOCUSED_STATE_SET, // 0 1 1 0 0
- ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET, // 0 1 1 0 1
- ENABLED_FOCUSED_SELECTED_STATE_SET, // 0 1 1 1 0
- ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 0 1 1 1 1
- PRESSED_STATE_SET, // 1 0 0 0 0
- PRESSED_WINDOW_FOCUSED_STATE_SET, // 1 0 0 0 1
- PRESSED_SELECTED_STATE_SET, // 1 0 0 1 0
- PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 0 0 1 1
- PRESSED_FOCUSED_STATE_SET, // 1 0 1 0 0
- PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET, // 1 0 1 0 1
- PRESSED_FOCUSED_SELECTED_STATE_SET, // 1 0 1 1 0
- PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 0 1 1 1
- PRESSED_ENABLED_STATE_SET, // 1 1 0 0 0
- PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET, // 1 1 0 0 1
- PRESSED_ENABLED_SELECTED_STATE_SET, // 1 1 0 1 0
- PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 1 0 1 1
- PRESSED_ENABLED_FOCUSED_STATE_SET, // 1 1 1 0 0
- PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET, // 1 1 1 0 1
- PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET, // 1 1 1 1 0
- PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, // 1 1 1 1 1
- };
設置background的代碼:
- /**
- * Set the background to a given Drawable, or remove the background. If the
- * background has padding, this View's padding is set to the background's
- * padding. However, when a background is removed, this View's padding isn't
- * touched. If setting the padding is desired, please use
- * {@link #setPadding(int, int, int, int)}.
- *
- * @param d The Drawable to use as the background, or null to remove the
- * background
- */
- public void setBackgroundDrawable(Drawable d) {
- ...
- if (d.isStateful()) {
- d.setState(getDrawableState());
- }
- d.setVisible(getVisibility() == VISIBLE, false);
- mBGDrawable = d;
- ...
- invalidate();
- }
紅色的部分就是首先判斷這個Drawable對象是否支持state切換,當然我們這裏的drawable是支持的。然後設置狀態,達到切換圖片的效果。
所以,以後作一些圖片需要根據狀態切換不同的效果可以用這個方法啦。。。