package com.xiaomi.xms.sales.widget;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.CheckBox;
import com.xiaomi.xms.sales.R;
/**
* SlidingButton is an iphone-style toggle checkbox. The user can click it to
* toggle the button's on/off status. Also they can drag the slider to toggle
* the button's on/off status.
*/
public class SlidingButton extends CheckBox {
private static final int MSG_ANIMATE = 1000;
private static final int MSG_TOGGLING_ANIMATE = 1001;
private static final int ANIMATION_FRAME_DURATION = 1000 / 60;
private static final int TAP_THRESHOLD = 6;
private static final float MAXIMUM_MINOR_VELOCITY = 150.0f;
private static final int ANIMATION_TOGGLINE_FRAMES = 20;
private Drawable mFrame; // Change from BitmapDrawable to Drawable for V5
private BitmapDrawable mSlider;
private BitmapDrawable mPressedSlider;
private BitmapDrawable mActiveSlider;
private BitmapDrawable mOnDisable;
private BitmapDrawable mOffDisable;
// For V5 start
private Bitmap mSlideOff;
private Paint mSlideOffPaint;
private Bitmap mSlideOn;
private Paint mSlideOnPaint;
private Drawable mSlideMask;
// For V5 end
private int[] mBarSlice;
private int[] mAlphaPixels;
private int mWidth;
private int mHeight;
private int mSliderWidth;
private int mSliderPositionStart; // Add for V5
private int mSliderPositionEnd; // Change from mSliderPosition to mSliderEndPosition for V5
private boolean bDoAlphaAnimation = false; // Add for V5
private boolean mAnimating = false;
private int mSliderOffset;
private int mLastX;
private int mOriginalTouchPointX;
private boolean mTracking;
private boolean mSliderMoved;
private final Handler mHandler = new SlidingHandler();
private long mAnimationLastTime;
private float mAnimationPosition;
private long mCurrentAnimationTime;
private float mAnimatedVelocity = 150.0f;
private int mTapThreshold;
private long mCurrentTogglingAnimationTime;
public static interface OnCheckedChangedListener {
void onCheckedChanged(boolean isChecked);
}
private OnCheckedChangedListener mOnCheckedChangedListener = null;
public void setOnCheckedChangedListener(OnCheckedChangedListener listener) {
mOnCheckedChangedListener = listener;
}
public SlidingButton(Context context) {
this(context, null);
}
public SlidingButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize(context, attrs, defStyle);
}
public SlidingButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
private void initialize(Context context, AttributeSet attrs, int defStyle) {
setDrawingCacheEnabled(false);
final float density = getResources().getDisplayMetrics().density;
mTapThreshold = (int) (TAP_THRESHOLD * density + 0.5f);
// Change from BitmapDrawable to Drawable for V5
Resources res = context.getResources();
mFrame = res.getDrawable(R.drawable.sliding_btn_frame);//(Drawable) a.getDrawable(R.styleable.SlidingButton_buttonFrame);
mSlider = (BitmapDrawable) res.getDrawable(R.drawable.sliding_btn_slider);//(BitmapDrawable) a.getDrawable(R.styleable.SlidingButton_buttonSlider);
mPressedSlider = (BitmapDrawable) res.getDrawable(R.drawable.sliding_btn_slider_pressed);//(BitmapDrawable) a.getDrawable(R.styleable.SlidingButton_buttonSliderPressed);
mOnDisable = (BitmapDrawable) res.getDrawable(R.drawable.sliding_btn_on_disable);//(BitmapDrawable) a.getDrawable(R.styleable.SlidingButton_buttonOnDisable);
mOffDisable = (BitmapDrawable) res.getDrawable(R.drawable.sliding_btn_off_disable);//(BitmapDrawable) a.getDrawable(R.styleable.SlidingButton_buttonOffDisable);
mWidth = mFrame.getIntrinsicWidth();
mHeight = mFrame.getIntrinsicHeight();
mActiveSlider = mSlider;
mSliderWidth = Math.min(mWidth, mSlider.getIntrinsicWidth());
mSliderPositionStart = 0;
mSliderPositionEnd = mWidth - mSliderWidth;
// Add from V5
mSliderOffset = mSliderPositionStart;
// For V5 start
BitmapDrawable slideOff = (BitmapDrawable)res.getDrawable(R.drawable.sliding_btn_off);//a.getDrawable(R.styleable.SlidingButton_buttonBarOff);
mSlideOff = Bitmap.createScaledBitmap(slideOff.getBitmap(),
mWidth * 2 - mSliderWidth,
mHeight,
true);
BitmapDrawable slidingOn = (BitmapDrawable) res.getDrawable(R.drawable.sliding_btn_on);//(R.styleable.SlidingButton_buttonBarOn);
mSlideOn = Bitmap.createScaledBitmap(slidingOn.getBitmap(),
mWidth * 2 - mSliderWidth,
mHeight, true);
mSlideMask = res.getDrawable(R.drawable.sliding_btn_mask);//(Drawable) a.getDrawable(R.styleable.SlidingButton_buttonMask);
// For V5 end
mFrame.setBounds(0, 0, mWidth, mHeight);
mOnDisable.setBounds(0, 0, mWidth, mHeight);
mOffDisable.setBounds(0, 0, mWidth, mHeight);
// scale the mask to match size of frame
mAlphaPixels = new int[mWidth * mHeight];
Bitmap source = ((BitmapDrawable)res.getDrawable(R.drawable.sliding_btn_mask)).getBitmap();
Bitmap alphaCutter = Bitmap.createScaledBitmap(
source,
mWidth,
mHeight,
false);
alphaCutter.getPixels(mAlphaPixels, 0, mWidth, 0, 0, mWidth, mHeight);
if (alphaCutter != source) {
alphaCutter.recycle();
}
// scale the bottom bar to match size of frame
mBarSlice = new int[mWidth * mHeight];
mSlideOffPaint = new Paint();
mSlideOnPaint = new Paint();
}
@Override
public void setChecked(boolean checked) {
boolean oldState = isChecked();
super.setChecked(checked);
mActiveSlider = mSlider;
mSliderOffset = checked ? mSliderPositionEnd : mSliderPositionStart;
if (oldState != checked) {
if (bDoAlphaAnimation) {
doTogglingAnimation(0);
} else {
mSlideOnPaint.setAlpha(checked ? 255 : 0);
mSlideOffPaint.setAlpha(!checked ? 255 : 0);
invalidate();
}
}
}
@Override
public void setButtonDrawable(Drawable d) {
//delibrately do nothing
return;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mWidth, mHeight);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (! isEnabled())
return false;
int action = event.getAction();
int x = (int) event.getX();
int y = (int) event.getY();
Rect sliderFrame = new Rect(mSliderOffset, 0, mSliderOffset + mSliderWidth, mHeight);
switch (action) {
case MotionEvent.ACTION_DOWN:
if (sliderFrame.contains(x, y)) {
mTracking = true;
mActiveSlider = mPressedSlider;
invalidate();
} else {
mTracking = false;
}
mLastX = x;
mOriginalTouchPointX = x;
mSliderMoved = false;
break;
case MotionEvent.ACTION_MOVE:
if (mTracking) {
moveSlider(x - mLastX);
mLastX = x;
if (Math.abs(x - mOriginalTouchPointX) >= mTapThreshold) {
mSliderMoved = true;
getParent().requestDisallowInterceptTouchEvent(true);
}
}
break;
case MotionEvent.ACTION_UP:
if (mTracking) {
if (!mSliderMoved) {
animateToggle();
} else {
if (mSliderOffset >= mSliderPositionStart && mSliderOffset <= mSliderPositionEnd / 2) {
animateOff();
} else {
animateOn();
}
}
} else {
animateToggle();
}
mTracking = false;
mSliderMoved = false;
break;
case MotionEvent.ACTION_CANCEL:
mTracking = false;
mSliderMoved = false;
break;
}
return true;
}
private void animateToggle() {
if (isChecked()) {
animateOff();
} else {
animateOn();
}
}
private void animateOn() {
performFling(MAXIMUM_MINOR_VELOCITY);
invalidate();
}
private void animateOff() {
performFling(-MAXIMUM_MINOR_VELOCITY);
invalidate();
}
private void moveSlider(int offsetX) {
// check the edge condition
mSliderOffset += offsetX;
if (mSliderOffset < mSliderPositionStart) {
mSliderOffset = mSliderPositionStart;
} else if (mSliderOffset > mSliderPositionEnd) {
mSliderOffset = mSliderPositionEnd;
}
invalidate();
}
private void performFling(float velocity) {
mAnimating = true;
mAnimationPosition = 0;
mAnimatedVelocity = velocity;
long now = SystemClock.uptimeMillis();
mAnimationLastTime = now;
mCurrentAnimationTime = now + ANIMATION_FRAME_DURATION;
mHandler.removeMessages(MSG_ANIMATE);
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
}
private void doAnimation() {
if (!mAnimating) {
return;
}
incrementAnimation();
moveSlider((int) mAnimationPosition);
if (mSliderOffset <= mSliderPositionStart || mSliderOffset >= mSliderPositionEnd) {
mHandler.removeMessages(MSG_ANIMATE);
mAnimating = false;
bDoAlphaAnimation = true;
setChecked(mSliderOffset >= mSliderPositionEnd);
if (mOnCheckedChangedListener != null) {
mOnCheckedChangedListener.onCheckedChanged(isChecked());
}
} else {
mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE), mCurrentAnimationTime);
}
}
private void incrementAnimation() {
long now = SystemClock.uptimeMillis();
float t = (now - mAnimationLastTime) / 1000.0f; // ms -> s
final float position = mAnimationPosition;
mAnimationPosition = position + mAnimatedVelocity * t;
mAnimationLastTime = now;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (!isEnabled()) {
if (isChecked()) {
mOnDisable.draw(canvas);
} else {
mOffDisable.draw(canvas);
}
} else {
// For V5 start
// draw the background (on or off)
drawSlidingBar(canvas);
// draw the frame
mFrame.draw(canvas);
// draw mask
mSlideMask.draw(canvas);
// draw the slider
mActiveSlider.setBounds(mSliderOffset, 0, mSliderWidth + mSliderOffset, mHeight);
mActiveSlider.draw(canvas);
// V5 end
}
}
// Change code for V5 (do not cut bitmap everytime)
private void drawSlidingBar(Canvas canvas) {
// get the bar slice
int barOffset = mSliderPositionEnd - mSliderOffset;
if (mSlideOnPaint.getAlpha() != 0) {
mSlideOn.getPixels(mBarSlice, 0, mWidth, barOffset, 0, mWidth, mHeight);
// cut the edge of bar slice
cutEdge(mWidth, mHeight, mBarSlice);
canvas.drawBitmap(mBarSlice, 0, mWidth, 0, 0, mWidth, mHeight, true, mSlideOnPaint);
}
if (mSlideOffPaint.getAlpha() != 0) {
mSlideOff.getPixels(mBarSlice, 0, mWidth, barOffset, 0, mWidth, mHeight);
// cut the edge of bar slice
cutEdge(mWidth, mHeight, mBarSlice);
canvas.drawBitmap(mBarSlice, 0, mWidth, 0, 0, mWidth, mHeight, true, mSlideOffPaint);
}
}
private void cutEdge(int baseWidth, int baseHeight, int[] basePixels) {
// get the mask for cutting edges of content of the base
int sRGBMask = 0x00ffffff;
int sAlphaShift = 24;
for (int i = baseWidth * baseHeight - 1; i >= 0; i--) {
basePixels[i] = basePixels[i]
& sRGBMask
+ (((basePixels[i] >>> sAlphaShift) * (mAlphaPixels[i] >>> sAlphaShift) / 0xff) << sAlphaShift);
}
}
private void doTogglingAnimation(int frame) {
if (mSlideOn == mSlideOff) {
// Needs no animation if the two slide are same
return;
}
mHandler.removeMessages(MSG_TOGGLING_ANIMATE);
if (frame == 0) {
mCurrentTogglingAnimationTime = SystemClock.uptimeMillis();
}
if (frame < ANIMATION_TOGGLINE_FRAMES) {
++frame;
int alpha = 255 * frame / ANIMATION_TOGGLINE_FRAMES;
if (isChecked()) {
mSlideOffPaint.setAlpha(255 - alpha);
mSlideOnPaint.setAlpha(alpha);
} else {
mSlideOffPaint.setAlpha(alpha);
mSlideOnPaint.setAlpha(255 - alpha);
}
mCurrentTogglingAnimationTime += ANIMATION_FRAME_DURATION;
Message msg = mHandler.obtainMessage(MSG_TOGGLING_ANIMATE, frame, 0);
mHandler.sendMessageAtTime(msg, mCurrentTogglingAnimationTime);
invalidate();
}
bDoAlphaAnimation = false;
}
private class SlidingHandler extends Handler {
public void handleMessage(Message m) {
switch (m.what) {
case MSG_ANIMATE:
doAnimation();
break;
case MSG_TOGGLING_ANIMATE:
doTogglingAnimation(m.arg1);
break;
}
}
}
}