package carbon.widget;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import carbon.Carbon;
import carbon.R;
import carbon.animation.AnimatedColorStateList;
import carbon.drawable.DefaultPrimaryColorStateList;
import carbon.drawable.EdgeEffect;
public class HorizontalScrollView extends android.widget.HorizontalScrollView implements TintedView, VisibleView {
private int mTouchSlop;
EdgeEffect leftGlow;
EdgeEffect rightGlow;
private boolean drag = true;
private float prevX;
private int overscrollMode;
long prevScroll = 0;
public static final int OVER_SCROLL_ALWAYS = 0;
public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1;
public static final int OVER_SCROLL_NEVER = 2;
public HorizontalScrollView(Context context) {
super(context);
initHorizontalScrollView(null, android.R.attr.horizontalScrollViewStyle);
}
public HorizontalScrollView(Context context, AttributeSet attrs) {
super(Carbon.getThemedContext(context, attrs, R.styleable.HorizontalScrollView, android.R.attr.horizontalScrollViewStyle, R.styleable.HorizontalScrollView_carbon_theme), attrs);
initHorizontalScrollView(attrs, android.R.attr.horizontalScrollViewStyle);
}
public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(Carbon.getThemedContext(context, attrs, R.styleable.HorizontalScrollView, defStyleAttr, R.styleable.HorizontalScrollView_carbon_theme), attrs, defStyleAttr);
initHorizontalScrollView(attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(Carbon.getThemedContext(context, attrs, R.styleable.HorizontalScrollView, defStyleAttr, R.styleable.HorizontalScrollView_carbon_theme), attrs, defStyleAttr, defStyleRes);
initHorizontalScrollView(attrs, defStyleAttr);
}
private static int[] tintIds = new int[]{
R.styleable.HorizontalScrollView_carbon_tint,
R.styleable.HorizontalScrollView_carbon_tintMode,
R.styleable.HorizontalScrollView_carbon_backgroundTint,
R.styleable.HorizontalScrollView_carbon_backgroundTintMode,
R.styleable.HorizontalScrollView_carbon_animateColorChanges
};
private void initHorizontalScrollView(AttributeSet attrs, int defStyleAttr) {
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.HorizontalScrollView, defStyleAttr, R.style.carbon_HorizontalScrollView);
for (int i = 0; i < a.getIndexCount(); i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.HorizontalScrollView_carbon_overScroll) {
setOverScrollMode(a.getInt(attr, OVER_SCROLL_ALWAYS));
}
}
Carbon.initTint(this, a, tintIds);
a.recycle();
setWillNotDraw(false);
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (leftGlow != null) {
final int scrollX = getScrollX();
if (!leftGlow.isFinished()) {
final int restoreCount = canvas.save();
final int height = getHeight() - getPaddingTop() - getPaddingBottom();
canvas.rotate(270);
canvas.translate(-height + getPaddingTop(), Math.min(0, scrollX));
leftGlow.setSize(height, getWidth());
if (leftGlow.draw(canvas)) {
postInvalidate();
}
canvas.restoreToCount(restoreCount);
}
if (!rightGlow.isFinished()) {
final int restoreCount = canvas.save();
final int width = getWidth();
final int height = getHeight() - getPaddingTop() - getPaddingBottom();
canvas.rotate(90);
canvas.translate(-getPaddingTop(),
-(Math.max(computeHorizontalScrollRange() - getWidth(), scrollX) + width));
rightGlow.setSize(height, width);
if (rightGlow.draw(canvas)) {
postInvalidate();
}
canvas.restoreToCount(restoreCount);
}
}
}
@Override
public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_MOVE:
float deltaX = prevX - ev.getX();
if (!drag && Math.abs(deltaX) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
drag = true;
if (deltaX > 0) {
deltaX -= mTouchSlop;
} else {
deltaX += mTouchSlop;
}
}
if (drag) {
final int oldX = getScrollX();
final int range = computeHorizontalScrollRange() - getWidth();
boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
if (canOverscroll) {
float pulledToY = oldX + deltaX;
if (pulledToY < 0) {
leftGlow.onPull(deltaX / getWidth(), 1.f - ev.getY() / getHeight());
if (!rightGlow.isFinished())
rightGlow.onRelease();
} else if (pulledToY > range) {
rightGlow.onPull(deltaX / getWidth(), ev.getY() / getHeight());
if (!leftGlow.isFinished())
leftGlow.onRelease();
}
if (leftGlow != null && (!leftGlow.isFinished() || !rightGlow.isFinished()))
postInvalidate();
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (drag) {
drag = false;
if (leftGlow != null) {
leftGlow.onRelease();
rightGlow.onRelease();
}
}
break;
}
prevX = ev.getX();
return super.dispatchTouchEvent(ev);
}
@Override
protected void onScrollChanged(int x, int y, int prevX, int prevY) {
super.onScrollChanged(x, y, prevX, prevY);
if (drag || leftGlow == null)
return;
final int range = computeHorizontalScrollRange() - getWidth();
boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
if (canOverscroll) {
int dx = x - prevX;
long t = System.currentTimeMillis();
int velx = (int) (dx * 1000.0f / (t - prevScroll));
if (computeHorizontalScrollOffset() == 0 && dx < 0) {
leftGlow.onAbsorb(-velx);
} else if (computeHorizontalScrollOffset() == range && dx > 0) {
rightGlow.onAbsorb(velx);
}
prevScroll = t;
}
}
@Override
public void setOverScrollMode(int mode) {
if (mode != OVER_SCROLL_NEVER) {
if (leftGlow == null) {
Context context = getContext();
leftGlow = new EdgeEffect(context);
rightGlow = new EdgeEffect(context);
updateTint();
}
} else {
leftGlow = null;
rightGlow = null;
}
super.setOverScrollMode(OVER_SCROLL_NEVER);
this.overscrollMode = mode;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateTint();
}
// -------------------------------
// tint
// -------------------------------
ColorStateList tint;
PorterDuff.Mode tintMode;
ColorStateList backgroundTint;
PorterDuff.Mode backgroundTintMode;
boolean animateColorChanges;
ValueAnimator.AnimatorUpdateListener tintAnimatorListener = animation -> {
updateTint();
ViewCompat.postInvalidateOnAnimation(HorizontalScrollView.this);
};
ValueAnimator.AnimatorUpdateListener backgroundTintAnimatorListener = animation -> {
updateBackgroundTint();
ViewCompat.postInvalidateOnAnimation(HorizontalScrollView.this);
};
@Override
public void setTint(ColorStateList list) {
this.tint = animateColorChanges && !(list instanceof AnimatedColorStateList) ? AnimatedColorStateList.fromList(list, tintAnimatorListener) : list;
updateTint();
}
@Override
public void setTint(int color) {
if (color == 0) {
setTint(new DefaultPrimaryColorStateList(getContext()));
} else {
setTint(ColorStateList.valueOf(color));
}
}
@Override
public ColorStateList getTint() {
return tint;
}
private void updateTint() {
if (tint == null)
return;
int color = tint.getColorForState(getDrawableState(), tint.getDefaultColor());
if (leftGlow != null)
leftGlow.setColor(color);
if (rightGlow != null)
rightGlow.setColor(color);
}
@Override
public void setTintMode(@NonNull PorterDuff.Mode mode) {
this.tintMode = mode;
updateTint();
}
@Override
public PorterDuff.Mode getTintMode() {
return tintMode;
}
@Override
public void setBackgroundTint(ColorStateList list) {
this.backgroundTint = animateColorChanges && !(list instanceof AnimatedColorStateList) ? AnimatedColorStateList.fromList(list, backgroundTintAnimatorListener) : list;
updateBackgroundTint();
}
@Override
public void setBackgroundTint(int color) {
if (color == 0) {
setBackgroundTint(new DefaultPrimaryColorStateList(getContext()));
} else {
setBackgroundTint(ColorStateList.valueOf(color));
}
}
@Override
public ColorStateList getBackgroundTint() {
return backgroundTint;
}
private void updateBackgroundTint() {
if (getBackground() == null)
return;
if (backgroundTint != null && backgroundTintMode != null) {
int color = backgroundTint.getColorForState(getDrawableState(), backgroundTint.getDefaultColor());
getBackground().setColorFilter(new PorterDuffColorFilter(color, tintMode));
} else {
getBackground().setColorFilter(null);
}
}
@Override
public void setBackgroundTintMode(@NonNull PorterDuff.Mode mode) {
this.backgroundTintMode = mode;
updateBackgroundTint();
}
@Override
public PorterDuff.Mode getBackgroundTintMode() {
return backgroundTintMode;
}
public boolean isAnimateColorChangesEnabled() {
return animateColorChanges;
}
public void setAnimateColorChangesEnabled(boolean animateColorChanges) {
this.animateColorChanges = animateColorChanges;
if (tint != null && !(tint instanceof AnimatedColorStateList))
setTint(AnimatedColorStateList.fromList(tint, tintAnimatorListener));
if (backgroundTint != null && !(backgroundTint instanceof AnimatedColorStateList))
setBackgroundTint(AnimatedColorStateList.fromList(backgroundTint, backgroundTintAnimatorListener));
}
// -------------------------------
// scroll bars
// -------------------------------
protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(tint != null ? tint.getColorForState(getDrawableState(), tint.getDefaultColor()) : Color.WHITE, tintMode);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) {
scrollBar.setColorFilter(tint != null ? tint.getColorForState(getDrawableState(), tint.getDefaultColor()) : Color.WHITE, tintMode);
scrollBar.setBounds(l, t, r, b);
scrollBar.draw(canvas);
}
}