diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeEffect.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeEffect.java index e7c65e8a7..37f91f74c 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeEffect.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeEffect.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright 2015 Hippo Seven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,87 +16,45 @@ package com.hippo.ehviewer.gallery.ui; -import android.content.Context; -import android.graphics.Canvas; import android.graphics.Rect; +import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import com.hippo.ehviewer.R; import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; -import com.hippo.ehviewer.gallery.glrenderer.ResourceTexture; -// This is copied from android.widget.EdgeEffect with some small modifications: -// (1) Copy the images (overscroll_{edge|glow}.png) to local resources. -// (2) Use "GLCanvas" instead of "Canvas" for draw()'s parameter. -// (3) Use a private Drawable class (which inherits from ResourceTexture) -// instead of android.graphics.drawable.Drawable to hold the images. -// The private Drawable class is used to translate original Canvas calls to -// corresponding GLCanvas calls. - -/** - * This class performs the graphical effect used at the edges of scrollable widgets - * when the user scrolls beyond the content bounds in 2D space. - * - *
EdgeEffect is stateful. Custom widgets using EdgeEffect should create an - * instance for each edge that should show the effect, feed it input data using - * the methods {@link #onAbsorb(int)}, {@link #onPull(float)}, and {@link #onRelease()}, - * and draw the effect using {@link #draw(GLCanvas)} in the widget's overridden - * {@link android.view.View#draw(Canvas)} method. If {@link #isFinished()} returns - * false after drawing, the edge effect's animation is not yet complete and the widget - * should schedule another drawing pass to continue the animation.
- * - *When drawing, widgets should draw their main content and child views first,
- * usually by invoking super.draw(canvas) from an overridden draw
- * method. (This will invoke onDraw and dispatch drawing to child views as needed.)
- * The edge effect may then be drawn on top of the view's content using the
- * {@link #draw(GLCanvas)} method.
Views using EdgeEffect should favor {@link #onPull(float, float)} when the displacement + * of the pull point is known.
+ * * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to * 1.f (full length of the view) or negative values to express change * back toward the edge reached to initiate the effect. */ public void onPull(float deltaDistance) { - final long now = AnimationTime.get(); + onPull(deltaDistance, 0.5f); + } + + /** + * A view should call this when content is pulled away from an edge by the user. + * This will update the state of the current visual effect and its associated animation. + * The host view should always {@link android.view.View#invalidate()} after this + * and draw the results accordingly. + * + * @param deltaDistance Change in distance since the last call. Values may be 0 (no change) to + * 1.f (full length of the view) or negative values to express change + * back toward the edge reached to initiate the effect. + * @param displacement The displacement from the starting side of the effect of the point + * initiating the pull. In the case of touch this is the finger position. + * Values may be from 0-1. + */ + public void onPull(float deltaDistance, float displacement) { + final long now = AnimationUtils.currentAnimationTimeMillis(); + mTargetDisplacement = displacement; if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { return; } if (mState != STATE_PULL) { - mGlowScaleY = PULL_GLOW_BEGIN; + mGlowScaleY = Math.max(PULL_GLOW_BEGIN, mGlowScaleY); } mState = STATE_PULL; @@ -195,30 +178,20 @@ public class EdgeEffect { mDuration = PULL_TIME; mPullDistance += deltaDistance; - float distance = Math.abs(mPullDistance); - - mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA)); - mEdgeScaleY = mEdgeScaleYStart = Math.max( - HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f)); + final float absdd = Math.abs(deltaDistance); mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA, - mGlowAlpha + - (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); + mGlowAlpha + (absdd * PULL_DISTANCE_ALPHA_GLOW_FACTOR)); - float glowChange = Math.abs(deltaDistance); - if (deltaDistance > 0 && mPullDistance < 0) { - glowChange = -glowChange; - } if (mPullDistance == 0) { - mGlowScaleY = 0; + mGlowScaleY = mGlowScaleYStart = 0; + } else { + final float scale = Math.max(0, 1 - 1 / + (float) Math.sqrt(Math.abs(mPullDistance) * mBounds.height()) - 0.3f) / 0.7f; + + mGlowScaleY = mGlowScaleYStart = scale; } - // Do not allow glow to get larger than MAX_GLOW_HEIGHT. - mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max( - 0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR)); - - mEdgeAlphaFinish = mEdgeAlpha; - mEdgeScaleYFinish = mEdgeScaleY; mGlowAlphaFinish = mGlowAlpha; mGlowScaleYFinish = mGlowScaleY; } @@ -237,17 +210,13 @@ public class EdgeEffect { } mState = STATE_RECEDE; - mEdgeAlphaStart = mEdgeAlpha; - mEdgeScaleYStart = mEdgeScaleY; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; - mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.f; mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; - mStartTime = AnimationTime.get(); + mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; } @@ -263,37 +232,44 @@ public class EdgeEffect { */ public void onAbsorb(int velocity) { mState = STATE_ABSORB; - velocity = Math.max(MIN_VELOCITY, Math.abs(velocity)); + velocity = Math.min(Math.max(MIN_VELOCITY, Math.abs(velocity)), MAX_VELOCITY); - mStartTime = AnimationTime.get(); - mDuration = 0.1f + (velocity * 0.03f); + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = 0.15f + (velocity * 0.02f); - // The edge should always be at least partially visible, regardless - // of velocity. - mEdgeAlphaStart = 0.f; - mEdgeScaleY = mEdgeScaleYStart = 0.f; // The glow depends more on the velocity, and therefore starts out // nearly invisible. - mGlowAlphaStart = 0.5f; - mGlowScaleYStart = 0.f; + mGlowAlphaStart = 0.3f; + mGlowScaleYStart = Math.max(mGlowScaleY, 0.f); - // Factor the velocity by 8. Testing on device shows this works best to - // reflect the strength of the user's scrolling. - mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1)); - // Edge should never get larger than the size of its asset. - mEdgeScaleYFinish = Math.max( - HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f)); // Growth for the size of the glow should be quadratic to properly // respond // to a user's scrolling speed. The faster the scrolling speed, the more // intense the effect should be for both the size and the saturation. - mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f); + mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f) / 2, 1.f); // Alpha should change for the glow as well as size. mGlowAlphaFinish = Math.max( mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA)); + mTargetDisplacement = 0.5f; } + /** + * Set the color of this edge effect in argb. + * + * @param color Color in argb + */ + public void setColor(int color) { + mColor = color; + } + + /** + * Return the color of this edge effect in argb. + * @return The color of this edge effect in argb + */ + public int getColor() { + return mColor; + } /** * Draw into the provided canvas. Assumes that the canvas has been rotated @@ -308,97 +284,77 @@ public class EdgeEffect { public boolean draw(GLCanvas canvas) { update(); - final int edgeHeight = mEdge.getIntrinsicHeight(); - final int edgeWidth = mEdge.getIntrinsicWidth(); - final int glowHeight = mGlow.getIntrinsicHeight(); - final int glowWidth = mGlow.getIntrinsicWidth(); + canvas.save(); - mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255)); + final float centerX = mBounds.centerX(); + final float centerY = mBounds.height() - mRadius; - int glowBottom = (int) Math.min( - glowHeight * mGlowScaleY * glowHeight/ glowWidth * 0.6f, - glowHeight * MAX_GLOW_HEIGHT); - if (mWidth < mMinWidth) { - // Center the glow and clip it. - int glowLeft = (mWidth - mMinWidth)/2; - mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, glowBottom); - } else { - // Stretch the glow to fit. - mGlow.setBounds(0, 0, mWidth, glowBottom); + canvas.translate(-centerX, 0f); + canvas.scale(1.f, Math.min(mGlowScaleY, 1.f) * mBaseGlowScale, 0); + canvas.translate(centerX, 0f); + + final float displacement = Math.max(0, Math.min(mDisplacement, 1.f)) - 0.5f; + float translateX = mBounds.width() * displacement / 2; + + canvas.translate(translateX, 0); + canvas.fillOval(centerX, centerY, mRadius, mRadius, mColor); + canvas.restore(); + + boolean oneLastFrame = false; + if (mState == STATE_RECEDE && mGlowScaleY == 0) { + mState = STATE_IDLE; + oneLastFrame = true; } - mGlow.draw(canvas); + return mState != STATE_IDLE || oneLastFrame; + } - mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255)); - - int edgeBottom = (int) (edgeHeight * mEdgeScaleY); - if (mWidth < mMinWidth) { - // Center the edge and clip it. - int edgeLeft = (mWidth - mMinWidth)/2; - mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, edgeBottom); - } else { - // Stretch the edge to fit. - mEdge.setBounds(0, 0, mWidth, edgeBottom); - } - mEdge.draw(canvas); - - return mState != STATE_IDLE; + /** + * Return the maximum height that the edge effect will be drawn at given the original + * {@link #setSize(int, int) input size}. + * @return The maximum height of the edge effect + */ + public int getMaxHeight() { + return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f); } private void update() { - final long time = AnimationTime.get(); + final long time = AnimationUtils.currentAnimationTimeMillis(); final float t = Math.min((time - mStartTime) / mDuration, 1.f); final float interp = mInterpolator.getInterpolation(t); - mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp; - mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp; mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; + mDisplacement = (mDisplacement + mTargetDisplacement) / 2; if (t >= 1.f - EPSILON) { switch (mState) { case STATE_ABSORB: mState = STATE_RECEDE; - mStartTime = AnimationTime.get(); + mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = RECEDE_TIME; - mEdgeAlphaStart = mEdgeAlpha; - mEdgeScaleYStart = mEdgeScaleY; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; - // After absorb, the glow and edge should fade to nothing. - mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.f; + // After absorb, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; break; case STATE_PULL: mState = STATE_PULL_DECAY; - mStartTime = AnimationTime.get(); + mStartTime = AnimationUtils.currentAnimationTimeMillis(); mDuration = PULL_DECAY_TIME; - mEdgeAlphaStart = mEdgeAlpha; - mEdgeScaleYStart = mEdgeScaleY; mGlowAlphaStart = mGlowAlpha; mGlowScaleYStart = mGlowScaleY; - // After pull, the glow and edge should fade to nothing. - mEdgeAlphaFinish = 0.f; - mEdgeScaleYFinish = 0.f; + // After pull, the glow should fade to nothing. mGlowAlphaFinish = 0.f; mGlowScaleYFinish = 0.f; break; case STATE_PULL_DECAY: - // When receding, we want edge to decrease more slowly - // than the glow. - float factor = mGlowScaleYFinish != 0 ? 1 - / (mGlowScaleYFinish * mGlowScaleYFinish) - : Float.MAX_VALUE; - mEdgeScaleY = mEdgeScaleYStart + - (mEdgeScaleYFinish - mEdgeScaleYStart) * - interp * factor; mState = STATE_RECEDE; break; case STATE_RECEDE: @@ -407,37 +363,4 @@ public class EdgeEffect { } } } - - private static class Drawable extends ResourceTexture { - private Rect mBounds = new Rect(); - private int mAlpha = 255; - - public Drawable(Context context, int resId) { - super(context, resId); - } - - public int getIntrinsicWidth() { - return getWidth(); - } - - public int getIntrinsicHeight() { - return getHeight(); - } - - public void setBounds(int left, int top, int right, int bottom) { - mBounds.set(left, top, right, bottom); - } - - public void setAlpha(int alpha) { - mAlpha = alpha; - } - - public void draw(GLCanvas canvas) { - canvas.save(GLCanvas.SAVE_FLAG_ALPHA); - canvas.multiplyAlpha(mAlpha / 255.0f); - Rect b = mBounds; - draw(canvas, b.left, b.top, b.width(), b.height()); - canvas.restore(); - } - } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeView.java index 4676db9d6..285582018 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/EdgeView.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright 2015 Hippo Seven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,11 +17,12 @@ package com.hippo.ehviewer.gallery.ui; import android.content.Context; +import android.content.res.TypedArray; import android.opengl.Matrix; +import com.hippo.ehviewer.R; import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; -// EdgeView draws EdgeEffect (blue glow) at four sides of the view. public class EdgeView extends GLView { @SuppressWarnings("unused") private static final String TAG = "EdgeView"; @@ -44,8 +45,14 @@ public class EdgeView extends GLView { private float[] mMatrix = new float[4 * 16]; public EdgeView(Context context) { + final TypedArray a = context.obtainStyledAttributes( + R.styleable.Theme); + final int themeColor = a.getColor( + R.styleable.Theme_colorPrimary, 0xff666666); + a.recycle(); + int color = (themeColor & 0xffffff) | 0x33000000; for (int i = 0; i < 4; i++) { - mEffect[i] = new EdgeEffect(context); + mEffect[i] = new EdgeEffect(color); } } @@ -100,7 +107,7 @@ public class EdgeView extends GLView { // Called when the content is pulled away from the edge. // offset is in pixels. direction is one of {TOP, LEFT, BOTTOM, RIGHT}. - public void onPull(int offset, int direction) { + public void onPull(float offset, int direction) { int fullLength = ((direction & 1) == 0) ? getWidth() : getHeight(); mEffect[direction].onPull((float)offset / fullLength); if (!mEffect[direction].isFinished()) { @@ -108,6 +115,15 @@ public class EdgeView extends GLView { } } + public void onPull(float offset, float position, int direction) { + int fullLength = ((direction & 1) == 0) ? getWidth() : getHeight(); + int fullPosition = ((direction & 1) == 0) ? getHeight() : getWidth(); + mEffect[direction].onPull(offset / fullLength, position / fullPosition); + if (!mEffect[direction].isFinished()) { + invalidate(); + } + } + // Call when the object is released after being pulled. public void onRelease() { boolean more = false; diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java index 60d68d670..5950e2a49 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java @@ -245,13 +245,13 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { if (mFirstShownIndex > 0) { scrollToPage(mFirstShownIndex - 1); } else { - mEdgeView.onPull(getWidth() * 2, EdgeView.LEFT); + mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.LEFT); } } else { if (mFirstShownIndex < mAdapter.getPages() - 1) { scrollToPage(mFirstShownIndex + 1); } else { - mEdgeView.onPull(getWidth() * 2, EdgeView.RIGHT); + mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.RIGHT); } } } @@ -352,7 +352,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { } @Override - public boolean onScroll(float dx, float dy, float totalX, float totalY) { + public boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y) { if (mScale) { return false; } @@ -387,14 +387,14 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mEdgeView.onRelease(); } mDeltaX += remain; - mEdgeView.onPull(-mDeltaX, EdgeView.LEFT); + mEdgeView.onPull(-mDeltaX, y, EdgeView.LEFT); } else { if (mDeltaX < 0) { mDeltaX = 0; mEdgeView.onRelease(); } mDeltaX += remain; - mEdgeView.onPull(mDeltaX, EdgeView.RIGHT); + mEdgeView.onPull(mDeltaX, y, EdgeView.RIGHT); } } else if (mMode == Mode.TOP_TO_BOTTOM) { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java index e80ff6eeb..4eb05a2fb 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java @@ -34,7 +34,7 @@ public class GestureRecognizer { boolean onSingleTapConfirmed(float x, float y); boolean onDoubleTap(float x, float y); boolean onDoubleTapConfirmed(float x, float y); - boolean onScroll(float dx, float dy, float totalX, float totalY); + boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y); boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); boolean onScaleBegin(float focusX, float focusY); boolean onScale(float focusX, float focusY, float scale); @@ -109,7 +109,7 @@ public class GestureRecognizer { public boolean onScroll( MotionEvent e1, MotionEvent e2, float dx, float dy) { return mListener.onScroll( - dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY()); + dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY(), e2.getX(), e2.getY()); } @Override