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

转载请注明出处: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。

具体代码实现如下:



    


4、AndroidManifest.xml

具体代码如下:




    

    
        
            
                

                
            
        
    


三、运行效果

Android之——史上最简单自定义开关按钮的实现_第1张图片

Android之——史上最简单自定义开关按钮的实现_第2张图片

四、温馨提示

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

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

你可能感兴趣的:(Android,Android)