Android自訂View實現開關按鈕_Android

來源:互聯網
上載者:User

 前言:Android自訂View對於剛入門乃至工作幾年的程式員來說都是非常恐懼的,但也是Android進階學習的必經之路,平時項目中經常會有一些苛刻的需求,我們可以在GitHub上找到各種各樣的效果,能用則用,不能用自己花功夫改改也能草草了事。不過隨著工作經驗和工作性質,越來越覺得自訂View是時候有必要自己花點功夫研究一下。

一、經過這兩天的努力,自己也嘗試著寫了一個Demo,效果很簡單,就是開關按鈕的實現。

可能有的人會說這效果so easy,找UI切三張圖就完事了,何必大費周折自訂。你說的沒錯,不過這裡只是用來學習自訂View來展示這麼一個常見案例。

自訂控制項

1.為什麼自訂View?

Android自身帶的控制項不能滿足需求, 需要根據自己的需求定義控制項.

2.Android 的介面繪製流程?

 

onMeasure()——onLayout()——onDraw()方法都在Activity生命週期的onResume()方法之後執行。

3.Android自訂View的方式?

整合View:View流程

onMeasure() (在這個方法裡指定自己的寬高) -> onDraw() (繪製自己的內容)

整合ViewGroup:ViewGroup流程

onMeasure() (指定自己的寬高, 所有子View的寬高)-> onLayout() (擺放所有子View) -> onDraw() (繪製內容)

自訂View實現開關按鈕步驟:

寫個類繼承View,

拷貝包含包名的全路徑到xml中,

介面中找到該控制項, 設定初始資訊,

根據需求繪製介面內容,

響應使用者的觸摸事件,

建立一個狀態更新監聽.

1.自訂ToggleView整合View,並且重新三個構造方法。

注意:構造方法為什麼要重寫三個?

ToggleView(Context context)一個參數的構造方法是用於代碼建立控制項時調用的

ToggleView(Context context, AttributeSet attrs)用於在xml裡使用, 可指定自訂屬性

ToggleView(Context context, AttributeSet attrs, int defStyle)用於在xml裡使用, 可指定自訂屬性, 如果指定了樣式, 則走此建構函式

我們在XML中定義了背景圖片、開關按鈕圖片和開關預設狀態,要擷取在XML檔案定義的屬性就在包含三個參數的構造方法裡用TypedArray類來擷取。

在attrs.xml聲明節點declare-styleable

<declare-styleable name="ToggleView"><attr name="switch_background" format="reference" /><attr name="slide_button" format="reference" /><attr name="switch_state" format="boolean" /></declare-styleable>/*** 用於在xml裡使用, 可指定自訂屬性, 如果指定了樣式, 則走此建構函式* @param context* @param attrs* @param defStyle*/public ToggleView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);// 擷取配置的自訂屬性TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ToggleView, defStyle, 0);int switchBackgroundResource = a.getResourceId(R.styleable.ToggleView_switch_background, -1);int slideButtonResource = a.getResourceId(R.styleable.ToggleView_slide_button, -1);mSwitchState = a.getBoolean(R.styleable.ToggleView_switch_state, false);//擷取背景圖片和開關圖片後設定圖片,便於在onMeasure()方法中設定View寬和高,防止NullsetSwitchBackgroundResource(switchBackgroundResource);setSlideButtonResource(slideButtonResource);init();}

2.自訂ToggleView整合View後,在XML檔案裡不要忘記添加命名空間

“xmlns:cb=”http://schemas.android.com/apk/res-auto””

然後將自訂View的完整路徑粘貼到XML中,這點類似於Android v4包下的ViewPager控制項

以下便是demo中XML檔案代碼:

設定開關背景圖片

- cb:switch_background=”@drawable/switch_background”

設定開關按鈕圖片

- cb:slide_button=”@drawable/slide_button”

設定開關預設狀態

- cb:switch_state=”false”

3.介面中找到該控制項, 設定初始資訊

在Activity中通過findViewById方法找到自訂的View控制項,和系統的組件操作沒區別。

private ToggleView toggleView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);toggleView = (ToggleView) findViewById(R.id.toggleView);// toggleView.setSwitchBackgroundResource(R.drawable.switch_background);// toggleView.setSlideButtonResource(R.drawable.slide_button);// toggleView.setSwitchState(true);// // 設定開關更新監聽toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){@Overridepublic void onStateUpdate(boolean state) {Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show();}});}

4.根據需求繪製介面內容

已經通過onMeasure()方法設定了View的寬度和高度,下面開始繪製的操作就全部在onDraw()方法中進行,onDraw(Canvas canvas) 方法中canvas參數:畫布, 畫板. 在上邊繪製的內容都會顯示到介面上.

// 根據開關狀態boolean, 直接設定圖片位置if(mSwitchState){// 開int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);}else {// 關canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);}

開關開啟時,開關按鈕的位置在開關背景中的位置計算:

int newLeft = switchBackgroupBitmap.getWidth() - 

slideButtonBitmap.getWidth(); 背景的寬度-按鈕的寬度就是當前開關按鈕所在的X軸上的位置點

開關關閉時,當前開關按鈕所在的X軸上的位置點=0

5.響應使用者的觸摸事件

在完成以上3步操作後,你會發現,只有在第一次進入後XML初始化預設開關狀態的boolean值才會有變化,此後點擊是沒有任何效果的,這個時候我們就要想辦法監聽手勢事件,重寫onTouchEvent(MotionEvent event)方法,相信大多數朋友對這個方法並不陌生。

