自定义开关

多时候,我们在很多无论是Android还是IOS的APP中都会遇到这样的一种效果,有一个按钮,我们点击一下,便会滑动一下,一会显示“开”,一会显示“关”,这便是开关按钮了,比如:很多Android手机的设置功能里,就有很多功能是用开关按钮实现的,那么这些开关按钮时如何实现的呢?下面,就让我们一起来实现这个功能吧。

一、原理

我们在界面的某一个区域里放置一个背景图A,这个图片一边为“开”,一边为“关”,在这个图片上放置一个图片B,图B大约为图A的一半,恰好可以覆盖掉图A上的“开”或者“关”,当我们手指点击图片的时候,图B在图A上滑动,相应的覆盖“开”或者“关”,这样就实现了开关按钮的效果。

二、实现

1、自定义View类MyToggle

这个类继承自View类同时实现了OnTouchListener接口,这个类实现的功能比较多,我们分解来看这个类。

1)属性字段

这个类中定义了不少的属性字段,每个属性字段的具体含义详见代码注释

具体实现代码如下:

  1. //开关开启的背景图片  
  2. private Bitmap bkgSwitchOn;  
  3. //开关关闭的背景图片  
  4. private Bitmap bkgSwitchOff;  
  5. //开关的滚动图片  
  6. private Bitmap btnSlip;  
  7. //当前开关是否为开启状态  
  8. private boolean toggleStateOn;  
  9. //开关状态的监听事件  
  10. private OnToggleStateListener toggleStateListener;  
  11. //记录开关·当前的状态  
  12. private boolean isToggleStateListenerOn;  
  13. //手指按下屏幕时的x坐标  
  14. private float proX;  
  15. //手指滑动过程中当前x坐标  
  16. private float currentX;  
  17. //是否处于滑动状态  
  18. private boolean isSlipping;  
  19. //记录上一次开关的状态  
  20. private boolean proToggleState = true;  
  21. //开关开启时的矩形  
  22. private Rect rect_on;  
  23. //开关关闭时的矩形  
  24. private Rect rect_off;  
2)覆写View类的构造方法

我们在构造方法里完成的操作就是调用我们自己创建的init()方法

具体实现代码如下:

  1. public MyToggle(Context context) {  
  2.     super(context);  
  3.     init(context);  
  4. }  
  5.   
  6. public MyToggle(Context context, AttributeSet attrs) {  
  7.     super(context, attrs);  
  8.     init(context);  
  9. }  
3)创建init方法

这个方法中实现的操作就是设置触摸事件。

具体实现代码如下:

  1. //初始化方法  
  2. private void init(Context context) {  
  3.     setOnTouchListener(this);  
  4.   
  5. }  
4)手指触摸事件回调方法onTouch

这个方法是当手指操作手机屏幕时,Android自动回调的方法,我们在这个方法中,监听手指的按下、移动和抬起事件,记录手指当前的X坐标来判断图片B的移动方向,从而实现图片B在图片A上的移动来达到按钮开和关的效果。

具体实现代码如下:

  1. @Override  
  2. public boolean onTouch(View v, MotionEvent event) {  
  3.     switch (event.getAction()) {  
  4.     case MotionEvent.ACTION_DOWN:  
  5.         //记录手指按下时的x坐标  
  6.         proX = event.getX();   
  7.         currentX = proX;  
  8.         //将滑动标识设置为true  
  9.         isSlipping = true;  
  10.         break;  
  11.   
  12.     case MotionEvent.ACTION_MOVE:  
  13.         //记录手指滑动过程中当前x坐标  
  14.         currentX = event.getX();  
  15.         break;  
  16.   
  17.     case MotionEvent.ACTION_UP:  
  18.         //手指抬起时将是否滑动的标识设置为false  
  19.         isSlipping = false;  
  20.         //处于关闭状态  
  21.         if(currentX < bkgSwitchOn.getWidth() / 2 ){  
  22.             toggleStateOn = false;  
  23.         } else { // 处于开启状态  
  24.             toggleStateOn = true;  
  25.         }  
  26.           
  27.         // 如果使用了开关监听器,同时开关的状态发生了改变,这时使用该代码  
  28.         if(isToggleStateListenerOn && toggleStateOn != proToggleState){  
  29.             proToggleState = toggleStateOn;  
  30.             toggleStateListener.onToggleState(toggleStateOn);  
  31.         }  
  32.         break;  
  33.     }  
  34.       
  35.     invalidate();//重绘  
  36.     return true;  
  37. }  
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坐标

