package carbon.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
import carbon.R;
import carbon.animation.AnimUtils;
public class Snackbar extends FrameLayout implements GestureDetector.OnGestureListener {
public static int INFINITE = -1;
private Context context;
private float swipe;
private ValueAnimator animator;
private List<View> pushedViews = new ArrayList<>();
GestureDetector gestureDetector = new GestureDetector(this);
private Rect rect = new Rect();
private boolean tapOutsideToDismiss;
private ViewGroup container;
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (swipeToDismiss && animator == null && getParent() != null) {
swipe = e2.getX() - e1.getX();
content.setTranslationX(swipe);
content.setAlpha(Math.max(0, 1 - 2 * Math.abs(swipe) / content.getMeasuredWidth()));
postInvalidate();
if (Math.abs(swipe) > content.getMeasuredWidth() / 4) {
handler.removeCallbacks(hideRunnable);
animator = ObjectAnimator.ofFloat(swipe, content.getMeasuredWidth() / 2.0f * Math.signum(swipe));
animator.setDuration(200);
animator.addUpdateListener(valueAnimator -> {
float s = (Float) valueAnimator.getAnimatedValue();
content.setTranslationX(s);
float alpha = Math.max(0, 1 - 2 * Math.abs((Float) valueAnimator.getAnimatedValue()) / content.getMeasuredWidth());
content.setAlpha(alpha);
postInvalidate();
});
animator.start();
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
hideInternal();
animator = null;
}
});
for (final View pushedView : pushedViews) {
ValueAnimator animator = ValueAnimator.ofFloat(-1, 0);
animator.setDuration(200);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(valueAnimator -> {
MarginLayoutParams lp = (MarginLayoutParams) content.getLayoutParams();
pushedView.setTranslationY((content.getHeight() + lp.bottomMargin) * (Float) valueAnimator.getAnimatedValue());
if (pushedView.getParent() != null)
((View) pushedView.getParent()).postInvalidate();
});
animator.start();
}
}
return true;
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
public interface OnDismissListener {
void onDismiss();
}
private TextView message;
private Button button;
private Style style;
private long duration;
private Runnable hideRunnable = this::dismiss;
private Handler handler;
private LinearLayout content;
OnDismissListener onDismissListener;
boolean swipeToDismiss = true;
static List<Snackbar> next = new ArrayList<>();
public enum Style {
Floating, Docked, Auto
}
public Snackbar(Context context) {
super(context);
this.context = context;
initSnackbar(R.attr.carbon_snackbarTheme);
}
public Snackbar(Context context, String message, String action, int duration) {
super(context);
this.context = context;
initSnackbar(R.attr.carbon_snackbarTheme);
setMessage(message);
setAction(action);
setDuration(duration);
setTapOutsideToDismissEnabled(false);
}
private void initSnackbar(int defStyleAttr) {
setBackgroundDrawable(new ColorDrawable(context.getResources().getColor(android.R.color.transparent)));
TypedValue outValue = new TypedValue();
getContext().getTheme().resolveAttribute(defStyleAttr, outValue, true);
int theme = outValue.resourceId;
Context themedContext = new ContextThemeWrapper(getContext(), theme);
View.inflate(themedContext, R.layout.carbon_snackbar, this);
content = (LinearLayout) findViewById(R.id.carbon_snackbarContent);
content.setElevation(getResources().getDimension(R.dimen.carbon_elevationSnackbar));
content.setInAnimator(AnimUtils.getFlyInAnimator());
content.setOutAnimator(AnimUtils.getFlyOutAnimator());
message = (TextView) content.findViewById(R.id.carbon_messageText);
button = (Button) content.findViewById(R.id.carbon_actionButton);
handler = new Handler();
}
public void show(final ViewGroup container) {
synchronized (Snackbar.class) {
this.container = container;
if (!next.contains(this))
next.add(this);
if (next.indexOf(this) == 0) {
Rect windowFrame = new Rect();
container.getWindowVisibleDisplayFrame(windowFrame);
Rect drawingRect = new Rect();
container.getDrawingRect(drawingRect);
setPadding(0, 0, 0, drawingRect.bottom - windowFrame.bottom);
container.addView(this);
content.setVisibility(INVISIBLE);
content.animateVisibility(View.VISIBLE);
for (final View pushedView : pushedViews) {
ValueAnimator animator = ValueAnimator.ofFloat(0, -1);
animator.setDuration(200);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(valueAnimator -> {
MarginLayoutParams lp = (MarginLayoutParams) content.getLayoutParams();
pushedView.setTranslationY((content.getHeight() + lp.bottomMargin) * (Float) valueAnimator.getAnimatedValue());
if (pushedView.getParent() != null)
((View) pushedView.getParent()).postInvalidate();
});
animator.start();
}
if (duration != INFINITE)
handler.postDelayed(hideRunnable, duration);
}
}
}
public void show() {
show(container);
}
public static void clearQueue() {
next.clear();
}
public void dismiss() {
synchronized (Snackbar.class) {
handler.removeCallbacks(hideRunnable);
if (onDismissListener != null)
onDismissListener.onDismiss();
content.getOutAnimator().addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
hideInternal();
}
});
content.animateVisibility(GONE);
for (final View pushedView : pushedViews) {
ValueAnimator animator = ValueAnimator.ofFloat(-1, 0);
animator.setDuration(200);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(valueAnimator -> {
MarginLayoutParams lp = (MarginLayoutParams) content.getLayoutParams();
pushedView.setTranslationY((content.getHeight() + lp.bottomMargin) * (Float) valueAnimator.getAnimatedValue());
if (pushedView.getParent() != null)
((View) pushedView.getParent()).postInvalidate();
});
animator.start();
}
}
}
private void hideInternal() {
synchronized (Snackbar.class) {
if (getParent() == null)
return;
((ViewGroup) getParent()).removeView(this);
if (next.contains(this))
next.remove(this);
if (next.size() != 0)
next.get(0).show();
}
}
public void setOnClickListener(View.OnClickListener l) {
button.setOnClickListener(l);
}
public void addPushedView(View view) {
pushedViews.add(view);
}
public void removePushedView(View view) {
pushedViews.remove(view);
}
public void setAction(String action) {
if (action != null) {
button.setText(action);
button.setVisibility(View.VISIBLE);
content.setPadding(content.getPaddingLeft(), 0, (int) context.getResources().getDimension(R.dimen.carbon_paddingHalf), 0);
} else {
content.setPadding(content.getPaddingLeft(), 0, content.getPaddingLeft(), 0);
button.setVisibility(View.GONE);
}
}
public String getAction() {
return button.getText().toString();
}
public void setMessage(String message) {
this.message.setText(message);
}
public String getMessage() {
return message.getText().toString();
}
public Style getStyle() {
return style;
}
public void setStyle(Style style) {
this.style = style;
if (style == Style.Auto)
this.style = getResources().getBoolean(R.bool.carbon_isPhone) ? Style.Docked : Style.Floating;
FrameLayout.LayoutParams layoutParams = generateDefaultLayoutParams();
if (style == Style.Floating) {
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
int margin = (int) context.getResources().getDimension(R.dimen.carbon_margin);
layoutParams.setMargins(margin, 0, margin, margin);
layoutParams.gravity = Gravity.START | Gravity.BOTTOM;
content.setCornerRadius((int) context.getResources().getDimension(R.dimen.carbon_cornerRadiusButton));
} else {
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.setMargins(0, 0, 0, 0);
layoutParams.gravity = Gravity.BOTTOM;
content.setCornerRadius(0);
}
content.setLayoutParams(layoutParams);
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
public boolean isSwipeToDismissEnabled() {
return swipeToDismiss;
}
public void setSwipeToDismissEnabled(boolean swipeToDismiss) {
this.swipeToDismiss = swipeToDismiss;
setOnDispatchTouchListener((v, event) -> {
content.getHitRect(rect);
if (rect.contains((int) event.getX(), (int) event.getY())) {
return gestureDetector.onTouchEvent(event);
} else if (isTapOutsideToDismissEnabled()) {
dismiss();
}
return false;
});
content.setOnTouchListener((v, event) -> {
if (isSwipeToDismissEnabled()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
swipe = 0;
handler.removeCallbacks(hideRunnable);
if (animator != null) {
animator.cancel();
animator = null;
swipe = content.getTranslationX();
}
return true;
} else if ((event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) && animator == null) {
animator = ObjectAnimator.ofFloat(swipe, 0);
animator.setDuration(200);
animator.addUpdateListener(animation -> {
float s = (Float) animation.getAnimatedValue();
content.setTranslationX(s);
content.setAlpha(Math.max(0, 1 - 2 * Math.abs(s) / content.getWidth()));
postInvalidate();
});
animator.start();
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
animator.cancel();
animator = null;
if (duration != INFINITE)
handler.postDelayed(hideRunnable, duration);
}
});
return true;
}
}
return false;
});
}
public boolean isTapOutsideToDismissEnabled() {
return tapOutsideToDismiss;
}
public void setTapOutsideToDismissEnabled(boolean tapOutsideToDismiss) {
this.tapOutsideToDismiss = tapOutsideToDismiss;
}
public void setOnDismissListener(OnDismissListener onDismissListener) {
this.onDismissListener = onDismissListener;
}
@Override
public void setInAnimator(Animator inAnim) {
content.setInAnimator(inAnim);
}
@Override
public Animator getInAnimator() {
return content.getInAnimator();
}
@Override
public void setOutAnimator(Animator outAnim) {
content.setOutAnimator(outAnim);
}
@Override
public Animator getOutAnimator() {
return content.getOutAnimator();
}
}