先看效果图:
关闭状态
打开状态
滑动过程中
Demo下载地址:
http://download.youkuaiyun.com/detail/lwj704684897/6820013
import android.content.Context;
import android.content.res.TypedArray;
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.VelocityTracker;
import android.view.View;
import android.view.View.OnTouchListener;
import xx.R;
public class SlipButton extends View implements OnTouchListener {
public static final String TAG = "SlipButton";
// 开关开启时的背景,关闭时的背景,滑动按钮
private Bitmap slipImg;
//the background of the slip button
private Bitmap bg;
private Bitmap slipBubble;
private Matrix matrix = new Matrix();
private Paint paint = new Paint();
// 滑动按钮的左边坐标
private float leftPosOfSlipImg;
//the middle value of the slip image,note: its not the half of the
//slip image width,its the place where to show the slip on or off
//so its smaller than the half of the slip image
private int slipOnEndPos = 0;
private int slipOffStartPos = 0;
private int bgWidth = 0;
private Rect on_Rect;
private Rect off_Rect;
private Rect src_Rect;
private Rect dst_Rect;
// 是否正在滑动
private boolean isSlipping = false;
// 当前开关状态,true为开启,false为关闭
private boolean isSwitchOn = false;
// 手指按下时的水平坐标X,当前的水平坐标X
private float previousX, currentX;
// 开关监听器
private OnSwitchListener onSwitchListener;
// 是否设置了开关监听器
private boolean isSwitchListenerOn = false;
private VelocityTracker mVelocityTracker;
private static final int SNAP_VELOCITY = 100;
public SlipButton(Context context) {
super(context);
init();
}
public SlipButton(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.SlipButton);
boolean isOn = a.getBoolean(R.styleable.SlipButton_slipOn, false);
int count = a.getIndexCount();
//get the specified recourse id in xml file
int bgId = 0;
int slipBtId = 0;
for(int i = 0;i < count; i++) {
int attr = a.getIndex(i);
switch(attr) {
case R.styleable.SlipButton_bg:
bgId = a.getResourceId(attr, 0);
break;
case R.styleable.SlipButton_slipImg:
slipBtId = a.getResourceId(attr, 0);
break;
}
}
a.recycle();
//decode the image defined in xml file
setImageRes(bgId, slipBtId);
setSwitchState(isOn);
init();
}
/**
* set touch listener
*/
private void init() {
setOnTouchListener(this);
}
public void setImageRes(int bgId, int slipId){
bg = BitmapFactory.decodeResource(getResources(),bgId);
slipImg = BitmapFactory.decodeResource(getResources(), slipId);
slipBubble = BitmapFactory.decodeResource(getResources(),R.drawable.sb_slip_bubble);
bgWidth = bg.getWidth();
//The end position of the switch on state
//25 is the value which half of slip image add to the bubble's right side
slipOnEndPos = slipImg.getWidth()/2 + slipBubble.getWidth()/2;
//The start position of the switch off state
slipOffStartPos = slipImg.getWidth()/2 - slipBubble.getWidth()/2;
on_Rect = new Rect(0, 0,slipOnEndPos, slipImg.getHeight());
off_Rect = new Rect(slipOffStartPos, 0, slipImg.getWidth(), slipImg.getHeight());
src_Rect = new Rect(0, 0, bg.getWidth(), slipImg.getHeight());
dst_Rect = new Rect(0, 0, bg.getWidth(), bg.getHeight());
//this method should be called to initialize the rect state
setDrawImgRect();
}
public void setSwitchState(boolean switchState) {
isSwitchOn = switchState;
if (isSwitchListenerOn) {
onSwitchListener.onSwitched(isSwitchOn);
}
//reset the draw rect
setDrawImgRect();
//redraw the view
invalidate();
}
public boolean getSwitchState() {
return isSwitchOn;
}
public void updateSwitchState(boolean switchState) {
isSwitchOn = switchState;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(slipImg, src_Rect, dst_Rect, paint);
canvas.drawBitmap(bg, matrix, paint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(bg.getWidth(),
bg.getHeight());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
boolean isUpAlreadyCatch = false;
boolean previousState = false;
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
currentX = event.getX();
previousState = isSwitchOn;
//check whether need change the state of the switch
changeSwitchState(event.getX());
if (isSwitchListenerOn && (previousState != isSwitchOn)) {
onSwitchListener.onSwitched(isSwitchOn);
}
break;
case MotionEvent.ACTION_DOWN:
if (event.getX() > bg.getWidth() || event.getY() > bg.getHeight()) {
return false;
}
isSlipping = true;
previousX = event.getX();
currentX = previousX;
break;
case MotionEvent.ACTION_UP:
//catch up motion, the default should not change the state of the switch
isUpAlreadyCatch = true;
previousState = isSwitchOn;
changeSwitchState(event.getX());
default:
do {
if (isUpAlreadyCatch) {
break;
}
//when not catch the up motion, check the state of the switch by the move speed of the touch
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) velocityTracker.getXVelocity();
previousState = isSwitchOn;
//when the move speed greater than the specified speed, we consider it has a state change
if (velocityX > SNAP_VELOCITY) {
isSwitchOn = true;
break;
}
if (velocityX < -SNAP_VELOCITY) {
isSwitchOn = false;
break;
}
} while (false);
isSlipping = false;
//call the listener
if (isSwitchListenerOn && (previousState != isSwitchOn)) {
onSwitchListener.onSwitched(isSwitchOn);
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
//reset the draw rect
setDrawImgRect();
//redraw the view
invalidate();
return true;
}
private void changeSwitchState(float currentPos) {
if (currentPos >= (bgWidth / 2)) {
isSwitchOn = true;
} else {
isSwitchOn = false;
}
}
private void setDrawImgRect(){
do{
//when slipping, calculator the left position of the slip image to be drawn
if(isSlipping) {
if (currentX > bgWidth) {
leftPosOfSlipImg = on_Rect.left;
} else {
leftPosOfSlipImg = slipOffStartPos - currentX;
}
}
if(isSlipping && leftPosOfSlipImg >= 0 && leftPosOfSlipImg <= bgWidth){
break;
}
//need reset the position of the slip image
if(isSwitchOn) {
leftPosOfSlipImg = on_Rect.left;
}else {
leftPosOfSlipImg = off_Rect.left;
}
}while(false);
int srcLeft = (int) leftPosOfSlipImg;
src_Rect.left = srcLeft;
src_Rect.right = srcLeft + bgWidth;
}
public void setOnSwitchListener(OnSwitchListener listener) {
onSwitchListener = listener;
isSwitchListenerOn = true;
}
public interface OnSwitchListener {
abstract void onSwitched(boolean isSwitchOn);
}
}
布局中引用自定义View
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:test="http://schemas.android.com/apk/res/com.test.project"
style="@style/LayoutBase.Vertical">
<LinearLayout
android:id="@+id/layout_toast"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="0dp">
<TextView
android:id="@+id/tv_toast"
style="@style/TextBoxBase.LeftLabel"
android:layout_width="0dp"
android:layout_weight="4"
android:text="@string/allow_to_outside_call"
/>
<com.sktlab.bizconfmobile.customview.SlipButton
android:id="@+id/sb_state"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
test:bg="@drawable/sb_bg"
test:slipImg="@drawable/sb_slip_img"
test:slipOn="false"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_verify"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
>
<EditText
android:id="@+id/et_input_content"
style="@style/TextBoxBase.LeftLabel"
android:layout_width="0dp"
android:layout_weight="4"
android:paddingLeft="5dp"
android:inputType="number"
/>
<Button
android:id="@+id/bt_input_confirm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:background="@drawable/selector_home_add_conf_bt_verify"
android:text="@string/ok" />
</LinearLayout>
</LinearLayout>
需要在项目中添加自定义属性:
<declare-styleable name="SlipButton">
<attr name="bg" format="reference"/>
<attr name="slipImg" format="reference"/>
<attr name="slipOn" format="boolean"></attr>
</declare-styleable>