Android之——史上最简单自定义开关按钮的实现

论坛 期权论坛 脚本     
匿名技术用户   2020-12-22 18:36   199   0
转载请注明出处: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上的移动来达到按钮开和关的效果。

具体实现代码如下:

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

三、运行效果



四、温馨提示

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

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

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP