那么還是針對我們之前寫的自定義控件:開關按鈕為例來說,在之前的基礎上,我們來看看有哪些屬性是可以自定義的:按鈕的背景圖片,按鈕的滑塊圖片,和按鈕的狀態(是開還是關),實際上都應該是可以在xml文件中直接定義的。
不妨先來看看之前我們在代碼中不依靠自定義屬性的時候,是如何寫的,我們可以在initView方法中找到這樣幾行代碼:
backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); slideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button); currentState=false;
會發現,我們是直接引用的資源文件,而不是在布局xml文件中使用的定義屬性的方式,下面我們一步步來看看要怎么樣做才可以定義使用上自定義屬性:
第一步:在res/values文件夾中添加attrs.xml文件
實際上,這個文件名並不一定要寫成attrs.xml,但是按照android源碼的寫法並且也便於別人查看代碼的時候明確這個文件的用意,還是寫成attrs.xml。
下面要如何寫呢,我們還是可以參看一下安卓的源碼:打開源碼文件夾下\frameworks\base\core\res\res\values\attrs.xml文件,我們會發現這里面定義了很多attr的標簽,里面不乏一些我們常見的屬性:
<attr name="layout_width" format="dimension">
等等,在layout_width這個標簽上面我們還可以發現一個
<declare-styleable name="ViewGroup_Layout">
declare-styleable標簽的里包含了很多根ViewGruop相關的屬性。
而在這個attrs.xml文件的最外面,是一個<resources>的標簽
到這里,我們基本上就明白了一個attrs.xml文件的結構了:
首先要一個<resources>的父標簽,然后里面可以包含一個declare-styleable的標簽,在這個標簽里面我們再定義出三個attr 標簽,分別代表我們需要定義的三個屬性:按鈕的背景圖片,按鈕的滑塊圖片,和按鈕的狀態;那么剩下的一個問題就是attr標簽中的format代表什么意思。實際上format代表的是這條屬性的值的類型:
1.reference:參考指定Theme中資源ID,這個類型意思就是你傳的值可以是引用資源
2.string:字符串,如果你想別人既能直接寫值也可以用類似"@string/test"引用資源的方式,可以寫成format="string|reference"
3.Color:顏色
4.boolean:布爾值
5.dimension:尺寸值
6.float:浮點型
7.integer:整型
8.fraction:百分數
9.enum:枚舉 ,如果你提供的屬性只能讓別人選擇,不能隨便傳入,就可以寫成這樣
<attr name="language">
<enum name="china" value="1"/>
<enum name="English" value="2"/>
</attr>
10.flag:位或運算
declare-styleable子元素:
定義一個styleable對象,每個styleable對象就是一組attr屬性的集合 注意:這里的name屬性並不是一定要和自定義類名相同,只是為了好區分對應類的屬性而已
注意:上面的屬性資源文件定義了該屬性之后,至於到底是哪個自定義View組件中來使用該屬性,該屬性到底能發揮什么作用, 就不歸該屬性資源文件管了,也就是說這個屬性資源文件是個公共的,大家都可以用,但是為了方便管理,一般都是一個自定義View里的屬性寫成一個declare-styleable集合.屬性資源所定義的屬性到底可以返回什么作用,取決於自定義組件的代碼實現
在這里,我們的attrs.xml文件寫成下面這樣:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="DJSwitch"> <attr name="current_state" format="boolean"/> <attr name="backgroundImage" format="reference"/> <attr name="slideImage" format="reference"/> </declare-styleable> </resources>
第二步:在自定義控件的類中拿到attrs.xml文件中定義的屬性的對應的值,然后賦值給我們需要設置的變量,在這里就是 背景圖片,滑塊圖片和開關狀態 這三個值。
要如何做呢?我們先將上面給出的
backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.switch_background); slideButton = BitmapFactory.decodeResource(getResources(), R.drawable.slide_button); currentState=false;
這三句注釋掉,然后換成下面的代碼
/** * 初始化View */ private void initView(Context context, AttributeSet attrs) { /* * 從資源庫中加載一個image對象 * ios UIImage *backgroundImage = [UIImage imageNamed:@"app_switch_bg"]; * 也就是說,android里面的圖像實體bitmap,可以當成是ios中的UIImage * 區別在於,ios中UIImage可以通過類方法來獲取,android里面則是通過工廠方法來實現 */ /*switch_bg_img = BitmapFactory.decodeResource(getResources(), R.drawable.app_switch_bg); switch_btn_img = BitmapFactory.decodeResource(getResources(), R.drawable.app_switch_btn); */ // 可以把這個TypedArray看成是一個包含了DJSwitch屬性集合的HashMap TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.DJSwitch); // 獲取該集合中共有多少個index int indexCount = typedArray.getIndexCount(); for (int i = 0; i < indexCount; i++) { int id = typedArray.getIndex(i); switch (id) { case R.styleable.DJSwitch_backgroundImage: switch_bg_img = ((BitmapDrawable)typedArray.getDrawable(id)).getBitmap(); break; case R.styleable.DJSwitch_slideImage: switch_btn_img = ((BitmapDrawable)typedArray.getDrawable(id)).getBitmap(); break; case R.styleable.DJSwitch_current_state: currentState = typedArray.getBoolean(id, false); break; default: break; } } switchBtnMaxSlideDistance = switch_bg_img.getWidth() - switch_btn_img.getWidth(); if (currentState) { switchBtnX = switchBtnMaxSlideDistance; } else { switchBtnX = 0; } typedArray.recycle(); // 添加監聽 setOnClickListener(new MyOnSwitchClickListener()); // 可以理解為ios中的addTarget方法,或addGestureRecognizer }
注釋上面已經寫得很清楚了,TypedArray會把XML文件所引用的自定義屬性和值保存在一個Map表中,因此我們可以根據該Map的鍵(即:屬性的ID)取出該屬性對應的值。
第三步:在布局文件中使用自定義屬性,並設置屬性值:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:alex="http://schemas.android.com/apk/res/com.example.test" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- 為了清楚的看見該View的大小及位置,給其定義背景 --> <com.example.test.view.DJSwitch android:id="@+id/sw_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#ff0000" android:layout_centerInParent="true" alex:current_state="false" alex:backgroundImage="@drawable/app_switch_bg" alex:slideImage="@drawable/app_switch_btn" /> </RelativeLayout>
在上面的代碼,我們發現我們寫成了alex:這樣的標頭,這個實際上是命名空間的簡寫(默認是app),所以我們必須要添加一個命名空間,參看一下Android的命名空間是如何寫的:
xmlns:android="http://schemas.android.com/apk/res/android"
在這里xmlns:android里面的android,是可以變化的,這里我們就改為alex,然后對於http://schemas.android.com/apk/res/android這個部分,最后的android也要改的,這里必須改成整個應用的包名,我們可以去清單文件中查找,這里是com.example.togglebutton,所以整個寫下來就是:xmlns:alex="http://schemas.android.com/apk/res/com.example.test"
於是這里一個完整的布局文件寫為:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:alex="http://schemas.android.com/apk/res/com.example.test" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <!-- 為了清楚的看見該View的大小及位置,給其定義背景 --> <com.example.test.view.DJSwitch android:id="@+id/sw_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#ff0000" android:layout_centerInParent="true" alex:current_state="false" alex:backgroundImage="@drawable/app_switch_bg" alex:slideImage="@drawable/app_switch_btn" /> </RelativeLayout>
至此,在XML自定義按鈕屬性的基本步驟已經全部完畢了。不過如果我們想同時在代碼中設置該按鈕的屬性該如何做呢?,很簡單,在代碼中為這三個自定義的按鈕屬性分別建立一個成員變量。
DJSwitch.java
/** * 標識當前按鈕狀態 * 類似於iOS中.h文件里聲明property 為BOOL類型的屬性 */ private boolean currentState; /** 滑動按鈕背景 */ private Bitmap switch_bg_img; /** 滑動按鈕滑塊 */ private Bitmap switch_btn_img;
同時提供三個接口方法
/** * 設置當前按鈕開關狀態 * @param isOpen true: 開,false: 關 */ public void setCurrentSwitchState(boolean isOpen) { this.currentState = isOpen; if (isOpen) { switchBtnX = switchBtnMaxSlideDistance; } else { switchBtnX = 0; } invalidate(); } /** * 設置當前按鈕背景圖片 * @param resId 資源ID */ public void setBackgroundImage(int resId) { switch_bg_img = BitmapFactory.decodeResource(getResources(), resId); // 因背景圖片更改,須同時更新滑塊最大移動距離 switchBtnMaxSlideDistance = switch_bg_img.getWidth() - switch_btn_img.getWidth(); invalidate(); // 相當於iOS中setNeedDisplay方法 } /** * 設置滑動按鈕圖片 * @param resId 資源ID */ public void setSlideBtnImage(int resId) { switch_btn_img = BitmapFactory.decodeResource(getResources(), resId); switchBtnMaxSlideDistance = switch_bg_img.getWidth() - switch_btn_img.getWidth(); invalidate(); }
MainActivity.java
package com.example.test; import com.example.test.view.DJSwitch; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { private DJSwitch sw_switch; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initUIView(); } private void initUIView() { sw_switch = (DJSwitch) findViewById(R.id.sw_switch); sw_switch.setCurrentSwitchState(true); sw_switch.setSlideBtnImage(R.drawable.ic_launcher); } }
最終效果: