/**
* 开关控件,通过左右滑动控件实现开关的切换效果 <br>
* 使用时需要设置开关状态监听{@link OnSwitchStateChangeListener} <br>
* 切换开关状态有2种方式: <br>
* <p>
* 1.{@link #on()}, {@link #off()}, 该方式分开操作, 并且带头动作动画 <br>
* <p>
* 2.{@link #turn(boolean, boolean), 该方式通过传入布尔值控制开关以及是否显示切换动画 <br>
*
*/
public class UISwitch extends View {
/**
* 用来监听开关状态变化的接口
*
*/
public interface OnSwitchStateChangeListener {
public void onChange(boolean state);
}
/** 最大滑动时间 **/
private static final int MAX_SETTLE_DURATION = 5000;
private static final int MESSAGE_SCROLL = 1;
private static final int DEFAULE_TRACKER_NEXT = 600;
/** 开, 关, 按钮 **/
private Bitmap on, off, handle;
private Paint mPaint;
private Paint handlePaint;
private Scroller mScroll;
private VelocityTracker mVelocityTracker;
private OnSwitchStateChangeListener l;
private int width, height;
private int mLastX;
/** 滑动偏移量 **/
private int mOffset;
/** 最大最小偏移 **/
private int mMaxOffset, mMinOffset;
/** 开关状态 **/
private boolean mState;
private boolean mOnEnable;
/** 是否进行了拖拽 **/
private boolean mIsBeingDragged;
/** 触摸优化 **/
private int mTouchSlop;
private int drawableIds[] = new int[] { R.drawable.switch_btn_on,
R.drawable.switch_btn_off, R.drawable.switch_btn_handle };
/** 滑动事件 **/
private Handler scrollHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (puteScrollOffset()) {
final int currX = mScroll.getCurrX();
final int finalX = mScroll.getFinalX();
if (finalX >= 0) {
mOffset -= currX;
} else {
mOffset += -currX;
}
// 校验偏移量
mOffset = Math.min(Math.max(mMinOffset, mOffset), mMaxOffset);
sendEmptyMessage(msg.what);
invalidate();
}
}
};
public UISwitch(Context context) {
this(context, null);
}
public UISwitch(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public UISwitch(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.UISwitch, defStyle, 0);
if (a != null) {
drawableIds[0] = a.getResourceId(
R.styleable.UISwitch_ui_switch_on_src,
R.drawable.switch_btn_on);
drawableIds[1] = a.getResourceId(
R.styleable.UISwitch_ui_switch_off_src,
R.drawable.switch_btn_off);
drawableIds[2] = a.getResourceId(
R.styleable.UISwitch_ui_switch_handle_src,
R.drawable.switch_btn_handle);
mState = a.getBoolean(R.styleable.UISwitch_ui_switch_state, false);
a.recycle();
}
init(context);
}
private void init(Context context) {
on = getBitmapFor(drawableIds[0]);
off = getBitmapFor(drawableIds[1]);
handle = getBitmapFor(drawableIds[2]);
mScroll = new Scroller(context, new AccelerateInterpolator());
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = ViewConfigurationCompat
.getScaledPagingTouchSlop(configuration);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setFilterBitmap(true);
handlePaint = new Paint();
handlePaint.setAntiAlias(true);
handlePaint.setDither(true);
handlePaint.setFilterBitmap(true);
setClickable(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (on == null || off == null || handle == null) {
throw new RuntimeException("invalid image resources");
}
width = Math.max(Math.max(on.getWidth(), off.getWidth()),
handle.getWidth());
height = Math.max(Math.max(on.getHeight(), off.getHeight()),
handle.getHeight());
setMeasuredDimension(width, height);
mMinOffset = 0;
mMaxOffset = width - handle.getWidth();
setState(mState);
// final int newW = MeasureSpec.makeMeasureSpec(width,
// MeasureSpec.EXACTLY);
// final int newH = MeasureSpec.makeMeasureSpec(height,
// MeasureSpec.EXACTLY);
//
// super.onMeasure(newW, newH);
}
@Override
protected void onDraw(Canvas canvas) {
final Bitmap bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888);
final Canvas c = new Canvas(bmp);
// 画开图像
c.drawBitmap(on, getSwitchLeft() - on.getWidth() + handle.getWidth()
+ mOffset, getSwitchTop(), mPaint);
// 画关图像
c.drawBitmap(off, getSwitchLeft() + mOffset, getSwitchTop(), mPaint);
// 画把手
c.drawBitmap(handle, getSwitchLeft() + mOffset, getSwitchTop(),
handlePaint);
// 圆角处理
// canvas.drawBitmap(bmp, m, mPaint);
canvas.drawBitmap(toRoundCorner(bmp, bmp.getHeight() / 2),
getSwitchLeft(), getSwitchTop(), mPaint);
}
/**
* 设置开关资源图片
* <br>(the bitmap array sequence must be 'on', 'off', 'handle')
*
* @param bmp
*/
public void setResourceBitmaps(Bitmap bmp[]) {
if (bmp == null) {
throw new NullPointerException("the resource bitmap do not null");
}
if (bmp.length != 3) {
throw new ArrayIndexOutOfBoundsException("the resource bitmap array length must be 3");
}
on = bmp[0];
off = bmp[1];
handle = bmp[2];
width = Math.max(Math.max(on.getWidth(), off.getWidth()),
handle.getWidth());
height = Math.max(Math.max(on.getHeight(), off.getHeight()),
handle.getHeight());
onMeasure(0, 0);
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果速度监听对象为空
if (mVelocityTracker == null) {
// 获得该对象的实例
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final int action = event.getAction();
final int currX = (int) MotionEventCompat.getX(event, 0);
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//不让父控件获取手势
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
lightness(handlePaint, 0.9f);
if (puteScrollOffset()) {
mScroll.abortAnimation();
}
mLastX = currX;
break;
case MotionEvent.ACTION_MOVE:
final int dx = currX - mLastX;
if (!mIsBeingDragged) {
if (Math.abs(dx) > mTouchSlop / 4) {
mIsBeingDragged = true;
}
} else {
mOffset += currX - mLastX;
// 校验偏移量
mOffset = Math.min(Math.max(mMinOffset, mOffset), mMaxOffset);
mLastX = currX;
}
break;
case MotionEvent.ACTION_UP :
//不让父控件获取手势
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(false);
}
final VelocityTracker velocityTracker = mVelocityTracker;
puteCurrentVelocity(1000,
ViewConfiguration.get(getContext())
.getScaledMaximumFlingVelocity());
int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
velocityTracker, 0);
if (Math.abs(initialVelocity) > DEFAULE_TRACKER_NEXT
|| !mIsBeingDragged) {
if (!mIsBeingDragged) {
initialVelocity = 0;
}
turn(!mState, initialVelocity);
} else {
final int halfOffset = mMaxOffset / 2;
turn(mOffset > halfOffset, 0);
}
mIsBeingDragged = false;
if (mVelocityTracker != null) {
// 回收资源
mVelocityTracker.recycle();
mVelocityTracker = null;
}
lightness(handlePaint, 1f);
break;
}
invalidate();
return super.onTouchEvent(event);
}
/**
* 通过提供一个起点和直线距离开始滑动
*
* @param startx
* 起点x坐标
* @param starty
* 起点y坐标
* @param dx
* x距离
* @param dy
* y距离
* @param duration
* 持续滑动时间
*/
private void startScroll(int startx, int starty, int dx, int dy,
int duration) {
mScroll.startScroll(startx, starty, dx, dy, duration);
scrollHandler.removeMessages(MESSAGE_SCROLL);
scrollHandler.sendEmptyMessage(MESSAGE_SCROLL);
}
/**
* 获取滑动持续时间
*
* @param dx
* 滑动距离
* @param velocity
* 滑动速度
* @return
*/
private int getScrollDuration(int dx, int velocity) {
final int width = getWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
final float distance = halfWidth + halfWidth
* distanceInfluenceForSnapDuration(distanceRatio);
int duration = 0;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
duration = Math.abs(dx) * 4;
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
return duration;
}
// We want the duration of the page snap animation to be influenced by the
// distance that
// the screen has to travel, however, we don't want this duration to be
// effected in a
// purely linear fashion. Instead, we use this method to moderate the effect
// that the distance
// of travel has on the overall snap duration.
private float distanceInfluenceForSnapDuration(float f) {
f -= 0.5f; // center the values about 0.
f *= 0.3f * Math.PI / 2.0f;
return (float) Math.sin(f);
}
/**
* 将图片设置为圆角
*/
private Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Config.ARGB_8888);
final Canvas canvas = new Canvas(output);
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
canvas.drawARGB(0, 0, 0, 0);
canvas.drawRoundRect(rectF, pixels, pixels, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(0);
paint.setStrokeCap(Cap.ROUND);
paint.setStrokeJoin(Join.ROUND);
canvas.drawRoundRect(rectF, pixels, pixels, paint);
return output;
}
/**
* 获取资源位图
*
* @param resId
* @return
*/
private Bitmap getBitmapFor(int resId) {
Options op = new Options();
op.inScaled = true;
Bitmap bmp = BitmapFactory.decodeResource(getResources(), resId, op);
return bmp;
}
@Override
public void setEnabled(boolean enabled) {
setClickable(false);
setFocusable(false);
super.setEnabled(enabled);
}
/**
* 开关开启,含有切换效果
*/
public void on() {
turn(true, 0);
}
/**
* 开关关闭,含有切换效果
*/
public void off() {
turn(false, 0);
}
/**
* 开关操作
*
* @param on
* @param smoothScroll
* True to smoothly scroll to the new item, false to transition
* immediately
*/
public void turn(boolean on, boolean smoothScroll) {
if (smoothScroll) {
turn(on, 0);
} else {
setState(on);
}
}
/**
* 开关操作,含有切换效果
*
* @param on
* true/false, 开/关, on/off
* @param vx
* 切换速度, 0为系统默认
*/
private void turn(boolean on, int vx) {
int distance;
if (on) {
distance = mOffset - mMaxOffset;
} else {
distance = mOffset;
}
if (on != mState) {
if (l != null) {
l.onChange(on);
}
}
mState = on;
if (puteScrollOffset()) {
mScroll.abortAnimation();
}
startScroll(0, 0, distance, 0, getScrollDuration(distance, vx));
}
/**
* 设置开关状态,无状态切换动画
*/
private void setState(boolean state) {
if (state != mState) {
if (l != null) {
l.onChange(state);
}
}
mState = state;
if (state) {
mOffset = mMaxOffset;
} else {
mOffset = 0;
}
invalidate();
}
/***
* 设置开关状态变化事件
*
* @param l
*/
public void setOnStateChangeListener(OnSwitchStateChangeListener l) {
this.l = l;
}
private int getSwitchLeft() {
return 0;
}
private int getSwitchRight() {
return width;
}
private int getSwitchTop() {
return 0;
}
private int getSwitchBottom() {
return height;
}
/**
* 调整画笔亮度
*
* @param paint
* 画笔
* @param light
* 亮度单位 , 正常为1f, 范围0f - 2f
*/
private void lightness(Paint paint, float light) {
final ColorMatrix cm = new ColorMatrix();
cm.setScale(light, light, light, 1);
paint.setColorFilter(new ColorMatrixColorFilter(cm));
}
}
如果觉得《Android左右滑动控件实现开关的切换效果》对你有帮助,请点赞、收藏,并留下你的观点哦!