具体实现代码如下:

  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     //用来记录我们滑动块的位置  
  5.     int left_slip = 0;   
  6.     Matrix matrix = new Matrix();  
  7.     Paint paint = new Paint();  
  8.     if(currentX < bkgSwitchOn.getWidth() / 2){  
  9.         //在画布上绘制出开关状态为关闭时的  背景图片  
  10.         canvas.drawBitmap(bkgSwitchOff, matrix, paint);  
  11.     }else{  
  12.         //在画布上绘制出开关状态为开启时的  背景图片  
  13.         canvas.drawBitmap(bkgSwitchOn, matrix, paint);  
  14.     }  
  15.     if(isSlipping){//开关是否处于滑动状态  
  16.         // 滑动块 是否超过了整个滑动按钮的宽度   
  17.         if(currentX > bkgSwitchOn.getWidth()){  
  18.             //指定滑动块的位置  
  19.             left_slip = bkgSwitchOn.getWidth() - btnSlip.getWidth();  
  20.         } else {  
  21.             //设置当前滑动块的位置  
  22.             left_slip = (int) (currentX - btnSlip.getWidth() /2);  
  23.         }  
  24.     } else {//开关是否处于   不滑动状态   
  25.         if(toggleStateOn){  
  26.             left_slip = rect_on.left;  
  27.         } else {  
  28.             left_slip = rect_off.left;  
  29.         }  
  30.     }  
  31.       
  32.     if(left_slip < 0){  
  33.         left_slip = 0;  
  34.     } else if( left_slip > bkgSwitchOn.getWidth() - btnSlip.getWidth()){  
  35.         left_slip = bkgSwitchOn.getWidth() - btnSlip.getWidth();  
  36.     }  
  37.     //绘制图像  
  38.     canvas.drawBitmap(btnSlip, left_slip, 0, paint);  
  39. }  
6)计算开关的宽高

这里我通过覆写onMeasure来计算开关的宽度和高度

具体实现代码如下:

  1. //计算开关的宽高  
  2. @Override  
  3. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  4.     setMeasuredDimension(bkgSwitchOn.getWidth(), bkgSwitchOn.getHeight());  
  5. }  
7)设置图片资源信息

这个方法主要是供外界调用,向本类提供图片资源。

具体代码实现如下:

  1. /** 
  2.  * 设置图片资源信息 
  3.  * @param bkgSwitch_on 
  4.  * @param bkgSwitch_off 
  5.  * @param btn_Slip 
  6.  */  
  7. public void setImageRes(int bkgSwitch_on, int bkgSwitch_off, int btn_Slip) {  
  8.     bkgSwitchOn = BitmapFactory.decodeResource(getResources(), bkgSwitch_on);  
  9.     bkgSwitchOff = BitmapFactory.decodeResource(getResources(),bkgSwitch_off);  
  10.     btnSlip = BitmapFactory.decodeResource(getResources(), btn_Slip);  
  11.     rect_on = new Rect(bkgSwitchOn.getWidth() - btnSlip.getWidth(), 0,bkgSwitchOn.getWidth(), btnSlip.getHeight());  
  12.     rect_off = new Rect(00, btnSlip.getWidth(), btnSlip.getHeight());  
  13. }  
8)设置开关按钮的状态

通过传递一个boolean类型的状态,我们在这个方法中将这个状态标识记录下来。

