//
// _/_/_/ _/ _/_/_/_/_/
// _/ _/_/ _/_/ _/ _/ _/ _/_/ _/_/
// _/ _/_/ _/_/_/_/ _/_/_/_/ _/_/ _/ _/ _/ _/ _/
// _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/
// _/_/_/ _/_/_/ _/_/_/ _/ _/ _/_/_/_/_/ _/_/ _/_/
//
//
// Copyright (c) 2015-2016, Geek Zoo Studio
// http://www.geek-zoo.com
//
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
package com.insthub.O2OMobile.View;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.*;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.CheckBox;
import com.insthub.O2OMobile.R;
public class UISwitchButton extends CheckBox {
private Paint mPaint;
private RectF mSaveLayerRectF;
private float mFirstDownY;
private float mFirstDownX;
private int mClickTimeout;
private int mTouchSlop;
private final int MAX_ALPHA = 255;
private int mAlpha = MAX_ALPHA;
private boolean mChecked = true;
private boolean mBroadcasting;// 标示是否正在执行监听事件中
private boolean mTurningOn;// 标示位置是否达到开启状态
private PerformClick mPerformClick;
private OnCheckedChangeListener mOnCheckedChangeListener;
private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
private boolean mAnimating;// 标示是否继续执行移动动画
private final float VELOCITY = 350;// 定义按钮动画移动的最大长度
private float mVelocity;// 按钮动画移动的最大像素长度
private float mAnimationPosition;// 按钮动画移动的当前位置
private float mAnimatedVelocity;// 按钮动画移动的实际位移(+mVelocity/-mVelocity)
private Bitmap mBmBgGreen;// 绿色背景
private Bitmap mBmBgWhite;// 白色背景
private Bitmap mBmBtnNormal;// 未按下时按钮
private Bitmap mBmBtnPressed;// 按下时按钮
private Bitmap mBmCurBtnPic;// 当前显示的按钮图片
private Bitmap mBmCurBgPic;// 当前背景图片
private float mBgWidth;// 背景宽度
private float mBgHeight;// 背景宽度
private float mBtnWidth;// 按钮宽度
private float mOffBtnPos;// 按钮关闭时位置
private float mOnBtnPos;// 按钮开启时位置
private float mCurBtnPos;// 按钮当前位置
private float mStartBtnPos;// 开始按钮位置
private int COMMON_WIDTH_IN_PIXEL = 82;// 默认宽度
private int COMMON_HEIGHT_IN_PIXEL = 50;// 默认高度
private int mScreenWidth;
public UISwitchButton(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.checkboxStyle);
}
public UISwitchButton(Context context) {
this(context, null);
}
public UISwitchButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
Resources resources = context.getResources();
WindowManager windowManager = ((Activity) context).getWindow()
.getWindowManager();
Display display = windowManager.getDefaultDisplay();
mScreenWidth = display.getWidth();
COMMON_WIDTH_IN_PIXEL = mScreenWidth / 8;
COMMON_HEIGHT_IN_PIXEL = mScreenWidth / 14;
// get attrConfiguration
TypedArray array = context.obtainStyledAttributes(attrs,
R.styleable.SwitchButton);
int width = (int) array.getDimensionPixelSize(
R.styleable.SwitchButton_bmWidth, 0);
int height = (int) array.getDimensionPixelSize(
R.styleable.SwitchButton_bmHeight, 0);
array.recycle();
// size width or height
if (width <= 0 || height <= 0) {
width = COMMON_WIDTH_IN_PIXEL;
height = COMMON_HEIGHT_IN_PIXEL;
} else {
float scale = (float) COMMON_WIDTH_IN_PIXEL
/ COMMON_HEIGHT_IN_PIXEL;
if ((float) width / height > scale) {
width = (int) (height * scale);
} else if ((float) width / height < scale) {
height = (int) (width / scale);
}
}
// get viewConfiguration
mClickTimeout = ViewConfiguration.getPressedStateDuration()
+ ViewConfiguration.getTapTimeout();
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
// get Bitmap
mBmBgGreen = BitmapFactory.decodeResource(resources,
R.drawable.switch_btn_bg_blue);
mBmBgWhite = BitmapFactory.decodeResource(resources,
R.drawable.switch_btn_bg_white);
mBmBtnNormal = BitmapFactory.decodeResource(resources,
R.drawable.switch_btn_normal);
mBmBtnPressed = BitmapFactory.decodeResource(resources,
R.drawable.switch_btn_pressed);
// size Bitmap
mBmBgGreen = Bitmap.createScaledBitmap(mBmBgGreen, width, height, true);
mBmBgWhite = Bitmap.createScaledBitmap(mBmBgWhite, width, height, true);
mBmBtnNormal = Bitmap.createScaledBitmap(mBmBtnNormal, height, height,
true);
mBmBtnPressed = Bitmap.createScaledBitmap(mBmBtnPressed, height, height,
true);
mBmCurBtnPic = mBmBtnNormal;// 初始按钮图片
mBmCurBgPic = mChecked ? mBmBgGreen : mBmBgWhite;// 初始背景图片
mBgWidth = mBmBgGreen.getWidth();// 背景宽度
mBgHeight = mBmBgGreen.getHeight();// 背景高度
mBtnWidth = mBmBtnNormal.getWidth();// 按钮宽度
mOffBtnPos = 0;// 关闭时在最左边
mOnBtnPos = mBgWidth - mBtnWidth;// 开始时在右边
mCurBtnPos = mChecked ? mOnBtnPos : mOffBtnPos;// 按钮当前为初始位置
// get density
float density = resources.getDisplayMetrics().density;
mVelocity = (int) (VELOCITY * density + 0.5f);// 动画距离
mSaveLayerRectF = new RectF(0, 0, mBgWidth, mBgHeight);
}
@Override
public void setEnabled(boolean enabled) {
mAlpha = enabled ? MAX_ALPHA : MAX_ALPHA / 3;
super.setEnabled(enabled);
}
public boolean isChecked() {
return mChecked;
}
public void toggle() {
setChecked(!mChecked);
}
private void setCheckedDelayed(final boolean checked) {
postDelayed(new Runnable() {
@Override
public void run() {
setChecked(checked);
}
}, 10);
}
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
// 初始化按钮位置
mCurBtnPos = checked ? mOnBtnPos : mOffBtnPos;
// 改变背景图片
mBmCurBgPic = checked ? mBmBgGreen : mBmBgWhite;
invalidate();
if (mBroadcasting) {
// NO-OP
return;
}
// 正在执行监听事件
mBroadcasting = true;
if (mOnCheckedChangeListener != null) {
mOnCheckedChangeListener.onCheckedChanged(UISwitchButton.this,
mChecked);
}
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(
UISwitchButton.this, mChecked);
}
// 监听事件结束
mBroadcasting = false;
}
}
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
mOnCheckedChangeListener = listener;
}
void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
mOnCheckedChangeWidgetListener = listener;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
float x = event.getX();
float y = event.getY();
float deltaX = Math.abs(x - mFirstDownX);
float deltaY = Math.abs(y - mFirstDownY);
switch (action) {
case MotionEvent.ACTION_DOWN:
ViewParent mParent = getParent();
if (mParent != null) {
// 通知父控件不要拦截本view的触摸事件
mParent.requestDisallowInterceptTouchEvent(true);
}
mFirstDownX = x;
mFirstDownY = y;
mBmCurBtnPic = mBmBtnPressed;
mStartBtnPos = mChecked ? mOnBtnPos : mOffBtnPos;
break;
case MotionEvent.ACTION_MOVE:
float time = event.getEventTime() - event.getDownTime();
mCurBtnPos = mStartBtnPos + event.getX() - mFirstDownX;
if (mCurBtnPos >= mOnBtnPos) {
mCurBtnPos = mOnBtnPos;
}
if (mCurBtnPos <= mOffBtnPos) {
mCurBtnPos = mOffBtnPos;
}
mTurningOn = mCurBtnPos > mBgWidth / 2 - mBtnWidth / 2;
break;
case MotionEvent.ACTION_UP:
mBmCurBtnPic = mBmBtnNormal;
time = event.getEventTime() - event.getDownTime();
if (deltaY < mTouchSlop && deltaX < mTouchSlop
&& time < mClickTimeout) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
} else {
startAnimation(mTurningOn);
}
break;
}
invalidate();
return isEnabled();
}
private class PerformClick implements Runnable {
public void run() {
performClick();
}
}
@Override
public boolean performClick() {
startAnimation(!mChecked);
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.saveLayerAlpha(mSaveLayerRectF, mAlpha, Canvas.MATRIX_SAVE_FLAG
| Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
| Canvas.FULL_COLOR_LAYER_SAVE_FLAG
| Canvas.CLIP_TO_LAYER_SAVE_FLAG);
// 绘制底部图片
canvas.drawBitmap(mBmCurBgPic, 0, 0, mPaint);
// 绘制按钮
canvas.drawBitmap(mBmCurBtnPic, mCurBtnPos, 0, mPaint);
canvas.restore();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension((int) mBgWidth, (int) mBgHeight);
}
private void startAnimation(boolean turnOn) {
mAnimating = true;
mAnimatedVelocity = turnOn ? mVelocity : -mVelocity;
mAnimationPosition = mCurBtnPos;
new SwitchAnimation().run();
}
private void stopAnimation() {
mAnimating = false;
}
private final class SwitchAnimation implements Runnable {
@Override
public void run() {
if (!mAnimating) {
return;
}
doAnimation();
requestAnimationFrame(this);
}
}
private void doAnimation() {
mAnimationPosition += mAnimatedVelocity * ANIMATION_FRAME_DURATION
/ 1000;
if (mAnimationPosition <= mOffBtnPos) {
stopAnimation();
mAnimationPosition = mOffBtnPos;
setCheckedDelayed(false);
} else if (mAnimationPosition >= mOnBtnPos) {
stopAnimation();
mAnimationPosition = mOnBtnPos;
setCheckedDelayed(true);
}
mCurBtnPos = mAnimationPosition;
invalidate();
}
private static final int MSG_ANIMATE = 1000;
public static final int ANIMATION_FRAME_DURATION = 1000 / 60;
public void requestAnimationFrame(Runnable runnable) {
Message message = new Message();
message.what = MSG_ANIMATE;
message.obj = runnable;
mHandler.sendMessageDelayed(message, ANIMATION_FRAME_DURATION);
}
private Handler mHandler = new Handler() {
public void handleMessage(Message m) {
switch (m.what) {
case MSG_ANIMATE:
if (m.obj != null) {
((Runnable) m.obj).run();
}
break;
}
}
};
}