Android實(shí)現(xiàn)史上最簡(jiǎn)單自定義開關(guān)按鈕的方法
前言
很多時(shí)候,我們?cè)诤芏酂o論是Android還是IOS的APP中都會(huì)遇到這樣的一種效果,有一個(gè)按鈕,我們點(diǎn)擊一下,便會(huì)滑動(dòng)一下,一會(huì)顯示“開”,一會(huì)顯示“關(guān)”,這便是開關(guān)按鈕了,比如:很多Android手機(jī)的設(shè)置功能里,就有很多功能是用開關(guān)按鈕實(shí)現(xiàn)的,那么這些開關(guān)按鈕時(shí)如何實(shí)現(xiàn)的呢?下面,就讓我們一起來實(shí)現(xiàn)這個(gè)功能吧。
一、原理
我們?cè)诮缑娴哪骋粋€(gè)區(qū)域里放置一個(gè)背景圖A,這個(gè)圖片一邊為“開”,一邊為“關(guān)”,在這個(gè)圖片上放置一個(gè)圖片B,圖B大約為圖A的一半,恰好可以覆蓋掉圖A上的“開”或者“關(guān)”,當(dāng)我們手指點(diǎn)擊圖片的時(shí)候,圖B在圖A上滑動(dòng),相應(yīng)的覆蓋“開”或者“關(guān)”,這樣就實(shí)現(xiàn)了開關(guān)按鈕的效果。
二、實(shí)現(xiàn)
1、自定義View類MyToggle
這個(gè)類繼承自View類同時(shí)實(shí)現(xiàn)了OnTouchListener接口,這個(gè)類實(shí)現(xiàn)的功能比較多,我們分解來看這個(gè)類。
1)屬性字段
這個(gè)類中定義了不少的屬性字段,每個(gè)屬性字段的具體含義詳見代碼注釋
具體實(shí)現(xiàn)代碼如下:
//開關(guān)開啟的背景圖片 private Bitmap bkgSwitchOn; //開關(guān)關(guān)閉的背景圖片 private Bitmap bkgSwitchOff; //開關(guān)的滾動(dòng)圖片 private Bitmap btnSlip; //當(dāng)前開關(guān)是否為開啟狀態(tài) private boolean toggleStateOn; //開關(guān)狀態(tài)的監(jiān)聽事件 private OnToggleStateListener toggleStateListener; //記錄開關(guān)·當(dāng)前的狀態(tài) private boolean isToggleStateListenerOn; //手指按下屏幕時(shí)的x坐標(biāo) private float proX; //手指滑動(dòng)過程中當(dāng)前x坐標(biāo) private float currentX; //是否處于滑動(dòng)狀態(tài) private boolean isSlipping; //記錄上一次開關(guān)的狀態(tài) private boolean proToggleState \= true; //開關(guān)開啟時(shí)的矩形 private Rect rect\_on; //開關(guān)關(guān)閉時(shí)的矩形 private Rect rect\_off;
2)覆寫View類的構(gòu)造方法
我們?cè)跇?gòu)造方法里完成的操作就是調(diào)用我們自己創(chuàng)建的init()方法
具體實(shí)現(xiàn)代碼如下:
public MyToggle(Context context) { super(context); init(context); } public MyToggle(Context context, AttributeSet attrs) { super(context, attrs); init(context); }
3)創(chuàng)建init方法
這個(gè)方法中實(shí)現(xiàn)的操作就是設(shè)置觸摸事件。
具體實(shí)現(xiàn)代碼如下:
//初始化方法 private void init(Context context) { setOnTouchListener(this); }
4)手指觸摸事件回調(diào)方法onTouch
這個(gè)方法是當(dāng)手指操作手機(jī)屏幕時(shí),Android自動(dòng)回調(diào)的方法,我們?cè)谶@個(gè)方法中,監(jiān)聽手指的按下、移動(dòng)和抬起事件,記錄手指當(dāng)前的X坐標(biāo)來判斷圖片B的移動(dòng)方向,從而實(shí)現(xiàn)圖片B在圖片A上的移動(dòng)來達(dá)到按鈕開和關(guān)的效果。
具體實(shí)現(xiàn)代碼如下:
@Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION\_DOWN: //記錄手指按下時(shí)的x坐標(biāo) proX \= event.getX(); currentX \= proX; //將滑動(dòng)標(biāo)識(shí)設(shè)置為true isSlipping \= true; break; case MotionEvent.ACTION\_MOVE: //記錄手指滑動(dòng)過程中當(dāng)前x坐標(biāo) currentX \= event.getX(); break; case MotionEvent.ACTION\_UP: //手指抬起時(shí)將是否滑動(dòng)的標(biāo)識(shí)設(shè)置為false isSlipping \= false; //處于關(guān)閉狀態(tài) if(currentX < bkgSwitchOn.getWidth() / 2 ){ toggleStateOn \= false; } else { // 處于開啟狀態(tài) toggleStateOn \= true; } // 如果使用了開關(guān)監(jiān)聽器,同時(shí)開關(guān)的狀態(tài)發(fā)生了改變,這時(shí)使用該代碼 if(isToggleStateListenerOn && toggleStateOn != proToggleState){ proToggleState \= toggleStateOn; toggleStateListener.onToggleState(toggleStateOn); } break; } invalidate();//重繪 return true; }
5)界面重繪方法onDraw
這個(gè)方法主要實(shí)現(xiàn)的是界面的重繪操作。
只要的思路是:
畫背景圖A:
當(dāng)前手指滑動(dòng)X坐標(biāo)currentX大于圖A寬度的一般時(shí),按鈕背景為開啟狀態(tài);
當(dāng)前手指滑動(dòng)X坐標(biāo)currentX小于圖A寬度的一般時(shí),按鈕背景為關(guān)閉狀態(tài);
記錄滑塊B的X坐標(biāo):
B滑動(dòng)時(shí):
當(dāng)前手指滑動(dòng)X坐標(biāo)currentX大于背景圖A的寬度,則B坐標(biāo)為圖A寬度減去圖B寬度
當(dāng)前手指滑動(dòng)X坐標(biāo)currentX小于背景圖A的寬度,則B坐標(biāo)為當(dāng)前X坐標(biāo)currentX減去滑塊寬度的一半
B靜止:
當(dāng)按鈕處于“開”狀態(tài),則B坐標(biāo)為“開”狀態(tài)的最左邊X坐標(biāo)
當(dāng)按鈕處于“關(guān)”狀態(tài),則B坐標(biāo)為“關(guān)”狀態(tài)的最左邊X坐標(biāo)
具體實(shí)現(xiàn)代碼如下:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //用來記錄我們滑動(dòng)塊的位置 int left\_slip \= 0; Matrix matrix \= new Matrix(); Paint paint \= new Paint(); if(currentX < bkgSwitchOn.getWidth() / 2){ //在畫布上繪制出開關(guān)狀態(tài)為關(guān)閉時(shí)的 背景圖片 canvas.drawBitmap(bkgSwitchOff, matrix, paint); }else{ //在畫布上繪制出開關(guān)狀態(tài)為開啟時(shí)的 背景圖片 canvas.drawBitmap(bkgSwitchOn, matrix, paint); } if(isSlipping){//開關(guān)是否處于滑動(dòng)狀態(tài) // 滑動(dòng)塊 是否超過了整個(gè)滑動(dòng)按鈕的寬度 if(currentX \> bkgSwitchOn.getWidth()){ //指定滑動(dòng)塊的位置 left\_slip \= bkgSwitchOn.getWidth() \- btnSlip.getWidth(); } else { //設(shè)置當(dāng)前滑動(dòng)塊的位置 left\_slip \= (int) (currentX \- btnSlip.getWidth() /2); } } else {//開關(guān)是否處于 不滑動(dòng)狀態(tài) if(toggleStateOn){ left\_slip \= rect\_on.left; } else { left\_slip \= rect\_off.left; } } if(left\_slip < 0){ left\_slip \= 0; } else if( left\_slip \> bkgSwitchOn.getWidth() \- btnSlip.getWidth()){ left\_slip \= bkgSwitchOn.getWidth() \- btnSlip.getWidth(); } //繪制圖像 canvas.drawBitmap(btnSlip, left\_slip, 0, paint); }
6)計(jì)算開關(guān)的寬高
這里我通過覆寫onMeasure來計(jì)算開關(guān)的寬度和高度
具體實(shí)現(xiàn)代碼如下:
//計(jì)算開關(guān)的寬高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(bkgSwitchOn.getWidth(), bkgSwitchOn.getHeight()); }
7)設(shè)置圖片資源信息
這個(gè)方法主要是供外界調(diào)用,向本類提供圖片資源。
具體代碼實(shí)現(xiàn)如下:
/\*\* \* 設(shè)置圖片資源信息 \* @param bkgSwitch\_on \* @param bkgSwitch\_off \* @param btn\_Slip \*/ public void setImageRes(int bkgSwitch\_on, int bkgSwitch\_off, int btn\_Slip) { bkgSwitchOn \= BitmapFactory.decodeResource(getResources(), bkgSwitch\_on); bkgSwitchOff \= BitmapFactory.decodeResource(getResources(),bkgSwitch\_off); btnSlip \= BitmapFactory.decodeResource(getResources(), btn\_Slip); rect\_on \= new Rect(bkgSwitchOn.getWidth() \- btnSlip.getWidth(), 0,bkgSwitchOn.getWidth(), btnSlip.getHeight()); rect\_off \= new Rect(0, 0, btnSlip.getWidth(), btnSlip.getHeight()); }
8)設(shè)置開關(guān)按鈕的狀態(tài)
通過傳遞一個(gè)boolean類型的狀態(tài),我們?cè)谶@個(gè)方法中將這個(gè)狀態(tài)標(biāo)識(shí)記錄下來。
具體實(shí)現(xiàn)代碼如下:
/\*\* \* 設(shè)置開關(guān)按鈕的狀態(tài) \* @param state \*/ public void setToggleState(boolean state) { toggleStateOn \= state; }
9)自定義開關(guān)狀態(tài)監(jiān)聽器
我在這個(gè)類中定義了一個(gè)開關(guān)狀態(tài)監(jiān)聽器接口OnToggleStateListener,里面有一個(gè)onToggleState方法來執(zhí)行按鈕的狀態(tài)變化監(jiān)聽操作。
具體代碼實(shí)現(xiàn)如下:
/\*\* \* 自定義開關(guān)狀態(tài)監(jiān)聽器 \* @author liuyazhuang \* \*/ interface OnToggleStateListener { abstract void onToggleState(boolean state); }
10)設(shè)置開關(guān)監(jiān)聽器
創(chuàng)建setOnToggleStateListener方法,傳遞一個(gè)OnToggleStateListener監(jiān)聽器對(duì)象,通過外界創(chuàng)建OnToggleStateListener對(duì)象,并將OnToggleStateListener對(duì)象傳遞進(jìn)來,我們只需要將外界傳遞過來的OnToggleStateListener對(duì)象記錄下來,同時(shí)當(dāng)我們調(diào)用OnToggleStateListener接口中的onToggleState方法時(shí),便實(shí)現(xiàn)了回調(diào)外界OnToggleStateListener實(shí)現(xiàn)類中的onToggleState方法。
具體代碼實(shí)現(xiàn)如下:
//設(shè)置開關(guān)監(jiān)聽器并將是否設(shè)置了開關(guān)監(jiān)聽器設(shè)置為true public void setOnToggleStateListener(OnToggleStateListener listener) { toggleStateListener \= listener; isToggleStateListenerOn \= true; }
11)MyToggle完整代碼如下:
package com.lyz.slip.toggle; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; /\*\* \* 自定義開關(guān)類 \* @author liuyazhuang \* \*/ public class MyToggle extends View implements OnTouchListener { //開關(guān)開啟的背景圖片 private Bitmap bkgSwitchOn; //開關(guān)關(guān)閉的背景圖片 private Bitmap bkgSwitchOff; //開關(guān)的滾動(dòng)圖片 private Bitmap btnSlip; //當(dāng)前開關(guān)是否為開啟狀態(tài) private boolean toggleStateOn; //開關(guān)狀態(tài)的監(jiān)聽事件 private OnToggleStateListener toggleStateListener; //記錄開關(guān)·當(dāng)前的狀態(tài) private boolean isToggleStateListenerOn; //手指按下屏幕時(shí)的x坐標(biāo) private float proX; //手指滑動(dòng)過程中當(dāng)前x坐標(biāo) private float currentX; //是否處于滑動(dòng)狀態(tài) private boolean isSlipping; //記錄上一次開關(guān)的狀態(tài) private boolean proToggleState \= true; //開關(guān)開啟時(shí)的矩形 private Rect rect\_on; //開關(guān)關(guān)閉時(shí)的矩形 private Rect rect\_off; public MyToggle(Context context) { super(context); init(context); } public MyToggle(Context context, AttributeSet attrs) { super(context, attrs); init(context); } //初始化方法 private void init(Context context) { setOnTouchListener(this); } @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION\_DOWN: //記錄手指按下時(shí)的x坐標(biāo) proX \= event.getX(); currentX \= proX; //將滑動(dòng)標(biāo)識(shí)設(shè)置為true isSlipping \= true; break; case MotionEvent.ACTION\_MOVE: //記錄手指滑動(dòng)過程中當(dāng)前x坐標(biāo) currentX \= event.getX(); break; case MotionEvent.ACTION\_UP: //手指抬起時(shí)將是否滑動(dòng)的標(biāo)識(shí)設(shè)置為false isSlipping \= false; //處于關(guān)閉狀態(tài) if(currentX < bkgSwitchOn.getWidth() / 2 ){ toggleStateOn \= false; } else { // 處于開啟狀態(tài) toggleStateOn \= true; } // 如果使用了開關(guān)監(jiān)聽器,同時(shí)開關(guān)的狀態(tài)發(fā)生了改變,這時(shí)使用該代碼 if(isToggleStateListenerOn && toggleStateOn != proToggleState){ proToggleState \= toggleStateOn; toggleStateListener.onToggleState(toggleStateOn); } break; } invalidate();//重繪 return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //用來記錄我們滑動(dòng)塊的位置 int left\_slip \= 0; Matrix matrix \= new Matrix(); Paint paint \= new Paint(); if(currentX < bkgSwitchOn.getWidth() / 2){ //在畫布上繪制出開關(guān)狀態(tài)為關(guān)閉時(shí)的 背景圖片 canvas.drawBitmap(bkgSwitchOff, matrix, paint); }else{ //在畫布上繪制出開關(guān)狀態(tài)為開啟時(shí)的 背景圖片 canvas.drawBitmap(bkgSwitchOn, matrix, paint); } if(isSlipping){//開關(guān)是否處于滑動(dòng)狀態(tài) // 滑動(dòng)塊 是否超過了整個(gè)滑動(dòng)按鈕的寬度 if(currentX \> bkgSwitchOn.getWidth()){ //指定滑動(dòng)塊的位置 left\_slip \= bkgSwitchOn.getWidth() \- btnSlip.getWidth(); } else { //設(shè)置當(dāng)前滑動(dòng)塊的位置 left\_slip \= (int) (currentX \- btnSlip.getWidth() /2); } } else {//開關(guān)是否處于 不滑動(dòng)狀態(tài) if(toggleStateOn){ left\_slip \= rect\_on.left; } else { left\_slip \= rect\_off.left; } } if(left\_slip < 0){ left\_slip \= 0; } else if( left\_slip \> bkgSwitchOn.getWidth() \- btnSlip.getWidth()){ left\_slip \= bkgSwitchOn.getWidth() \- btnSlip.getWidth(); } //繪制圖像 canvas.drawBitmap(btnSlip, left\_slip, 0, paint); } //計(jì)算開關(guān)的寬高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(bkgSwitchOn.getWidth(), bkgSwitchOn.getHeight()); } /\*\* \* 設(shè)置圖片資源信息 \* @param bkgSwitch\_on \* @param bkgSwitch\_off \* @param btn\_Slip \*/ public void setImageRes(int bkgSwitch\_on, int bkgSwitch\_off, int btn\_Slip) { bkgSwitchOn \= BitmapFactory.decodeResource(getResources(), bkgSwitch\_on); bkgSwitchOff \= BitmapFactory.decodeResource(getResources(),bkgSwitch\_off); btnSlip \= BitmapFactory.decodeResource(getResources(), btn\_Slip); rect\_on \= new Rect(bkgSwitchOn.getWidth() \- btnSlip.getWidth(), 0,bkgSwitchOn.getWidth(), btnSlip.getHeight()); rect\_off \= new Rect(0, 0, btnSlip.getWidth(), btnSlip.getHeight()); } /\*\* \* 設(shè)置開關(guān)按鈕的狀態(tài) \* @param state \*/ public void setToggleState(boolean state) { toggleStateOn \= state; } /\*\* \* 自定義開關(guān)狀態(tài)監(jiān)聽器 \* @author liuyazhuang \* \*/ interface OnToggleStateListener { abstract void onToggleState(boolean state); } //設(shè)置開關(guān)監(jiān)聽器并將是否設(shè)置了開關(guān)監(jiān)聽器設(shè)置為true public void setOnToggleStateListener(OnToggleStateListener listener) { toggleStateListener \= listener; isToggleStateListenerOn \= true; } }
2、MainActivity
這個(gè)類實(shí)現(xiàn)很簡(jiǎn)單,主要的功能就是加載界面布局,初始化界面控件,調(diào)用MyToggle類中的方法實(shí)現(xiàn)按鈕的開關(guān)效果
具體代碼實(shí)現(xiàn)如下:
package com.lyz.slip.toggle; import android.app.Activity; import android.os.Bundle; import android.widget.Toast; import com.lyz.slip.toggle.MyToggle.OnToggleStateListener; /\*\* \* 程序主入口 \* @author liuyazhuang \* \*/ public class MainActivity extends Activity { //自定義開關(guān)對(duì)象 private MyToggle toggle; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity\_main); toggle \= (MyToggle) findViewById(R.id.toggle); //設(shè)置開關(guān)顯示所用的圖片 toggle.setImageRes(R.drawable.bkg\_switch, R.drawable.bkg\_switch, R.drawable.btn\_slip); //設(shè)置開關(guān)的默認(rèn)狀態(tài) true開啟狀態(tài) toggle.setToggleState(true); //設(shè)置開關(guān)的監(jiān)聽 toggle.setOnToggleStateListener(new OnToggleStateListener() { @Override public void onToggleState(boolean state) { // TODO Auto-generated method stub if(state){ Toast.makeText(getApplicationContext(), "開關(guān)開啟", 0).show(); } else { Toast.makeText(getApplicationContext(), "開關(guān)關(guān)閉", 0).show(); } } }); } }
3、布局文件activity_main.xml
這里我引用了自己定義的View類MyToggle。
具體代碼實(shí)現(xiàn)如下:
<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" \> <com.lyz.slip.toggle.MyToggle android:id\="@+id/toggle" android:layout\_width\="wrap\_content" android:layout\_height\="wrap\_content" android:layout\_centerInParent\="true"/> </RelativeLayout\>
4、AndroidManifest.xml
具體代碼如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android\="http://schemas.android.com/apk/res/android" package\="com.lyz.slip.toggle" android:versionCode\="1" android:versionName\="1.0" \> <uses-sdk android:minSdkVersion\="10" android:targetSdkVersion\="18" /> <application android:allowBackup\="true" android:icon\="@drawable/ic\_launcher" android:label\="@string/app\_name" android:theme\="@style/AppTheme" \> <activity android:name\="com.lyz.slip.toggle.MainActivity" android:label\="@string/app\_name" \> <intent-filter\> <action android:name\="android.intent.action.MAIN" /> <category android:name\="android.intent.category.LAUNCHER" /> </intent-filter\> </activity\> </application\> </manifest\>
三、運(yùn)行效果
四、溫馨提示
大家可以到鏈接下載Android自定義開關(guān)按鈕實(shí)現(xiàn)示例完整源代碼
本實(shí)例中,為了方面,我把一些文字直接寫在了布局文件中和相關(guān)的類中,大家在真實(shí)的項(xiàng)目中要把這些文字寫在string.xml文件中,在外部引用這些資源,切記,這是作為一個(gè)Android程序員最基本的開發(fā)常識(shí)和規(guī)范,我在這里只是為了方便直接寫在了類和布局文件中。
到此這篇關(guān)于Android實(shí)現(xiàn)史上最簡(jiǎn)單自定義開關(guān)按鈕的文章就介紹到這了,更多相關(guān)Android自定義開關(guān)按鈕內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中NestedScrolling滑動(dòng)機(jī)制詳解
本篇文章主要介紹了Android中NestedScrolling滑動(dòng)機(jī)制詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02Android studio實(shí)現(xiàn)PopupWindow彈出框效果
這篇文章主要為大家詳細(xì)介紹了Android studio實(shí)現(xiàn)PopupWindow彈出框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Android中關(guān)于Binder常見面試問題小結(jié)
這篇文章主要介紹了Android中關(guān)于Binder幾個(gè)面試問題,binder是一種進(jìn)程間通訊的機(jī)制,進(jìn)程間通訊需要了解用戶空間和內(nèi)核空間,本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06Android實(shí)現(xiàn)局部圖片滑動(dòng)指引效果示例
現(xiàn)在滑動(dòng)效果用的比較多,尤其是在手機(jī)端上面,本文介紹了Android實(shí)現(xiàn)局部圖片滑動(dòng)指引效果示例,現(xiàn)在就分享給大家,也給大家做個(gè)參考。2016-10-10Android自定義控件ImageView實(shí)現(xiàn)點(diǎn)擊之后出現(xiàn)陰影效果
這篇文章主要為大家詳細(xì)介紹了Android自定義控件ImageView實(shí)現(xiàn)點(diǎn)擊之后有陰影效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12詳解Android studio 動(dòng)態(tài)fragment的用法
這篇文章主要介紹了Android studio 動(dòng)態(tài)fragment的用法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Android中修改TabLayout底部導(dǎo)航條Indicator長(zhǎng)短的方法
Tablayout在我們?nèi)粘i_發(fā)中經(jīng)常會(huì)遇到,下面這篇文章主要給大家介紹了在Android中修改TabLayout底部導(dǎo)航條Indicator長(zhǎng)短的方法,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友們下面來一起看看吧。2017-06-06