MotionEvent有三種狀態:

MotionEvent.ACTION_DOWN: //按下螢幕
MotionEvent.ACTION_MOVE: //手指在螢幕上移動
MotionEvent.ACTION_UP //離開螢幕

當前需要考慮的問題是:

當手指按下螢幕後MotionEvent.ACTION_DOWN(在當前開關背景View中)開關的X軸位置應該移動到手指按下的位置;

當手指在螢幕上移動MotionEvent.ACTION_MOVE(在當前開關背景View中)開關按鈕X軸應該隨著手指移動的位置改變;

當手指離開螢幕後MotionEvent.ACTION_UP(在當前開關背景View中)開關按鈕應該判斷手指離開的位置是否是當前背景的一半位置,如果X軸位置大於View背景寬度的1/2、那麼應該處於開啟狀態,如果X軸位置小於View背景寬度的1/2,那麼應該處於關閉狀態。

如圖所示:

private OnSwitchStateUpdateListener onSwitchStateUpdateListener;// 重寫觸摸事件, 響應使用者的觸摸.@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:isTouchMode = true;System.out.println("event: ACTION_DOWN: " + event.getX());currentX = event.getX();break;case MotionEvent.ACTION_MOVE:System.out.println("event: ACTION_MOVE: " + event.getX());currentX = event.getX();break;case MotionEvent.ACTION_UP:isTouchMode = false;System.out.println("event: ACTION_UP: " + event.getX());currentX = event.getX();float center = switchBackgroupBitmap.getWidth() / 2.0f;// 根據當前按下的位置, 和控制項中心的位置進行比較. boolean state = currentX > center;// 如果開關狀態變化了, 通知介面. 裡邊開關狀態更新了.if(state != mSwitchState && onSwitchStateUpdateListener != null){// 把最新的boolean, 狀態傳出去了onSwitchStateUpdateListener.onStateUpdate(state);}mSwitchState = state;break;default:break;}// 重繪介面invalidate(); // 會引發onDraw()被調用, 裡邊的變數會重新生效.介面會更新return true; // 消費了使用者的觸摸事件, 才可以收到其他的事件.}

注意:

以上監聽onTouchEvent(MotionEvent

event)方法後還存在一個問題,不知道大家有沒有發現,我們沒有設定開關按鈕的邊界值,什麼意思呢?就是手指滑動的時候左邊和右邊可以畫出當前背景之外。

所以這裡需要對左右兩邊的X軸位置進行處理:

// Canvas 畫布, 畫板. 在上邊繪製的內容都會顯示到介面上.@Overrideprotected void onDraw(Canvas canvas) {// 1. 繪製背景canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);// 2. 繪製滑塊if(isTouchMode){// 根據目前使用者觸摸到的位置畫滑塊// 讓滑塊向左移動自身一半大小的位置float newLeft = currentX - slideButtonBitmap.getWidth() / 2.0f;int maxLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();// 限定滑區塊範圍if(newLeft < 0){newLeft = 0; // 左邊範圍}else if (newLeft > maxLeft) {newLeft = maxLeft; // 右邊範圍}canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);}else {// 根據開關狀態boolean, 直接設定圖片位置if(mSwitchState){// 開int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth();canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);}else {// 關canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);}}}

6.建立一個狀態更新監聽.

基本上所以工作已經完成,這樣我們一個自訂View已經大功告成了,當你完成這個效果後,你可能會發現有點類似於CheckBox。既然類似於CheckBox,我們知道當CheckBox點擊選中和取消選中的時候都會有ischecked()方法來擷取選中狀態,所以我們這個自訂的開關按鈕自然不能少這個功能,否則我們在介面上只有效果展示,卻沒有邏輯處理的地方。

public interface OnSwitchStateUpdateListener{// 狀態回調, 把目前狀態傳出去void onStateUpdate(boolean state);}public void setOnSwitchStateUpdateListener(OnSwitchStateUpdateListener onSwitchStateUpdateListener) {this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;}

代碼很簡單,寫一個介面,然後定義一個回調方法返回開關狀態,需要注意的是,在手指離開螢幕的時候,我們需要判斷此次操作是否改變了開關的狀態,如果沒有變化我們不做操作,如果跟上次狀態不同,則通知Activity狀態更改!

case MotionEvent.ACTION_UP:isTouchMode = false;System.out.println("event: ACTION_UP: " + event.getX());currentX = event.getX(); float center = switchBackgroupBitmap.getWidth() / 2.0f;// 根據當前按下的位置, 和控制項中心的位置進行比較. boolean state = currentX > center;// 如果開關狀態變化了, 通知介面. 裡邊開關狀態更新了.if(state != mSwitchState && onSwitchStateUpdateListener != null){// 把最新的boolean, 狀態傳出去了onSwitchStateUpdateListener.onStateUpdate(state);}mSwitchState = state;break;

Activity中回調也是非常簡單的,類似於Android系統為我們提供的setOnClickListener回調介面,之前一直用系統定義的監聽介面,這次通過簡單的一個自訂View,我們也可以給自己的View寫回調介面了。是不是覺得是件很開心的事情呢?

// 設定開關更新監聽toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){@Overridepublic void onStateUpdate(boolean state) {Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show();}});

以上所述是小編給大家介紹的Android自訂View實現開關按鈕,希望對大家有所協助,如果大家有任何疑問請給我留言,小編會及時回複大家的。在此也非常感謝大家對雲棲社區網站的支援!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.