Improve GalleryActivity
This commit is contained in:
1 parent
9ee91aae19
commit
bc29540a48
40 files changed
+2442
-530
No files matched your search
@@ -66,7 +66,7 @@ public class ImageHandler {
|
||||
sDiskCache = diskCache;
|
||||
}
|
||||
|
||||
public ImageHandler(GalleryBase galleryBase, int pages, Mode mode, UniFile downloadDir) {
|
||||
ImageHandler(GalleryBase galleryBase, int pages, Mode mode, UniFile downloadDir) {
|
||||
mGalleryBase = galleryBase;
|
||||
mMode = mode;
|
||||
mFilenames = new AtomicReferenceArray<>(pages);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.hippo.ehviewer.gallery;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
|
||||
import com.hippo.ehviewer.client.EhConfig;
|
||||
@@ -101,6 +100,9 @@ public class SpiderQueen implements Runnable {
|
||||
private final ReentrantLock mWorkerArrayLock = new ReentrantLock();
|
||||
// Lock for mRequestTokens and mSpiderInfo.tokens
|
||||
private final Object mTokenLock = new Object();
|
||||
// The lock to make sure onGetSize call first
|
||||
public boolean mGetSizeFinished;
|
||||
private final Object mGetSizeLock = new Object();
|
||||
|
||||
public SpiderQueen(EhHttpClient httpClient, File spiderInfoDir,
|
||||
GalleryBase galleryBase, ImageHandler.Mode mode, UniFile downloadDir,
|
||||
@@ -476,6 +478,29 @@ public class SpiderQueen implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
public void setGetSizeFinished(final boolean getSizeFinished) {
|
||||
synchronized (mGetSizeLock) {
|
||||
if (mGetSizeFinished != getSizeFinished) {
|
||||
mGetSizeFinished = getSizeFinished;
|
||||
if (getSizeFinished) {
|
||||
mGetSizeLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void waitUntilFinishGettingSize() {
|
||||
synchronized (mGetSizeLock) {
|
||||
while (!mGetSizeFinished) {
|
||||
try {
|
||||
mGetSizeLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignored, we'll start waiting again
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void doIt() throws Exception {
|
||||
Say.d(TAG, "Spider Queen do it");
|
||||
|
||||
@@ -538,18 +563,19 @@ public class SpiderQueen implements Runnable {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// TODO make sure onGetPages first then other listener
|
||||
// 7. tell listener pages
|
||||
mSpiderListener.onGetPages(spiderInfo.pages);
|
||||
|
||||
// 6. Make workers and render to work
|
||||
setGetSizeFinished(false);
|
||||
ensureWorker();
|
||||
mRender = new Render();
|
||||
Thread thread = new Thread(mRender, "Render");
|
||||
thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
thread.start();
|
||||
|
||||
// 7. tell listener pages
|
||||
mSpiderListener.onGetPages(spiderInfo.pages);
|
||||
setGetSizeFinished(true);
|
||||
|
||||
|
||||
// 8. Get tokens
|
||||
handleToken(spiderInfo, ehConfig);
|
||||
|
||||
@@ -688,6 +714,9 @@ public class SpiderQueen implements Runnable {
|
||||
GalleryBase gb = mGalleryBase;
|
||||
ImageHandler handler = mImageHandler;
|
||||
|
||||
// Wait for post send onGetSize over
|
||||
waitUntilFinishGettingSize();
|
||||
|
||||
while (!mStop) {
|
||||
// Get index
|
||||
final int index = getRequestIndex();
|
||||
@@ -812,13 +841,11 @@ public class SpiderQueen implements Runnable {
|
||||
|
||||
private void waitUntilUnpaused() {
|
||||
synchronized (mPauseLock) {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
while (mPauseAccess) {
|
||||
try {
|
||||
mPauseLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignored, we'll start waiting again
|
||||
}
|
||||
while (mPauseAccess) {
|
||||
try {
|
||||
mPauseLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignored, we'll start waiting again
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -837,6 +864,9 @@ public class SpiderQueen implements Runnable {
|
||||
public void run() {
|
||||
Say.d(TAG, "Render starts");
|
||||
|
||||
// Wait for post send onGetSize over
|
||||
waitUntilFinishGettingSize();
|
||||
|
||||
while (!mStop) {
|
||||
|
||||
waitUntilUnpaused();
|
||||
|
||||
@@ -49,20 +49,49 @@ abstract public class Animation {
|
||||
private static final long ANIMATION_START = -1;
|
||||
private static final long NO_ANIMATION = -2;
|
||||
|
||||
/**
|
||||
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
|
||||
* or a positive value, the animation restarts from the beginning.
|
||||
*/
|
||||
public static final int RESTART = 1;
|
||||
/**
|
||||
* When the animation reaches the end and <code>repeatCount</code> is INFINITE
|
||||
* or a positive value, the animation reverses direction on every iteration.
|
||||
*/
|
||||
public static final int REVERSE = 2;
|
||||
/**
|
||||
* This value used used with the {@link #setRepeatCount(int)} property to repeat
|
||||
* the animation indefinitely.
|
||||
*/
|
||||
public static final int INFINITE = -1;
|
||||
|
||||
private long mStartTime = NO_ANIMATION;
|
||||
private int mDuration;
|
||||
private long mDuration;
|
||||
private Interpolator mInterpolator;
|
||||
private int mRepeatCount;
|
||||
private int mRepeatMode;
|
||||
|
||||
private int mRunnedCount;
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
public void setDuration(int duration) {
|
||||
public void setDuration(long duration) {
|
||||
mDuration = duration;
|
||||
}
|
||||
|
||||
public void setRepeatCount(int repeatCount) {
|
||||
mRepeatCount = repeatCount;
|
||||
}
|
||||
|
||||
public void setRepeatMode(int repeatMode) {
|
||||
mRepeatMode = repeatMode;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mStartTime = ANIMATION_START;
|
||||
mRunnedCount = 0;
|
||||
}
|
||||
|
||||
public void setStartTime(long time) {
|
||||
@@ -77,14 +106,29 @@ abstract public class Animation {
|
||||
mStartTime = NO_ANIMATION;
|
||||
}
|
||||
|
||||
// TODO mRepeatMode
|
||||
public boolean calculate(long currentTimeMillis) {
|
||||
if (mStartTime == NO_ANIMATION) return false;
|
||||
if (mStartTime == ANIMATION_START) mStartTime = currentTimeMillis;
|
||||
int elapse = (int) (currentTimeMillis - mStartTime);
|
||||
if (mStartTime == NO_ANIMATION) {
|
||||
return false;
|
||||
}
|
||||
if (mStartTime == ANIMATION_START) {
|
||||
mStartTime = currentTimeMillis;
|
||||
}
|
||||
|
||||
long elapse = currentTimeMillis - mStartTime;
|
||||
float x = MathUtils.clamp((float) elapse / mDuration, 0f, 1f);
|
||||
Interpolator i = mInterpolator;
|
||||
onCalculate(i != null ? i.getInterpolation(x) : x);
|
||||
if (elapse >= mDuration) mStartTime = NO_ANIMATION;
|
||||
|
||||
if (elapse >= mDuration) {
|
||||
mRunnedCount++;
|
||||
if (mRunnedCount >= mRepeatCount && mRepeatCount != INFINITE) {
|
||||
mStartTime = NO_ANIMATION;
|
||||
} else {
|
||||
mStartTime += elapse;
|
||||
}
|
||||
}
|
||||
|
||||
return mStartTime != NO_ANIMATION;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,10 @@ public class FloatAnimation extends Animation {
|
||||
private final float mTo;
|
||||
private float mCurrent;
|
||||
|
||||
public FloatAnimation(float from, float to, int duration) {
|
||||
public FloatAnimation(float from, float to) {
|
||||
mFrom = from;
|
||||
mTo = to;
|
||||
mCurrent = from;
|
||||
setDuration(duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
@@ -143,6 +144,10 @@ public abstract class BasicTexture implements Texture {
|
||||
canvas.drawTexture(this, x, y, w, h);
|
||||
}
|
||||
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
canvas.drawTexture(this, source, target);
|
||||
}
|
||||
|
||||
// onBind is called before GLCanvas binds this texture.
|
||||
// It should make sure the data is uploaded to GL memory.
|
||||
abstract protected boolean onBind(GLCanvas canvas);
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.opengl.GLUtils;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.gallery.util.IntArray;
|
||||
import com.hippo.yorozuya.IntArray;
|
||||
import com.hippo.yorozuya.ColorUtils;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
@@ -23,7 +23,7 @@ import android.opengl.GLUtils;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.gallery.util.IntArray;
|
||||
import com.hippo.yorozuya.IntArray;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
import java.nio.Buffer;
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.util.FloatMath;
|
||||
|
||||
// StringTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
@@ -72,7 +71,7 @@ public class StringTexture extends CanvasTexture {
|
||||
|
||||
private static StringTexture newInstance(String text, TextPaint paint) {
|
||||
FontMetricsInt metrics = paint.getFontMetricsInt();
|
||||
int width = (int) FloatMath.ceil(paint.measureText(text));
|
||||
int width = (int) Math.ceil(paint.measureText(text));
|
||||
int height = metrics.bottom - metrics.top;
|
||||
// The texture size needs to be at least 1x1.
|
||||
if (width <= 0) width = 1;
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.graphics.PorterDuff.Mode;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.hippo.ehviewer.gallery.ui.GLRoot;
|
||||
|
||||
@@ -36,6 +37,9 @@ import java.util.ArrayList;
|
||||
// upload the whole bitmap but we reduce the time of uploading each tile
|
||||
// so it make the animation more smooth and prevents jank.
|
||||
public class TiledTexture implements Texture {
|
||||
|
||||
private static final String TAG = TiledTexture.class.getSimpleName();
|
||||
|
||||
private static final int CONTENT_SIZE = 254;
|
||||
private static final int BORDER_SIZE = 1;
|
||||
private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
|
||||
@@ -52,6 +56,9 @@ public class TiledTexture implements Texture {
|
||||
private static Canvas sCanvas;
|
||||
private static Paint sBitmapPaint;
|
||||
private static Paint sPaint;
|
||||
private static OnFreeBitmapListener sOnFreeBitmapListener;
|
||||
|
||||
private Bitmap mBitmap;
|
||||
|
||||
private int mUploadIndex = 0;
|
||||
|
||||
@@ -62,6 +69,15 @@ public class TiledTexture implements Texture {
|
||||
private final RectF mSrcRect = new RectF();
|
||||
private final RectF mDestRect = new RectF();
|
||||
|
||||
public interface OnFreeBitmapListener {
|
||||
|
||||
void onFreeBitmapListener(Bitmap bitmap);
|
||||
}
|
||||
|
||||
public static void setOnFreeBitmapListener(OnFreeBitmapListener listener) {
|
||||
sOnFreeBitmapListener = listener;
|
||||
}
|
||||
|
||||
public static class Uploader implements GLRoot.OnGLIdleListener {
|
||||
private final ArrayDeque<TiledTexture> mTextures =
|
||||
new ArrayDeque<>(INIT_CAPACITY);
|
||||
@@ -95,6 +111,7 @@ public class TiledTexture implements Texture {
|
||||
while (now < dueTime && !deque.isEmpty()) {
|
||||
TiledTexture t = deque.peekFirst();
|
||||
if (t.uploadNextTile(canvas)) {
|
||||
t.freeBitmap();
|
||||
deque.removeFirst();
|
||||
mGlRoot.requestRender();
|
||||
}
|
||||
@@ -202,7 +219,8 @@ public class TiledTexture implements Texture {
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
public TiledTexture(Bitmap bitmap) {
|
||||
public TiledTexture(@NonNull Bitmap bitmap) {
|
||||
mBitmap = bitmap;
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
ArrayList<Tile> list = new ArrayList<Tile>();
|
||||
@@ -233,6 +251,20 @@ public class TiledTexture implements Texture {
|
||||
freeTile(mTiles[i]);
|
||||
}
|
||||
}
|
||||
freeBitmap();
|
||||
}
|
||||
|
||||
private void freeBitmap() {
|
||||
if (mBitmap != null) {
|
||||
onFreeBitmap(mBitmap);
|
||||
mBitmap = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void onFreeBitmap(Bitmap bitmap) {
|
||||
if (sOnFreeBitmapListener != null) {
|
||||
sOnFreeBitmapListener.onFreeBitmapListener(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
public static void freeResources() {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
|
||||
public class DownUpDetector {
|
||||
public interface DownUpListener {
|
||||
void onDown(MotionEvent e);
|
||||
void onUp(MotionEvent e);
|
||||
void onPointerDown(MotionEvent e);
|
||||
void onPointerUp(MotionEvent e);
|
||||
}
|
||||
|
||||
private boolean mStillDown;
|
||||
private DownUpListener mListener;
|
||||
|
||||
public DownUpDetector(DownUpListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private void setState(boolean down, MotionEvent e) {
|
||||
if (down == mStillDown) return;
|
||||
mStillDown = down;
|
||||
if (down) {
|
||||
mListener.onDown(e);
|
||||
} else {
|
||||
mListener.onUp(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent ev) {
|
||||
switch (ev.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
setState(true, ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
mListener.onPointerDown(ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
mListener.onPointerUp(ev);
|
||||
setState(false, ev);
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
setState(false, ev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDown() {
|
||||
return mStillDown;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
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.
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>When drawing, widgets should draw their main content and child views first,
|
||||
* usually by invoking <code>super.draw(canvas)</code> from an overridden <code>draw</code>
|
||||
* 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.</p>
|
||||
*/
|
||||
public class EdgeEffect {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "EdgeEffect";
|
||||
|
||||
// Time it will take the effect to fully recede in ms
|
||||
private static final int RECEDE_TIME = 1000;
|
||||
|
||||
// Time it will take before a pulled glow begins receding in ms
|
||||
private static final int PULL_TIME = 167;
|
||||
|
||||
// Time it will take in ms for a pulled glow to decay to partial strength before release
|
||||
private static final int PULL_DECAY_TIME = 1000;
|
||||
|
||||
private static final float MAX_ALPHA = 0.8f;
|
||||
private static final float HELD_EDGE_ALPHA = 0.7f;
|
||||
private static final float HELD_EDGE_SCALE_Y = 0.5f;
|
||||
private static final float HELD_GLOW_ALPHA = 0.5f;
|
||||
private static final float HELD_GLOW_SCALE_Y = 0.5f;
|
||||
|
||||
private static final float MAX_GLOW_HEIGHT = 4.f;
|
||||
|
||||
private static final float PULL_GLOW_BEGIN = 1.f;
|
||||
private static final float PULL_EDGE_BEGIN = 0.6f;
|
||||
|
||||
// Minimum velocity that will be absorbed
|
||||
private static final int MIN_VELOCITY = 100;
|
||||
|
||||
private static final float EPSILON = 0.001f;
|
||||
|
||||
private final Drawable mEdge;
|
||||
private final Drawable mGlow;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private final int MIN_WIDTH = 300;
|
||||
private final int mMinWidth;
|
||||
|
||||
private float mEdgeAlpha;
|
||||
private float mEdgeScaleY;
|
||||
private float mGlowAlpha;
|
||||
private float mGlowScaleY;
|
||||
|
||||
private float mEdgeAlphaStart;
|
||||
private float mEdgeAlphaFinish;
|
||||
private float mEdgeScaleYStart;
|
||||
private float mEdgeScaleYFinish;
|
||||
private float mGlowAlphaStart;
|
||||
private float mGlowAlphaFinish;
|
||||
private float mGlowScaleYStart;
|
||||
private float mGlowScaleYFinish;
|
||||
|
||||
private long mStartTime;
|
||||
private float mDuration;
|
||||
|
||||
private final Interpolator mInterpolator;
|
||||
|
||||
private static final int STATE_IDLE = 0;
|
||||
private static final int STATE_PULL = 1;
|
||||
private static final int STATE_ABSORB = 2;
|
||||
private static final int STATE_RECEDE = 3;
|
||||
private static final int STATE_PULL_DECAY = 4;
|
||||
|
||||
// How much dragging should effect the height of the edge image.
|
||||
// Number determined by user testing.
|
||||
private static final int PULL_DISTANCE_EDGE_FACTOR = 7;
|
||||
|
||||
// How much dragging should effect the height of the glow image.
|
||||
// Number determined by user testing.
|
||||
private static final int PULL_DISTANCE_GLOW_FACTOR = 7;
|
||||
private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 1.1f;
|
||||
|
||||
private static final int VELOCITY_EDGE_FACTOR = 8;
|
||||
private static final int VELOCITY_GLOW_FACTOR = 16;
|
||||
|
||||
private int mState = STATE_IDLE;
|
||||
|
||||
private float mPullDistance;
|
||||
|
||||
/**
|
||||
* Construct a new EdgeEffect with a theme appropriate for the provided context.
|
||||
* @param context Context used to provide theming and resource information for the EdgeEffect
|
||||
*/
|
||||
public EdgeEffect(Context context) {
|
||||
mEdge = new Drawable(context, R.drawable.overscroll_edge);
|
||||
mGlow = new Drawable(context, R.drawable.overscroll_glow);
|
||||
|
||||
mMinWidth = (int) (context.getResources().getDisplayMetrics().density * MIN_WIDTH + 0.5f);
|
||||
mInterpolator = new DecelerateInterpolator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of this edge effect in pixels.
|
||||
*
|
||||
* @param width Effect width in pixels
|
||||
* @param height Effect height in pixels
|
||||
*/
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports if this EdgeEffect's animation is finished. If this method returns false
|
||||
* after a call to {@link #draw(GLCanvas)} the host widget should schedule another
|
||||
* drawing pass to continue the animation.
|
||||
*
|
||||
* @return true if animation is finished, false if drawing should continue on the next frame.
|
||||
*/
|
||||
public boolean isFinished() {
|
||||
return mState == STATE_IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately finish the current animation.
|
||||
* After this call {@link #isFinished()} will return true.
|
||||
*/
|
||||
public void finish() {
|
||||
mState = STATE_IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public void onPull(float deltaDistance) {
|
||||
final long now = AnimationTime.get();
|
||||
if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
|
||||
return;
|
||||
}
|
||||
if (mState != STATE_PULL) {
|
||||
mGlowScaleY = PULL_GLOW_BEGIN;
|
||||
}
|
||||
mState = STATE_PULL;
|
||||
|
||||
mStartTime = now;
|
||||
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));
|
||||
|
||||
mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
|
||||
mGlowAlpha +
|
||||
(Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
|
||||
|
||||
float glowChange = Math.abs(deltaDistance);
|
||||
if (deltaDistance > 0 && mPullDistance < 0) {
|
||||
glowChange = -glowChange;
|
||||
}
|
||||
if (mPullDistance == 0) {
|
||||
mGlowScaleY = 0;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when the object is released after being pulled.
|
||||
* This will begin the "decay" phase of the effect. After calling this method
|
||||
* the host view should {@link android.view.View#invalidate()} and thereby
|
||||
* draw the results accordingly.
|
||||
*/
|
||||
public void onRelease() {
|
||||
mPullDistance = 0;
|
||||
|
||||
if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
mDuration = RECEDE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call when the effect absorbs an impact at the given velocity.
|
||||
* Used when a fling reaches the scroll boundary.
|
||||
*
|
||||
* <p>When using a {@link android.widget.Scroller} or {@link android.widget.OverScroller},
|
||||
* the method <code>getCurrVelocity</code> will provide a reasonable approximation
|
||||
* to use here.</p>
|
||||
*
|
||||
* @param velocity Velocity at impact in pixels per second.
|
||||
*/
|
||||
public void onAbsorb(int velocity) {
|
||||
mState = STATE_ABSORB;
|
||||
velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
|
||||
|
||||
mStartTime = AnimationTime.get();
|
||||
mDuration = 0.1f + (velocity * 0.03f);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
// Alpha should change for the glow as well as size.
|
||||
mGlowAlphaFinish = Math.max(
|
||||
mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draw into the provided canvas. Assumes that the canvas has been rotated
|
||||
* accordingly and the size has been set. The effect will be drawn the full
|
||||
* width of X=0 to X=width, beginning from Y=0 and extending to some factor <
|
||||
* 1.f of height.
|
||||
*
|
||||
* @param canvas Canvas to draw into
|
||||
* @return true if drawing should continue beyond this frame to continue the
|
||||
* animation
|
||||
*/
|
||||
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();
|
||||
|
||||
mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
mGlow.draw(canvas);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private void update() {
|
||||
final long time = AnimationTime.get();
|
||||
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;
|
||||
|
||||
if (t >= 1.f - EPSILON) {
|
||||
switch (mState) {
|
||||
case STATE_ABSORB:
|
||||
mState = STATE_RECEDE;
|
||||
mStartTime = AnimationTime.get();
|
||||
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;
|
||||
mGlowAlphaFinish = 0.f;
|
||||
mGlowScaleYFinish = 0.f;
|
||||
break;
|
||||
case STATE_PULL:
|
||||
mState = STATE_PULL_DECAY;
|
||||
mStartTime = AnimationTime.get();
|
||||
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;
|
||||
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:
|
||||
mState = STATE_IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.opengl.Matrix;
|
||||
|
||||
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";
|
||||
|
||||
public static final int INVALID_DIRECTION = -1;
|
||||
public static final int TOP = 0;
|
||||
public static final int LEFT = 1;
|
||||
public static final int BOTTOM = 2;
|
||||
public static final int RIGHT = 3;
|
||||
|
||||
// Each edge effect has a transform matrix, and each matrix has 16 elements.
|
||||
// We put all the elements in one array. These constants specify the
|
||||
// starting index of each matrix.
|
||||
private static final int TOP_M = TOP * 16;
|
||||
private static final int LEFT_M = LEFT * 16;
|
||||
private static final int BOTTOM_M = BOTTOM * 16;
|
||||
private static final int RIGHT_M = RIGHT * 16;
|
||||
|
||||
private EdgeEffect[] mEffect = new EdgeEffect[4];
|
||||
private float[] mMatrix = new float[4 * 16];
|
||||
|
||||
public EdgeView(Context context) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
mEffect[i] = new EdgeEffect(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(
|
||||
boolean changeSize, int left, int top, int right, int bottom) {
|
||||
if (!changeSize) return;
|
||||
|
||||
int w = right - left;
|
||||
int h = bottom - top;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if ((i & 1) == 0) { // top or bottom
|
||||
mEffect[i].setSize(w, h);
|
||||
} else { // left or right
|
||||
mEffect[i].setSize(h, w);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up transforms for the four edges. Without transforms an
|
||||
// EdgeEffect draws the TOP edge from (0, 0) to (w, Y * h) where Y
|
||||
// is some factor < 1. For other edges we need to move, rotate, and
|
||||
// flip the effects into proper places.
|
||||
Matrix.setIdentityM(mMatrix, TOP_M);
|
||||
Matrix.setIdentityM(mMatrix, LEFT_M);
|
||||
Matrix.setIdentityM(mMatrix, BOTTOM_M);
|
||||
Matrix.setIdentityM(mMatrix, RIGHT_M);
|
||||
|
||||
Matrix.rotateM(mMatrix, LEFT_M, 90, 0, 0, 1);
|
||||
Matrix.scaleM(mMatrix, LEFT_M, 1, -1, 1);
|
||||
|
||||
Matrix.translateM(mMatrix, BOTTOM_M, 0, h, 0);
|
||||
Matrix.scaleM(mMatrix, BOTTOM_M, 1, -1, 1);
|
||||
|
||||
Matrix.translateM(mMatrix, RIGHT_M, w, 0, 0);
|
||||
Matrix.rotateM(mMatrix, RIGHT_M, 90, 0, 0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(GLCanvas canvas) {
|
||||
super.render(canvas);
|
||||
boolean more = false;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
|
||||
canvas.multiplyMatrix(mMatrix, i * 16);
|
||||
more |= mEffect[i].draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
if (more) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
int fullLength = ((direction & 1) == 0) ? getWidth() : getHeight();
|
||||
mEffect[direction].onPull((float)offset / fullLength);
|
||||
if (!mEffect[direction].isFinished()) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// Call when the object is released after being pulled.
|
||||
public void onRelease() {
|
||||
boolean more = false;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
mEffect[i].onRelease();
|
||||
more |= !mEffect[i].isFinished();
|
||||
}
|
||||
if (more) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
// Call when the effect absorbs an impact at the given velocity.
|
||||
// Used when a fling reaches the scroll boundary. velocity is in pixels
|
||||
// per second. direction is one of {TOP, LEFT, BOTTOM, RIGHT}.
|
||||
public void onAbsorb(int velocity, int direction) {
|
||||
mEffect[direction].onAbsorb(velocity);
|
||||
if (!mEffect[direction].isFinished()) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ public class FrameView extends GLView {
|
||||
}
|
||||
|
||||
// Consider min
|
||||
maxWidth = Math.max(maxWidth, getMinimumWidth());
|
||||
maxHeight = Math.max(maxHeight, getMinimumHeight());
|
||||
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
|
||||
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
|
||||
|
||||
// The final
|
||||
maxWidth = getDefaultSize(maxWidth, widthSpec);
|
||||
|
||||
@@ -35,11 +35,11 @@ import com.hippo.ehviewer.gallery.glrenderer.BasicTexture;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLES11Canvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLES20Canvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.TextTexture;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.UploadedTexture;
|
||||
import com.hippo.ehviewer.gallery.util.ApiHelper;
|
||||
import com.hippo.ehviewer.gallery.util.GalleryUtils;
|
||||
import com.hippo.ehviewer.gallery.util.MotionEventHelper;
|
||||
import com.hippo.ehviewer.gallery.util.Profile;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
@@ -72,9 +72,6 @@ public class GLRootView extends GLSurfaceView
|
||||
|
||||
private static final boolean DEBUG_DRAWING_STAT = false;
|
||||
|
||||
private static final boolean DEBUG_PROFILE = false;
|
||||
private static final boolean DEBUG_PROFILE_SLOW_ONLY = false;
|
||||
|
||||
private static final int FLAG_INITIALIZED = 1;
|
||||
private static final int FLAG_NEED_LAYOUT = 2;
|
||||
|
||||
@@ -144,9 +141,6 @@ public class GLRootView extends GLSurfaceView
|
||||
|
||||
// Uncomment this to enable gl error check.
|
||||
// setDebugFlags(DEBUG_CHECK_GL_ERROR);
|
||||
|
||||
// Uncomment this to enable gl error check.
|
||||
// setDebugFlags(DEBUG_CHECK_GL_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -313,7 +307,7 @@ public class GLRootView extends GLSurfaceView
|
||||
mRenderLock.unlock();
|
||||
}
|
||||
|
||||
if (DEBUG_FPS || DEBUG_PROFILE) {
|
||||
if (DEBUG_FPS) {
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
|
||||
} else {
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
||||
@@ -330,10 +324,6 @@ public class GLRootView extends GLSurfaceView
|
||||
Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + ", gl10: " + gl1.toString());
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
|
||||
GalleryUtils.setRenderThread();
|
||||
if (DEBUG_PROFILE) {
|
||||
Log.d(TAG, "Start profiling");
|
||||
Profile.enable(20); // take a sample every 20ms
|
||||
}
|
||||
GL11 gl = (GL11) gl1;
|
||||
AssertUtils.assertTrue(mGL == gl);
|
||||
|
||||
@@ -356,11 +346,9 @@ public class GLRootView extends GLSurfaceView
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
AnimationTime.update();
|
||||
long t0;
|
||||
if (DEBUG_PROFILE_SLOW_ONLY) {
|
||||
Profile.hold();
|
||||
t0 = System.nanoTime();
|
||||
}
|
||||
|
||||
long t0 = System.nanoTime();
|
||||
|
||||
mRenderLock.lock();
|
||||
|
||||
while (mFreeze) {
|
||||
@@ -388,25 +376,16 @@ public class GLRootView extends GLSurfaceView
|
||||
// });
|
||||
//}
|
||||
|
||||
if (DEBUG_PROFILE_SLOW_ONLY) {
|
||||
long t = System.nanoTime();
|
||||
long durationInMs = (t - mLastDrawFinishTime) / 1000000;
|
||||
long durationDrawInMs = (t - t0) / 1000000;
|
||||
mLastDrawFinishTime = t;
|
||||
long t = System.nanoTime();
|
||||
long duration = (t - t0) / 1000000;
|
||||
|
||||
if (durationInMs > 34) { // 34ms -> we skipped at least 2 frames
|
||||
Log.v(TAG, "----- SLOW (" + durationDrawInMs + "/" +
|
||||
durationInMs + ") -----");
|
||||
Profile.commit();
|
||||
} else {
|
||||
Profile.drop();
|
||||
}
|
||||
if (duration > 34) {
|
||||
Log.v(TAG, "--- " + duration + " ---");
|
||||
}
|
||||
}
|
||||
|
||||
private void onDrawFrameLocked(GL10 gl) {
|
||||
if (DEBUG_FPS) outputFps();
|
||||
|
||||
// release the unbound textures and deleted buffers.
|
||||
mCanvas.deleteRecycledResources();
|
||||
|
||||
@@ -549,12 +528,6 @@ public class GLRootView extends GLSurfaceView
|
||||
public void onPause() {
|
||||
unfreeze();
|
||||
super.onPause();
|
||||
if (DEBUG_PROFILE) {
|
||||
Log.d(TAG, "Stop profiling");
|
||||
Profile.disableAll();
|
||||
Profile.dumpToFile("/sdcard/gallery.prof");
|
||||
Profile.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -493,8 +493,16 @@ public class GLView {
|
||||
}
|
||||
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
setMeasuredSize(getDefaultSize(getMinimumWidth(), widthSpec),
|
||||
getDefaultSize(getMinimumHeight(), heightSpec));
|
||||
setMeasuredSize(getDefaultSize(getSuggestedMinimumWidth(), widthSpec),
|
||||
getDefaultSize(getSuggestedMinimumHeight(), heightSpec));
|
||||
}
|
||||
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
return getMinimumHeight();
|
||||
}
|
||||
|
||||
protected int getSuggestedMinimumWidth() {
|
||||
return getMinimumWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.hippo.ehviewer.gallery.glrenderer.TextTexture;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
|
||||
public class GalleryPageView extends FrameView {
|
||||
|
||||
public ScaledImageView mImageView;
|
||||
public LinearView mLinearView;
|
||||
public TextView mIndexView;
|
||||
public ProgressView mProgressView;
|
||||
|
||||
private Rect mSeen = new Rect();
|
||||
|
||||
private static TextTexture sTextTexture;
|
||||
|
||||
public static void setTextTexture(TextTexture textTexture) {
|
||||
sTextTexture = textTexture;
|
||||
}
|
||||
|
||||
public GalleryPageView(Context context) {
|
||||
mImageView = new ScaledImageView();
|
||||
mLinearView = new LinearView();
|
||||
mLinearView.setInterval(LayoutUtils.dp2pix(context, 24)); // 12dp
|
||||
mIndexView = new TextView(sTextTexture);
|
||||
mProgressView = new ProgressView();
|
||||
mProgressView.setMinimumWidth(LayoutUtils.dp2pix(context, 56)); // 56dp
|
||||
mProgressView.setMinimumHeight(LayoutUtils.dp2pix(context, 56)); // 56dp
|
||||
|
||||
GravityLayoutParams lp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mLinearView.addComponent(mIndexView, lp);
|
||||
lp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mLinearView.addComponent(mProgressView, lp);
|
||||
|
||||
lp = new GravityLayoutParams(LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT);
|
||||
addComponent(mImageView, lp);
|
||||
|
||||
lp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT);
|
||||
lp.gravity = Gravity.CENTER;
|
||||
addComponent(mLinearView, lp);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResourceType")
|
||||
private void updateSeen() {
|
||||
Rect bounds = bounds();
|
||||
Rect parentBounds = mParent.bounds();
|
||||
mSeen.set(bounds);
|
||||
if (!mSeen.intersect(0, 0, parentBounds.width(), parentBounds.height())) {
|
||||
mSeen.set(0, 0, 0, 0);
|
||||
} else {
|
||||
mSeen.offset(-bounds.left, -bounds.top);
|
||||
}
|
||||
mImageView.setSeen(mSeen);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changeSize, left, top, right, bottom);
|
||||
updateSeen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetLeftAndRight(int offset) {
|
||||
super.offsetLeftAndRight(offset);
|
||||
updateSeen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void offsetTopAndBottom(int offset) {
|
||||
super.offsetTopAndBottom(offset);
|
||||
updateSeen();
|
||||
}
|
||||
|
||||
public void setScaleOffset(GalleryView.Scale scale,
|
||||
GalleryView.StartPosition startPosition, float lastScale) {
|
||||
mImageView.setScaleOffset(scale, startPosition, lastScale);
|
||||
}
|
||||
|
||||
public void scroll(int dx, int dy, int[] remain) {
|
||||
mImageView.scroll(dx, dy, remain);
|
||||
}
|
||||
|
||||
public void scale(float focusX, float focusY, float scale) {
|
||||
mImageView.scale(focusX, focusY, scale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
package com.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.gallery.anim.Animation;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.ehviewer.gallery.util.GalleryUtils;
|
||||
import com.hippo.yorozuya.IntArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GalleryView extends GLView implements GestureRecognizer.Listener {
|
||||
|
||||
private static final String TAG = GalleryView.class.getSimpleName();
|
||||
|
||||
private static final int NO_POSITION = -1;
|
||||
|
||||
private static final int MAX_SETTLE_DURATION = 600; // ms
|
||||
|
||||
private static final int UNKNOWN_SEEN = 0;
|
||||
private static final int SEEN = 1;
|
||||
private static final int UNSEEN = 2;
|
||||
|
||||
private boolean mEnableRequestLayout = true;
|
||||
|
||||
private GestureRecognizer mGestureRecognizer;
|
||||
|
||||
private Adapter mAdapter;
|
||||
|
||||
private int mFirstShownIndex = 0;
|
||||
private int mLayoutOffset = 0;
|
||||
|
||||
private SparseArray<GalleryPageView> mAttachedPage = new SparseArray<>();
|
||||
private List<GLView> mInvalidAttachedComponent = new ArrayList<>();
|
||||
private IntArray mMissedPageInfo = new IntArray(5 * 3);
|
||||
private List<GalleryPageView> mUsedPage = new ArrayList<>();
|
||||
private SparseArray<GalleryPageView> mPageMap = new SparseArray<>();
|
||||
|
||||
private EdgeView mEdgeView;
|
||||
|
||||
private boolean mScale = false;
|
||||
private boolean mScroll = false;
|
||||
|
||||
private int mDeltaX;
|
||||
private int[] mScrollRemain = new int[2];
|
||||
|
||||
private boolean mHaveChangePage = false;
|
||||
|
||||
private Mode mMode = Mode.NONE;
|
||||
private Mode mLastLayoutMode = Mode.NONE;
|
||||
|
||||
private Scale mScaleMode = Scale.FIT_HEIGHT; // TODO
|
||||
private StartPosition mStartPosition = StartPosition.TOP_RIGHT; // TODO
|
||||
private float mLastScale = 2f;
|
||||
|
||||
private int mProgressSpec = MeasureSpec.makeMeasureSpec(48, MeasureSpec.EXACTLY);
|
||||
public int mInterval = 24;
|
||||
|
||||
private SmoothScroller mSmoothScroller;
|
||||
|
||||
private static final Interpolator sInterpolator = new Interpolator() {
|
||||
|
||||
@Override
|
||||
public float getInterpolation(float t) {
|
||||
t -= 1.0f;
|
||||
return t * t * t * t * t + 1.0f;
|
||||
}
|
||||
};
|
||||
|
||||
public enum Mode {
|
||||
NONE, // Just a progress view
|
||||
LEFT_TO_RIGHT,
|
||||
RIGHT_TO_LEFT,
|
||||
TOP_TO_BOTTOM
|
||||
}
|
||||
|
||||
public enum Scale {
|
||||
ORIGIN (0),
|
||||
FIT_WIDTH (1),
|
||||
FIT_HEIGHT (2),
|
||||
FIT (3),
|
||||
FIXED (4);
|
||||
|
||||
Scale(int ni) {
|
||||
nativeInt = ni;
|
||||
}
|
||||
final int nativeInt;
|
||||
}
|
||||
|
||||
public enum StartPosition {
|
||||
TOP_LEFT,
|
||||
TOP_RIGHT,
|
||||
BOTTOM_LEFT,
|
||||
BOTTOM_RIGHT,
|
||||
CENTER
|
||||
}
|
||||
|
||||
public GalleryView(Context context) {
|
||||
mEdgeView = new EdgeView(context);
|
||||
mGestureRecognizer = new GestureRecognizer(context, this);
|
||||
mSmoothScroller = new SmoothScroller();
|
||||
mSmoothScroller.setInterpolator(sInterpolator);
|
||||
|
||||
setBackgroundColor(GalleryUtils.intColorToFloatARGBArray(context.getResources().getColor(R.color.gallery_background)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachToRoot(GLRoot root) {
|
||||
super.onAttachToRoot(root);
|
||||
|
||||
mEdgeView.onAttachToRoot(root);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachFromRoot() {
|
||||
super.onDetachFromRoot();
|
||||
|
||||
mEdgeView.onDetachFromRoot();
|
||||
}
|
||||
|
||||
public void setMode(Mode mode) {
|
||||
if (mMode != mode) {
|
||||
mMode = mode;
|
||||
superRequestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAdapter(Adapter adapter) {
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
public void setProgressSize(int size) {
|
||||
mProgressSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
|
||||
superRequestLayout();
|
||||
}
|
||||
|
||||
public void setInterval(int interval) {
|
||||
mInterval = interval;
|
||||
superRequestLayout();
|
||||
}
|
||||
|
||||
public void superRequestLayout() {
|
||||
super.requestLayout();
|
||||
}
|
||||
|
||||
public void scrollToPage(int page) {
|
||||
if (page < 0 || page >= mAdapter.getPages()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mFirstShownIndex = page;
|
||||
mLayoutOffset = 0;
|
||||
fill();
|
||||
}
|
||||
|
||||
public void smoothScrollToPage(int page) {
|
||||
|
||||
}
|
||||
|
||||
private void scrollX(int offset) {
|
||||
if (offset == 0 || (mMode != Mode.LEFT_TO_RIGHT && mMode != Mode.RIGHT_TO_LEFT)) {
|
||||
return;
|
||||
}
|
||||
offChildsetLeftAndRight(offset); // TODO Really need it ?
|
||||
|
||||
mLayoutOffset += offset;
|
||||
boolean reverse = mMode == Mode.RIGHT_TO_LEFT;
|
||||
|
||||
// Fix TODO What if offset is very large
|
||||
int width = getWidth();
|
||||
if (!mHaveChangePage) {
|
||||
if (mLayoutOffset > mInterval) {
|
||||
mHaveChangePage = true;
|
||||
mFirstShownIndex += reverse ? 1 : -1;
|
||||
mLayoutOffset = -width + mLayoutOffset - mInterval;
|
||||
} else if (mLayoutOffset < -mInterval) {
|
||||
mHaveChangePage = true;
|
||||
mFirstShownIndex += reverse ? -1 : 1;
|
||||
mLayoutOffset = width + mLayoutOffset + mInterval;
|
||||
}
|
||||
} else {
|
||||
if (mLayoutOffset > width) {
|
||||
mHaveChangePage = false;
|
||||
mFirstShownIndex += reverse ? 1 : -1;
|
||||
mLayoutOffset = mLayoutOffset - width - mInterval;
|
||||
} else if (mLayoutOffset < -width) {
|
||||
mHaveChangePage = false;
|
||||
mFirstShownIndex += reverse ? -1 : 1;
|
||||
mLayoutOffset = mLayoutOffset + width + mInterval;
|
||||
} else if (mLayoutOffset < mInterval && mLayoutOffset > -mInterval) {
|
||||
mHaveChangePage = false;
|
||||
}
|
||||
}
|
||||
|
||||
fill();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean dispatchTouchEvent(MotionEvent event) {
|
||||
// Do not pass event to component, so handle event here
|
||||
mGestureRecognizer.onTouchEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(float x, float y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(float x, float y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to scroll to make mLayoutOffset == 0
|
||||
private int onScrollX1(int dx, int totalX) {
|
||||
int remain;
|
||||
int actualDx;
|
||||
if (mLayoutOffset > 0 && dx > 0) {
|
||||
remain = dx - mLayoutOffset;
|
||||
if (remain > 0) {
|
||||
// Get remain
|
||||
actualDx = dx - remain;
|
||||
} else {
|
||||
// No remain
|
||||
remain = 0;
|
||||
actualDx = dx;
|
||||
}
|
||||
} else if (mLayoutOffset < 0 && dx < 0) {
|
||||
remain = dx - mLayoutOffset;
|
||||
if (remain < 0) {
|
||||
// Get remain
|
||||
actualDx = dx - remain;
|
||||
} else {
|
||||
// No remain
|
||||
remain = 0;
|
||||
actualDx = dx;
|
||||
}
|
||||
} else {
|
||||
remain = 0;
|
||||
actualDx = dx;
|
||||
}
|
||||
|
||||
onScrollX2(actualDx, totalX);
|
||||
|
||||
return remain;
|
||||
}
|
||||
|
||||
private int onScrollX2(int dx, int totalX) {
|
||||
int remain = 0;
|
||||
if (mMode == Mode.LEFT_TO_RIGHT) {
|
||||
if (mFirstShownIndex == 0 && mLayoutOffset - dx > 0) { // Start
|
||||
scrollX(-mLayoutOffset);
|
||||
remain = dx - mLayoutOffset;
|
||||
} else if (mFirstShownIndex == mAdapter.getPages() - 1 && mLayoutOffset - dx < 0) { // end
|
||||
scrollX(-mLayoutOffset);
|
||||
remain = dx - mLayoutOffset;
|
||||
} else {
|
||||
scrollX(-dx);
|
||||
}
|
||||
} else if (mMode == Mode.RIGHT_TO_LEFT) {
|
||||
if (mFirstShownIndex == 0 && mLayoutOffset - dx < 0) { // Start
|
||||
scrollX(-mLayoutOffset);
|
||||
remain = dx - mLayoutOffset;
|
||||
} else if (mFirstShownIndex == mAdapter.getPages() - 1 && mLayoutOffset - dx > 0) { // end
|
||||
scrollX(-mLayoutOffset);
|
||||
remain = dx - mLayoutOffset;
|
||||
} else {
|
||||
scrollX(-dx);
|
||||
}
|
||||
}
|
||||
return remain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(float dx, float dy, float totalX, float totalY) {
|
||||
if (mScale) {
|
||||
return false;
|
||||
}
|
||||
mScroll = true;
|
||||
|
||||
int dxI = (int) dx;
|
||||
int dyI = (int) dy;
|
||||
int totalXI = (int) totalX;
|
||||
if (mMode == Mode.LEFT_TO_RIGHT || mMode == Mode.RIGHT_TO_LEFT) {
|
||||
int remain = dxI;
|
||||
if (remain != 0 && mLayoutOffset != 0) {
|
||||
remain = onScrollX1(dxI, totalXI);
|
||||
}
|
||||
if (remain != 0 && mLayoutOffset == 0) {
|
||||
GalleryPageView page = mPageMap.get(mFirstShownIndex);
|
||||
if (page != null) {
|
||||
page.scroll(remain, dyI, mScrollRemain);
|
||||
remain = mScrollRemain[0];
|
||||
}
|
||||
}
|
||||
if (remain != 0) {
|
||||
remain = onScrollX2(dxI, totalXI);
|
||||
}
|
||||
|
||||
if (remain == 0) {
|
||||
if (mDeltaX != 0) {
|
||||
mDeltaX = 0;
|
||||
mEdgeView.onRelease();
|
||||
}
|
||||
} else if (remain < 0) {
|
||||
if (mDeltaX > 0) {
|
||||
mDeltaX = 0;
|
||||
mEdgeView.onRelease();
|
||||
}
|
||||
mDeltaX += remain;
|
||||
mEdgeView.onPull(-mDeltaX, EdgeView.LEFT);
|
||||
} else {
|
||||
if (mDeltaX < 0) {
|
||||
mDeltaX = 0;
|
||||
mEdgeView.onRelease();
|
||||
}
|
||||
mDeltaX += remain;
|
||||
mEdgeView.onPull(mDeltaX, EdgeView.RIGHT);
|
||||
}
|
||||
|
||||
} else if (mMode == Mode.TOP_TO_BOTTOM) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(float focusX, float focusY) {
|
||||
return onScale(focusX, focusY, 1.0f);
|
||||
}
|
||||
|
||||
private boolean canScale() {
|
||||
if (mMode != Mode.LEFT_TO_RIGHT && mMode != Mode.RIGHT_TO_LEFT) {
|
||||
return false;
|
||||
}
|
||||
if (mLayoutOffset != 0) {
|
||||
return false;
|
||||
}
|
||||
if (mScroll) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(float focusX, float focusY, float scale) {
|
||||
if (!canScale()) {
|
||||
return false;
|
||||
}
|
||||
mScale = true;
|
||||
|
||||
GalleryPageView page = mPageMap.get(mFirstShownIndex);
|
||||
if (page != null) {
|
||||
page.scale(focusX, focusY, scale);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDown(float x, float y) {
|
||||
mScale = false;
|
||||
mScroll = false;
|
||||
mSmoothScroller.forceStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUp() {
|
||||
mDeltaX = 0;
|
||||
mEdgeView.onRelease();
|
||||
|
||||
if ((mMode == Mode.LEFT_TO_RIGHT || mMode == Mode.RIGHT_TO_LEFT) && mLayoutOffset != 0) {
|
||||
// Scroll
|
||||
final float pageDelta = 7 * (float) Math.abs(mLayoutOffset) / (getWidth() + mInterval);
|
||||
int duration = (int) ((pageDelta + 1) * 100);
|
||||
duration = Math.min(duration, MAX_SETTLE_DURATION);
|
||||
mSmoothScroller.startSmoothScroll(-mLayoutOffset, 0, duration);
|
||||
} else if (mMode == Mode.TOP_TO_BOTTOM) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerDown(float x, float y) {
|
||||
if (canScale()) {
|
||||
mScale = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerUp() {
|
||||
|
||||
}
|
||||
|
||||
private void offChildsetLeftAndRight(int offset) {
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
getComponent(i).offsetLeftAndRight(offset);
|
||||
}
|
||||
}
|
||||
|
||||
private void offChildsetTopAndBottom(int offset) {
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
getComponent(i).offsetTopAndBottom(offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
if (mEnableRequestLayout) {
|
||||
super.requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) {
|
||||
mSmoothScroller.forceStop();
|
||||
mEdgeView.layout(left, top, right, bottom);
|
||||
|
||||
/* TODO set all seen UNKNOWN_SEEN to setScaleAndOffset
|
||||
if (mLastLayoutMode != mMode) {
|
||||
mLastLayoutMode = mMode;
|
||||
for () {
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
fill();
|
||||
}
|
||||
|
||||
public GalleryPageView getPage(int index) {
|
||||
return mPageMap.get(index);
|
||||
}
|
||||
|
||||
private void unbindGalleryPage(GalleryPageView view) {
|
||||
LayoutParams lp = (LayoutParams) view.getLayoutParams();
|
||||
int index = lp.index;
|
||||
lp.index = NO_POSITION;
|
||||
if (index != NO_POSITION) {
|
||||
mAdapter.unbindPage(view, index);
|
||||
}
|
||||
}
|
||||
|
||||
private void attachePage(GalleryPageView page, int index) {
|
||||
mPageMap.put(index, page);
|
||||
}
|
||||
|
||||
private void detachPage(int index) {
|
||||
mPageMap.remove(index);
|
||||
}
|
||||
|
||||
private void detachPage(GalleryPageView page) {
|
||||
LayoutParams lp = (LayoutParams) page.getLayoutParams();
|
||||
int index = lp.index;
|
||||
lp.index = NO_POSITION;
|
||||
if (index != NO_POSITION) {
|
||||
mPageMap.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void fillX(int start, int end, int startIndex, boolean reverse) {
|
||||
for (int i = 0, n = getComponentCount(); i < n; i++) {
|
||||
GLView component = getComponent(i);
|
||||
if (component instanceof GalleryPageView) {
|
||||
mAttachedPage.put(((LayoutParams) component.getLayoutParams()).index,
|
||||
(GalleryPageView) component);
|
||||
} else {
|
||||
mInvalidAttachedComponent.add(component);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove invalid view
|
||||
int n = mInvalidAttachedComponent.size();
|
||||
while (n-- > 0) {
|
||||
removeComponentAt(n);
|
||||
}
|
||||
mInvalidAttachedComponent.clear();
|
||||
|
||||
// Fill
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
|
||||
int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
|
||||
int offset = width + mInterval;
|
||||
if (reverse) {
|
||||
offset = -offset;
|
||||
}
|
||||
int pages = mAdapter.getPages();
|
||||
for (int spec = start, i = startIndex; reverse ? spec > end : spec < end && i < pages; i++) {
|
||||
GalleryPageView page = mAttachedPage.get(i);
|
||||
if (page != null) {
|
||||
mAttachedPage.remove(i);
|
||||
page.measure(widthSpec, heightSpec);
|
||||
if (reverse) {
|
||||
page.layout(spec - width, 0, spec, height);
|
||||
} else {
|
||||
page.layout(spec, 0, spec + width, height);
|
||||
}
|
||||
} else {
|
||||
// Add index, left, top, right, bottom
|
||||
mMissedPageInfo.add(i);
|
||||
if (reverse) {
|
||||
mMissedPageInfo.add(spec - width);
|
||||
mMissedPageInfo.add(0);
|
||||
mMissedPageInfo.add(spec);
|
||||
mMissedPageInfo.add(height);
|
||||
} else {
|
||||
mMissedPageInfo.add(spec);
|
||||
mMissedPageInfo.add(0);
|
||||
mMissedPageInfo.add(spec + width);
|
||||
mMissedPageInfo.add(height);
|
||||
}
|
||||
}
|
||||
spec += offset;
|
||||
}
|
||||
|
||||
// Fill missed page
|
||||
int count = mAttachedPage.size();
|
||||
n = mMissedPageInfo.size();
|
||||
for (int i = 0; i < n; i += 5) {
|
||||
int index = mMissedPageInfo.get(i);
|
||||
GalleryPageView page;
|
||||
if (i < count) {
|
||||
page = mAttachedPage.valueAt(i);
|
||||
// Unbind
|
||||
detachPage(page);
|
||||
unbindGalleryPage(page);
|
||||
mUsedPage.add(page);
|
||||
} else {
|
||||
page = mAdapter.createPage();
|
||||
addComponent(page);
|
||||
}
|
||||
// Bind
|
||||
((LayoutParams) page.getLayoutParams()).index = index;
|
||||
((LayoutParams) page.getLayoutParams()).seen = UNKNOWN_SEEN;
|
||||
attachePage(page, index);
|
||||
// Update index in LayoutParams
|
||||
mAdapter.bindPage(page, index);
|
||||
page.measure(widthSpec, heightSpec);
|
||||
page.layout(mMissedPageInfo.get(i + 1), mMissedPageInfo.get(i + 2),
|
||||
mMissedPageInfo.get(i + 3), mMissedPageInfo.get(i + 4));
|
||||
|
||||
}
|
||||
mMissedPageInfo.clear();
|
||||
|
||||
// Clear unuse page
|
||||
for (int i = 0; i < count; i++) {
|
||||
GalleryPageView page = mAttachedPage.valueAt(i);
|
||||
if (mUsedPage.contains(page)) {
|
||||
continue;
|
||||
}
|
||||
detachPage(page);
|
||||
unbindGalleryPage(page);
|
||||
removeComponent(page);
|
||||
}
|
||||
mAttachedPage.clear();
|
||||
mUsedPage.clear();
|
||||
|
||||
// Update seen
|
||||
for (int i = 0; i < getComponentCount(); i++) {
|
||||
GalleryPageView page = (GalleryPageView) getComponent(i);
|
||||
LayoutParams lp = ((LayoutParams) page.getLayoutParams());
|
||||
int oldSeen = lp.seen;
|
||||
int seen = page.bounds().intersects(0, 0, width, height) ? SEEN : UNSEEN;
|
||||
lp.seen = seen;
|
||||
if (oldSeen == UNKNOWN_SEEN || (oldSeen == SEEN && seen == UNSEEN)) {
|
||||
page.setScaleOffset(mScaleMode, mStartPosition, mLastScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillY(int start, int end, int startIndex, boolean reverse) {
|
||||
|
||||
}
|
||||
|
||||
private void fill() {
|
||||
// Disable request layout
|
||||
mEnableRequestLayout = false;
|
||||
|
||||
if (mMode == Mode.NONE) {
|
||||
// Try to get old ProgressView
|
||||
ProgressView progressView = null;
|
||||
if (getComponentCount() == 1) {
|
||||
GLView component = getComponent(0);
|
||||
if (component instanceof ProgressView) {
|
||||
progressView = (ProgressView) component;
|
||||
}
|
||||
}
|
||||
if (progressView == null) {
|
||||
progressView = new ProgressView();
|
||||
}
|
||||
|
||||
// Remove all component and unbind
|
||||
int i = getComponentCount();
|
||||
while (i-- > 0) {
|
||||
GLView component = getComponent(i);
|
||||
removeComponentAt(i);
|
||||
|
||||
// Remove from map, unbind gallery page
|
||||
if (component instanceof GalleryPageView) {
|
||||
detachPage((GalleryPageView) component);
|
||||
unbindGalleryPage((GalleryPageView) component);
|
||||
}
|
||||
}
|
||||
|
||||
addComponent(progressView);
|
||||
|
||||
// Measure and layout progress
|
||||
progressView.measure(mProgressSpec, mProgressSpec);
|
||||
int progressWidth = progressView.getMeasuredWidth();
|
||||
int progressHeight = progressView.getMeasuredHeight();
|
||||
int progressLeft = getWidth() / 2 - progressWidth / 2;
|
||||
int progressTop = getHeight() / 2 - progressHeight / 2;
|
||||
progressView.layout(progressLeft, progressTop, progressLeft + progressWidth, progressTop + progressHeight);
|
||||
|
||||
// Bind progress view, just setIndeterminate(true);
|
||||
progressView.setIndeterminate(true);
|
||||
} else if (mMode == Mode.LEFT_TO_RIGHT || mMode == Mode.RIGHT_TO_LEFT) {
|
||||
int width = getWidth();
|
||||
int start;
|
||||
int end;
|
||||
int startIndex;
|
||||
boolean reverse = mMode == Mode.RIGHT_TO_LEFT;
|
||||
|
||||
if (reverse) {
|
||||
if (mFirstShownIndex == 0) {
|
||||
start = width;
|
||||
startIndex = 0;
|
||||
} else {
|
||||
startIndex = mFirstShownIndex - 1;
|
||||
start = width * 2 + mInterval;
|
||||
}
|
||||
if (mFirstShownIndex == mAdapter.getPages() - 1) {
|
||||
end = 0;
|
||||
} else {
|
||||
end = -width - mInterval * 2 - 1;
|
||||
}
|
||||
} else {
|
||||
if (mFirstShownIndex == 0) {
|
||||
start = 0;
|
||||
startIndex = 0;
|
||||
} else {
|
||||
startIndex = mFirstShownIndex - 1;
|
||||
start = -width - mInterval;
|
||||
}
|
||||
if (mFirstShownIndex == mAdapter.getPages() - 1) {
|
||||
end = width;
|
||||
} else {
|
||||
end = width * 2 + mInterval * 2 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Set offset
|
||||
start += mLayoutOffset;
|
||||
|
||||
fillX(start, end, startIndex, reverse);
|
||||
}
|
||||
|
||||
mEnableRequestLayout = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void render(GLCanvas canvas) {
|
||||
if (mSmoothScroller.calculate(AnimationTime.get())) {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
super.render(canvas);
|
||||
mEdgeView.render(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkLayoutParams(GLView.LayoutParams p) {
|
||||
return p instanceof LayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GLView.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GLView.LayoutParams generateLayoutParams(GLView.LayoutParams p) {
|
||||
return p == null ? generateDefaultLayoutParams() : new LayoutParams(p);
|
||||
}
|
||||
|
||||
public static class LayoutParams extends GLView.LayoutParams {
|
||||
|
||||
private int index = NO_POSITION;
|
||||
private int seen = UNKNOWN_SEEN;
|
||||
|
||||
public LayoutParams(GLView.LayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public LayoutParams(int width, int height) {
|
||||
super(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Adapter {
|
||||
|
||||
public abstract int getPages();
|
||||
|
||||
public abstract GalleryPageView createPage();
|
||||
|
||||
public abstract void bindPage(GalleryPageView view, int index);
|
||||
|
||||
public abstract void unbindPage(GalleryPageView view, int index);
|
||||
}
|
||||
|
||||
public class SmoothScroller extends Animation {
|
||||
|
||||
private int mDx;
|
||||
private int mDy;
|
||||
private int mLastX;
|
||||
private int mLastY;
|
||||
|
||||
public void startSmoothScroll(int dx, int dy, int duration) {
|
||||
mDx = dx;
|
||||
mDy = dy;
|
||||
mLastX = 0;
|
||||
mLastY = 0;
|
||||
setDuration(duration);
|
||||
start();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
int x = (int) (mDx * progress);
|
||||
int y = (int) (mDy * progress);
|
||||
scrollX(x - mLastX);
|
||||
mLastX = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
import android.support.v4.view.GestureDetectorCompat;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
|
||||
// This class aggregates three gesture detectors: GestureDetector,
|
||||
// ScaleGestureDetector, and DownUpDetector.
|
||||
public class GestureRecognizer {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "GestureRecognizer";
|
||||
|
||||
public interface Listener {
|
||||
boolean onSingleTapUp(float x, float y);
|
||||
boolean onDoubleTap(float x, float y);
|
||||
boolean onScroll(float dx, float dy, float totalX, float totalY);
|
||||
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
|
||||
boolean onScaleBegin(float focusX, float focusY);
|
||||
boolean onScale(float focusX, float focusY, float scale);
|
||||
void onScaleEnd();
|
||||
void onDown(float x, float y);
|
||||
void onUp();
|
||||
void onPointerDown(float x, float y);
|
||||
void onPointerUp();
|
||||
}
|
||||
|
||||
private final GestureDetectorCompat mGestureDetector;
|
||||
private final ScaleGestureDetector mScaleDetector;
|
||||
private final DownUpDetector mDownUpDetector;
|
||||
private final Listener mListener;
|
||||
|
||||
public GestureRecognizer(Context context, Listener listener) {
|
||||
mListener = listener;
|
||||
mGestureDetector = new GestureDetectorCompat(context, new MyGestureListener(),
|
||||
null /* ignoreMultitouch */);
|
||||
mScaleDetector = new ScaleGestureDetector(
|
||||
context, new MyScaleListener());
|
||||
mDownUpDetector = new DownUpDetector(new MyDownUpListener());
|
||||
}
|
||||
|
||||
public void onTouchEvent(MotionEvent event) {
|
||||
mScaleDetector.onTouchEvent(event);
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
mDownUpDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public boolean isDown() {
|
||||
return mDownUpDetector.isDown();
|
||||
}
|
||||
|
||||
public void cancelScale() {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(
|
||||
now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
|
||||
mScaleDetector.onTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
|
||||
private class MyGestureListener
|
||||
extends GestureDetector.SimpleOnGestureListener {
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
return mListener.onSingleTapUp(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
return mListener.onDoubleTap(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(
|
||||
MotionEvent e1, MotionEvent e2, float dx, float dy) {
|
||||
return mListener.onScroll(
|
||||
dx, dy, e2.getX() - e1.getX(), e2.getY() - e1.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||
float velocityY) {
|
||||
return mListener.onFling(e1, e2, velocityX, velocityY);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyScaleListener
|
||||
extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
return mListener.onScaleBegin(
|
||||
detector.getFocusX(), detector.getFocusY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
return mListener.onScale(detector.getFocusX(),
|
||||
detector.getFocusY(), detector.getScaleFactor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
mListener.onScaleEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private class MyDownUpListener implements DownUpDetector.DownUpListener {
|
||||
@Override
|
||||
public void onDown(MotionEvent e) {
|
||||
mListener.onDown(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUp(MotionEvent e) {
|
||||
mListener.onUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerDown(MotionEvent e) {
|
||||
mListener.onPointerDown(e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPointerUp(MotionEvent e) {
|
||||
mListener.onPointerUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
package com.hippo.ehviewer.gallery.ui;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.TiledTexture;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
|
||||
public class ScaledImageView extends GLView {
|
||||
|
||||
// TODO adjust scale max and min accroding to image size and screen size
|
||||
private static final float SCALE_MIN = 1 / 10.0f;
|
||||
private static final float SCALE_MAX = 10.0f;
|
||||
|
||||
private TiledTexture mTiledTexture;
|
||||
|
||||
// Corresponding source is [0, 0, mTiledTexture.getWidth(), mTiledTexture.getHeight()]
|
||||
private RectF mTarget = new RectF();
|
||||
private RectF mSeen = new RectF();
|
||||
private RectF mSourceAfterSeen = new RectF();
|
||||
private RectF mTargetAfterSeen = new RectF();
|
||||
private boolean mScaleOffsetDirty = true;
|
||||
private boolean mSeenDirty = true;
|
||||
|
||||
private GalleryView.Scale mScaleMode;
|
||||
private GalleryView.StartPosition mStartPosition;
|
||||
private float mLastScale;
|
||||
private float mScale;
|
||||
|
||||
public void setTiledTexture(TiledTexture tiledTexture) {
|
||||
mTiledTexture = tiledTexture;
|
||||
setScaleOffset(mScaleMode, mStartPosition, mLastScale);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public TiledTexture getTiledTexture() {
|
||||
return mTiledTexture;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumWidth() {
|
||||
return Math.max(super.getSuggestedMinimumWidth(),
|
||||
mTiledTexture == null ? 0 : mTiledTexture.getWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSuggestedMinimumHeight() {
|
||||
return Math.max(super.getSuggestedMinimumHeight(),
|
||||
mTiledTexture == null ? 0 : mTiledTexture.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* If target is shorter then screen, make it in screen center. If target is
|
||||
* longer then parent, make sure target fill parent over
|
||||
*/
|
||||
private void adjustPosition() {
|
||||
RectF target = mTarget;
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
float targetWidth = target.width();
|
||||
float targetHeight = target.height();
|
||||
|
||||
if (targetWidth > screenWidth) {
|
||||
float fixXOffset = target.left;
|
||||
if (fixXOffset > 0) {
|
||||
target.left -= fixXOffset;
|
||||
target.right -= fixXOffset;
|
||||
} else if ((fixXOffset = screenWidth - target.right) > 0) {
|
||||
target.left += fixXOffset;
|
||||
target.right += fixXOffset;
|
||||
}
|
||||
} else {
|
||||
float left = (screenWidth - targetWidth) / 2;
|
||||
target.offsetTo(left, target.top);
|
||||
}
|
||||
if (targetHeight > screenHeight) {
|
||||
float fixYOffset = target.top;
|
||||
if (fixYOffset > 0) {
|
||||
target.top -= fixYOffset;
|
||||
target.bottom -= fixYOffset;
|
||||
} else if ((fixYOffset = screenHeight - target.bottom) > 0) {
|
||||
target.top += fixYOffset;
|
||||
target.bottom += fixYOffset;
|
||||
}
|
||||
} else {
|
||||
float top = (screenHeight - targetHeight) / 2;
|
||||
target.offsetTo(target.left, top);
|
||||
}
|
||||
}
|
||||
|
||||
public void setScaleOffset(GalleryView.Scale scale,
|
||||
GalleryView.StartPosition startPosition, float lastScale) {
|
||||
mScaleMode = scale;
|
||||
mStartPosition = startPosition;
|
||||
mLastScale = lastScale;
|
||||
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
|
||||
if (mTiledTexture == null || screenWidth == 0 || screenHeight == 0) {
|
||||
mScaleOffsetDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int imageWidth = mTiledTexture.getWidth();
|
||||
int imageHeight = mTiledTexture.getHeight();
|
||||
|
||||
// Set scale
|
||||
float targetWidth;
|
||||
float targetHeight;
|
||||
switch (scale) {
|
||||
case ORIGIN:
|
||||
mScale = 1.0f;
|
||||
targetWidth = imageWidth;
|
||||
targetHeight = imageHeight;
|
||||
break;
|
||||
case FIT_WIDTH:
|
||||
mScale = (float) screenWidth / imageWidth;
|
||||
targetWidth = screenWidth;
|
||||
targetHeight = imageHeight * mScale;
|
||||
break;
|
||||
case FIT_HEIGHT:
|
||||
mScale = (float) screenHeight / imageHeight;
|
||||
targetWidth = imageWidth * mScale;
|
||||
targetHeight = screenHeight;
|
||||
break;
|
||||
case FIT:
|
||||
float scaleX = (float) screenWidth / imageWidth;
|
||||
float scaleY = (float) screenHeight / imageHeight;
|
||||
if (scaleX < scaleY) {
|
||||
mScale = scaleX;
|
||||
targetWidth = screenWidth;
|
||||
targetHeight = imageHeight * scaleX;
|
||||
} else {
|
||||
mScale = scaleY;
|
||||
targetWidth = imageWidth * scaleY;
|
||||
targetHeight = screenHeight;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FIXED:
|
||||
default:
|
||||
mScale = lastScale;
|
||||
targetWidth = imageWidth * lastScale;
|
||||
targetHeight = imageHeight * lastScale;
|
||||
break;
|
||||
}
|
||||
|
||||
// adjust scale, not too big, not too small
|
||||
if (mScale < SCALE_MIN) {
|
||||
mScale = SCALE_MIN;
|
||||
targetWidth = imageWidth * SCALE_MIN;
|
||||
targetHeight = imageHeight * SCALE_MIN;
|
||||
} else if (mScale > SCALE_MAX) {
|
||||
mScale = SCALE_MAX;
|
||||
targetWidth = imageWidth * SCALE_MAX;
|
||||
targetHeight = imageHeight * SCALE_MAX;
|
||||
}
|
||||
|
||||
RectF target = mTarget;
|
||||
// set start position
|
||||
switch (startPosition) {
|
||||
case TOP_LEFT:
|
||||
target.left = 0;
|
||||
target.top = 0;
|
||||
break;
|
||||
case TOP_RIGHT:
|
||||
target.left = screenWidth - targetWidth;
|
||||
target.top = 0;
|
||||
break;
|
||||
case BOTTOM_LEFT:
|
||||
target.left = 0;
|
||||
target.top = screenHeight - targetHeight;
|
||||
break;
|
||||
case BOTTOM_RIGHT:
|
||||
target.left = screenWidth - targetWidth;
|
||||
target.top = screenHeight - targetHeight;
|
||||
break;
|
||||
case CENTER:
|
||||
default:
|
||||
target.left = (screenWidth - targetWidth) / 2;
|
||||
target.top = (screenHeight - targetHeight) / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
target.right = target.left + targetWidth;
|
||||
target.bottom = target.top + targetHeight;
|
||||
|
||||
// adjust position
|
||||
adjustPosition();
|
||||
|
||||
mScaleOffsetDirty = false;
|
||||
mSeenDirty = true;
|
||||
}
|
||||
|
||||
public void scroll(int dx, int dy, int[] remain) {
|
||||
if (mTiledTexture == null) {
|
||||
remain[0] = dx;
|
||||
remain[1] = dy;
|
||||
return;
|
||||
}
|
||||
|
||||
RectF target = mTarget;
|
||||
int screenWidth = getWidth();
|
||||
int screenHeight = getHeight();
|
||||
float targetWidth = target.width();
|
||||
float targetHeight = target.height();
|
||||
|
||||
if (targetWidth > screenWidth) {
|
||||
target.offset(-dx, 0);
|
||||
|
||||
float fixXOffset = target.left;
|
||||
if (fixXOffset > 0) {
|
||||
target.left -= fixXOffset;
|
||||
target.right -= fixXOffset;
|
||||
remain[0] = -(int) fixXOffset;
|
||||
} else if ((fixXOffset = screenWidth - target.right) > 0) {
|
||||
target.left += fixXOffset;
|
||||
target.right += fixXOffset;
|
||||
remain[0] = (int) fixXOffset;
|
||||
} else {
|
||||
remain[0] = 0;
|
||||
}
|
||||
} else {
|
||||
remain[0] = dx;
|
||||
}
|
||||
if (targetHeight > screenHeight) {
|
||||
target.offset(0, -dy);
|
||||
|
||||
float fixYOffset = target.top;
|
||||
if (fixYOffset > 0) {
|
||||
target.top -= fixYOffset;
|
||||
target.bottom -= fixYOffset;
|
||||
remain[1] = -(int) fixYOffset;
|
||||
} else if ((fixYOffset = screenHeight - target.bottom) > 0) {
|
||||
target.top += fixYOffset;
|
||||
target.bottom += fixYOffset;
|
||||
remain[1] = (int) fixYOffset;
|
||||
} else {
|
||||
remain[1] = 0;
|
||||
}
|
||||
} else {
|
||||
remain[1] = dy;
|
||||
}
|
||||
|
||||
if (dx != remain[0] || dy != remain[1]) {
|
||||
mSeenDirty = true;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void scale(float focusX, float focusY, float scale) {
|
||||
if (mTiledTexture == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((mScale == SCALE_MAX && scale > 1f) || (mScale == SCALE_MIN && scale < 1f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float newScale = mScale * scale;
|
||||
newScale = MathUtils.clamp(newScale, SCALE_MIN, SCALE_MAX);
|
||||
mScale = newScale;
|
||||
RectF target = mTarget;
|
||||
float left = (focusX - ((focusX - target.left) * scale));
|
||||
float top = (focusY - ((focusY - target.top) * scale));
|
||||
target.set(left, top,
|
||||
(left + (mTiledTexture.getWidth() * newScale)),
|
||||
(top + (mTiledTexture.getHeight() * newScale)));
|
||||
|
||||
// adjust position
|
||||
adjustPosition();
|
||||
|
||||
mSeenDirty = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setSeen(Rect seen) {
|
||||
mSeen.set(seen);
|
||||
mSeenDirty = true;
|
||||
}
|
||||
|
||||
public void applySeen() {
|
||||
int width = mTiledTexture.getWidth();
|
||||
int height = mTiledTexture.getHeight();
|
||||
RectF target = mTarget;
|
||||
RectF targetAfterSeen = mTargetAfterSeen;
|
||||
RectF sourceAfterSeen = mSourceAfterSeen;
|
||||
|
||||
targetAfterSeen.set(target);
|
||||
if (targetAfterSeen.intersect(mSeen)) {
|
||||
sourceAfterSeen.left = MathUtils.lerp(0, width,
|
||||
MathUtils.delerp(target.left, target.right, targetAfterSeen.left));
|
||||
sourceAfterSeen.right = MathUtils.lerp(0, width,
|
||||
MathUtils.delerp(target.left, target.right, targetAfterSeen.right));
|
||||
sourceAfterSeen.top = MathUtils.lerp(0, height,
|
||||
MathUtils.delerp(target.top, target.bottom, targetAfterSeen.top));
|
||||
sourceAfterSeen.bottom = MathUtils.lerp(0, height,
|
||||
MathUtils.delerp(target.top, target.bottom, targetAfterSeen.bottom));
|
||||
} else {
|
||||
// Can't be seen
|
||||
sourceAfterSeen.set(0f, 0f, 0f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRender(GLCanvas canvas) {
|
||||
if (mTiledTexture != null) {
|
||||
|
||||
if (mScaleOffsetDirty) {
|
||||
setScaleOffset(mScaleMode, mStartPosition, mLastScale);
|
||||
}
|
||||
if (mSeenDirty) {
|
||||
applySeen();
|
||||
mSeenDirty = false;
|
||||
}
|
||||
// Draw
|
||||
if (!mSourceAfterSeen.isEmpty()) {
|
||||
mTiledTexture.draw(canvas, mSourceAfterSeen, mTargetAfterSeen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,8 @@ public class TextView extends GLView {
|
||||
textHight = (int) mTextTexture.getTextHeight(mText);
|
||||
}
|
||||
|
||||
setMeasuredSize(getDefaultSize(Math.max(getMinimumWidth(), textWidth), widthSpec),
|
||||
getDefaultSize(Math.max(getMinimumHeight(), textHight), heightSpec));
|
||||
setMeasuredSize(getDefaultSize(Math.max(getSuggestedMinimumWidth(), textWidth), widthSpec),
|
||||
getDefaultSize(Math.max(getSuggestedMinimumHeight(), textHight), heightSpec));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery.util;
|
||||
|
||||
public class IntArray {
|
||||
private static final int INIT_CAPACITY = 8;
|
||||
|
||||
private int mData[] = new int[INIT_CAPACITY];
|
||||
private int mSize = 0;
|
||||
|
||||
public void add(int value) {
|
||||
if (mData.length == mSize) {
|
||||
int temp[] = new int[mSize + mSize];
|
||||
System.arraycopy(mData, 0, temp, 0, mSize);
|
||||
mData = temp;
|
||||
}
|
||||
mData[mSize++] = value;
|
||||
}
|
||||
|
||||
public int removeLast() {
|
||||
mSize--;
|
||||
return mData[mSize];
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
// For testing only
|
||||
public int[] toArray(int[] result) {
|
||||
if (result == null || result.length < mSize) {
|
||||
result = new int[mSize];
|
||||
}
|
||||
System.arraycopy(mData, 0, result, 0, mSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] getInternalArray() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mSize = 0;
|
||||
if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY];
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery.util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
// The Profile class is used to collect profiling information for a thread. It
|
||||
// samples stack traces for a thread periodically. enable() and disable() is
|
||||
// used to enable and disable profiling for the calling thread. The profiling
|
||||
// information can then be dumped to a file using the dumpToFile() method.
|
||||
//
|
||||
// The disableAll() method can be used to disable profiling for all threads and
|
||||
// can be called in onPause() to ensure all profiling is disabled when an
|
||||
// activity is paused.
|
||||
public class Profile {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "Profile";
|
||||
private static final int NS_PER_MS = 1000000;
|
||||
|
||||
// This is a watchdog entry for one thread.
|
||||
// For every cycleTime period, we dump the stack of the thread.
|
||||
private static class WatchEntry {
|
||||
Thread thread;
|
||||
|
||||
// Both are in milliseconds
|
||||
int cycleTime;
|
||||
int wakeTime;
|
||||
|
||||
boolean isHolding;
|
||||
ArrayList<String[]> holdingStacks = new ArrayList<>();
|
||||
}
|
||||
|
||||
// This is a watchdog thread which dumps stacks of other threads periodically.
|
||||
private static Watchdog sWatchdog = new Watchdog();
|
||||
|
||||
private static class Watchdog {
|
||||
private ArrayList<WatchEntry> mList = new ArrayList<>();
|
||||
private HandlerThread mHandlerThread;
|
||||
private Handler mHandler;
|
||||
private Runnable mProcessRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (Watchdog.this) {
|
||||
processList();
|
||||
}
|
||||
}
|
||||
};
|
||||
private Random mRandom = new Random();
|
||||
private ProfileData mProfileData = new ProfileData();
|
||||
|
||||
public Watchdog() {
|
||||
mHandlerThread = new HandlerThread("Watchdog Handler",
|
||||
Process.THREAD_PRIORITY_FOREGROUND);
|
||||
mHandlerThread.start();
|
||||
mHandler = new Handler(mHandlerThread.getLooper());
|
||||
}
|
||||
|
||||
public synchronized void addWatchEntry(Thread thread, int cycleTime) {
|
||||
WatchEntry e = new WatchEntry();
|
||||
e.thread = thread;
|
||||
e.cycleTime = cycleTime;
|
||||
int firstDelay = 1 + mRandom.nextInt(cycleTime);
|
||||
e.wakeTime = (int) (System.nanoTime() / NS_PER_MS) + firstDelay;
|
||||
mList.add(e);
|
||||
processList();
|
||||
}
|
||||
|
||||
public synchronized void removeWatchEntry(Thread thread) {
|
||||
for (int i = 0; i < mList.size(); i++) {
|
||||
if (mList.get(i).thread == thread) {
|
||||
mList.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
processList();
|
||||
}
|
||||
|
||||
public synchronized void removeAllWatchEntries() {
|
||||
mList.clear();
|
||||
processList();
|
||||
}
|
||||
|
||||
private void processList() {
|
||||
mHandler.removeCallbacks(mProcessRunnable);
|
||||
if (mList.size() == 0) return;
|
||||
|
||||
int currentTime = (int) (System.nanoTime() / NS_PER_MS);
|
||||
int nextWakeTime = 0;
|
||||
|
||||
for (WatchEntry entry : mList) {
|
||||
if (currentTime > entry.wakeTime) {
|
||||
entry.wakeTime += entry.cycleTime;
|
||||
sampleStack(entry);
|
||||
}
|
||||
|
||||
if (entry.wakeTime > nextWakeTime) {
|
||||
nextWakeTime = entry.wakeTime;
|
||||
}
|
||||
}
|
||||
|
||||
long delay = nextWakeTime - currentTime;
|
||||
mHandler.postDelayed(mProcessRunnable, delay);
|
||||
}
|
||||
|
||||
private void sampleStack(WatchEntry entry) {
|
||||
Thread thread = entry.thread;
|
||||
StackTraceElement[] stack = thread.getStackTrace();
|
||||
String[] lines = new String[stack.length];
|
||||
for (int i = 0; i < stack.length; i++) {
|
||||
lines[i] = stack[i].toString();
|
||||
}
|
||||
if (entry.isHolding) {
|
||||
entry.holdingStacks.add(lines);
|
||||
} else {
|
||||
mProfileData.addSample(lines);
|
||||
}
|
||||
}
|
||||
|
||||
private WatchEntry findEntry(Thread thread) {
|
||||
for (int i = 0; i < mList.size(); i++) {
|
||||
WatchEntry entry = mList.get(i);
|
||||
if (entry.thread == thread) return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized void dumpToFile(String filename) {
|
||||
mProfileData.dumpToFile(filename);
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
mProfileData.reset();
|
||||
}
|
||||
|
||||
public synchronized void hold(Thread t) {
|
||||
WatchEntry entry = findEntry(t);
|
||||
|
||||
// This can happen if the profiling is disabled (probably from
|
||||
// another thread). Same check is applied in commit() and drop()
|
||||
// below.
|
||||
if (entry == null) return;
|
||||
|
||||
entry.isHolding = true;
|
||||
}
|
||||
|
||||
public synchronized void commit(Thread t) {
|
||||
WatchEntry entry = findEntry(t);
|
||||
if (entry == null) return;
|
||||
ArrayList<String[]> stacks = entry.holdingStacks;
|
||||
for (int i = 0; i < stacks.size(); i++) {
|
||||
mProfileData.addSample(stacks.get(i));
|
||||
}
|
||||
entry.isHolding = false;
|
||||
entry.holdingStacks.clear();
|
||||
}
|
||||
|
||||
public synchronized void drop(Thread t) {
|
||||
WatchEntry entry = findEntry(t);
|
||||
if (entry == null) return;
|
||||
entry.isHolding = false;
|
||||
entry.holdingStacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Enable profiling for the calling thread. Periodically (every cycleTimeInMs
|
||||
// milliseconds) sample the stack trace of the calling thread.
|
||||
public static void enable(int cycleTimeInMs) {
|
||||
Thread t = Thread.currentThread();
|
||||
sWatchdog.addWatchEntry(t, cycleTimeInMs);
|
||||
}
|
||||
|
||||
// Disable profiling for the calling thread.
|
||||
public static void disable() {
|
||||
sWatchdog.removeWatchEntry(Thread.currentThread());
|
||||
}
|
||||
|
||||
// Disable profiling for all threads.
|
||||
public static void disableAll() {
|
||||
sWatchdog.removeAllWatchEntries();
|
||||
}
|
||||
|
||||
// Dump the profiling data to a file.
|
||||
public static void dumpToFile(String filename) {
|
||||
sWatchdog.dumpToFile(filename);
|
||||
}
|
||||
|
||||
// Reset the collected profiling data.
|
||||
public static void reset() {
|
||||
sWatchdog.reset();
|
||||
}
|
||||
|
||||
// Hold the future samples coming from current thread until commit() or
|
||||
// drop() is called, and those samples are recorded or ignored as a result.
|
||||
// This must called after enable() to be effective.
|
||||
public static void hold() {
|
||||
sWatchdog.hold(Thread.currentThread());
|
||||
}
|
||||
|
||||
public static void commit() {
|
||||
sWatchdog.commit(Thread.currentThread());
|
||||
}
|
||||
|
||||
public static void drop() {
|
||||
sWatchdog.drop(Thread.currentThread());
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.gallery.util;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.yorozuya.IOUtils;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
// ProfileData keeps profiling samples in a tree structure.
|
||||
// The addSample() method adds a sample. The dumpToFile() method saves the data
|
||||
// to a file. The reset() method clears all samples.
|
||||
public class ProfileData {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "ProfileData";
|
||||
|
||||
private static class Node {
|
||||
public int id; // this is the name of this node, mapped from mNameToId
|
||||
public Node parent;
|
||||
public int sampleCount;
|
||||
public ArrayList<Node> children;
|
||||
public Node(Node parent, int id) {
|
||||
this.parent = parent;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
private Node mRoot;
|
||||
private int mNextId;
|
||||
private HashMap<String, Integer> mNameToId;
|
||||
private DataOutputStream mOut;
|
||||
private byte mScratch[] = new byte[4]; // scratch space for writeInt()
|
||||
|
||||
public ProfileData() {
|
||||
mRoot = new Node(null, -1); // The id of the root node is unused.
|
||||
mNameToId = new HashMap<>();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mRoot = new Node(null, -1);
|
||||
mNameToId.clear();
|
||||
mNextId = 0;
|
||||
}
|
||||
|
||||
private int nameToId(String name) {
|
||||
Integer id = mNameToId.get(name);
|
||||
if (id == null) {
|
||||
id = ++mNextId; // The tool doesn't want id=0, so we start from 1.
|
||||
mNameToId.put(name, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public void addSample(String[] stack) {
|
||||
int[] ids = new int[stack.length];
|
||||
for (int i = 0; i < stack.length; i++) {
|
||||
ids[i] = nameToId(stack[i]);
|
||||
}
|
||||
|
||||
Node node = mRoot;
|
||||
for (int i = stack.length - 1; i >= 0; i--) {
|
||||
if (node.children == null) {
|
||||
node.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
int id = ids[i];
|
||||
ArrayList<Node> children = node.children;
|
||||
int j;
|
||||
for (j = 0; j < children.size(); j++) {
|
||||
if (children.get(j).id == id) break;
|
||||
}
|
||||
if (j == children.size()) {
|
||||
children.add(new Node(node, id));
|
||||
}
|
||||
|
||||
node = children.get(j);
|
||||
}
|
||||
|
||||
node.sampleCount++;
|
||||
}
|
||||
|
||||
public void dumpToFile(String filename) {
|
||||
try {
|
||||
mOut = new DataOutputStream(new FileOutputStream(filename));
|
||||
// Start record
|
||||
writeInt(0);
|
||||
writeInt(3);
|
||||
writeInt(1);
|
||||
writeInt(20000); // Sampling period: 20ms
|
||||
writeInt(0);
|
||||
|
||||
// Samples
|
||||
writeAllStacks(mRoot, 0);
|
||||
|
||||
// End record
|
||||
writeInt(0);
|
||||
writeInt(1);
|
||||
writeInt(0);
|
||||
writeAllSymbols();
|
||||
} catch (IOException ex) {
|
||||
Log.w("Failed to dump to file", ex);
|
||||
} finally {
|
||||
IOUtils.closeQuietly(mOut);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes out one stack, consisting of N+2 words:
|
||||
// first word: sample count
|
||||
// second word: depth of the stack (N)
|
||||
// N words: each word is the id of one address in the stack
|
||||
private void writeOneStack(Node node, int depth) throws IOException {
|
||||
writeInt(node.sampleCount);
|
||||
writeInt(depth);
|
||||
while (depth-- > 0) {
|
||||
writeInt(node.id);
|
||||
node = node.parent;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAllStacks(Node node, int depth) throws IOException {
|
||||
if (node.sampleCount > 0) {
|
||||
writeOneStack(node, depth);
|
||||
}
|
||||
|
||||
ArrayList<Node> children = node.children;
|
||||
if (children != null) {
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
writeAllStacks(children.get(i), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Writes out the symbol table. Each line is like:
|
||||
// 0x17e java.util.ArrayList.isEmpty(ArrayList.java:319)
|
||||
private void writeAllSymbols() throws IOException {
|
||||
for (Entry<String, Integer> entry : mNameToId.entrySet()) {
|
||||
mOut.writeBytes(String.format("0x%x %s\n", entry.getValue(), entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeInt(int v) throws IOException {
|
||||
mScratch[0] = (byte) v;
|
||||
mScratch[1] = (byte) (v >> 8);
|
||||
mScratch[2] = (byte) (v >> 16);
|
||||
mScratch[3] = (byte) (v >> 24);
|
||||
mOut.write(mScratch);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,308 @@
|
||||
|
||||
package com.hippo.ehviewer.ui;
|
||||
|
||||
import com.hippo.app.StatsActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
public class GalleryActivity extends StatsActivity {
|
||||
import com.hippo.app.StatsActivity;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.data.GalleryBase;
|
||||
import com.hippo.ehviewer.gallery.GalleryProvider;
|
||||
import com.hippo.ehviewer.gallery.GalleryProviderListener;
|
||||
import com.hippo.ehviewer.gallery.GallerySpider;
|
||||
import com.hippo.ehviewer.gallery.ImageHandler;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.TextTexture;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.TiledTexture;
|
||||
import com.hippo.ehviewer.gallery.ui.GLRootView;
|
||||
import com.hippo.ehviewer.gallery.ui.GLView;
|
||||
import com.hippo.ehviewer.gallery.ui.GalleryPageView;
|
||||
import com.hippo.ehviewer.gallery.ui.GalleryView;
|
||||
import com.hippo.util.SystemUiHelper;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GalleryActivity extends StatsActivity implements TiledTexture.OnFreeBitmapListener, GalleryProviderListener {
|
||||
|
||||
public static final String KEY_GALLERY_BASE = "gallery_base";
|
||||
|
||||
private SystemUiHelper mSystemUiHelper;
|
||||
|
||||
private Resources mResources;
|
||||
|
||||
private TiledTexture.Uploader mUploader;
|
||||
|
||||
private GalleryView mGalleryView;
|
||||
|
||||
private GalleryAdapter mAdapter;
|
||||
|
||||
private TextTexture mTextTexture;
|
||||
|
||||
private GallerySpider mGallerySpider;
|
||||
|
||||
private GalleryBase mGalleryBase;
|
||||
|
||||
private boolean mDieYoung;
|
||||
|
||||
private GalleryView.Mode mMode;
|
||||
|
||||
private int mSize;
|
||||
|
||||
private boolean handleIntent(Intent intent) {
|
||||
if (intent != null) {
|
||||
mGalleryBase = intent.getParcelableExtra(KEY_GALLERY_BASE);
|
||||
return mGalleryBase != null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (!handleIntent(getIntent())) {
|
||||
mDieYoung = true;
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mGallerySpider = GallerySpider.obtain(mGalleryBase, ImageHandler.Mode.DOWNLOAD);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
mDieYoung = true;
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
mGallerySpider.addGalleryProviderListener(this);
|
||||
|
||||
mSystemUiHelper = new SystemUiHelper(this, SystemUiHelper.LEVEL_IMMERSIVE,
|
||||
SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES | SystemUiHelper.FLAG_IMMERSIVE_STICKY);
|
||||
mSystemUiHelper.hide();
|
||||
|
||||
setContentView(R.layout.activity_gallery);
|
||||
GLRootView glRootView = (GLRootView) findViewById(R.id.gl_root_view);
|
||||
|
||||
mResources = getResources();
|
||||
// Prepare
|
||||
TiledTexture.prepareResources();
|
||||
TiledTexture.setOnFreeBitmapListener(this);
|
||||
mUploader = new TiledTexture.Uploader(glRootView);
|
||||
Typeface tf = Typeface.createFromAsset(mResources.getAssets(), "fonts/number.ttf");
|
||||
mTextTexture = TextTexture.create(tf,
|
||||
mResources.getDimensionPixelSize(R.dimen.gallery_index_text),
|
||||
mResources.getColor(R.color.secondary_text_dark),
|
||||
new char[]{'.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'});
|
||||
mUploader.addTexture(mTextTexture);
|
||||
GalleryPageView.setTextTexture(mTextTexture);
|
||||
|
||||
mGalleryView = new GalleryView(this);
|
||||
mAdapter = new GalleryAdapter();
|
||||
mGalleryView.setAdapter(mAdapter);
|
||||
mGalleryView.setProgressSize(LayoutUtils.dp2pix(this, 56));
|
||||
mGalleryView.setInterval(LayoutUtils.dp2pix(this, 48));
|
||||
|
||||
mSize = mGallerySpider.size();
|
||||
if (mSize <= 0) {
|
||||
setMode(GalleryView.Mode.NONE);
|
||||
} else {
|
||||
setMode(GalleryView.Mode.LEFT_TO_RIGHT);
|
||||
}
|
||||
|
||||
glRootView.setContentPane(mGalleryView);
|
||||
}
|
||||
|
||||
private void setMode(GalleryView.Mode mode) {
|
||||
mMode = mode;
|
||||
mGalleryView.setMode(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
if (!mDieYoung) {
|
||||
GalleryPageView.setTextTexture(null);
|
||||
mTextTexture.recycle();
|
||||
TiledTexture.setOnFreeBitmapListener(null);
|
||||
TiledTexture.freeResources();
|
||||
mGallerySpider.removeGalleryProviderListener(this);
|
||||
GallerySpider.release(mGallerySpider);
|
||||
mGallerySpider = null;
|
||||
}
|
||||
// TODO free all TileTexture in GalleryView
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
mSystemUiHelper.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFreeBitmapListener(Bitmap bitmap) {
|
||||
if (mGallerySpider != null) {
|
||||
mGallerySpider.releaseBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTotallyFailed(Exception e) {
|
||||
//e.printStackTrace(); // TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartlyFailed(Exception e) {
|
||||
//e.printStackTrace(); // TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetSize(int size) {
|
||||
if (mSize != size) {
|
||||
mSize = size;
|
||||
setMode(GalleryView.Mode.LEFT_TO_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetBitmap(int index, @Nullable Bitmap bitmap) {
|
||||
GalleryPageView page = mGalleryView.getPage(index);
|
||||
if (page != null) {
|
||||
if (bitmap == null) {
|
||||
bindBadBitmap(page);
|
||||
} else {
|
||||
bindBitmap(page, bitmap);
|
||||
}
|
||||
} else {
|
||||
if (mGallerySpider != null) {
|
||||
mGallerySpider.releaseBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPagePercent(int index, float percent) {
|
||||
GalleryPageView page = mGalleryView.getPage(index);
|
||||
if (page != null) {
|
||||
bindPercent(page, percent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSucceed(int index) {
|
||||
GalleryPageView page = mGalleryView.getPage(index);
|
||||
if (page != null) {
|
||||
bind(page, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFailed(int index, Exception e) {
|
||||
//e.printStackTrace(); // TODO
|
||||
GalleryPageView page = mGalleryView.getPage(index);
|
||||
if (page != null) {
|
||||
bindFailed(page);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearTiledTextureInPage(GalleryPageView view) {
|
||||
TiledTexture tiledTexture = view.mImageView.getTiledTexture();
|
||||
if (tiledTexture != null) {
|
||||
view.mImageView.setTiledTexture(null);
|
||||
tiledTexture.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void bindBadBitmap(GalleryPageView view) {
|
||||
clearTiledTextureInPage(view);
|
||||
view.mProgressView.setVisibility(GLView.INVISIBLE);
|
||||
view.mIndexView.setVisibility(GLView.VISIBLE);
|
||||
view.mIndexView.setText(".3");
|
||||
}
|
||||
|
||||
private void bindBitmap(GalleryPageView view, Bitmap bitmap) {
|
||||
TiledTexture tiledTexture = new TiledTexture(bitmap);
|
||||
mUploader.addTexture(tiledTexture);
|
||||
|
||||
clearTiledTextureInPage(view);
|
||||
view.mProgressView.setVisibility(GLView.INVISIBLE);
|
||||
view.mIndexView.setVisibility(GLView.INVISIBLE);
|
||||
view.mImageView.setTiledTexture(tiledTexture);
|
||||
}
|
||||
|
||||
private void bindPercent(GalleryPageView view, float precent) {
|
||||
clearTiledTextureInPage(view);
|
||||
view.mProgressView.setVisibility(GLView.VISIBLE);
|
||||
view.mProgressView.setIndeterminate(false);
|
||||
view.mProgressView.setProgress(precent);
|
||||
view.mIndexView.setVisibility(GLView.VISIBLE);
|
||||
}
|
||||
|
||||
private void bindNone(GalleryPageView view) {
|
||||
clearTiledTextureInPage(view);
|
||||
view.mProgressView.setVisibility(GLView.VISIBLE);
|
||||
view.mProgressView.setIndeterminate(true);
|
||||
view.mIndexView.setVisibility(GLView.VISIBLE);
|
||||
}
|
||||
|
||||
private void bindFailed(GalleryPageView view) {
|
||||
clearTiledTextureInPage(view);
|
||||
view.mProgressView.setVisibility(GLView.INVISIBLE);
|
||||
view.mIndexView.setVisibility(GLView.VISIBLE);
|
||||
view.mIndexView.setText(".1");
|
||||
}
|
||||
|
||||
private void bindUnknown(GalleryPageView view) {
|
||||
clearTiledTextureInPage(view);
|
||||
view.mProgressView.setVisibility(GLView.INVISIBLE);
|
||||
view.mIndexView.setVisibility(GLView.VISIBLE);
|
||||
view.mIndexView.setText(".2");
|
||||
}
|
||||
|
||||
private void bind(GalleryPageView view, int index) {
|
||||
Object result = mGallerySpider.request(index);
|
||||
if (result instanceof Float) {
|
||||
bindPercent(view, (Float) result);
|
||||
} else if (result == GalleryProvider.RESULT_WAIT) {
|
||||
// Just wait
|
||||
} else if (result == GalleryProvider.RESULT_NONE) {
|
||||
bindNone(view);
|
||||
} else if (result == GalleryProvider.RESULT_FAILED) {
|
||||
bindFailed(view);
|
||||
} else {
|
||||
bindUnknown(view);
|
||||
}
|
||||
}
|
||||
|
||||
class GalleryAdapter extends GalleryView.Adapter {
|
||||
|
||||
@Override
|
||||
public int getPages() {
|
||||
return mMode == GalleryView.Mode.NONE ? 1 : mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryPageView createPage() {
|
||||
return new GalleryPageView(GalleryActivity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindPage(GalleryPageView view, int index) {
|
||||
view.mProgressView.setColor(mResources.getColor(R.color.theme_accent));
|
||||
view.mIndexView.setText(Integer.toString(index + 1));
|
||||
bind(view, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbindPage(GalleryPageView view, int index) {
|
||||
view.mProgressView.setIndeterminate(false);
|
||||
clearTiledTextureInPage(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,7 +281,7 @@ public class GalleryDetailScene extends Scene implements View.OnClickListener,
|
||||
|
||||
if (v == mRead) {
|
||||
Intent intent = new Intent(getStageActivity(), GalleryActivity.class);
|
||||
//intent.putExtra(GalleryActivity.KEY_GALLERY_BASE, mGalleryDetail);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_BASE, mGalleryDetail);
|
||||
getStageActivity().startActivity(intent);
|
||||
} else if (v == mDownload) {
|
||||
|
||||
|
||||
Reference in new issue
Block a user