1. 程式人生 > >Android自定義控制元件系列二:自定義開關按鈕(一)

Android自定義控制元件系列二:自定義開關按鈕(一)

這一次我們將會實現一個完整純粹的自定義控制元件,而不是像之前的組合控制元件一樣,拿系統的控制元件來實現;計劃分為三部分:自定義控制元件的基本部分,和自定義控制元件的自定義屬性

下面就開始第一部分的編寫,本次以一個定義的開關按鈕為例,下面就開始吧:

先看看效果,一個點選開關按鈕,實現點選切換開關狀態:


為了能夠講解清晰,還是來一些基本的介紹。

首先需要明確的就是自定義控制元件還是繼承自View這個類,Google在View這個類裡面提供了相當多的方法供我們使用,使用這些方法我們可以實現相當多的效果和功能,在這裡需要用到幾個主要的方法;

自定義控制元件的步驟、用到的主要方法:

1、首先需要定義一個類,繼承自View;對於繼承View的類,會需要實現至少一個構造方法;實際上這裡一共有三個構造方法:

public View (Context context)是在java程式碼建立檢視的時候被呼叫(使用new的方式),如果是從xml填充的檢視,就不會呼叫這個


public View (Context context, AttributeSet attrs)這個是在xml建立但是沒有指定style的時候被呼叫


public View (Context context, AttributeSet attrs, int defStyle)這個是在第二個基礎上新增style的時候被呼叫的

所以對於這裡來說,如果不使用style, 我們重點關注第二個構造方法即可

2、對於任何一個控制元件來說,它需要顯示在我們的介面上,那麼肯定需要定義它的大小;

在這裡Google提供了一個方法:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);我們去看這個方法的內部,實際上是呼叫了protected final void setMeasuredDimension(int measuredWidth, int measuredHeight);這個方法,其中第一個引數是view的寬,第二個引數是view的高,這樣我們就可以設定view的寬高了,但是要注意,這樣設定的單位都是畫素

3、對於一個需要顯示的控制元件來說,我們往往還需要確定它的位置:

這就要求重寫onLayout

方法;但是實際上這個方法在自定義view的時候使用的不多,原因是因為對於位置來說,控制元件只有建議權而沒有決定權,決定權一般在父控制元件那裡。

4、對於一個控制元件,需要顯示,我們當然需要將它繪製出來,這裡就需要重寫onDraw方法,來將這個控制元件繪製出來

5、當控制元件狀態改變的時候,我們很可能需要重新整理view的顯示狀態,這時候就需要呼叫invalidate()方法,這個方法實際上會重新呼叫onDraw方法來重繪控制元件

6、在定義控制元件的過程中,如果需要對view設定點選事件,可以直接使用setOnClickListener方法,而不需要寫view.setOnClickListener;

7、在佈局檔案中將這個自定義控制元件定義出來,注意名字要使用全類名;而且,由於是繼承自view控制元件,所以在xml檔案中如果是view本身的屬性都可以直接使用,比如:android:layout_width等等

這裡比較關鍵的地方就在於這個onDraw方法,我們一起來看一下:

