From a1c11a44729c0ce090139151164f77e530fdfeea Mon Sep 17 00:00:00 2001 From: seven332 Date: Tue, 11 Aug 2015 22:40:13 +0800 Subject: [PATCH] Add animation to Slider --- .../ehviewer/gallery/anim/Animation.java | 23 ++ .../gallery/glrenderer/UploadedTexture.java | 2 +- .../com/hippo/ehviewer/gallery/ui/Slider.java | 207 ++++++++++++++++-- app/src/main/res/drawable/slider_bubble.xml | 40 ++++ app/src/main/res/values/pathdata.xml | 2 + 5 files changed, 260 insertions(+), 14 deletions(-) create mode 100644 app/src/main/res/drawable/slider_bubble.xml 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 +