package com.wangdaye.mysplash.common.ui.widget.rippleButton;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.Size;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.CardView;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.github.rahatarmanahmed.cpv.CircularProgressView;
import com.wangdaye.mysplash.R;
import com.wangdaye.mysplash.common.utils.manager.ThemeManager;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Ripple button.
*
* A Button widget that has reveal animation and control state operation.
*
* The process of transform state is as follow:
*
* 0. {@link MotionEvent#ACTION_UP}.
* 1. execute reveal animation in {@link RippleView} & asynchronous task start.
*
* 2-1-0. task complete.
* 2-1-1. {@link #switchUI()} & execute fade animation to hide {@link RippleView}.
*
* 2-2-0. reveal animation complete.
* 2-2-1. execute fade animation to show {@link CircularProgressView}.
* 2-2-3. task complete --> {@link #switchUI()} & execute fade animation to hide
* {@link CircularProgressView} and {@link RippleView}.
*
* */
public class RippleButton extends CardView
implements View.OnClickListener, RippleView.RippleAnimatingCallback {
@BindView(R.id.container_ripple_button)
RelativeLayout container;
@BindView(R.id.container_ripple_button_text)
TextView text;
@BindView(R.id.container_ripple_button_ripple)
RippleView ripple;
@BindView(R.id.container_ripple_button_progress)
CircularProgressView progress;
private ProgressAlphaAnimation progressAlphaAnimation;
private OnSwitchListener listener;
// if set true, there will has no difference between this view and a Button.
private boolean dontAnimate;
private boolean animating;
private boolean switchOn;
// When the view is performing an asynchronous task, there will be 2 cases.
// 1. If the transform animation complete first, the animation callback will set
// waitingResponse to true and wait for the asynchronous task complete.
// 2. If the asynchronous task complete first, the situation will turn.
private boolean waitingResponse;
private boolean waitingAnimation;
private boolean switchSucceed; // save the result of the asynchronous task.
@Size(2)
private String[] buttonTitles;
@Size(2)
private int[] backgroundColors;
@Size(2)
private int[] widgetColors;
private Animation rippleAlphaShow = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
ripple.setAlpha((float) (0.5 + 0.5 * interpolatedTime));
}
};
private Animation rippleAlphaHide = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
ripple.setAlpha(1 - interpolatedTime);
}
};
private class ProgressAlphaAnimation extends Animation {
private float from;
private float to;
ProgressAlphaAnimation(float from, float to) {
this.from = from;
this.to = to;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
progress.setAlpha(from + (to - from) * interpolatedTime);
}
}
public RippleButton(Context context) {
super(context);
this.initialize(null, 0);
}
public RippleButton(Context context, AttributeSet attrs) {
super(context, attrs);
this.initialize(attrs, 0);
}
public RippleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initialize(attrs, defStyleAttr);
}
private void initialize(AttributeSet attrs, int defStyleAttr) {
View v = LayoutInflater.from(getContext()).inflate(R.layout.container_ripple_button, this, false);
addView(v);
ButterKnife.bind(this, this);
initData(attrs, defStyleAttr);
initWidget();
}
private void initData(AttributeSet attrs, int defStyleAttr) {
setSwitchOn(false);
setAnimating(false);
if (ThemeManager.getInstance(getContext()).isLightTheme()) {
backgroundColors = new int[] {
ContextCompat.getColor(getContext(), R.color.colorPrimaryDark_light),
ContextCompat.getColor(getContext(), R.color.colorTextTitle_light)};
widgetColors = new int[] {
ContextCompat.getColor(getContext(), R.color.colorTextTitle_light),
ContextCompat.getColor(getContext(), R.color.colorTextTitle_dark)};
} else {
backgroundColors = new int[] {
ContextCompat.getColor(getContext(), R.color.colorPrimaryDark_dark),
ContextCompat.getColor(getContext(), R.color.colorTextTitle_dark)};
widgetColors = new int[] {
ContextCompat.getColor(getContext(), R.color.colorTextTitle_dark),
ContextCompat.getColor(getContext(), R.color.colorTextTitle_light)};
}
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RippleButton, defStyleAttr, 0);
String titleOn = a.getString(R.styleable.RippleButton_rb_title_on);
String titleOff = a.getString(R.styleable.RippleButton_rb_title_off);
a.recycle();
this.buttonTitles = new String[] {
TextUtils.isEmpty(titleOff) ? "OFF" : titleOff,
TextUtils.isEmpty(titleOn) ? "ON" : titleOn};
}
private void initWidget() {
setOnClickListener(this);
setPreventCornerOverlap(false);
setRadius(getResources().getDimensionPixelSize(R.dimen.middle_elevation));
setCardElevation(getResources().getDimensionPixelSize(R.dimen.tiny_elevation));
ripple.setRippleAnimatingCallback(this);
forceSwitch(isSwitchOn());
rippleAlphaShow.setDuration(100);
rippleAlphaShow.setInterpolator(new AccelerateDecelerateInterpolator());
rippleAlphaHide.setDuration(200);
rippleAlphaHide.setInterpolator(new AccelerateDecelerateInterpolator());
}
// draw.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
container.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.UNSPECIFIED));
// width.
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY:
// do nothing.
break;
case MeasureSpec.AT_MOST:
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(container.getMeasuredWidth(), width),
MeasureSpec.EXACTLY);
break;
case MeasureSpec.UNSPECIFIED:
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
container.getMeasuredWidth(),
MeasureSpec.EXACTLY);
break;
}
// height.
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
switch (heightMode) {
case MeasureSpec.EXACTLY:
// do nothing.
break;
case MeasureSpec.AT_MOST:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(container.getMeasuredHeight(), height),
MeasureSpec.EXACTLY);
break;
case MeasureSpec.UNSPECIFIED:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
container.getMeasuredHeight(),
MeasureSpec.EXACTLY);
break;
}
container.measure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
// touch.
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
if (listener != null) {
if (isDontAnimate()) {
if (listener != null) {
listener.onSwitch(!isSwitchOn());
}
} else {
animSwitch((int) event.getX(), (int) event.getY());
}
}
break;
}
return true;
}
// control.
// interface.
private void switchUI() {
setSwitchOn(!isSwitchOn());
setCardBackgroundColor(backgroundColors[isSwitchOn() ? 1 : 0]);
text.setTextColor(widgetColors[isSwitchOn() ? 1 : 0]);
text.setText(buttonTitles[isSwitchOn() ? 1 : 0]);
}
public boolean isSwitchOn() {
return switchOn;
}
public void setSwitchOn(boolean on) {
this.switchOn = on;
}
public void setButtonTitles(String[] titles) {
if (titles != null && titles.length >= 2) {
buttonTitles = titles;
text.setText(buttonTitles[isSwitchOn() ? 1 : 0]);
}
}
// force.
public void forceSwitch(boolean switchTo) {
setSwitchOn(switchTo);
setAnimating(false);
setCardBackgroundColor(backgroundColors[isSwitchOn() ? 1 : 0]);
text.setTextColor(widgetColors[isSwitchOn() ? 1 : 0]);
text.setText(buttonTitles[isSwitchOn() ? 1 : 0]);
ripple.setAlpha(0);
ripple.setColor(widgetColors[isSwitchOn() ? 0 : 1]);
progress.setAlpha(0);
progress.setColor(widgetColors[isSwitchOn() ? 0 : 1]);
}
public void forceProgress(boolean switchTo) {
setSwitchOn(!switchTo);
setAnimating(true);
setWaitingResponse(true);
setCardBackgroundColor(backgroundColors[isSwitchOn() ? 1 : 0]);
text.setTextColor(widgetColors[isSwitchOn() ? 1 : 0]);
text.setText(buttonTitles[isSwitchOn() ? 1 : 0]);
ripple.setColor(widgetColors[isSwitchOn() ? 0 : 1]);
ripple.setAlpha(1);
progress.setColor(widgetColors[isSwitchOn() ? 0 : 1]);
progress.setAlpha(1);
}
// anim transform.
public void animSwitch(int x, int y) {
if (!isAnimating() && !ripple.isDrawing()) {
setAnimating(true);
if (listener != null) {
listener.onSwitch(!isSwitchOn());
}
// alpha.
rippleAlphaShow.cancel();
rippleAlphaShow.reset();
startAnimation(rippleAlphaShow);
// ripple.
progress.setColor(widgetColors[isSwitchOn() ? 0 : 1]);
ripple.drawRipple(backgroundColors[isSwitchOn() ? 0 : 1], x, y);
}
}
private void doProgressAnimation(float from, float to) {
if (progressAlphaAnimation != null) {
progressAlphaAnimation.cancel();
}
progressAlphaAnimation = new ProgressAlphaAnimation(from, to);
progressAlphaAnimation.setDuration(150);
progressAlphaAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
progress.startAnimation(progressAlphaAnimation);
}
public boolean isDontAnimate() {
return dontAnimate;
}
public void setDontAnimate(boolean dontAnimate) {
this.dontAnimate = dontAnimate;
}
public boolean isAnimating() {
return animating;
}
public void setAnimating(boolean animating) {
setWaitingResponse(false);
setWaitingAnimation(false);
setSwitchSucceed(false);
this.animating = animating;
}
// callback.
/**
* A interface method for outside. When a asynchronous task complete, the callback need user
* this method to set the result of task.
*
* @param succeed result of task.
* */
public void setSwitchResult(boolean succeed) {
setSwitchSucceed(succeed);
if (isWaitingResponse()) {
// transform animation has already completed.
if (isSwitchSucceed()) {
switchUI();
}
if (progressAlphaAnimation != null) {
progressAlphaAnimation.cancel();
}
progressAlphaAnimation = new ProgressAlphaAnimation(progress.getAlpha(), 0);
progressAlphaAnimation.setDuration(150);
progressAlphaAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
progress.startAnimation(progressAlphaAnimation);
rippleAlphaHide.cancel();
rippleAlphaHide.reset();
rippleAlphaHide.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// do nothing.
}
@Override
public void onAnimationEnd(Animation animation) {
setAnimating(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
// do nothing.
}
});
startAnimation(rippleAlphaHide);
} else {
// task complete but animation doesn't complete.
setWaitingAnimation(true);
}
}
public boolean isSwitchSucceed() {
return switchSucceed;
}
public void setSwitchSucceed(boolean switchSucceed) {
this.switchSucceed = switchSucceed;
}
public boolean isWaitingAnimation() {
return waitingAnimation;
}
public void setWaitingAnimation(boolean waitingAnimation) {
this.waitingAnimation = waitingAnimation;
}
public boolean isWaitingResponse() {
return waitingResponse;
}
public void setWaitingResponse(boolean waitingResponse) {
this.waitingResponse = waitingResponse;
}
// interface.
// on switch swipeListener.
public interface OnSwitchListener {
void onSwitch(boolean switchTo);
}
public void setOnSwitchListener(OnSwitchListener l) {
this.listener = l;
}
// on click listener.
@Override
public void onClick(View view) {
// do nothing.
}
// ripple animating callback.
@Override
public void animationDone() {
if (isWaitingAnimation()) {
if (isSwitchSucceed()) {
switchUI();
}
rippleAlphaHide.cancel();
rippleAlphaHide.reset();
rippleAlphaHide.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// do nothing.
}
@Override
public void onAnimationEnd(Animation animation) {
setAnimating(false);
}
@Override
public void onAnimationRepeat(Animation animation) {
// do nothing.
}
});
startAnimation(rippleAlphaHide);
} else {
setWaitingResponse(true);
doProgressAnimation(0, 1);
}
}
}