android自定義控件(1)-自定義控件屬性


 

那么還是針對我們之前寫的自定義控件:開關按鈕為例來說,在之前的基礎上,我們來看看有哪些屬性是可以自定義的:按鈕的背景圖片,按鈕的滑塊圖片,和按鈕的狀態(是開還是關),實際上都應該是可以在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);
    }

}

最終效果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM