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

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

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

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

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

首先需要明確的就是自定義控件還是繼承自View這個類,Google在View這個類裡面提供瞭相當多的方法供我們使用,使用這些方法我們可以實現相當多的效果和功能,在這裡需要用到幾個主要的方法;vcD4KPHA+PGJyPgo8L3A+CjxwPjxzdHJvbmc+19S2qNLlv9i8/rXEsr3W6KGi08O1vbXE1vfSqre9t6ijujwvc3Ryb25nPjwvcD4KPHA+MaGiytfPyNDo0qq2qNLl0ru49sDgo6y8zLPQ19RWaWV3o7u21NPavMyz0FZpZXe1xMDgo6y74dDo0qrKtc/W1sHJ2dK7uPa5udTst723qKO7yrW8ysnP1eLA79K7ubLT0Mj9uPa5udTst723qKO6PC9wPgo8cD48YnI+CjwvcD4KPHA+PHN0cm9uZz5wdWJsaWMgVmlldyAoQ29udGV4dCBjb250ZXh0KTwvc3Ryb25nPsrH1NpqYXZhtPrC67S0vajK0828tcTKsbrysbu199PDKMq508NuZXe1xLe9yr0po6zI57n7yse003htbMzus+S1xMrTzbyjrL7Nsru74bX308PV4rj2PC9wPgo8cD48YnI+CjxzdHJvbmc+cHVibGljIFZpZXcgKENvbnRleHQgY29udGV4dCwgQXR0cmlidXRlU2V0IGF0dHJzKTwvc3Ryb25nPtXiuPbKx9TaeG1stLS9qLWrysfDu9PQ1ri2qHN0eWxltcTKsbrysbu199PDPC9wPgo8cD48YnI+CjxzdHJvbmc+cHVibGljIFZpZXcgKENvbnRleHQgY29udGV4dCwgQXR0cmlidXRlU2V0IGF0dHJzLCBpbnQgZGVmU3R5bGUpPC9zdHJvbmc+1eK49srH1Nq12rb+uPa7+bShyc/M7bzTc3R5bGW1xMqxuvKxu7X308O1xDxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+y/nS1LbU09rV4sDvwLTLtaOsyOe5+7K7yrnTw3N0eWxlo6wgztLDx9bYteO52Neitdq2/rj2ubnU7Le9t6i8tL/JPC9wPgo8cD48YnI+CjwvcD4KPHA+MqGittTT2sjOus7Su7j2v9i8/sC0y7WjrMv80OjSqs/Uyr7U2s7Sw8e1xL3nw+bJz6OsxMfDtL/PtqjQ6NKqtqjS5cv8tcS089Cho7vU2tXiwO9Hb29nbGXM4bmpwcvSu7j2t723qKO6PHN0cm9uZz5wcm90ZWN0ZWQgdm9pZCBvbk1lYXN1cmUoaW50IHdpZHRoTWVhc3VyZVNwZWMsIGludCBoZWlnaHRNZWFzdXJlU3BlYymjuzwvc3Ryb25nPs7Sw8fIpb+01eK49re9t6i1xMTasr+jrMq1vMrJz8rHtffTw8HLPHN0cm9uZz5wcm90ZWN0ZWQKIGZpbmFsIHZvaWQgc2V0TWVhc3VyZWREaW1lbnNpb24oaW50IG1lYXN1cmVkV2lkdGgsIGludCBtZWFzdXJlZEhlaWdodCk7PC9zdHJvbmc+1eK49re9t6ijrMbk1tC12tK7uPayzsr9ysd2aWV3tcS/7aOstdq2/rj2ss7K/crHdmlld7XEuN+jrNXi0fnO0sPHvs2/ydLUyejWw3ZpZXe1xL/tuN/By6OsPHN0cm9uZz61q8rH0qrXotLio6zV4tH5yejWw7XEtaXOu7a8ysfP8cvYPC9zdHJvbmc+PC9wPgo8cD48YnI+CjwvcD4KPHA+M6GittTT2tK7uPbQ6NKqz9TKvrXEv9i8/sC0y7WjrM7Sw8fN+c35u7nQ6NKqyLe2qMv8tcTOu9bDo7rV4r7N0qrH89bY0LQ8c3Ryb25nPm9uTGF5b3V0PC9zdHJvbmc+t723qKO7tavKx8q1vMrJz9XiuPa3vbeo1NrX1Lao0uV2aWV3tcTKsbryyrnTw7XEsru24KOs1K3S8srH0vLOqrbU09rOu9bDwLTLtaOsv9i8/ta709C9qNLpyKi2+MO709C+9raoyKijrL72tqjIqNK7sOPU2ri4v9i8/sTHwO+hozwvcD4KPHA+PGJyPgo8L3A+CjxwPjShorbU09rSu7j2v9i8/qOs0OjSqs/Uyr6jrM7Sw8e1sci70OjSqr2ry/y75tbGs/bAtKOs1eLA777N0OjSqtbY0LRvbkRyYXe3vbeoo6zAtL2r1eK49r/YvP675tbGs/bAtDwvcD4KPHA+PGJyPgo8L3A+CjxwPjWhorWxv9i8/te0zKy4xLHktcTKsbryo6zO0sPHuty/ycTc0OjSqsui0MJ2aWV3tcTP1Mq+17TMrKOs1eLKsbryvs3Q6NKqtffTw2ludmFsaWRhdGUoKbe9t6ijrNXiuPa3vbeoyrW8ysnPu+HW2NDCtffTw29uRHJhd7e9t6jAtNbYu+a/2Lz+PC9wPgo8cD48YnI+CjwvcD4KPHA+NqGi1Nq2qNLlv9i8/rXEuf2zzNbQo6zI57n70OjSqrbUdmlld8no1sO147v3ysK8/qOsv8nS1NaxvdPKudPDPHN0cm9uZz5zZXRPbkNsaWNrTGlzdGVuZXI8L3N0cm9uZz63vbeoo6y2+LK70OjSqtC0dmlldy5zZXRPbkNsaWNrTGlzdGVuZXKjuzwvcD4KPHA+PGJyPgo8L3A+CjxwPjehotTasry+1s7EvP7W0L2r1eK49tfUtqjS5b/YvP62qNLls/bAtKOs16LS4sP719bSqsq508PIq8Dgw/uju7b4x9KjrNPJ09rKx7zMs9DX1HZpZXe/2Lz+o6zL+dLU1Np4bWzOxLz+1tDI57n7ysd2aWV3sb7J7bXEyvTQ1La8v8nS1NaxvdPKudPDo6yxyMjno7o8c3Ryb25nPmFuZHJvaWQ6bGF5b3V0X3dpZHRoPC9zdHJvbmc+tci1yDwvcD4KPHA+PGJyPgo8L3A+CjxwPtXiwO+xyL3PPHN0cm9uZz652Lz8tcS12Le9PC9zdHJvbmc+vs3U2tPa1eK49jxzdHJvbmc+b25EcmF3PC9zdHJvbmc+t723qKOsztLDx9K7xvDAtL+00rvPwqO6PC9wPgo8cD48YnI+CjwvcD4KPHA+PC9wPgo8cHJlIGNsYXNzPQ==”brush:java;”>/**
* 畫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;
		}
	}

}

在佈局文件中將其定義出來:



    


在這裡由於沒有寫任何點擊觸發業務的邏輯,隻是一個單純的控件,所以在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);

	}
}

至此一個自定義的開關按鈕就完成瞭,後面兩篇將會介紹如何在上面實現點擊拖動開關的效果和如何實現自定義屬性,謝謝支持!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。