Android自定義View實(shí)現(xiàn)開關(guān)按鈕
前言:Android自定義View對(duì)于剛?cè)腴T乃至工作幾年的程序員來說都是非??謶值?,但也是Android進(jìn)階學(xué)習(xí)的必經(jīng)之路,平時(shí)項(xiàng)目中經(jīng)常會(huì)有一些苛刻的需求,我們可以在GitHub上找到各種各樣的效果,能用則用,不能用自己花功夫改改也能草草了事。不過隨著工作經(jīng)驗(yàn)和工作性質(zhì),越來越覺得自定義View是時(shí)候有必要自己花點(diǎn)功夫研究一下。
一、經(jīng)過這兩天的努力,自己也嘗試著寫了一個(gè)Demo,效果很簡(jiǎn)單,就是開關(guān)按鈕的實(shí)現(xiàn)。
可能有的人會(huì)說這效果so easy,找UI切三張圖就完事了,何必大費(fèi)周折自定義。你說的沒錯(cuò),不過這里只是用來學(xué)習(xí)自定義View來展示這么一個(gè)常見案例。
自定義控件
1.為什么自定義View?
Android自身帶的控件不能滿足需求, 需要根據(jù)自己的需求定義控件.
2.Android 的界面繪制流程?
onMeasure()——onLayout()——onDraw()方法都在Activity生命周期的onResume()方法之后執(zhí)行。
3.Android自定義View的方式?
集成View:View流程
onMeasure() (在這個(gè)方法里指定自己的寬高) -> onDraw() (繪制自己的內(nèi)容)
集成ViewGroup:ViewGroup流程
onMeasure() (指定自己的寬高, 所有子View的寬高)-> onLayout() (擺放所有子View) -> onDraw() (繪制內(nèi)容)
自定義View實(shí)現(xiàn)開關(guān)按鈕步驟:
寫個(gè)類繼承View,
拷貝包含包名的全路徑到xml中,
界面中找到該控件, 設(shè)置初始信息,
根據(jù)需求繪制界面內(nèi)容,
響應(yīng)用戶的觸摸事件,
創(chuàng)建一個(gè)狀態(tài)更新監(jiān)聽.
1.自定義ToggleView集成View,并且重新三個(gè)構(gòu)造方法。
注意:構(gòu)造方法為什么要重寫三個(gè)?
ToggleView(Context context)一個(gè)參數(shù)的構(gòu)造方法是用于代碼創(chuàng)建控件時(shí)調(diào)用的
ToggleView(Context context, AttributeSet attrs)用于在xml里使用, 可指定自定義屬性
ToggleView(Context context, AttributeSet attrs, int defStyle)用于在xml里使用, 可指定自定義屬性, 如果指定了樣式, 則走此構(gòu)造函數(shù)
我們?cè)赬ML中定義了背景圖片、開關(guān)按鈕圖片和開關(guān)默認(rèn)狀態(tài),要獲取在XML文件定義的屬性就在包含三個(gè)參數(shù)的構(gòu)造方法里用TypedArray類來獲取。
在attrs.xml聲明節(jié)點(diǎn)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里使用, 可指定自定義屬性, 如果指定了樣式, 則走此構(gòu)造函數(shù) * @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); //獲取背景圖片和開關(guān)圖片后設(shè)置圖片,便于在onMeasure()方法中設(shè)置View寬和高,防止Null setSwitchBackgroundResource(switchBackgroundResource); setSlideButtonResource(slideButtonResource); init(); }
2.自定義ToggleView集成View后,在XML文件里不要忘記添加命名空間
“xmlns:cb=”http://schemas.android.com/apk/res-auto””
然后將自定義View的完整路徑粘貼到XML中,這點(diǎn)類似于Android v4包下的ViewPager控件
以下便是demo中XML文件代碼:
設(shè)置開關(guān)背景圖片
- cb:switch_background=”@drawable/switch_background”
設(shè)置開關(guān)按鈕圖片
- cb:slide_button=”@drawable/slide_button”
設(shè)置開關(guān)默認(rèn)狀態(tài)
- cb:switch_state=”false”
3.界面中找到該控件, 設(shè)置初始信息
在Activity中通過findViewById方法找到自定義的View控件,和系統(tǒng)的組件操作沒區(qū)別。
private ToggleView toggleView; @Override protected 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); // // 設(shè)置開關(guān)更新監(jiān)聽 toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){ @Override public void onStateUpdate(boolean state) { Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show(); } }); }
4.根據(jù)需求繪制界面內(nèi)容
已經(jīng)通過onMeasure()方法設(shè)置了View的寬度和高度,下面開始繪制的操作就全部在onDraw()方法中進(jìn)行,onDraw(Canvas canvas) 方法中canvas參數(shù):畫布, 畫板. 在上邊繪制的內(nèi)容都會(huì)顯示到界面上.
// 根據(jù)開關(guān)狀態(tài)boolean, 直接設(shè)置圖片位置 if(mSwitchState){// 開 int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth(); canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint); }else {// 關(guān) canvas.drawBitmap(slideButtonBitmap, 0, 0, paint); }
開關(guān)打開時(shí),開關(guān)按鈕的位置在開關(guān)背景中的位置計(jì)算:
int newLeft = switchBackgroupBitmap.getWidth() -
slideButtonBitmap.getWidth(); 背景的寬度-按鈕的寬度就是當(dāng)前開關(guān)按鈕所在的X軸上的位置點(diǎn)
開關(guān)關(guān)閉時(shí),當(dāng)前開關(guān)按鈕所在的X軸上的位置點(diǎn)=0
5.響應(yīng)用戶的觸摸事件
在完成以上3步操作后,你會(huì)發(fā)現(xiàn),只有在第一次進(jìn)入后XML初始化默認(rèn)開關(guān)狀態(tài)的boolean值才會(huì)有變化,此后點(diǎn)擊是沒有任何效果的,這個(gè)時(shí)候我們就要想辦法監(jiān)聽手勢(shì)事件,重寫onTouchEvent(MotionEvent event)方法,相信大多數(shù)朋友對(duì)這個(gè)方法并不陌生。
MotionEvent有三種狀態(tài):
MotionEvent.ACTION_DOWN: //按下屏幕
MotionEvent.ACTION_MOVE: //手指在屏幕上移動(dòng)
MotionEvent.ACTION_UP //離開屏幕
當(dāng)前需要考慮的問題是:
當(dāng)手指按下屏幕后MotionEvent.ACTION_DOWN(在當(dāng)前開關(guān)背景View中)開關(guān)的X軸位置應(yīng)該移動(dòng)到手指按下的位置;
當(dāng)手指在屏幕上移動(dòng)MotionEvent.ACTION_MOVE(在當(dāng)前開關(guān)背景View中)開關(guān)按鈕X軸應(yīng)該隨著手指移動(dòng)的位置改變;
當(dāng)手指離開屏幕后MotionEvent.ACTION_UP(在當(dāng)前開關(guān)背景View中)開關(guān)按鈕應(yīng)該判斷手指離開的位置是否是當(dāng)前背景的一半位置,如果X軸位置大于View背景寬度的1/2、那么應(yīng)該處于打開狀態(tài),如果X軸位置小于View背景寬度的1/2,那么應(yīng)該處于關(guān)閉狀態(tài)。
如圖所示:
private OnSwitchStateUpdateListener onSwitchStateUpdateListener; // 重寫觸摸事件, 響應(yīng)用戶的觸摸. @Override public 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; // 根據(jù)當(dāng)前按下的位置, 和控件中心的位置進(jìn)行比較. boolean state = currentX > center; // 如果開關(guān)狀態(tài)變化了, 通知界面. 里邊開關(guān)狀態(tài)更新了. if(state != mSwitchState && onSwitchStateUpdateListener != null){ // 把最新的boolean, 狀態(tài)傳出去了 onSwitchStateUpdateListener.onStateUpdate(state); } mSwitchState = state; break; default: break; } // 重繪界面 invalidate(); // 會(huì)引發(fā)onDraw()被調(diào)用, 里邊的變量會(huì)重新生效.界面會(huì)更新 return true; // 消費(fèi)了用戶的觸摸事件, 才可以收到其他的事件. }
注意:
以上監(jiān)聽onTouchEvent(MotionEvent
event)方法后還存在一個(gè)問題,不知道大家有沒有發(fā)現(xiàn),我們沒有設(shè)置開關(guān)按鈕的邊界值,什么意思呢?就是手指滑動(dòng)的時(shí)候左邊和右邊可以畫出當(dāng)前背景之外。
所以這里需要對(duì)左右兩邊的X軸位置進(jìn)行處理:
// Canvas 畫布, 畫板. 在上邊繪制的內(nèi)容都會(huì)顯示到界面上. @Override protected void onDraw(Canvas canvas) { // 1. 繪制背景 canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint); // 2. 繪制滑塊 if(isTouchMode){ // 根據(jù)當(dāng)前用戶觸摸到的位置畫滑塊 // 讓滑塊向左移動(dòng)自身一半大小的位置 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 { // 根據(jù)開關(guān)狀態(tài)boolean, 直接設(shè)置圖片位置 if(mSwitchState){// 開 int newLeft = switchBackgroupBitmap.getWidth() - slideButtonBitmap.getWidth(); canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint); }else {// 關(guān) canvas.drawBitmap(slideButtonBitmap, 0, 0, paint); } } }
6.創(chuàng)建一個(gè)狀態(tài)更新監(jiān)聽.
基本上所以工作已經(jīng)完成,這樣我們一個(gè)自定義View已經(jīng)大功告成了,當(dāng)你完成這個(gè)效果后,你可能會(huì)發(fā)現(xiàn)有點(diǎn)類似于CheckBox。既然類似于CheckBox,我們知道當(dāng)CheckBox點(diǎn)擊選中和取消選中的時(shí)候都會(huì)有ischecked()方法來獲取選中狀態(tài),所以我們這個(gè)自定義的開關(guān)按鈕自然不能少這個(gè)功能,否則我們?cè)诮缑嫔现挥行Ч故?,卻沒有邏輯處理的地方。
public interface OnSwitchStateUpdateListener{ // 狀態(tài)回調(diào), 把當(dāng)前狀態(tài)傳出去 void onStateUpdate(boolean state); } public void setOnSwitchStateUpdateListener( OnSwitchStateUpdateListener onSwitchStateUpdateListener) { this.onSwitchStateUpdateListener = onSwitchStateUpdateListener; }
代碼很簡(jiǎn)單,寫一個(gè)接口,然后定義一個(gè)回調(diào)方法返回開關(guān)狀態(tài),需要注意的是,在手指離開屏幕的時(shí)候,我們需要判斷此次操作是否改變了開關(guān)的狀態(tài),如果沒有變化我們不做操作,如果跟上次狀態(tài)不同,則通知Activity狀態(tài)更改!
case MotionEvent.ACTION_UP: isTouchMode = false; System.out.println("event: ACTION_UP: " + event.getX()); currentX = event.getX(); float center = switchBackgroupBitmap.getWidth() / 2.0f; // 根據(jù)當(dāng)前按下的位置, 和控件中心的位置進(jìn)行比較. boolean state = currentX > center; // 如果開關(guān)狀態(tài)變化了, 通知界面. 里邊開關(guān)狀態(tài)更新了. if(state != mSwitchState && onSwitchStateUpdateListener != null){ // 把最新的boolean, 狀態(tài)傳出去了 onSwitchStateUpdateListener.onStateUpdate(state); } mSwitchState = state; break;
Activity中回調(diào)也是非常簡(jiǎn)單的,類似于Android系統(tǒng)為我們提供的setOnClickListener回調(diào)接口,之前一直用系統(tǒng)定義的監(jiān)聽接口,這次通過簡(jiǎn)單的一個(gè)自定義View,我們也可以給自己的View寫回調(diào)接口了。是不是覺得是件很開心的事情呢?
// 設(shè)置開關(guān)更新監(jiān)聽 toggleView.setOnSwitchStateUpdateListener(new ToggleView.OnSwitchStateUpdateListener(){ @Override public void onStateUpdate(boolean state) { Toast.makeText(getApplicationContext(), "state: " + state, Toast.LENGTH_SHORT).show(); } });
以上所述是小編給大家介紹的Android自定義View實(shí)現(xiàn)開關(guān)按鈕,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android App中各種數(shù)據(jù)保存方式的使用實(shí)例總結(jié)
這篇文章主要介紹了Android App中各種數(shù)據(jù)保存方式的使用實(shí)例,列舉了SharedPreferences接口、機(jī)身空間存儲(chǔ)、SD卡存儲(chǔ)和SQLite數(shù)據(jù)庫四種方式的代碼例子,需要的朋友可以參考下2016-04-04Android Studio 4.0新特性及升級(jí)異常問題的解決方案
這篇文章主要介紹了Android Studio 4.0新特性及升級(jí)異常的相關(guān)問題,本文給大家分享解決方案,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06教你五分鐘實(shí)現(xiàn)Android超漂亮的刻度輪播控件實(shí)例教程
說到輪播圖,想必大家都不陌生,下面這篇文章主要給大家介紹了關(guān)于如何利用五分鐘快速實(shí)現(xiàn)一款超漂亮的Android刻度輪播控件的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧2018-09-09Android小程序?qū)崿F(xiàn)個(gè)人信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Android小程序?qū)崿F(xiàn)個(gè)人信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05Android程序開發(fā)之使用Design包實(shí)現(xiàn)QQ動(dòng)畫側(cè)滑效果和滑動(dòng)菜單導(dǎo)航
這篇文章主要介紹了Android程序開發(fā)之使用Design包實(shí)現(xiàn)QQ動(dòng)畫側(cè)滑效果和滑動(dòng)菜單導(dǎo)航的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Android自定義Gallery控件實(shí)現(xiàn)3D圖片瀏覽器
這篇文章主要介紹了Android自定義Gallery控件實(shí)現(xiàn)3D圖片瀏覽器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04Android可自定義神奇動(dòng)效的卡片切換視圖實(shí)例
今天小編就為大家分享一篇關(guān)于Android可自定義神奇動(dòng)效的卡片切換視圖實(shí)例,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02Android Flutter圖片處理之高斯模糊的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了如何利用Android Flutter實(shí)現(xiàn)高斯模糊效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08