diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java b/app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java
index f2a987e0f..e4e4efd86 100644
--- a/app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java
+++ b/app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java
@@ -16,6 +16,7 @@
package com.hippo.ehviewer.gallery.anim;
+import android.os.SystemClock;
import android.view.animation.Interpolator;
import com.hippo.yorozuya.MathUtils;
@@ -72,6 +73,7 @@ abstract public class Animation {
private int mRepeatMode;
private int mRunnedCount;
+ private long mLastFrameTime;
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
@@ -89,13 +91,32 @@ abstract public class Animation {
mRepeatMode = repeatMode;
}
+ public boolean isRunning() {
+ return mStartTime > 0;
+ }
+
+ public long getLastFrameTime() {
+ return mLastFrameTime;
+ }
+
public void start() {
if (mStartTime == NO_ANIMATION) {
mStartTime = ANIMATION_START;
mRunnedCount = 0;
+ mLastFrameTime = 0;
}
}
+ public void startAt(long time) {
+ start();
+ setStartTime(time);
+ }
+
+ public void startNow() {
+ start();
+ setStartTime(SystemClock.uptimeMillis());
+ }
+
public void setStartTime(long time) {
mStartTime = time;
}
@@ -111,6 +132,7 @@ abstract public class Animation {
public void forceReset() {
mStartTime = ANIMATION_START;
mRunnedCount = 0;
+ mLastFrameTime = 0;
}
// TODO mRepeatMode
@@ -121,6 +143,7 @@ abstract public class Animation {
if (mStartTime == ANIMATION_START) {
mStartTime = currentTimeMillis;
}
+ mLastFrameTime = currentTimeMillis;
long elapse = currentTimeMillis - mStartTime;
float x = MathUtils.clamp(mDuration == 0.0f ? 1.0f : (float) elapse / mDuration, 0.0f, 1.0f); // Avoid NaN
diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java
index 071a6a161..486bf72af 100644
--- a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java
+++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java
@@ -165,7 +165,7 @@ public abstract class UploadedTexture extends BasicTexture {
protected abstract void onFreeBitmap(Bitmap bitmap);
- protected void invalidateContent() {
+ public void invalidateContent() {
if (mBitmap != null) freeBitmap();
mContentValid = false;
mWidth = UNSPECIFIED;
diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java
index cb71e4158..1e93d69a8 100644
--- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java
+++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java
@@ -17,41 +17,126 @@
package com.hippo.ehviewer.gallery.ui;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.Typeface;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import com.hippo.ehviewer.R;
+import com.hippo.ehviewer.gallery.anim.FloatAnimation;
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
+import com.hippo.ehviewer.gallery.glrenderer.TextTexture;
+import com.hippo.ehviewer.gallery.glrenderer.XmlResourceTexture;
+import com.hippo.util.AnimationUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
+import com.hippo.yorozuya.SimpleHandler;
public class Slider extends GLView {
private int mStart;
private int mEnd;
private int mProgress;
- // 0.0f to 1.0f
- private float mDrawProgress = 0.0f;
+ private float mPercent;
+ private int mDrawProgress;
+ private float mDrawPercent;
+ private int mTargetProgress;
+ private float mTargetPercent;
private boolean mReverse = false;
private int m2dp;
private int m5dp;
private int m6dp;
+ private int m8dp;
private int m12dp;
private int mColor = Color.WHITE;
+ private XmlResourceTexture mBubble;
+ private TextTexture mTextTexture;
+
+ private boolean mShowBubble;
+
+ private float mDrawBubbleScale = 0f;
+
+ private FloatAnimation mProgressAnimation;
+ private FloatAnimation mBubbleScaleAnimation;
+
private OnSetProgressListener mListener;
+ private CheckForShowBubble mCheckForShowBubble;
+
public Slider(Context context) {
m2dp = LayoutUtils.dp2pix(context, 2);
m5dp = LayoutUtils.dp2pix(context, 5);
m6dp = LayoutUtils.dp2pix(context, 6);
+ m8dp = LayoutUtils.dp2pix(context, 8);
m12dp = LayoutUtils.dp2pix(context, 12);
setMinimumHeight(m12dp);
setMinimumWidth(512);
+
+ Resources resources = context.getResources();
+ mBubble = new XmlResourceTexture(context, R.drawable.slider_bubble);
+ mTextTexture = TextTexture.create(
+ Typeface.createFromAsset(resources.getAssets(), "fonts/gallery_progress.ttf"),
+ resources.getDimensionPixelOffset(R.dimen.text_super_small),
+ resources.getColor(R.color.primary_text_dark),
+ new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'});
+
+ mProgressAnimation = new FloatAnimation() {
+ @Override
+ protected void onCalculate(float progress) {
+ super.onCalculate(progress);
+ mDrawPercent = get();
+ mDrawProgress = Math.round(MathUtils.lerp((float) mStart, mEnd, mDrawPercent));
+ }
+ };
+ mProgressAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
+
+ mBubbleScaleAnimation = new FloatAnimation() {
+ @Override
+ protected void onCalculate(float progress) {
+ super.onCalculate(progress);
+ mDrawBubbleScale = get();
+ }
+ };
+ }
+
+ private void startProgressAnimation(float percent) {
+ boolean running = mProgressAnimation.isRunning();
+ mProgressAnimation.forceStop();
+ mProgressAnimation.setRange(mDrawPercent, percent);
+ mProgressAnimation.setDuration(Math.min(500, (long) (10 * getWidth() * Math.abs(mDrawPercent - percent))));
+ // Avoid fast swipe to block changing
+ long startTime = mProgressAnimation.getLastFrameTime();
+ if (running && startTime > 0) {
+ mProgressAnimation.startAt(startTime);
+ } else {
+ mProgressAnimation.startNow();
+ }
+ invalidate();
+ }
+
+ private void startShowBubbleAnimation() {
+ mBubbleScaleAnimation.forceStop();
+ mBubbleScaleAnimation.setRange(mDrawBubbleScale, 1.0f);
+ mBubbleScaleAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
+ mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 1.0f)));
+ mBubbleScaleAnimation.start();
+ invalidate();
+ }
+
+ private void startHideBubbleAnimation() {
+ mBubbleScaleAnimation.forceStop();
+ mBubbleScaleAnimation.setRange(mDrawBubbleScale, 0.0f);
+ mBubbleScaleAnimation.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
+ mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 0.0f)));
+ mBubbleScaleAnimation.start();
+ invalidate();
}
public void setColor(int color) {
@@ -62,6 +147,15 @@ public class Slider extends GLView {
mStart = start;
mEnd = end;
setProgress(mProgress);
+
+ // Fix bubble width
+ float maxWidth = mTextTexture.getMaxWidth() * Integer.toString(end).length() + m8dp;
+ int bubbleWidth = mBubble.getWidth();
+ int bubbleHeight = mBubble.getHeight();
+ if (maxWidth > bubbleWidth) {
+ mBubble.invalidateContent();
+ mBubble.setBound((int) maxWidth, bubbleHeight);
+ }
}
public void setProgress(int progress) {
@@ -69,7 +163,11 @@ public class Slider extends GLView {
if (mProgress != progress) {
int oldProgress = mProgress;
mProgress = progress;
- mDrawProgress = MathUtils.delerp(mStart, mEnd, mProgress);
+ mPercent = MathUtils.delerp(mStart, mEnd, mProgress);
+ mTargetProgress = progress;
+ mTargetPercent = mPercent;
+ startProgressAnimation(mPercent);
+
if (mListener != null) {
mListener.onSetProgress(progress, oldProgress, false);
}
@@ -98,9 +196,20 @@ public class Slider extends GLView {
getDefaultSize(getSuggestedMinimumHeight(), heightSpec));
}
+ private void update() {
+ boolean invalidate = false;
+
+ invalidate |= mProgressAnimation.calculate(AnimationTime.get());
+ invalidate |= mBubbleScaleAnimation.calculate(AnimationTime.get());
+
+ if (invalidate) {
+ invalidate();
+ }
+ }
+
@Override
protected void onRender(GLCanvas canvas) {
- super.onRender(canvas);
+ update();
int width = getWidth();
if (width < m12dp * 2) {
@@ -111,10 +220,55 @@ public class Slider extends GLView {
// Draw bar
canvas.fillRect(padding.left + dp6, padding.top + m5dp,
width - dp6 - dp6 - padding.left - padding.right, m2dp, mColor);
+
+ float drawProgressX = padding.left + dp6 + (width - dp6 - dp6 - padding.left - padding.right) *
+ (mReverse ? (1.0f - mDrawPercent) : mDrawPercent);
+
+ // Draw bubble
+ float startX = drawProgressX;
+ float startY = padding.top + dp6;
+ float scale = mDrawBubbleScale;
+ if (scale != 0.0f) {
+ canvas.save();
+ canvas.translate(startX, startY);
+ canvas.scale(scale, scale, 1.0f);
+ canvas.translate(-startX, -startY);
+
+ String str = Integer.toString(mDrawProgress);
+ float strWidth = mTextTexture.getTextWidth(str);
+
+ int bubbleY = padding.top -m2dp - mBubble.getHeight();
+ int bubbleX = (int) drawProgressX - mBubble.getWidth() / 2;
+
+ mBubble.draw(canvas, bubbleX, bubbleY);
+ mTextTexture.drawText(canvas, str, (int) (drawProgressX - strWidth / 2), bubbleY + m8dp);
+
+ canvas.restore();
+ }
+
// Draw controllor
- canvas.fillOval(padding.left + dp6 + (width - dp6 - dp6 - padding.left - padding.right) *
- (mReverse ? (1.0f - mDrawProgress) : mDrawProgress),
- padding.top + dp6, dp6, dp6, mColor);
+ scale = 1.0f - mDrawBubbleScale;
+ if (scale != 0.0f) {
+ canvas.save();
+ canvas.translate(startX, startY);
+ canvas.scale(scale, scale, 1.0f);
+ canvas.translate(-startX, -startY);
+
+ canvas.fillOval(drawProgressX, padding.top + dp6, dp6, dp6, mColor);
+
+ canvas.restore();
+ }
+ }
+ }
+
+ private void setShowBubble(boolean showBubble) {
+ if (mShowBubble != showBubble) {
+ mShowBubble = showBubble;
+ if (showBubble) {
+ startShowBubbleAnimation();
+ } else {
+ startHideBubbleAnimation();
+ }
}
}
@@ -133,22 +287,41 @@ public class Slider extends GLView {
int progress = Math.round(MathUtils.lerp((float) mStart, (float) mEnd,
MathUtils.clamp((mReverse ? (getWidth() - padding.right - dp6 - x) : (x - dp6 - padding.left)) /
(getWidth() - dp6 - dp6 - padding.left - padding.right), 0.0f, 1.0f)));
- float oldDrawProgress = mDrawProgress;
- mDrawProgress = MathUtils.delerp(mStart, mEnd, progress);
- if (mDrawProgress != oldDrawProgress) {
- invalidate();
+ float percent = MathUtils.delerp(mStart, mEnd, progress);
+
+ // ACTION_CANCEL not changed
+ if (action == MotionEvent.ACTION_CANCEL) {
+ progress = mProgress;
+ percent = mPercent;
+ }
+
+ if (mTargetProgress != progress) {
+ mTargetProgress = progress;
+ mTargetPercent = percent;
+ startProgressAnimation(percent);
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
- if (progress != mProgress) {
+ SimpleHandler.getInstance().removeCallbacks(mCheckForShowBubble);
+ setShowBubble(false);
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ if (mCheckForShowBubble == null) {
+ mCheckForShowBubble = new CheckForShowBubble();
+ }
+ SimpleHandler.getInstance().postDelayed(mCheckForShowBubble, ViewConfiguration.getTapTimeout());
+ }
+
+ if (action == MotionEvent.ACTION_UP) {
+ if (mProgress != progress) {
int oldProgress = mProgress;
mProgress = progress;
+ mPercent = mDrawPercent;
+
if (mListener != null) {
mListener.onSetProgress(progress, oldProgress, true);
}
}
}
-
break;
}
@@ -159,4 +332,12 @@ public class Slider extends GLView {
void onSetProgress(int newProgress, int oldProgress, boolean byUser);
}
+
+ private final class CheckForShowBubble implements Runnable {
+
+ @Override
+ public void run() {
+ setShowBubble(true);
+ }
+ }
}
diff --git a/app/src/main/res/drawable/slider_bubble.xml b/app/src/main/res/drawable/slider_bubble.xml
new file mode 100644
index 000000000..3bfd0356c
--- /dev/null
+++ b/app/src/main/res/drawable/slider_bubble.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/pathdata.xml b/app/src/main/res/values/pathdata.xml
index 3c842e94c..e2cb9e8fa 100644
--- a/app/src/main/res/values/pathdata.xml
+++ b/app/src/main/res/values/pathdata.xml
@@ -48,4 +48,6 @@
M11.6,7v7.1c0,1-0.3,1.8-0.9,2.4c-0.6,0.6-1.5,1-2.5,1.1l-0.4,0c-1.1,0-2.1-0.3-2.7-0.9c-0.7-0.6-1-1.5-1-2.6V7h1.4v7.1c0,0.8,0.2,1.3,0.6,1.8c0.4,0.4,1,0.6,1.8,0.6c0.8,0,1.4-0.2,1.8-0.6c0.4-0.4,0.6-1,0.6-1.8V7H11.6M17.3,10.9c-0.2,0-0.4-0.1-0.7-0.1c-0.9,0-1.5,0.4-1.8,1.1v5.5h-1.3V9.7h1.3l0,0.9c0.4-0.7,1.1-1,1.9-1c0.3,0,0.5,0,0.6,0.1V10.9M20,17.5h-1.3V6.4H20V17.5z
M11,19V9A2,2 0 0,0 9,7H5V17H9A2,2 0 0,1 11,19M13,9V19A2,2 0 0,1 15,17H19V7H15A2,2 0 0,0 13,9M21,19H15A2,2 0 0,0 13,21H11A2,2 0 0,0 9,19H3V5H9A2,2 0 0,1 11,7H13A2,2 0 0,1 15,5H21V19Z
+ M13,0c7.2,0,13,5.9,13,13.2c0,3.7-2,7.6-7.3,12.9L13,32l-5.8-5.8C2,20.8,0,16.9,0,13.3C0,5.9,5.8,0,13,0z
+