在項目中肯定會經常性的使用到開關這樣的控件,android系統也提供了內置的滑動開關,但是面對形形色色的開關需求,原生的肯定滿足不了,那么這個時候我們肯定是需要自己來定義一個滿足需求的開關,本篇文章主要介紹通過view來繪制一個滑動開關.
ok,當這些都準備好了之后,我們先來分析一下,滑動開關的狀態有兩種,一是open,一是close,而且一定會有一個初始值,要么是open,要么是close,之后會根據手指的滑動或點擊來切換open/close的狀態.不管這么多了,我們先給他一個初始值,默認為close,設置一個成員變量, isOpen
private boolean isOpen = false;既然初始值為false,那么滑塊的位置在剛開始應該是這樣的,滑塊的左邊距在0的位置,當為true的時候,滑塊的左邊距在最右邊,那么既然是初始值,我們肯定需要在初始化的時候就要畫出來,那首選的地方肯定就是構造方法里面了,所以兩個參數的構造方法就變成了這個樣子:
public SwitchButton(Context context, AttributeSet attrs) { this(context, attrs, 0); if (isOpen) { mSlidLeft = mMaxLeft; } else { mSlidLeft = 0; }}mMaxLeft這個值就是最右邊,之后會詳細解釋這個值的由來
初始化的狀態位置設置好之后,我們就要進行繪制了,繪制的話需要兩個方面,一是需要將背景塊繪制上去,二是需要把滑塊繪制到背景塊之上.
繪制的話我們通常會面臨兩個問題,一是有現成的切圖給我們,這個相對會比較方便一些;二是沒有切圖給我們,我們需要自己寫shape等,這樣就稍微有些麻煩,在這里我會全部都講解一下的.好了,下面開始具體看一下init()的方法.
private void init() { changeUiState(isOpen); //計算出當前滑動塊的left的最大值 mMaxLeft = mSwitchBitmap.getWidth() - mSlidBitmap.getWidth(); //給控件設置點擊事件 this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (isClick) {//如果是點擊事件,以下代碼才執行 //根據當前滑動開關的開關狀態,來進行切換 if (isOpen) { // 將開關設置為關 mSlidLeft = 0; } else { // 將開關設置為開 mSlidLeft = mMaxLeft; } isOpen = !isOpen; changeUiState(isOpen); invalidate();//強制讓控件進行重繪操作,就是重新執行ondraw方法 } } });} private void changeUiState(boolean isOpen) { if(isOpen){ //獲取滑動開關的圖片對象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg_on); //獲取滑動塊的圖片對象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_point_on); }else { //獲取滑動開關的圖片對象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg_off); //獲取滑動塊的圖片對象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_point_off); }}changeUiState()這部分會多次用到,所以要把它抽取出來
這樣的我,我們基本上就可以把圖片給繪制上去了,但是,當然還差了兩步,測量和繪制,好了,現在開始看看測量和繪制的代碼:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mSwitchBitmap.getWidth(), mSwitchBitmap.getHeight()); } @Override protected void onDraw(Canvas canvas) { //將滑動開關的背景繪制上來 canvas.drawBitmap(mSwitchBitmap, 0, 0, null); //如果滑塊的左邊距大于滑塊的寬度,那么我們就認為是open狀態 if ((mSlidLeft + 5) > mSlidBitmap.getWidth()) { //將滑動塊的圖片繪制上來 canvas.drawBitmap(mSlidBitmap, mSlidLeft - 5, 18, null); } else { //將滑動塊的圖片繪制上來 canvas.drawBitmap(mSlidBitmap, mSlidLeft + 5, 18, null); } }有朋友問我+5和-5代表什么意思,其實在這里就是一個padding值,去掉也可以,有一個空隙看著舒服一些.
現在呢,總算是可以繪制上去了,但是既然是滑動開關怎么能少了滑動呢,上面只給出了一個onClick點擊切換的動作,想要滑動切換,我們肯定要實現onTouch事件的,下面看一下onTouch事件的內容:
@Overridepublic boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //1、記錄手指按下的起始點 mStartX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: //2、記錄手指移動后的結束點 int endX = (int) event.getX(); //3、計算出間距 int diffX = endX - mStartX; //記錄下移動的總間距 moveX += Math.abs(diffX); //4、重新計算滑蓋的left的值 mSlidLeft += diffX; if (mSlidLeft < 0) {//設置左邊界 mSlidLeft = 0; } if (mSlidLeft > mMaxLeft) {//設置右邊界 mSlidLeft = mMaxLeft; } changeUiState(isOpen); //5、讓控件進行重繪 invalidate(); //6、更新起始點 mStartX = endX; break; case MotionEvent.ACTION_UP: //手指抬起時,根據觸摸的間距,大于一定的像素值,則是觸摸事件,否則點擊事件 if (moveX < 5) { //點擊事件 isClick = true; } else { //觸摸事件 isClick = false; } //清零moveX moveX = 0; if (!isClick) {//如果判斷出來,當前是觸摸事件,以下代碼才執行 int center = mMaxLeft / 2; if (mSlidLeft < center) { //關 mSlidLeft = 0; isOpen = false; } else { //開 mSlidLeft = mMaxLeft; isOpen = true; } changeUiState(isOpen); invalidate(); } break; } return super.onTouchEvent(event);}這部分代碼就不再多做解釋,相信朋友們都已經非常熟悉了,到了這里的話,基本上就告一段落了,滑動開關已經可以滑動切換啦~ 下面看一下是怎么使用的,同許多的自定義view一樣,我們在xml里面直接用起來:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#524" tools:context="com.cn.chaos.swithcbutton.MainActivity"> <com.cn.chaos.swithcbutton.SwitchButton android:layout_centerInParent="true" android:id="@+id/switchbutton" android:layout_width="wrap_content" android:layout_height="wrap_content"/></RelativeLayout>ok.好了,現在當你點擊運行之后,就會在你的屏幕上看到SwitchButton啦~ 
不過這時候,到了這里還不能滿足我們的需求,既然是開關,那么我們使用的時候就要知道它什么時候開了,什么時候關了,不然這個開關就是擺設,既然這樣的話,我們肯定是要對它的開關狀態進行監聽,首選的方法自然是接口回調監聽,在它狀態改變的時候,我們把它的狀態通過接口的方式暴露出來:
//1、創建出對應的接口,并創建接口方法,在接口方法,你需要什么數據就將該類型的數據作為參數定義接口方法中 public interface OnCheckChangeListener { public void onCheckChanged(boolean isOpen); } //2、設置set方法,用成員變量進行記錄 public void setOnCheckChangeListener(OnCheckChangeListener listener) { this.mOnCheckChangeListener = listener; }既然是在狀態發生改變的時候進行接口暴露,我們來仔細的排查前面的代碼,發現只有兩個地方最為合適,一是它的點擊回調,另外一個是onTouch里面的up事件,那我們就在這兩個地方注冊監聽:
于是init()方法就成了這樣:
private void init() { // mPaint = new Paint(); //mPaint.setColor(Color.RED); changeUiState(isOpen); //計算出當前滑動塊的left的最大值 mMaxLeft = mSwitchBitmap.getWidth() - mSlidBitmap.getWidth(); //給控件設置點擊事件 this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (isClick) {//如果是點擊事件,以下代碼才執行 //根據當前滑動開關的開關狀態,來進行切換 if (isOpen) { // 將開關設置為關 mSlidLeft = 0; } else { // 將開關設置為開 mSlidLeft = mMaxLeft; } isOpen = !isOpen; changeUiState(isOpen); invalidate();//強制讓控件進行重繪操作,就是重新執行ondraw方法 //3、在對應的邏輯處理處調用接口對象的接口方法,將數據回調回去 if (mOnCheckChangeListener != null) { mOnCheckChangeListener.onCheckChanged(isOpen); } } } });}onTouch()方法就成了這樣:
@Overridepublic boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //1、記錄手指按下的起始點 mStartX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: //2、記錄手指移動后的結束點 int endX = (int) event.getX(); //3、計算出間距 int diffX = endX - mStartX; //記錄下移動的總間距 moveX += Math.abs(diffX); //4、重新計算滑蓋的left的值 mSlidLeft += diffX; if (mSlidLeft < 0) {//設置左邊界 mSlidLeft = 0; } if (mSlidLeft > mMaxLeft) {//設置右邊界 mSlidLeft = mMaxLeft; } changeUiState(isOpen); //5、讓控件進行重繪 invalidate(); //6、更新起始點 mStartX = endX; break; case MotionEvent.ACTION_UP: //手指抬起時,根據觸摸的間距,大于一定的像素值,則是觸摸事件,否則點擊事件 if (moveX < 5) { //點擊事件 isClick = true; } else { //觸摸事件 isClick = false; } //清零moveX moveX = 0; if (!isClick) {//如果判斷出來,當前是觸摸事件,以下代碼才執行 int center = mMaxLeft / 2; if (mSlidLeft < center) { //關 mSlidLeft = 0; isOpen = false; } else { //開 mSlidLeft = mMaxLeft; isOpen = true; } changeUiState(isOpen); invalidate(); if (mOnCheckChangeListener != null) { mOnCheckChangeListener.onCheckChanged(isOpen); } } break; } return super.onTouchEvent(event);}然后我們在java代碼中這樣用:
mSwitchButton = (SwitchButton) findViewById(R.id.switchbutton); mSwitchButton.setOnCheckChangeListener(new SwitchButton.OnCheckChangeListener() { @Override public void onCheckChanged(boolean isOpen) { Toast.makeText(MainActivity.this, "當前滑動開關的狀態:" + isOpen, Toast.LENGTH_SHORT).show(); } });ok這個時候,我們可以看一下效果:

不知道朋友們還記不記得前面遺留的一個問題:繪制的話我們通常會面臨兩個問題,一是有現成的切圖給我們;二是沒有切圖給我們,我們需要自己寫shape,之前我們用的是現成的切圖,現在我們就用一下自己寫的shape,由于我們只是換一下背景圖片,所以只要對 changeUiState()這個方法進行修改就ok了,其他的完全用不著改動.好了,下面看一下如何用shape來設置SwitchButton的背景,最開始的話我是這樣直接套上去的:
private void changeUiState(boolean isOpen) { if(isOpen){ //獲取滑動開關的圖片對象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switchbutton_bg_on); //獲取滑動塊的圖片對象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switch_point_on); }else { //獲取滑動開關的圖片對象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switchbutton_bg_off); //獲取滑動塊的圖片對象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switch_point_off); }}結果發現會報錯,因為shape是無法直接轉成bitmap格式的,現在我們只要把shape轉成bitmap就萬事大吉了.不過,這個一時間還真是想不起來,于是就琢磨,我把一個imageview的backgroud設置成shape,然后把imageview轉成bitmap不也行嘛,于是就改成了如下這樣,改完之后發現,哈哈,可以啦!
ImageView switchImg = new ImageView(getContext());ImageView slideImg = new ImageView(getContext());private void changeUiState(boolean isOpen) { if(isOpen){ switchImg.setBackgroundResource(R.drawable.shape_switchbutton_bg_on); slideImg.setBackgroundResource(R.drawable.shape_switch_point_on); }else { switchImg.setBackgroundResource(R.drawable.shape_switchbutton_bg_off); slideImg.setBackgroundResource(R.drawable.shape_switch_point_off); } mSwitchBitmap = getViewBitmap(switchImg,80,40); mSlidBitmap = getViewBitmap(slideImg,30,30); } public Bitmap getViewBitmap(View comBitmap, int width, int height) { Bitmap bitmap = null; if (comBitmap != null) { comBitmap.clearFocus(); comBitmap.setPressed(false); boolean willNotCache = comBitmap.willNotCacheDrawing(); comBitmap.setWillNotCacheDrawing(false); int color = comBitmap.getDrawingCacheBackgroundColor(); comBitmap.setDrawingCacheBackgroundColor(0); float alpha = comBitmap.getAlpha(); comBitmap.setAlpha(1.0f); if (color != 0) { comBitmap.destroyDrawingCache(); } int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); comBitmap.measure(widthSpec, heightSpec); comBitmap.layout(0, 0, width, height); comBitmap.buildDrawingCache(); Bitmap cacheBitmap = comBitmap.getDrawingCache(); if (cacheBitmap == null) { Log.e("view.ProcessImageToBlur", "failed getViewBitmap(" + comBitmap + ")", new RuntimeException()); return null; } bitmap = Bitmap.createBitmap(cacheBitmap); // Restore the view comBitmap.setAlpha(alpha); comBitmap.destroyDrawingCache(); comBitmap.setWillNotCacheDrawing(willNotCache); comBitmap.setDrawingCacheBackgroundColor(color); } return bitmap;}最后的getViewBitmap()這個方法我為了方便是直接從大神的博客里面拿來的,下面貼出鏈接,http://blog.csdn.net/chenshijun0101/article/details/38022789?utm_source=tuicool&utm_medium=referral里面有好幾種轉換方法,我選取了其中一種,有興趣的同學還可以再看看.
到這里我們的SwitchButton就算正式完成了,謝謝大家,有什么不當之處還請多多指教!
項目源碼下載地址:http://download.csdn.net/detail/itchaosfun/9749199
新聞熱點
疑難解答