Android自定义View-SwitchView(自定义开关)
Android自定义开关,效果图如图:
一个是关的状态,一个是开的状态,通过监听回调将滑块滑动状态返回到调用界面。
实质上底层是一个背景,然后在这个背景之上增加了一个可移动滑块,通过判断滑块滑动之后的位置,在背景图中心点左边表示开关是开,在右边表示是关。
1.自定义view类
SwitchView:主要是用来绘制开关背景及滑块,并通过滑块移动位置来判断状态。
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/**
* @author M.xang
* @时间 2019年4月22日
* @描述 自定义开关控件
*/
public class SwitchView extends View {
private Bitmap switchBackgroupBitmap = null; // 背景图片
private Bitmap slideButtonBitmap = null; // 滑块图片
private Paint paint = null; // 画笔
private boolean mSwitchState = false; // 开关状态, 默认false
private float currentX;
private int width_bg = 0, height_bg = 0;
@SuppressWarnings("unused")
private int width_slide = 0, height_slide = 0;
/**
* 用于代码创建控件
*
* @param context
*/
public SwitchView(Context context) {
super(context);
init();
}
/**
* 用于在xml里使用, 可指定自定义属性
*
* @param context
* @param attrs
*/
public SwitchView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
// 获取配置的自定义属性
setSwitchBackgroundResource(R.drawable.switch_bg);// 开关背景
setSlideButtonResource(R.drawable.switch_control);// 滑块背景
}
private void init() {
paint = new Paint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 依据背景图片来确定控件大小
width_bg = switchBackgroupBitmap.getWidth();
height_bg = switchBackgroupBitmap.getHeight();
if (slideButtonBitmap != null) {
width_slide = slideButtonBitmap.getWidth();
height_slide = slideButtonBitmap.getHeight();
}
setMeasuredDimension(width_bg, height_bg);
}
// Canvas 画布, 画板. 在上边绘制的内容都会显示到界面上.
@Override
protected void onDraw(Canvas canvas) {
// 1. 绘制背景
canvas.drawBitmap(switchBackgroupBitmap, 0, 0, paint);
// 2. 绘制滑块
if (isTouchMode) {
// 根据当前用户触摸到的位置画滑块
// 让滑块向左移动自身一半大小的位置
float newLeft = currentX - width_slide / 2.0f;
int maxLeft = width_bg - width_slide;
// 限定滑块范围
if (newLeft < 0) {
newLeft = 0; // 左边范围
} else if (newLeft > maxLeft) {
newLeft = maxLeft; // 右边范围
}
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
} else {
// 根据开关状态boolean, 直接设置图片位置
if (mSwitchState) {// 开,开关滑块置于最右边,显示ON
canvas.drawBitmap(slideButtonBitmap, 0, 0, paint);
} else {// 关,开关滑块置于最左边,显示OFF
int newLeft = width_bg - width_slide;
if (slideButtonBitmap != null) {
canvas.drawBitmap(slideButtonBitmap, newLeft, 0, paint);
}
}
}
}
boolean isTouchMode = false;
private OnSwitchStateUpdateListener onSwitchStateUpdateListener;
// 重写触摸事件, 响应用户的触摸.
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isTouchMode = true;
currentX = event.getX();
break;
case MotionEvent.ACTION_MOVE:
currentX = event.getX();
break;
case MotionEvent.ACTION_UP:
isTouchMode = false;
currentX = event.getX();
float center = width_bg / 2.0f;
// 根据当前按下的位置, 和控件中心的位置进行比较.
boolean state = currentX < center;// 离开位置小于中心位置,点击了关,true
// 如果开关状态变化了, 通知界面. 里边开关状态更新了.
if (state != mSwitchState && onSwitchStateUpdateListener != null) {
// 把最新的boolean, 状态传出去了
onSwitchStateUpdateListener.onStateUpdate(state);
}
mSwitchState = state;
break;
}
// 重绘界面
invalidate(); // 会引发onDraw()被调用, 里边的变量会重新生效.界面会更新
return true; // 消费了用户的触摸事件, 才可以收到其他的事件.
}
/**
* 设置背景图
*
* @param switchBackground
*/
public void setSwitchBackgroundResource(int switchBackground) {
if (switchBackground > 0) {
switchBackgroupBitmap = BitmapFactory.decodeResource(getResources(), switchBackground);
} else {
Log.e("---SlideSwitchView--->", "没有设置开关背景图,应设置app:switch_background=\"@drawable/xxx\"");
}
}
/**
* 设置滑块图片资源
*
* @param slideButton
*/
public void setSlideButtonResource(int slideButton) {
if (slideButton > 0) {
slideButtonBitmap = BitmapFactory.decodeResource(getResources(), slideButton);
} else {
Log.e("---SlideSwitchView--->", "没有设置滑动按钮图,应设置app:slide_button=\"@drawable/xxx\"");
}
}
/**
* 设置开关初始状态
*
* @param mSwitchState true:默认开,false:默认关
*/
public void setSwitchState(boolean mSwitchState) {
this.mSwitchState = mSwitchState;
}
/**
* 设置开关初始状态
*
* @param state true:开,false:关
*/
public void setSwitchOpen(boolean state) {
mSwitchState = state;
invalidate(); // 会引发onDraw()被调用, 里边的变量会重新生效.界面会更新
}
/**
* @author M.xang
* @时间 2019年4月22日
* @描述 开关状态回调
*/
public interface OnSwitchStateUpdateListener {
/**
* 状态回调, 开关当前状态
*
* @param state
*/
void onStateUpdate(boolean state);
}
/**
** 将回调对象传到控件
*
* @param onSwitchStateUpdateListener
*/
public void setOnSwitchStateUpdateListener(OnSwitchStateUpdateListener onSwitchStateUpdateListener) {
this.onSwitchStateUpdateListener = onSwitchStateUpdateListener;
}
}
2.使用
再布局中的使用:
<com.view.SwitchView
android:id="@+id/sw_computer"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
在Activity中调用:
// 进行实例化
SwitchView sw_computer = sw_computer = UIUtil.findView(this, R.id.sw_computer);
// 设置开关的初始状态,默认是关闭的
sw_computer.setSwitchState(false);
// 开关点击事件
sw_computer.setOnSwitchStateUpdateListener(new OnSwitchStateUpdateListener() {
@Override
public void onStateUpdate(boolean state) {
// state 为 true是开,false是关
}
});