具体实现代码如下:

  1. /** 
  2.  * 设置开关按钮的状态 
  3.  * @param state 
  4.  */  
  5. public void setToggleState(boolean state) {  
  6.     toggleStateOn = state;  
  7. }  
9)自定义开关状态监听器

我在这个类中定义了一个开关状态监听器接口OnToggleStateListener,里面有一个onToggleState方法来执行按钮的状态变化监听操作。

具体代码实现如下:

  1. /** 
  2.  * 自定义开关状态监听器 
  3.  * @author liuyazhuang 
  4.  * 
  5.  */  
  6. interface OnToggleStateListener {  
  7.     abstract void onToggleState(boolean state);  
  8. }  
10)设置开关监听器

创建setOnToggleStateListener方法,传递一个OnToggleStateListener监听器对象,通过外界创建OnToggleStateListener对象,并将OnToggleStateListener对象传递进来,我们只需要将外界传递过来的OnToggleStateListener对象记录下来,同时当我们调用OnToggleStateListener接口中的onToggleState方法时,便实现了回调外界OnToggleStateListener实现类中的onToggleState方法。

具体代码实现如下:

  1. //设置开关监听器并将是否设置了开关监听器设置为true  
  2. public void setOnToggleStateListener(OnToggleStateListener listener) {  
  3.     toggleStateListener = listener;  
  4.     isToggleStateListenerOn = true;  
  5. }  
