Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android簡易實戰教程--第二十七話《自定義View入門案例之開關按鈕詳細分析》

Android簡易實戰教程--第二十七話《自定義View入門案例之開關按鈕詳細分析》

編輯:關於Android編程

對於自定義view,可能是一個比較大的瓶頸期。筆者也是如此,就像毛主席說的,抓住主要矛盾,一切都不難。一些大神也聲稱過自定義view並不難。PS筆者比較實在,是真還沒到感覺自定義view不難的水平!

這一篇文章,將對一個案例做詳細的代碼分析;即使說是入門的案例,但是掌握起來也不是那麼容易;筆者盡量把所有代碼標注注釋幫助理解,同時,我覺得對初學者入門可能還要一段時間。

首先簡單介紹view的繪制過程以及一些簡單的理論知識:

View的繪制流程(相對性)
1.mearsue: 測量,final,控制控件的大小
2.layout: 布局,用來控制自己的布局位置
3.draw: 繪制,用來控制控件的顯示樣式

mearsure --> layout ---> draw。這是系統的繪制調用過程。但是對於開發者而言,暴露的方法是下邊這三個方法:1.onMearsure: 2.onLayout:3.onDraw:

View的行為:
1.click,longClick,安卓系統沒有這些方法,只有ontauch方法,都是封裝好的
1.dispatchTouchEvent():touch分發,android希望用來處理是否分發touch事件
2.onInterceptTouchEvent():touch攔截,android希望處理是否攔截touch事件
1.是否攔截孩子touch
3.onTouchEvent(): touch處理, anroid希望開發人員封裝觸摸行為給用戶提供交換
4.setOnTouchListener():暴露給開發者,實現監聽的回調

View的刷新調用順序: invalidate() ---> draw() ---> onDraw()

好了,廢話了一大堆。也該來點代碼 ”解解渴“。先看一下自定義View類是咋個回事:

 

package com.itydl.Switch;

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

public class SwitchView extends View {

	private Bitmap mSwitchBackground;
	Bitmap mSwitchSlide;
	private boolean isOpened = true;// 滑塊的狀態,默認開啟

	/** 為行為打標記 **/
	private final static int STATE_NONE = 0;// 默認狀態值,空狀態
	private final static int STATE_DOWN = 1;// 按下狀態值
	private final static int STATE_MOVE = 2;// 移動狀態值
	private final static int STATE_UP = 3;// 點擊松開狀態值
	private float curentX;

	private int currentState = STATE_NONE;// 當前狀態標記,默認空狀態
	private OnSwitchClickListener mListener;

	public SwitchView(Context context) {
		this(context, null);
		// 構造方法,new的時候調用
	}

	public SwitchView(Context context, AttributeSet attrs) {
		super(context, attrs);
		//構造方法,寫布局的時候調用
	}

	/**
	 * 設置背景的圖片
	 * 
	 * @param resId
	 *            設置背景圖片的資源id
	 */
	public void setSwitchBackground(int resId) {
		// 背景圖片
		mSwitchBackground = BitmapFactory.decodeResource(getResources(), resId);
	}

	/**
	 * 設置滑塊的圖片
	 * 
	 * @param resId
	 *            設置滑塊圖片的資源id
	 */
	public void setSwitchSlide(int resId) {
		// 滑塊
		mSwitchSlide = BitmapFactory.decodeResource(getResources(), resId);
	}

