NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java

457 lines
18 KiB
Java

/*
* This is the source code of Telegram for Android v. 5.x.x.
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
* Copyright Nikolai Kudashov, 2013-2018.
*/
package org.telegram.ui.Components;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import android.util.StateSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.FrameLayout;
import androidx.core.graphics.ColorUtils;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.ui.ActionBar.Theme;
public class SeekBarView extends FrameLayout {
private final SeekBarAccessibilityDelegate seekBarAccessibilityDelegate;
private Paint innerPaint1;
private Paint outerPaint1;
private int thumbSize;
private int selectorWidth;
private int thumbX;
private AnimatedFloat animatedThumbX = new AnimatedFloat(this, 150, CubicBezierInterpolator.DEFAULT);
private int thumbDX;
private float progressToSet = -100;
private boolean pressed;
public SeekBarViewDelegate delegate;
private boolean reportChanges;
private float bufferedProgress;
private Drawable hoverDrawable;
private long lastUpdateTime;
private float currentRadius;
private int[] pressedState = new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed};
private float transitionProgress = 1f;
private int transitionThumbX;
private int separatorsCount;
private boolean twoSided;
private final Theme.ResourcesProvider resourcesProvider;
public interface SeekBarViewDelegate {
void onSeekBarDrag(boolean stop, float progress);
void onSeekBarPressed(boolean pressed);
default CharSequence getContentDescription() {
return null;
}
default int getStepsCount() {
return 0;
}
}
public SeekBarView(Context context) {
this(context, null);
}
public SeekBarView(Context context, Theme.ResourcesProvider resourcesProvider) {
this(context, false, resourcesProvider);
}
public SeekBarView(Context context, boolean inPercents, Theme.ResourcesProvider resourcesProvider) {
super(context);
this.resourcesProvider = resourcesProvider;
setWillNotDraw(false);
innerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
outerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
outerPaint1.setColor(getThemedColor(Theme.key_player_progress));
selectorWidth = AndroidUtilities.dp(32);
thumbSize = AndroidUtilities.dp(24);
currentRadius = AndroidUtilities.dp(6);
if (Build.VERSION.SDK_INT >= 21) {
hoverDrawable = Theme.createSelectorDrawable(ColorUtils.setAlphaComponent(getThemedColor(Theme.key_player_progress), 40), 1, AndroidUtilities.dp(16));
hoverDrawable.setCallback(this);
hoverDrawable.setVisible(true, false);
}
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
setAccessibilityDelegate(seekBarAccessibilityDelegate = new FloatSeekBarAccessibilityDelegate(inPercents) {
@Override
public float getProgress() {
return SeekBarView.this.getProgress();
}
@Override
public void setProgress(float progress) {
pressed = true;
SeekBarView.this.setProgress(progress);
if (delegate != null) {
delegate.onSeekBarDrag(true, progress);
}
pressed = false;
}
@Override
protected float getDelta() {
final int stepsCount = delegate.getStepsCount();
if (stepsCount > 0) {
return 1f / stepsCount;
} else {
return super.getDelta();
}
}
@Override
public CharSequence getContentDescription(View host) {
return delegate != null ? delegate.getContentDescription() : null;
}
});
}
public void setSeparatorsCount(int separatorsCount) {
this.separatorsCount = separatorsCount;
}
public void setColors(int inner, int outer) {
innerPaint1.setColor(inner);
outerPaint1.setColor(outer);
if (hoverDrawable != null) {
Theme.setSelectorDrawableColor(hoverDrawable, ColorUtils.setAlphaComponent(outer, 40), true);
}
}
public void setTwoSided(boolean value) {
twoSided = value;
}
public boolean isTwoSided() {
return twoSided;
}
public void setInnerColor(int color) {
innerPaint1.setColor(color);
}
public void setOuterColor(int color) {
outerPaint1.setColor(color);
if (hoverDrawable != null) {
Theme.setSelectorDrawableColor(hoverDrawable, ColorUtils.setAlphaComponent(color, 40), true);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return onTouch(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onTouch(event);
}
public void setReportChanges(boolean value) {
reportChanges = value;
}
public void setDelegate(SeekBarViewDelegate seekBarViewDelegate) {
delegate = seekBarViewDelegate;
}
boolean captured;
float sx, sy;
boolean onTouch(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
sx = ev.getX();
sy = ev.getY();
return true;
} else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
captured = false;
if (ev.getAction() == MotionEvent.ACTION_UP) {
final ViewConfiguration vc = ViewConfiguration.get(getContext());
if (Math.abs(ev.getY() - sy) < vc.getScaledTouchSlop()) {
int additionWidth = (getMeasuredHeight() - thumbSize) / 2;
if (!(thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbSize + additionWidth)) {
thumbX = (int) ev.getX() - thumbSize / 2;
if (thumbX < 0) {
thumbX = 0;
} else if (thumbX > getMeasuredWidth() - selectorWidth) {
thumbX = getMeasuredWidth() - selectorWidth;
}
}
thumbDX = (int) (ev.getX() - thumbX);
pressed = true;
}
}
if (pressed) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
if (twoSided) {
float w = (getMeasuredWidth() - selectorWidth) / 2;
if (thumbX >= w) {
delegate.onSeekBarDrag(false, (thumbX - w) / w);
} else {
delegate.onSeekBarDrag(false, -Math.max(0.01f, 1.0f - (w - thumbX) / w));
}
} else {
delegate.onSeekBarDrag(true, (float) thumbX / (float) (getMeasuredWidth() - selectorWidth));
}
}
if (Build.VERSION.SDK_INT >= 21 && hoverDrawable != null) {
hoverDrawable.setState(StateSet.NOTHING);
}
delegate.onSeekBarPressed(false);
pressed = false;
invalidate();
return true;
}
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
if (!captured) {
final ViewConfiguration vc = ViewConfiguration.get(getContext());
if (Math.abs(ev.getY() - sy) > vc.getScaledTouchSlop()) {
return false;
}
if (Math.abs(ev.getX() - sx) > vc.getScaledTouchSlop()) {
captured = true;
getParent().requestDisallowInterceptTouchEvent(true);
int additionWidth = (getMeasuredHeight() - thumbSize) / 2;
if (ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) {
if (!(thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbSize + additionWidth)) {
thumbX = (int) ev.getX() - thumbSize / 2;
if (thumbX < 0) {
thumbX = 0;
} else if (thumbX > getMeasuredWidth() - selectorWidth) {
thumbX = getMeasuredWidth() - selectorWidth;
}
}
thumbDX = (int) (ev.getX() - thumbX);
pressed = true;
delegate.onSeekBarPressed(true);
if (Build.VERSION.SDK_INT >= 21 && hoverDrawable != null) {
hoverDrawable.setState(pressedState);
hoverDrawable.setHotspot(ev.getX(), ev.getY());
}
invalidate();
return true;
}
}
} else {
if (pressed) {
thumbX = (int) (ev.getX() - thumbDX);
if (thumbX < 0) {
thumbX = 0;
} else if (thumbX > getMeasuredWidth() - selectorWidth) {
thumbX = getMeasuredWidth() - selectorWidth;
}
if (reportChanges) {
if (twoSided) {
float w = (getMeasuredWidth() - selectorWidth) / 2;
if (thumbX >= w) {
delegate.onSeekBarDrag(false, (thumbX - w) / w);
} else {
delegate.onSeekBarDrag(false, -Math.max(0.01f, 1.0f - (w - thumbX) / w));
}
} else {
delegate.onSeekBarDrag(false, (float) thumbX / (float) (getMeasuredWidth() - selectorWidth));
}
}
if (Build.VERSION.SDK_INT >= 21 && hoverDrawable != null) {
hoverDrawable.setHotspot(ev.getX(), ev.getY());
}
invalidate();
return true;
}
}
}
return false;
}
public float getProgress() {
if (getMeasuredWidth() == 0) {
return progressToSet;
}
return thumbX / (float) (getMeasuredWidth() - selectorWidth);
}
public void setProgress(float progress) {
setProgress(progress, false);
}
public void setProgress(float progress, boolean animated) {
if (getMeasuredWidth() == 0) {
progressToSet = progress;
return;
}
progressToSet = -100;
int newThumbX;
if (twoSided) {
int w = getMeasuredWidth() - selectorWidth;
float cx = w / 2;
if (progress < 0) {
newThumbX = (int) Math.ceil(cx + w / 2 * -(1.0f + progress));
} else {
newThumbX = (int) Math.ceil(cx + w / 2 * progress);
}
} else {
newThumbX = (int) Math.ceil((getMeasuredWidth() - selectorWidth) * progress);
}
if (thumbX != newThumbX) {
if (animated) {
transitionThumbX = thumbX;
transitionProgress = 0f;
}
thumbX = newThumbX;
if (thumbX < 0) {
thumbX = 0;
} else if (thumbX > getMeasuredWidth() - selectorWidth) {
thumbX = getMeasuredWidth() - selectorWidth;
}
invalidate();
}
}
public void setBufferedProgress(float progress) {
bufferedProgress = progress;
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (progressToSet != -100 && getMeasuredWidth() > 0) {
setProgress(progressToSet);
progressToSet = -100;
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == hoverDrawable;
}
public boolean isDragging() {
return pressed;
}
@Override
protected void onDraw(Canvas canvas) {
int thumbX = this.thumbX;
if (!twoSided && separatorsCount > 1) {
float step = (getMeasuredWidth() - selectorWidth) / ((float) separatorsCount - 1f);
thumbX = (int) animatedThumbX.set(Math.round((thumbX) / step) * step);
}
int y = (getMeasuredHeight() - thumbSize) / 2;
innerPaint1.setColor(getThemedColor(Theme.key_player_progressBackground));
canvas.drawRect(selectorWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), getMeasuredWidth() - selectorWidth / 2, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), innerPaint1);
if (!twoSided && separatorsCount > 1) {
for (int i = 0; i < separatorsCount; ++i) {
canvas.drawCircle(
AndroidUtilities.lerp(selectorWidth / 2, getMeasuredWidth() - selectorWidth / 2, i / ((float) separatorsCount - 1f)),
getMeasuredHeight() / 2,
AndroidUtilities.dp(1.6f),
innerPaint1
);
}
}
if (bufferedProgress > 0) {
innerPaint1.setColor(getThemedColor(Theme.key_player_progressCachedBackground));
canvas.drawRect(selectorWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), selectorWidth / 2 + bufferedProgress * (getMeasuredWidth() - selectorWidth), getMeasuredHeight() / 2 + AndroidUtilities.dp(1), innerPaint1);
}
if (twoSided) {
canvas.drawRect(getMeasuredWidth() / 2 - AndroidUtilities.dp(1), getMeasuredHeight() / 2 - AndroidUtilities.dp(6), getMeasuredWidth() / 2 + AndroidUtilities.dp(1), getMeasuredHeight() / 2 + AndroidUtilities.dp(6), outerPaint1);
if (thumbX > (getMeasuredWidth() - selectorWidth) / 2) {
canvas.drawRect(getMeasuredWidth() / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), selectorWidth / 2 + thumbX, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), outerPaint1);
} else {
canvas.drawRect(thumbX + selectorWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), getMeasuredWidth() / 2, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), outerPaint1);
}
} else {
canvas.drawRect(selectorWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), selectorWidth / 2 + thumbX, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), outerPaint1);
if (separatorsCount > 1) {
for (int i = 0; i < separatorsCount; ++i) {
float cx = AndroidUtilities.lerp(selectorWidth / 2, getMeasuredWidth() - selectorWidth / 2, i / ((float) separatorsCount - 1f));
if (cx > thumbX + selectorWidth / 2) {
break;
}
canvas.drawCircle(
cx,
getMeasuredHeight() / 2,
AndroidUtilities.dp(1.4f),
outerPaint1
);
}
}
}
if (hoverDrawable != null) {
int dx = thumbX + selectorWidth / 2 - AndroidUtilities.dp(16);
int dy = y + thumbSize / 2 - AndroidUtilities.dp(16);
hoverDrawable.setBounds(dx, dy, dx + AndroidUtilities.dp(32), dy + AndroidUtilities.dp(32));
hoverDrawable.draw(canvas);
}
boolean needInvalidate = false;
int newRad = AndroidUtilities.dp(pressed ? 8 : 6);
long newUpdateTime = SystemClock.elapsedRealtime();
long dt = newUpdateTime - lastUpdateTime;
if (dt > 18) {
dt = 16;
}
if (currentRadius != newRad) {
if (currentRadius < newRad) {
currentRadius += AndroidUtilities.dp(1) * (dt / 60.0f);
if (currentRadius > newRad) {
currentRadius = newRad;
}
} else {
currentRadius -= AndroidUtilities.dp(1) * (dt / 60.0f);
if (currentRadius < newRad) {
currentRadius = newRad;
}
}
needInvalidate = true;
}
if (transitionProgress < 1f) {
transitionProgress += dt / 225f;
if (transitionProgress < 1f) {
needInvalidate = true;
} else {
transitionProgress = 1f;
}
}
if (transitionProgress < 1f) {
final float oldCircleProgress = 1f - Easings.easeInQuad.getInterpolation(Math.min(1f, transitionProgress * 3f));
final float newCircleProgress = Easings.easeOutQuad.getInterpolation(transitionProgress);
if (oldCircleProgress > 0f) {
canvas.drawCircle(transitionThumbX + selectorWidth / 2, y + thumbSize / 2, currentRadius * oldCircleProgress, outerPaint1);
}
canvas.drawCircle(thumbX + selectorWidth / 2, y + thumbSize / 2, currentRadius * newCircleProgress, outerPaint1);
} else {
canvas.drawCircle(thumbX + selectorWidth / 2, y + thumbSize / 2, currentRadius, outerPaint1);
}
if (needInvalidate) {
postInvalidateOnAnimation();
}
}
public SeekBarAccessibilityDelegate getSeekBarAccessibilityDelegate() {
return seekBarAccessibilityDelegate;
}
private int getThemedColor(String key) {
Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null;
return color != null ? color : Theme.getColor(key);
}
}