转载请注明出处:http://blog.csdn.net/l1028386804/article/details/48102871

很多时候,我们在很多无论是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上的移动来达到按钮开和关的效果。

具体实现代码如下:

@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN://记录手指按下时的x坐标proX = event.getX(); currentX = proX;//将滑动标识设置为trueisSlipping = true;break;case MotionEvent.ACTION_MOVE://记录手指滑动过程中当前x坐标currentX = event.getX();break;case MotionEvent.ACTION_UP://手指抬起时将是否滑动的标识设置为falseisSlipping = 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坐标

具体实现代码如下:

@Overrideprotected 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来计算开关的宽度和高度

具体实现代码如下:

//计算开关的宽高@Overrideprotected 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方法。

具体代码实现如下:

//设置开关监听器并将是否设置了开关监听器设置为truepublic 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);}@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN://记录手指按下时的x坐标proX = event.getX(); currentX = proX;//将滑动标识设置为trueisSlipping = true;break;case MotionEvent.ACTION_MOVE://记录手指滑动过程中当前x坐标currentX = event.getX();break;case MotionEvent.ACTION_UP://手指抬起时将是否滑动的标识设置为falseisSlipping = false;//处于关闭状态if(currentX < bkgSwitchOn.getWidth() / 2 ){toggleStateOn = false;} else { // 处于开启状态toggleStateOn = true;}// 如果使用了开关监听器,同时开关的状态发生了改变,这时使用该代码if(isToggleStateListenerOn && toggleStateOn != proToggleState){proToggleState = toggleStateOn;toggleStateListener.onToggleState(toggleStateOn);}break;}invalidate();//重绘return true;}@Overrideprotected 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);}//计算开关的宽高@Overrideprotected 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);}//设置开关监听器并将是否设置了开关监听器设置为truepublic 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() {@Overridepublic void onToggleState(boolean state) {// TODO Auto-generated method stubif(state){Toast.makeText(getApplicationContext(), "开关开启", 0).show();} else {Toast.makeText(getApplicationContext(), "开关关闭", 0).show();}}});    }}

3、布局文件activity_main.xml

这里我引用了自己定义的View类MyToggle。

具体代码实现如下:

    

4、AndroidManifest.xml

具体代码如下:

<?xml version="1.0" encoding="utf-8"?>                                                                                    

三、运行效果



四、温馨提示

大家可以到链接http://download.csdn.net/detail/l1028386804/9063331下载Android自定义开关按钮实现示例完整源代码

本实例中,为了方面,我把一些文字直接写在了布局文件中和相关的类中,大家在真实的项目中要把这些文字写在string.xml文件中,在外部引用这些资源,切记,这是作为一个Android程序员最基本的开发常识和规范,我在这里只是为了方便直接写在了类和布局文件中。

更多相关文章

  1. 几行代码看程序员的水平——Android文件命名规范
  2. Android应用程序的生命周期
  3. 浅谈android的selector背景选择器
  4. android switch模块
  5. 解决ScrollView中嵌套RecyclerVIew产生滑动冲突问题
  6. 用Scroller完成一个简单的ViewPager
  7. android view滑动助手类OverScroller
  8. Android中ListView分页加载数据
  9. Android实现录屏直播+远程控制之MediaCodec编码篇

随机推荐

  1. github前100名的android开源库
  2. 《Android》Lesson11-UI布局
  3. Android(安卓)原生GPS定位 判断室内室外
  4. Android回音噪音处理Demo
  5. 关于Android(安卓)6.0及以上版本用Pendin
  6. activity的启动过程分析
  7. Android(安卓)开发中遇到的 Exception &
  8. [置顶] Android(安卓)app内存管理的16点
  9. Android(安卓)之 下拉(Spinner) 组件示例
  10. ActionBarSherlock的学习笔记(四) --------