Android自定义开关按钮
冰 河 人气:0前言
很多时候,我们在很多无论是Android还是IOS的APP中都会遇到这样的一种效果,有一个按钮,我们点击一下,便会滑动一下,一会显示“开”,一会显示“关”,这便是开关按钮了,比如:很多Android手机的设置功能里,就有很多功能是用开关按钮实现的,那么这些开关按钮时如何实现的呢?下面,就让我们一起来实现这个功能吧。
一、原理
我们在界面的某一个区域里放置一个背景图A,这个图片一边为“开”,一边为“关”,在这个图片上放置一个图片B,图B大约为图A的一半,恰好可以覆盖掉图A上的“开”或者“关”,当我们手指点击图片的时候,图B在图A上滑动,相应的覆盖“开”或者“关”,这样就实现了开关按钮的效果。
二、实现
1、自定义View类MyToggle
这个类继承自View类同时实现了OnTouchListener接口,这个类实现的功能比较多,我们分解来看这个类。
1)属性字段
这个类中定义了不少的属性字段,每个属性字段的具体含义详见代码注释
具体实现代码如下:
//开关开启的背景图片 private Bitmap bkgSwitchOn; //开关关闭的背景图片 private Bitmap bkgSwitchOff; //开关的滚动图片 private Bitmap btnSlip; //当前开关是否为开启状态 private boolean toggleStateOn; //开关状态的监听事件 private OnToggleStateListener toggleStateListener; //记录开关·当前的状态 private boolean isToggleStateListenerOn; //手指按下屏幕时的x坐标 private float proX; //手指滑动过程中当前x坐标 private float currentX; //是否处于滑动状态 private boolean isSlipping; //记录上一次开关的状态 private boolean proToggleState \= true; //开关开启时的矩形 private Rect rect\_on; //开关关闭时的矩形 private Rect rect\_off;
2)覆写View类的构造方法
我们在构造方法里完成的操作就是调用我们自己创建的init()方法
具体实现代码如下:
public MyToggle(Context context) { super(context); init(context); } public MyToggle(Context context, AttributeSet attrs) { super(context, attrs); init(context); }
3)创建init方法
这个方法中实现的操作就是设置触摸事件。
具体实现代码如下:
//初始化方法 private void init(Context context) { setOnTouchListener(this); }
4)手指触摸事件回调方法onTouch
这个方法是当手指操作手机屏幕时,Android自动回调的方法,我们在这个方法中,监听手指的按下、移动和抬起事件,记录手指当前的X坐标来判断图片B的移动方向,从而实现图片B在图片A上的移动来达到按钮开和关的效果。
具体实现代码如下:
@Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION\_DOWN: //记录手指按下时的x坐标 proX \= event.getX(); currentX \= proX; //将滑动标识设置为true isSlipping \= true; break; case MotionEvent.ACTION\_MOVE: //记录手指滑动过程中当前x坐标 currentX \= event.getX(); break; case MotionEvent.ACTION\_UP: //手指抬起时将是否滑动的标识设置为false isSlipping \= false; //处于关闭状态 if(currentX < bkgSwitchOn.getWidth() / 2 ){ toggleStateOn \= false; } else { // 处于开启状态 toggleStateOn \= true; } // 如果使用了开关监听器,同时开关的状态发生了改变,这时使用该代码 if(isToggleStateListenerOn && toggleStateOn != proToggleState){ proToggleState \= toggleStateOn; toggleStateListener.onToggleState(toggleStateOn); } break; } invalidate();//重绘 return true; }
5)界面重绘方法onDraw
这个方法主要实现的是界面的重绘操作。
只要的思路是:
画背景图A:
当前手指滑动X坐标currentX大于图A宽度的一般时,按钮背景为开启状态;
当前手指滑动X坐标currentX小于图A宽度的一般时,按钮背景为关闭状态;
记录滑块B的X坐标:
B滑动时:
当前手指滑动X坐标currentX大于背景图A的宽度,则B坐标为图A宽度减去图B宽度
当前手指滑动X坐标currentX小于背景图A的宽度,则B坐标为当前X坐标currentX减去滑块宽度的一半
B静止:
当按钮处于“开”状态,则B坐标为“开”状态的最左边X坐标
当按钮处于“关”状态,则B坐标为“关”状态的最左边X坐标
具体实现代码如下:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //用来记录我们滑动块的位置 int left\_slip \= 0; Matrix matrix \= new Matrix(); Paint paint \= new Paint(); if(currentX < bkgSwitchOn.getWidth() / 2){ //在画布上绘制出开关状态为关闭时的 背景图片 canvas.drawBitmap(bkgSwitchOff, matrix, paint); }else{ //在画布上绘制出开关状态为开启时的 背景图片 canvas.drawBitmap(bkgSwitchOn, matrix, paint); } if(isSlipping){//开关是否处于滑动状态 // 滑动块 是否超过了整个滑动按钮的宽度 if(currentX \> bkgSwitchOn.getWidth()){ //指定滑动块的位置 left\_slip \= bkgSwitchOn.getWidth() \- btnSlip.getWidth(); } else { //设置当前滑动块的位置 left\_slip \= (int) (currentX \- btnSlip.getWidth() /2); } } else {//开关是否处于 不滑动状态 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)计算开关的宽高
这里我通过覆写onMeasure来计算开关的宽度和高度
具体实现代码如下:
//计算开关的宽高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(bkgSwitchOn.getWidth(), bkgSwitchOn.getHeight()); }
7)设置图片资源信息
这个方法主要是供外界调用,向本类提供图片资源。
具体代码实现如下:
/\*\* \* 设置图片资源信息 \* @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)设置开关按钮的状态
通过传递一个boolean类型的状态,我们在这个方法中将这个状态标识记录下来。
具体实现代码如下:
/\*\* \* 设置开关按钮的状态 \* @param state \*/ public void setToggleState(boolean state) { toggleStateOn \= state; }
9)自定义开关状态监听器
我在这个类中定义了一个开关状态监听器接口OnToggleStateListener,里面有一个onToggleState方法来执行按钮的状态变化监听操作。
具体代码实现如下:
/\*\* \* 自定义开关状态监听器 \* @author liuyazhuang \* \*/ interface OnToggleStateListener { abstract void onToggleState(boolean state); }
10)设置开关监听器
创建setOnToggleStateListener方法,传递一个OnToggleStateListener监听器对象,通过外界创建OnToggleStateListener对象,并将OnToggleStateListener对象传递进来,我们只需要将外界传递过来的OnToggleStateListener对象记录下来,同时当我们调用OnToggleStateListener接口中的onToggleState方法时,便实现了回调外界OnToggleStateListener实现类中的onToggleState方法。
具体代码实现如下:
//设置开关监听器并将是否设置了开关监听器设置为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; /\*\* \* 自定义开关类 \* @author liuyazhuang \* \*/ public class MyToggle extends View implements OnTouchListener { //开关开启的背景图片 private Bitmap bkgSwitchOn; //开关关闭的背景图片 private Bitmap bkgSwitchOff; //开关的滚动图片 private Bitmap btnSlip; //当前开关是否为开启状态 private boolean toggleStateOn; //开关状态的监听事件 private OnToggleStateListener toggleStateListener; //记录开关·当前的状态 private boolean isToggleStateListenerOn; //手指按下屏幕时的x坐标 private float proX; //手指滑动过程中当前x坐标 private float currentX; //是否处于滑动状态 private boolean isSlipping; //记录上一次开关的状态 private boolean proToggleState \= true; //开关开启时的矩形 private Rect rect\_on; //开关关闭时的矩形 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: //记录手指按下时的x坐标 proX \= event.getX(); currentX \= proX; //将滑动标识设置为true isSlipping \= true; break; case MotionEvent.ACTION\_MOVE: //记录手指滑动过程中当前x坐标 currentX \= event.getX(); break; case MotionEvent.ACTION\_UP: //手指抬起时将是否滑动的标识设置为false isSlipping \= false; //处于关闭状态 if(currentX < bkgSwitchOn.getWidth() / 2 ){ toggleStateOn \= false; } else { // 处于开启状态 toggleStateOn \= true; } // 如果使用了开关监听器,同时开关的状态发生了改变,这时使用该代码 if(isToggleStateListenerOn && toggleStateOn != proToggleState){ proToggleState \= toggleStateOn; toggleStateListener.onToggleState(toggleStateOn); } break; } invalidate();//重绘 return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //用来记录我们滑动块的位置 int left\_slip \= 0; Matrix matrix \= new Matrix(); Paint paint \= new Paint(); if(currentX < bkgSwitchOn.getWidth() / 2){ //在画布上绘制出开关状态为关闭时的 背景图片 canvas.drawBitmap(bkgSwitchOff, matrix, paint); }else{ //在画布上绘制出开关状态为开启时的 背景图片 canvas.drawBitmap(bkgSwitchOn, matrix, paint); } if(isSlipping){//开关是否处于滑动状态 // 滑动块 是否超过了整个滑动按钮的宽度 if(currentX \> bkgSwitchOn.getWidth()){ //指定滑动块的位置 left\_slip \= bkgSwitchOn.getWidth() \- btnSlip.getWidth(); } else { //设置当前滑动块的位置 left\_slip \= (int) (currentX \- btnSlip.getWidth() /2); } } else {//开关是否处于 不滑动状态 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); } //计算开关的宽高 @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(bkgSwitchOn.getWidth(), bkgSwitchOn.getHeight()); } /\*\* \* 设置图片资源信息 \* @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()); } /\*\* \* 设置开关按钮的状态 \* @param state \*/ public void setToggleState(boolean state) { toggleStateOn \= state; } /\*\* \* 自定义开关状态监听器 \* @author liuyazhuang \* \*/ interface OnToggleStateListener { abstract void onToggleState(boolean state); } //设置开关监听器并将是否设置了开关监听器设置为true public void setOnToggleStateListener(OnToggleStateListener listener) { toggleStateListener \= listener; isToggleStateListenerOn \= true; } }
2、MainActivity
这个类实现很简单,主要的功能就是加载界面布局,初始化界面控件,调用MyToggle类中的方法实现按钮的开关效果
具体代码实现如下:
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 { //自定义开关对象 private MyToggle toggle; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity\_main); toggle \= (MyToggle) findViewById(R.id.toggle); //设置开关显示所用的图片 toggle.setImageRes(R.drawable.bkg\_switch, R.drawable.bkg\_switch, R.drawable.btn\_slip); //设置开关的默认状态 true开启状态 toggle.setToggleState(true); //设置开关的监听 toggle.setOnToggleStateListener(new OnToggleStateListener() { @Override public void onToggleState(boolean state) { // TODO Auto-generated method stub if(state){ Toast.makeText(getApplicationContext(), "开关开启", 0).show(); } else { Toast.makeText(getApplicationContext(), "开关关闭", 0).show(); } } }); } }
3、布局文件activity_main.xml
这里我引用了自己定义的View类MyToggle。
具体代码实现如下:
<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\>
三、运行效果
四、温馨提示
大家可以到链接下载Android自定义开关按钮实现示例完整源代码
本实例中,为了方面,我把一些文字直接写在了布局文件中和相关的类中,大家在真实的项目中要把这些文字写在string.xml文件中,在外部引用这些资源,切记,这是作为一个Android程序员最基本的开发常识和规范,我在这里只是为了方便直接写在了类和布局文件中。
加载全部内容