国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

自定義滑動(dòng)開關(guān)-SwitchButton

2019-11-09 16:37:05
字體:
供稿:網(wǎng)友

自定義滑動(dòng)開關(guān)-SwitchButton

在項(xiàng)目中肯定會(huì)經(jīng)常性的使用到開關(guān)這樣的控件,android系統(tǒng)也提供了內(nèi)置的滑動(dòng)開關(guān),但是面對(duì)形形色色的開關(guān)需求,原生的肯定滿足不了,那么這個(gè)時(shí)候我們肯定是需要自己來定義一個(gè)滿足需求的開關(guān),本篇文章主要介紹通過view來繪制一個(gè)滑動(dòng)開關(guān).

1.寫一個(gè)類繼承view,并重寫它的構(gòu)造方法,并實(shí)現(xiàn) 測(cè)量 布局?jǐn)[放 繪制三個(gè)方法

public class SwitchButton extends View { public SwitchButton(Context context) { this(context, null); } public SwitchButton(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //TODO: init方法 init(); } /** * 測(cè)量 * @param widthMeasureSpec 測(cè)量模式 * @param heightMeasureSpec */ @Override PRotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 布局?jǐn)[放 * @param changed * @param left * @param top * @param right * @param bottom */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom){ super.onLayout(changed, left, top, right, bottom); } /** * 繪制 * @param canvas 畫布:將控件繪制到畫布上才能顯示到屏幕上 */ @Override protected void onDraw(Canvas canvas) { }}

ok,當(dāng)這些都準(zhǔn)備好了之后,我們先來分析一下,滑動(dòng)開關(guān)的狀態(tài)有兩種,一是open,一是close,而且一定會(huì)有一個(gè)初始值,要么是open,要么是close,之后會(huì)根據(jù)手指的滑動(dòng)或點(diǎn)擊來切換open/close的狀態(tài).不管這么多了,我們先給他一個(gè)初始值,默認(rèn)為close,設(shè)置一個(gè)成員變量, isOpen

private boolean isOpen = false;

既然初始值為false,那么滑塊的位置在剛開始應(yīng)該是這樣的,滑塊的左邊距在0的位置,當(dāng)為true的時(shí)候,滑塊的左邊距在最右邊,那么既然是初始值,我們肯定需要在初始化的時(shí)候就要畫出來,那首選的地方肯定就是構(gòu)造方法里面了,所以兩個(gè)參數(shù)的構(gòu)造方法就變成了這個(gè)樣子:

public SwitchButton(Context context, AttributeSet attrs) { this(context, attrs, 0); if (isOpen) { mSlidLeft = mMaxLeft; } else { mSlidLeft = 0; }}

mMaxLeft這個(gè)值就是最右邊,之后會(huì)詳細(xì)解釋這個(gè)值的由來

初始化的狀態(tài)位置設(shè)置好之后,我們就要進(jìn)行繪制了,繪制的話需要兩個(gè)方面,一是需要將背景塊繪制上去,二是需要把滑塊繪制到背景塊之上.

2.繪制滑塊與背景

繪制的話我們通常會(huì)面臨兩個(gè)問題,一是有現(xiàn)成的切圖給我們,這個(gè)相對(duì)會(huì)比較方便一些;二是沒有切圖給我們,我們需要自己寫shape等,這樣就稍微有些麻煩,在這里我會(huì)全部都講解一下的.好了,下面開始具體看一下init()的方法.

private void init() { changeUiState(isOpen); //計(jì)算出當(dāng)前滑動(dòng)塊的left的最大值 mMaxLeft = mSwitchBitmap.getWidth() - mSlidBitmap.getWidth(); //給控件設(shè)置點(diǎn)擊事件 this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (isClick) {//如果是點(diǎn)擊事件,以下代碼才執(zhí)行 //根據(jù)當(dāng)前滑動(dòng)開關(guān)的開關(guān)狀態(tài),來進(jìn)行切換 if (isOpen) { // 將開關(guān)設(shè)置為關(guān) mSlidLeft = 0; } else { // 將開關(guān)設(shè)置為開 mSlidLeft = mMaxLeft; } isOpen = !isOpen; changeUiState(isOpen); invalidate();//強(qiáng)制讓控件進(jìn)行重繪操作,就是重新執(zhí)行ondraw方法 } } });} private void changeUiState(boolean isOpen) { if(isOpen){ //獲取滑動(dòng)開關(guān)的圖片對(duì)象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg_on); //獲取滑動(dòng)塊的圖片對(duì)象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_point_on); }else { //獲取滑動(dòng)開關(guān)的圖片對(duì)象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_bg_off); //獲取滑動(dòng)塊的圖片對(duì)象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.switch_point_off); }}

changeUiState()這部分會(huì)多次用到,所以要把它抽取出來

這樣的我,我們基本上就可以把圖片給繪制上去了,但是,當(dāng)然還差了兩步,測(cè)量和繪制,好了,現(xiàn)在開始看看測(cè)量和繪制的代碼:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(mSwitchBitmap.getWidth(), mSwitchBitmap.getHeight()); } @Override protected void onDraw(Canvas canvas) { //將滑動(dòng)開關(guān)的背景繪制上來 canvas.drawBitmap(mSwitchBitmap, 0, 0, null); //如果滑塊的左邊距大于滑塊的寬度,那么我們就認(rèn)為是open狀態(tài) if ((mSlidLeft + 5) > mSlidBitmap.getWidth()) { //將滑動(dòng)塊的圖片繪制上來 canvas.drawBitmap(mSlidBitmap, mSlidLeft - 5, 18, null); } else { //將滑動(dòng)塊的圖片繪制上來 canvas.drawBitmap(mSlidBitmap, mSlidLeft + 5, 18, null); } }

有朋友問我+5和-5代表什么意思,其實(shí)在這里就是一個(gè)padding值,去掉也可以,有一個(gè)空隙看著舒服一些.

3.實(shí)現(xiàn)onTouch事件

現(xiàn)在呢,總算是可以繪制上去了,但是既然是滑動(dòng)開關(guān)怎么能少了滑動(dòng)呢,上面只給出了一個(gè)onClick點(diǎn)擊切換的動(dòng)作,想要滑動(dòng)切換,我們肯定要實(shí)現(xiàn)onTouch事件的,下面看一下onTouch事件的內(nèi)容:

@Overridepublic boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //1、記錄手指按下的起始點(diǎn) mStartX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: //2、記錄手指移動(dòng)后的結(jié)束點(diǎn) int endX = (int) event.getX(); //3、計(jì)算出間距 int diffX = endX - mStartX; //記錄下移動(dòng)的總間距 moveX += Math.abs(diffX); //4、重新計(jì)算滑蓋的left的值 mSlidLeft += diffX; if (mSlidLeft < 0) {//設(shè)置左邊界 mSlidLeft = 0; } if (mSlidLeft > mMaxLeft) {//設(shè)置右邊界 mSlidLeft = mMaxLeft; } changeUiState(isOpen); //5、讓控件進(jìn)行重繪 invalidate(); //6、更新起始點(diǎn) mStartX = endX; break; case MotionEvent.ACTION_UP: //手指抬起時(shí),根據(jù)觸摸的間距,大于一定的像素值,則是觸摸事件,否則點(diǎn)擊事件 if (moveX < 5) { //點(diǎn)擊事件 isClick = true; } else { //觸摸事件 isClick = false; } //清零moveX moveX = 0; if (!isClick) {//如果判斷出來,當(dāng)前是觸摸事件,以下代碼才執(zhí)行 int center = mMaxLeft / 2; if (mSlidLeft < center) { //關(guān) mSlidLeft = 0; isOpen = false; } else { //開 mSlidLeft = mMaxLeft; isOpen = true; } changeUiState(isOpen); invalidate(); } break; } return super.onTouchEvent(event);}

這部分代碼就不再多做解釋,相信朋友們都已經(jīng)非常熟悉了,到了這里的話,基本上就告一段落了,滑動(dòng)開關(guān)已經(jīng)可以滑動(dòng)切換啦~ 下面看一下是怎么使用的,同許多的自定義view一樣,我們?cè)?a href="http://www.survivalescaperooms.com/xml.asp">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.好了,現(xiàn)在當(dāng)你點(diǎn)擊運(yùn)行之后,就會(huì)在你的屏幕上看到SwitchButton啦~ 這里寫圖片描述

4.設(shè)置開關(guān)的監(jiān)聽接口

不過這時(shí)候,到了這里還不能滿足我們的需求,既然是開關(guān),那么我們使用的時(shí)候就要知道它什么時(shí)候開了,什么時(shí)候關(guān)了,不然這個(gè)開關(guān)就是擺設(shè),既然這樣的話,我們肯定是要對(duì)它的開關(guān)狀態(tài)進(jìn)行監(jiān)聽,首選的方法自然是接口回調(diào)監(jiān)聽,在它狀態(tài)改變的時(shí)候,我們把它的狀態(tài)通過接口的方式暴露出來:

//1、創(chuàng)建出對(duì)應(yīng)的接口,并創(chuàng)建接口方法,在接口方法,你需要什么數(shù)據(jù)就將該類型的數(shù)據(jù)作為參數(shù)定義接口方法中 public interface OnCheckChangeListener { public void onCheckChanged(boolean isOpen); } //2、設(shè)置set方法,用成員變量進(jìn)行記錄 public void setOnCheckChangeListener(OnCheckChangeListener listener) { this.mOnCheckChangeListener = listener; }

既然是在狀態(tài)發(fā)生改變的時(shí)候進(jìn)行接口暴露,我們來仔細(xì)的排查前面的代碼,發(fā)現(xiàn)只有兩個(gè)地方最為合適,一是它的點(diǎn)擊回調(diào),另外一個(gè)是onTouch里面的up事件,那我們就在這兩個(gè)地方注冊(cè)監(jiān)聽:

于是init()方法就成了這樣:

private void init() { // mPaint = new Paint(); //mPaint.setColor(Color.RED); changeUiState(isOpen); //計(jì)算出當(dāng)前滑動(dòng)塊的left的最大值 mMaxLeft = mSwitchBitmap.getWidth() - mSlidBitmap.getWidth(); //給控件設(shè)置點(diǎn)擊事件 this.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (isClick) {//如果是點(diǎn)擊事件,以下代碼才執(zhí)行 //根據(jù)當(dāng)前滑動(dòng)開關(guān)的開關(guān)狀態(tài),來進(jìn)行切換 if (isOpen) { // 將開關(guān)設(shè)置為關(guān) mSlidLeft = 0; } else { // 將開關(guān)設(shè)置為開 mSlidLeft = mMaxLeft; } isOpen = !isOpen; changeUiState(isOpen); invalidate();//強(qiáng)制讓控件進(jìn)行重繪操作,就是重新執(zhí)行ondraw方法 //3、在對(duì)應(yīng)的邏輯處理處調(diào)用接口對(duì)象的接口方法,將數(shù)據(jù)回調(diào)回去 if (mOnCheckChangeListener != null) { mOnCheckChangeListener.onCheckChanged(isOpen); } } } });}

onTouch()方法就成了這樣:

@Overridepublic boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //1、記錄手指按下的起始點(diǎn) mStartX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE: //2、記錄手指移動(dòng)后的結(jié)束點(diǎn) int endX = (int) event.getX(); //3、計(jì)算出間距 int diffX = endX - mStartX; //記錄下移動(dòng)的總間距 moveX += Math.abs(diffX); //4、重新計(jì)算滑蓋的left的值 mSlidLeft += diffX; if (mSlidLeft < 0) {//設(shè)置左邊界 mSlidLeft = 0; } if (mSlidLeft > mMaxLeft) {//設(shè)置右邊界 mSlidLeft = mMaxLeft; } changeUiState(isOpen); //5、讓控件進(jìn)行重繪 invalidate(); //6、更新起始點(diǎn) mStartX = endX; break; case MotionEvent.ACTION_UP: //手指抬起時(shí),根據(jù)觸摸的間距,大于一定的像素值,則是觸摸事件,否則點(diǎn)擊事件 if (moveX < 5) { //點(diǎn)擊事件 isClick = true; } else { //觸摸事件 isClick = false; } //清零moveX moveX = 0; if (!isClick) {//如果判斷出來,當(dāng)前是觸摸事件,以下代碼才執(zhí)行 int center = mMaxLeft / 2; if (mSlidLeft < center) { //關(guān) mSlidLeft = 0; isOpen = false; } else { //開 mSlidLeft = mMaxLeft; isOpen = true; } changeUiState(isOpen); invalidate(); if (mOnCheckChangeListener != null) { mOnCheckChangeListener.onCheckChanged(isOpen); } } break; } return super.onTouchEvent(event);}

然后我們?cè)?a href="http://www.survivalescaperooms.com/article.asp?typeid=160">java代碼中這樣用:

mSwitchButton = (SwitchButton) findViewById(R.id.switchbutton); mSwitchButton.setOnCheckChangeListener(new SwitchButton.OnCheckChangeListener() { @Override public void onCheckChanged(boolean isOpen) { Toast.makeText(MainActivity.this, "當(dāng)前滑動(dòng)開關(guān)的狀態(tài):" + isOpen, Toast.LENGTH_SHORT).show(); } });

ok這個(gè)時(shí)候,我們可以看一下效果:

這里寫圖片描述

不知道朋友們還記不記得前面遺留的一個(gè)問題:繪制的話我們通常會(huì)面臨兩個(gè)問題,一是有現(xiàn)成的切圖給我們;二是沒有切圖給我們,我們需要自己寫shape,之前我們用的是現(xiàn)成的切圖,現(xiàn)在我們就用一下自己寫的shape,由于我們只是換一下背景圖片,所以只要對(duì) changeUiState()這個(gè)方法進(jìn)行修改就ok了,其他的完全用不著改動(dòng).好了,下面看一下如何用shape來設(shè)置SwitchButton的背景,最開始的話我是這樣直接套上去的:

private void changeUiState(boolean isOpen) { if(isOpen){ //獲取滑動(dòng)開關(guān)的圖片對(duì)象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switchbutton_bg_on); //獲取滑動(dòng)塊的圖片對(duì)象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switch_point_on); }else { //獲取滑動(dòng)開關(guān)的圖片對(duì)象 mSwitchBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switchbutton_bg_off); //獲取滑動(dòng)塊的圖片對(duì)象 mSlidBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_switch_point_off); }}

結(jié)果發(fā)現(xiàn)會(huì)報(bào)錯(cuò),因?yàn)閟hape是無法直接轉(zhuǎn)成bitmap格式的,現(xiàn)在我們只要把shape轉(zhuǎn)成bitmap就萬事大吉了.不過,這個(gè)一時(shí)間還真是想不起來,于是就琢磨,我把一個(gè)imageview的backgroud設(shè)置成shape,然后把imageview轉(zhuǎn)成bitmap不也行嘛,于是就改成了如下這樣,改完之后發(fā)現(xiàn),哈哈,可以啦!

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()這個(gè)方法我為了方便是直接從大神的博客里面拿來的,下面貼出鏈接,http://blog.csdn.net/chenshijun0101/article/details/38022789?utm_source=tuicool&utm_medium=referral里面有好幾種轉(zhuǎn)換方法,我選取了其中一種,有興趣的同學(xué)還可以再看看.

到這里我們的SwitchButton就算正式完成了,謝謝大家,有什么不當(dāng)之處還請(qǐng)多多指教!

項(xiàng)目源碼下載地址:http://download.csdn.net/detail/itchaosfun/9749199


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 兖州市| 开封市| 彩票| 北安市| 平湖市| 锡林郭勒盟| 酒泉市| 抚州市| 定西市| 永年县| 聊城市| 会宁县| 湘潭县| 双柏县| 荆州市| 德州市| 怀远县| 南陵县| 库尔勒市| 西藏| 芦山县| 会同县| 定日县| 宁城县| 云龙县| 黎城县| 寿光市| 望城县| 子长县| 北票市| 平潭县| 浦北县| 金沙县| 德江县| 新乐市| 隆尧县| 左贡县| 台州市| 梅州市| 应用必备| 淳安县|