	/**
	 * 當前控件測量自己的寬高時,調用此方法
	 */
	// 要不要繪制控件的大小?
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// 用於設置自己的大小。在onMearsure裡面調用setMeasuredDimension(width,height):
		// 用來設置(測量)自己的大小
		if (mSwitchBackground != null) {
			int width = mSwitchBackground.getWidth();// 獲取背景圖片的大小,寬度
			int height = mSwitchBackground.getHeight();// 獲取背景圖片的大小,高度
			setMeasuredDimension(width, height);// 設置背景大小。設置【控件自己的寬高】正好等於背景圖片的寬高
		} else {
			// 系統默認設置的大小(其實是填充父組件大小)
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		}
	}

	/**
	 * 當前控件開始繪制時,回調此方法
	 * 繪制開關的狀態
	 */
	// 要不要設置控件的樣子?繪制
	@Override
	protected void onDraw(Canvas canvas) {
		// 父類空實現,注銷下邊代碼
		// super.onDraw(canvas);
		// 繪制背景的顯示
		if (mSwitchBackground != null) {
			int left = 0;//bitmap相對於控件是貼在一起的,所以值為0
			int top = 0;
			/**
			 * bitmap The bitmap to be drawn 要繪制誰,繪制背景圖片 表示相對控件左上角坐標位置: 
			 * position of the left side of the bitmap being drawn ,繪制的背景圖片,相對於控件的相對寬度
			 * position of the top side of the bitmap being drawn ,繪制的背景圖片,相對於控件的相對高度
			 * paint used to draw the bitmap (may be null) 畫筆為null
			 */
			//繪制背景的顯示,背景圖片繪制出來。
			canvas.drawBitmap(mSwitchBackground, left, top, null);//在控件上畫圖片
		}

		if (mSwitchSlide == null) {
			return;
		}

		// 獲取控件滑塊的中點坐標
		int slideWidth = mSwitchSlide.getWidth();// 滑塊的寬度
		// 獲取背景的寬度坐標
		int switchBackWidth = mSwitchBackground.getWidth();// 背景寬度

		switch (currentState) {//onDraw()不知道具體什麼狀態,因此用使用狀態標記
		case STATE_DOWN:
		case STATE_MOVE://移動狀態和按下狀態是一樣的

			// 是否是按下
			if (!isOpened) {
				// 滑塊處於關閉狀態
				// 判斷左右,設置狀態
				if (curentX < slideWidth / 2f) {//點擊左側
					// 點擊的滑塊左側,不變化。但是要有一次繪制當前狀態位置,繪制在左側
					canvas.drawBitmap(mSwitchSlide, 0, 0, null);
				} else {
					// 點擊滑塊的右側,滑塊的中線和按下鼠標的x坐標對齊。計算滑塊左側的坐標值
					float left = curentX - slideWidth / 2f;
					// 獲取最大的left值(滑塊往右滑動,有臨界值,否則會滑出背景)
					float maxLeft = switchBackWidth - slideWidth;
					// 判斷是否越界
					if (left > maxLeft) {//如果越界
						left = maxLeft;// 設置滑塊左側坐標為最大位置
					}
					// 繪制這個位置
					canvas.drawBitmap(mSwitchSlide, left, 0, null);
				}

			} else {
				// 滑塊處於打開狀態
				// 獲取滑塊的中線坐標
				float middleX = switchBackWidth - slideWidth / 2f;
				if (curentX > middleX) {
					// 點擊的位置在滑塊的中線右側,無反應,設置默認
					canvas.drawBitmap(mSwitchSlide, switchBackWidth
							- slideWidth, 0, null);
				} else {
					// 點擊的位置在滑塊的中線左側,滑塊中線等於當前點擊的x值位置
					// 當前滑塊左邊的坐標
					float left = curentX - slideWidth / 2f;
					if (left < 0) {// 點擊左邊不要越界
						left = 0;
					}
					// 點擊位置沒有越界,滑塊中線等於當前點擊的x值位置
					canvas.drawBitmap(mSwitchSlide, left, 0, null);

				}

			}
			break;

		case STATE_UP:
			if (!isOpened) {
				// 關閉狀態
				canvas.drawBitmap(mSwitchSlide, 0, 0, null);

				// System.out.println("關閉");
			} else {

				canvas.drawBitmap(mSwitchSlide, switchBackWidth - slideWidth,
						0, null);

				// System.out.println("打開");
			}

			break;
		case STATE_NONE:// 設置默認狀態
			// 繪制滑塊,判斷狀態標記,來設置滑塊的默認狀態位置
			if (!isOpened) {// 關閉
				// 先寫死為(0,0)
				canvas.drawBitmap(mSwitchSlide, 0, 0, null);
			} else {
				// 打開狀態
				canvas.drawBitmap(mSwitchSlide, switchBackWidth - slideWidth,
						0, null);
			}

			break;

		default:
			break;
		}

	}

	/**
	 * 用戶觸摸調用
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// 重寫onTouchEvent方法就養成寫出三大狀態的習慣
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 狀態改變,行為標記改變
			currentState = STATE_DOWN;
			// 按下鼠標的相對橫坐標:
			curentX = event.getX();

			//獲取到最新坐標,需要繪制一下最新坐標。但是在onTouchEvent方法中是獲取不到canvas對象的。因此使用invalidate();
			
			invalidate();// View的刷新調用順序: invalidate() ---> draw() ---> onDraw()

			break;
		case MotionEvent.ACTION_MOVE:// 繪制過程和點擊繪制過程一樣,滑塊中點跟著當前鼠標x坐標變化
			currentState = STATE_MOVE;
			curentX = event.getX();

			invalidate();// View的刷新調用順序: invalidate() ---> draw() ---> onDraw().---->主線程調用
			//postInvalidate();//觸發刷新----->子線程調用的
			break;
		case MotionEvent.ACTION_UP:
			currentState = STATE_UP;
			curentX = event.getX();
			// 根據滑動松開位置情況,設置跳轉到打開還是關閉狀態標記
			// 獲取背景中點
			int switchBackWidth = mSwitchBackground.getWidth();
			if (curentX < switchBackWidth / 2f && isOpened) {// 節省資源,本來關閉的,不再觸發關閉打印。&& isOpened表示是否當前是打開狀態。
				// 狀態值設置為關閉狀態
				isOpened = false;
				if (mListener != null) {
					
					//調用接口方法,實際調用實現類方法
					mListener.onSwitchChanged(isOpened);
				}
			} else if (curentX >= switchBackWidth / 2f && !isOpened) {// && !isOpened表示當前是否是關閉狀態
				isOpened = true;
				if (mListener != null) {

					//調用接口方法,實際調用實現類方法
					mListener.onSwitchChanged(isOpened);
					
				}
			}

			invalidate();// View的刷新調用順序: invalidate() ---> draw() ---> onDraw()
			break;

		default:
			break;
		}
		// 消費觸摸事件,不使用父類的
		return true;
	}

	/**
	 * 調用此方法,設置開關狀態的點擊事件
	 * 
	 * @param listener
	 */
	public void setOnSwitchClickListener(OnSwitchClickListener listener) {
		this.mListener = listener;
	}

	/**
	 * 開關狀態監聽
	 * 
	 * @author lenovo
	 * 
	 */
	public interface OnSwitchClickListener {
		/**
		 * 接口回調,實現此方法,根據開關打開關閉做相應的事件。參數:打開true;關閉false
		 */
		void onSwitchChanged(boolean isOpend);
	}

}

 

自定義了view,就引入這個view——

 



    
    



 

既然設置了回調,就看看manactivity代碼:

 

package com.itydl.Switch;

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

import com.itydl.Switch.SwitchView.OnSwitchClickListener;

public class MainActivity extends Activity {

    private SwitchView mSwitchView;

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initView();
    }

	private void initView() {
		// TODO Auto-generated method stub
		setContentView(R.layout.activity_main);
		//獲取自定義view的實例
		mSwitchView = (SwitchView) findViewById(R.id.swit_switchview);
		//設置背景圖片,傳遞資源到自定義view
		mSwitchView.setSwitchBackground(R.drawable.switch_background);
		//設置滑塊資源圖片,把滑塊的資源圖片id傳遞到自定義view
		mSwitchView.setSwitchSlide(R.drawable.slide_button_background);
		
		//根據開關打開關閉,設置點擊事件
		mSwitchView.setOnSwitchClickListener(new OnSwitchClickListener() {
			
			@Override
			public void onSwitchChanged(boolean isOpend) {
				// TODO Auto-generated method stub
				Toast.makeText(getApplicationContext(), isOpend? "打開":"關閉", 0).show();
			}
		});
	}
    
}
運行結果如下:

 

\

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved