首页 > 学院 > 开发设计 > 正文

自定义滑动开关-SwitchButton

2019-11-09 17:40:47
字体:
来源:转载
供稿:网友

自定义滑动开关-SwitchButton

在项目中肯定会经常性的使用到开关这样的控件,android系统也提供了内置的滑动开关,但是面对形形色色的开关需求,原生的肯定满足不了,那么这个时候我们肯定是需要自己来定义一个满足需求的开关,本篇文章主要介绍通过view来绘制一个滑动开关.

1.写一个类继承view,并重写它的构造方法,并实现 测量 布局摆放 绘制三个方法

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(); } /** * 测量 * @param widthMeasureSpec 测量模式 * @param heightMeasureSpec */ @Override PRotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 布局摆放 * @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,当这些都准备好了之后,我们先来分析一下,滑动开关的状态有两种,一是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这个值就是最右边,之后会详细解释这个值的由来

初始化的状态位置设置好之后,我们就要进行绘制了,绘制的话需要两个方面,一是需要将背景块绘制上去,二是需要把滑块绘制到背景块之上.

2.绘制滑块与背景

绘制的话我们通常会面临两个问题,一是有现成的切图给我们,这个相对会比较方便一些;二是没有切图给我们,我们需要自己写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值,去掉也可以,有一个空隙看着舒服一些.

3.实现onTouch事件

现在呢,总算是可以绘制上去了,但是既然是滑动开关怎么能少了滑动呢,上面只给出了一个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啦~ 这里写图片描述

4.设置开关的监听接口

不过这时候,到了这里还不能满足我们的需求,既然是开关,那么我们使用的时候就要知道它什么时候开了,什么时候关了,不然这个开关就是摆设,既然这样的话,我们肯定是要对它的开关状态进行监听,首选的方法自然是接口回调监听,在它状态改变的时候,我们把它的状态通过接口的方式暴露出来:

//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


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表