11)MyToggle完整代码如下:
  1. package com.lyz.slip.toggle;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Bitmap;  
  5. import android.graphics.BitmapFactory;  
  6. import android.graphics.Canvas;  
  7. import android.graphics.Matrix;  
  8. import android.graphics.Paint;  
  9. import android.graphics.Rect;  
  10. import android.util.AttributeSet;  
  11. import android.view.MotionEvent;  
  12. import android.view.View;  
  13. import android.view.View.OnTouchListener;  
  14.   
  15. /** 
  16.  * 自定义开关类 
  17.  * @author liuyazhuang 
  18.  * 
  19.  */  
  20. public class MyToggle extends View implements OnTouchListener {  
  21.     //开关开启的背景图片  
  22.     private Bitmap bkgSwitchOn;  
  23.     //开关关闭的背景图片  
  24.     private Bitmap bkgSwitchOff;  
  25.     //开关的滚动图片  
  26.     private Bitmap btnSlip;  
  27.     //当前开关是否为开启状态  
  28.     private boolean toggleStateOn;  
  29.     //开关状态的监听事件  
  30.     private OnToggleStateListener toggleStateListener;  
  31.     //记录开关·当前的状态  
  32.     private boolean isToggleStateListenerOn;  
  33.     //手指按下屏幕时的x坐标  
  34.     private float proX;  
  35.     //手指滑动过程中当前x坐标  
  36.     private float currentX;  
  37.     //是否处于滑动状态  
  38.     private boolean isSlipping;  
  39.     //记录上一次开关的状态  
  40.     private boolean proToggleState = true;  
  41.     //开关开启时的矩形  
  42.     private Rect rect_on;  
  43.     //开关关闭时的矩形  
  44.     private Rect rect_off;  
  45.   
  46.     public MyToggle(Context context) {  
  47.         super(context);  
  48.         init(context);  
  49.     }  
  50.       
  51.     public MyToggle(Context context, AttributeSet attrs) {  
  52.         super(context, attrs);  
  53.         init(context);  
  54.     }  
  55.       
  56.     //初始化方法  
  57.     private void init(Context context) {  
  58.         setOnTouchListener(this);  
  59.   
  60.     }  
  61.   
  62.     @Override  
  63.     public boolean onTouch(View v, MotionEvent event) {  
  64.         switch (event.getAction()) {  
  65.         case MotionEvent.ACTION_DOWN:  
  66.             //记录手指按下时的x坐标  
  67.             proX = event.getX();   
  68.             currentX = proX;  
  69.             //将滑动标识设置为true  
  70.             isSlipping = true;  
  71.             break;  
  72.   
  73.         case MotionEvent.ACTION_MOVE:  
  74.             //记录手指滑动过程中当前x坐标  
  75.             currentX = event.getX();  
  76.             break;  
  77.   
  78.         case MotionEvent.ACTION_UP:  
  79.             //手指抬起时将是否滑动的标识设置为false  
  80.             isSlipping = false;  
  81.             //处于关闭状态  
  82.             if(currentX < bkgSwitchOn.getWidth() / 2 ){  
  83.                 toggleStateOn = false;  
  84.             } else { // 处于开启状态  
  85.                 toggleStateOn = true;  
  86.             }  
  87.               
  88.             // 如果使用了开关监听器,同时开关的状态发生了改变,这时使用该代码  
  89.             if(isToggleStateListenerOn && toggleStateOn != proToggleState){  
  90.                 proToggleState = toggleStateOn;  
  91.                 toggleStateListener.onToggleState(toggleStateOn);  
  92.             }  
  93.             break;  
  94.         }  
  95.           
  96.         invalidate();//重绘  
  97.         return true;  
  98.     }  
  99.   
  100.     @Override  
  101.     protected void onDraw(Canvas canvas) {  
  102.         super.onDraw(canvas);  
  103.         //用来记录我们滑动块的位置  
  104.         int left_slip = 0;   
  105.         Matrix matrix = new Matrix();  
  106.         Paint paint = new Paint();  
  107.         if(currentX < bkgSwitchOn.getWidth() / 2){  
  108.             //在画布上绘制出开关状态为关闭时的  背景图片  
  109.             canvas.drawBitmap(bkgSwitchOff, matrix, paint);  
  110.         }else{  
  111.             //在画布上绘制出开关状态为开启时的  背景图片  
  112.             canvas.drawBitmap(bkgSwitchOn, matrix, paint);  
  113.         }  
  114.         if(isSlipping){//开关是否处于滑动状态  
  115.             // 滑动块 是否超过了整个滑动按钮的宽度   
  116.             if(currentX > bkgSwitchOn.getWidth()){  
  117.                 //指定滑动块的位置  
  118.                 left_slip = bkgSwitchOn.getWidth() - btnSlip.getWidth();  
  119.             } else {  
  120.                 //设置当前滑动块的位置  
  121.                 left_slip = (int) (currentX - btnSlip.getWidth() /2);  
  122.             }  
  123.         } else {//开关是否处于   不滑动状态   
  124.             if(toggleStateOn){  
  125.                 left_slip = rect_on.left;  
  126.             } else {  
  127.                 left_slip = rect_off.left;  
  128.             }  
  129.         }  
  130.           
  131.         if(left_slip < 0){  
  132.             left_slip = 0;  
  133.         } else if( left_slip > bkgSwitchOn.getWidth() - btnSlip.getWidth()){  
  134.             left_slip = bkgSwitchOn.getWidth() - btnSlip.getWidth();  
  135.         }  
  136.         //绘制图像  
  137.         canvas.drawBitmap(btnSlip, left_slip, 0, paint);  
  138.     }  
  139.     //计算开关的宽高  
  140.     @Override  
  141.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  142.         setMeasuredDimension(bkgSwitchOn.getWidth(), bkgSwitchOn.getHeight());  
  143.     }  
  144.       
  145.     /** 
  146.      * 设置图片资源信息 
  147.      * @param bkgSwitch_on 
  148.      * @param bkgSwitch_off 
  149.      * @param btn_Slip 
  150.      */  
  151.     public void setImageRes(int bkgSwitch_on, int bkgSwitch_off, int btn_Slip) {  
  152.         bkgSwitchOn = BitmapFactory.decodeResource(getResources(), bkgSwitch_on);  
  153.         bkgSwitchOff = BitmapFactory.decodeResource(getResources(),bkgSwitch_off);  
  154.         btnSlip = BitmapFactory.decodeResource(getResources(), btn_Slip);  
  155.         rect_on = new Rect(bkgSwitchOn.getWidth() - btnSlip.getWidth(), 0,bkgSwitchOn.getWidth(), btnSlip.getHeight());  
  156.         rect_off = new Rect(00, btnSlip.getWidth(), btnSlip.getHeight());  
  157.     }  
  158.   
  159.     /** 
  160.      * 设置开关按钮的状态 
  161.      * @param state 
  162.      */  
  163.     public void setToggleState(boolean state) {  
  164.         toggleStateOn = state;  
  165.     }  
  166.   
  167.     /** 
  168.      * 自定义开关状态监听器 
  169.      * @author liuyazhuang 
  170.      * 
  171.      */  
  172.     interface OnToggleStateListener {  
  173.         abstract void onToggleState(boolean state);  
  174.     }  
  175.     //设置开关监听器并将是否设置了开关监听器设置为true  
  176.     public void setOnToggleStateListener(OnToggleStateListener listener) {  
  177.         toggleStateListener = listener;  
  178.         isToggleStateListenerOn = true;  
  179.     }  
  180.   
  181. }  