/**
	 * 畫view的方法,繪製當前view的內容
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		// super.onDraw(canvas);


		Paint paint = new Paint();
		// 開啟抗鋸齒
		paint.setAntiAlias(true);


		// 畫背景
		canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
		// 畫滑塊
		canvas.drawBitmap(slideButton, slideBtn_left, 0, paint);
	}


onDraw方法傳入的引數是一個Canvas畫布物件,這個實際上跟Java中的差不太多,我們要在畫布上畫畫也需要一個畫筆,我們這裡也將其初始化出來Paint paint = new Paint(),同時設定了一個抗鋸齒效果paint.setAntiAlias(true),然後呼叫drawBitmap的方法,先後繪製了開關的背景和開關的滑塊,分別入下圖:

                          

這裡要注意的一點就是,drawBitmap(Bitmap bitmap, float left, float top, Paint paint)方法中間的兩個float型別的引數,分別代表繪製圖形的左上角的x和y的座標(原點設定在左上角),所以這裡如果我們個繪製座標都傳入0,0,那麼開關會處在一個關的狀態,這裡,我們對於滑塊使用了一個變數slideBtn_left來設定其位置,那麼對於關閉狀態,slideBtn_left的值就應該為0,對於開啟狀態,slideBtn_left的值就應該是backgroundBitmap(背景)的寬度減去slideButton(滑塊)的寬度

那麼這樣一來,機制就比較清楚了,我們只需要在控制元件上設定一個點選事件,同時設定一個boolean變數代表開關的狀態,當點選的時候,切換這個boolean型別的變數為true或者false,同時變化slideButton的值為0或者backgroundBitmap.getWidth()-slideButton.getWidth(),然後再呼叫invalidate()方法重新整理控制元件,就可以實現基本的開關功能了

下面來看具體的程式碼,註解比較詳細:

自定義控制元件的類MyToggleButton.java,繼承自View:

package com.example.togglebutton.ui;

import com.example.togglebutton.R;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/*
 * 自定義view的幾個步驟:
 * 1、首先需要寫一個類來繼承自View
 * 2、需要得到view的物件,那麼需要重寫構造方法,其中一參的構造方法用於new,二參的構造方法用於xml佈局檔案使用,三參的構造方法可以傳入一個樣式
 * 3、需要設定view的大小,那麼需要重寫onMeasure方法
 * 4、需要設定view的位置,那麼需要重寫onLayout方法,但是這個方法在自定義view的時候用的不多,原因主要在於view的位置主要是由父控制元件來決定
 * 5、需要繪製出所需要顯示的view,那麼需要重寫onDraw方法
 * 6、當控制元件狀態改變的時候,需要重繪view,那麼呼叫invalidate();方法,這個方法實際上會重新呼叫onDraw方法
 * 7、在這其中,如果需要對view設定點選事件,可以直接呼叫setOnClickListener方法
 */

public class MyToggleButton extends View {

	/**
	 * 開關按鈕的背景
	 */
	private Bitmap backgroundBitmap;
	/**
	 * 開關按鈕的滑動部分
	 */
	private Bitmap slideButton;
	/**
	 * 滑動按鈕的左邊界
	 */
	private float slideBtn_left;
	/**
	 * 當前開關的狀態
	 */
	private boolean currentState = false;

	/**
	 * 在程式碼裡面建立物件的時候,使用此構造方法
	 * 
	 * @param context
	 */
	public MyToggleButton(Context context) {
		super(context);
	}

	/**
	 * 在佈局檔案中宣告的view,建立時由系統自動呼叫
	 * 
	 * @param context
	 * @param attrs
	 */
	public MyToggleButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		initView();
	}

	/**
	 * 測量尺寸時的回撥方法
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		// 設定當前view的大小 width:view的寬,單位都是畫素值 heigth:view的高,單位都是畫素值
		setMeasuredDimension(backgroundBitmap.getWidth(),
				backgroundBitmap.getHeight());
	}

	// 這個方法對於自定義view的時候幫助不大,因為view的位置一般由父元件來決定的
	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
	}

	/**
	 * 畫view的方法,繪製當前view的內容
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		// super.onDraw(canvas);

		Paint paint = new Paint();
		// 開啟抗鋸齒
		paint.setAntiAlias(true);

		// 畫背景
		canvas.drawBitmap(backgroundBitmap, 0, 0, paint);
		// 畫滑塊
		canvas.drawBitmap(slideButton, slideBtn_left, 0, paint);
	}

	/**
	 * 初始化view
	 */
	private void initView() {
		backgroundBitmap = BitmapFactory.decodeResource(getResources(),
				R.drawable.switch_background);
		slideButton = BitmapFactory.decodeResource(getResources(),
				R.drawable.slide_button);

		/*
		 * 點選事件
		 */
		setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {

				currentState = !currentState;
				flushState();
				flushView();
			}
		});
	}

	/**
	 * 重新整理檢視
	 */
	protected void flushView() {
		// 重新整理當前view會導致ondraw方法的執行
		invalidate();
	}

	/**
	 * 重新整理當前的狀態
	 */
	protected void flushState() {
		if (currentState) {
			slideBtn_left = backgroundBitmap.getWidth()
					- slideButton.getWidth();
		} else {
			slideBtn_left = 0;
		}
	}

}


在佈局檔案中將其定義出來:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >


    <com.example.togglebutton.ui.MyToggleButton
        android:id="@+id/my_toggle_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />


</RelativeLayout>

在這裡由於沒有寫任何點選觸發業務的邏輯,只是一個單純的控制元件,所以在MainActivity裡面沒有加入多的程式碼:

package com.example.togglebutton;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

	}
}

至此一個自定義的開關按鈕就完成了,後面兩篇將會介紹如何在上面實現 和如何實現自定義屬性,謝謝支援!