2、MainActivity

这个类实现很简单,主要的功能就是加载界面布局,初始化界面控件,调用MyToggle类中的方法实现按钮的开关效果
具体代码实现如下:

  1. package com.lyz.slip.toggle;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.widget.Toast;  
  6.   
  7. import com.lyz.slip.toggle.MyToggle.OnToggleStateListener;  
  8.   
  9. /** 
  10.  * 程序主入口 
  11.  * @author liuyazhuang 
  12.  * 
  13.  */  
  14. public class MainActivity extends Activity {  
  15.       
  16.     //自定义开关对象  
  17.     private MyToggle toggle;  
  18.     @Override  
  19.     public void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.           
  23.         toggle = (MyToggle) findViewById(R.id.toggle);  
  24.           
  25.         //设置开关显示所用的图片  
  26.         toggle.setImageRes(R.drawable.bkg_switch, R.drawable.bkg_switch, R.drawable.btn_slip);  
  27.           
  28.         //设置开关的默认状态    true开启状态  
  29.         toggle.setToggleState(true);  
  30.           
  31.         //设置开关的监听  
  32.         toggle.setOnToggleStateListener(new OnToggleStateListener() {  
  33.             @Override  
  34.             public void onToggleState(boolean state) {  
  35.                 // TODO Auto-generated method stub  
  36.                 if(state){  
  37.                     Toast.makeText(getApplicationContext(), "开关开启"0).show();  
  38.                 } else {  
  39.                     Toast.makeText(getApplicationContext(), "开关关闭"0).show();  
  40.                 }  
  41.             }  
  42.         });  
  43.     }  
  44. }  
3、布局文件activity_main.xml

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

具体代码实现如下:

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" >  
  5.   
  6.     <com.lyz.slip.toggle.MyToggle  
  7.         android:id="@+id/toggle"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:layout_centerInParent="true"/>  
  11.   
  12. </RelativeLayout>  
4、AndroidManifest.xml

具体代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.lyz.slip.toggle"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="10"  
  9.         android:targetSdkVersion="18" />  
  10.   
  11.     <application  
  12.         android:allowBackup="true"  
  13.         android:icon="@drawable/ic_launcher"  
  14.         android:label="@string/app_name"  
  15.         android:theme="@style/AppTheme" >  
  16.         <activity  
  17.             android:name="com.lyz.slip.toggle.MainActivity"  
  18.             android:label="@string/app_name" >  
  19.             <intent-filter>  
  20.                 <action android:name="android.intent.action.MAIN" />  
  21.   
  22.                 <category android:name="android.intent.category.LAUNCHER" />  
  23.             </intent-filter>  
  24.         </activity>  
  25.     </application>  
  26.   
  27. </manifest>  

三、运行效果


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值