Add GLGallery library

This commit is contained in:
seven332 committed 2016-05-31 00:01:05 +08:00
1 parent 94c50b27bf
commit ebaba88cde
20 files changed
+159 -4900

No files matched your search

@@ -29,9 +29,8 @@ import android.util.Log;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
import com.hippo.ehviewer.gallery.gl.GalleryView;
import com.hippo.ehviewer.gallery.gl.ImageView;
import com.hippo.ehviewer.ui.CommonOperations;
import com.hippo.glgallery.GalleryView;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
@@ -434,7 +433,7 @@ public class Settings {
}
private static final String KEY_READING_DIRECTION = "reading_direction";
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT;
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
@GalleryView.LayoutMode
public static int getReadingDirection() {
@@ -446,11 +445,11 @@ public class Settings {
}
private static final String KEY_PAGE_SCALING = "page_scaling";
private static final int DEFAULT_PAGE_SCALING = ImageView.SCALE_FIT;
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
@ImageView.Scale
@GalleryView.ScaleMode
public static int getPageScaling() {
return ImageView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
}
public static void putPageScaling(int value) {
@@ -458,11 +457,11 @@ public class Settings {
}
private static final String KEY_START_POSITION = "start_position";
private static final int DEFAULT_START_POSITION = ImageView.START_POSITION_TOP_RIGHT;
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
@ImageView.StartPosition
@GalleryView.StartPosition
public static int getStartPosition() {
return ImageView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
}
public static void putStartPosition(int value) {
@@ -23,7 +23,7 @@ import android.util.Log;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.gallery.gl.GalleryPageView;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.FileUtils;
@@ -41,7 +41,7 @@ import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class DirGalleryProvider extends GalleryProvider implements Runnable {
public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
private static final String TAG = DirGalleryProvider.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
@@ -22,13 +22,14 @@ import android.support.annotation.Nullable;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.spider.SpiderQueen;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.SimpleHandler;
import java.util.Locale;
public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.OnSpiderListener {
public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.OnSpiderListener {
private final Context mContext;
private final GalleryInfo mGalleryInfo;
@@ -1,277 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import com.hippo.beerbelly.LruCache;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.image.ImageWrapper;
import com.hippo.glview.view.GLRoot;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.ConcurrentPool;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.OSUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public abstract class GalleryProvider {
public static final int STATE_WAIT = -1;
public static final int STATE_ERROR = -2;
// With dot
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
".jpg", // Joint Photographic Experts Group
".jpeg",
".png", // Portable Network Graphics
".gif", // Graphics Interchange Format
};
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
private volatile GalleryProviderListener mGalleryProviderListener;
private volatile GLRoot mGLRoot;
private final ImageCache mImageCache = new ImageCache();
private boolean mStarted = false;
@UiThread
public void start() {
OSUtils.checkMainLoop();
if (mStarted) {
throw new IllegalStateException("Can't start it twice");
}
mStarted = true;
}
@UiThread
public void stop() {
OSUtils.checkMainLoop();
mImageCache.close();
}
public void setGLRoot(GLRoot glRoot) {
mGLRoot = glRoot;
}
/**
* @return {@link #STATE_WAIT} for wait, 0 for empty
*/
public abstract int size();
public final void request(int index) {
ImageWrapper imageWrapper = mImageCache.get(index);
if (imageWrapper != null) {
notifyPageSucceed(index, imageWrapper);
} else {
onRequest(index);
}
}
public final void forceRequest(int index) {
onForceRequest(index);
}
protected abstract void onRequest(int index);
protected abstract void onForceRequest(int index);
public final void cancelRequest(int index) {
onCancelRequest(index);
}
protected abstract void onCancelRequest(int index);
public abstract String getError();
public int getStartPage() {
return 0;
}
/**
* @return without extension
*/
@NonNull
public abstract String getImageFilename(int index);
public abstract boolean save(int index, @NonNull UniFile file);
/**
* @param filename without extension
*/
@Nullable
public abstract UniFile save(int index, @NonNull UniFile dir, @NonNull String filename);
public void putStartPage(int page) {}
public void setGalleryProviderListener(GalleryProviderListener listener) {
mGalleryProviderListener = listener;
}
public void notifyDataChanged() {
notify(NotifyTask.TYPE_DATA_CHANGED, -1, 0.0f, null, null);
}
public void notifyDataChanged(int index) {
notify(NotifyTask.TYPE_DATA_CHANGED, index, 0.0f, null, null);
}
public void notifyPageWait(int index) {
notify(NotifyTask.TYPE_WAIT, index, 0.0f, null, null);
}
public void notifyPagePercent(int index, float percent) {
notify(NotifyTask.TYPE_PERCENT, index, percent, null, null);
}
public void notifyPageSucceed(int index, Image image) {
ImageWrapper imageWrapper = new ImageWrapper(image);
mImageCache.add(index, imageWrapper);
notifyPageSucceed(index, imageWrapper);
}
public void notifyPageSucceed(int index, ImageWrapper image) {
notify(NotifyTask.TYPE_SUCCEED, index, 0.0f, image, null);
}
public void notifyPageFailed(int index, String error) {
notify(NotifyTask.TYPE_FAILED, index, 0.0f, null, error);
}
private void notify(@NotifyTask.Type int type, int index, float percent, ImageWrapper image, String error) {
GalleryProviderListener listener = mGalleryProviderListener;
if (listener == null) {
return;
}
GLRoot glRoot = mGLRoot;
if (glRoot == null) {
return;
}
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask(listener, mNotifyTaskPool);
}
task.setData(type, index, percent, image, error);
glRoot.addOnGLIdleListener(task);
}
private static class NotifyTask implements GLRoot.OnGLIdleListener {
@IntDef({TYPE_DATA_CHANGED, NotifyTask.TYPE_WAIT, TYPE_PERCENT, TYPE_SUCCEED, TYPE_FAILED})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
public static final int TYPE_DATA_CHANGED = 0;
public static final int TYPE_WAIT = 1;
public static final int TYPE_PERCENT = 2;
public static final int TYPE_SUCCEED = 3;
public static final int TYPE_FAILED = 4;
private final GalleryProviderListener mGalleryProviderListener;
private final ConcurrentPool<NotifyTask> mPool;
@Type
private int mType;
private int mIndex;
private float mPercent;
private ImageWrapper mImage;
private String mError;
public NotifyTask(GalleryProviderListener galleryProviderListener, ConcurrentPool<NotifyTask> pool) {
mGalleryProviderListener = galleryProviderListener;
mPool = pool;
}
public void setData(@Type int type, int index, float percent, ImageWrapper image, String error) {
mType = type;
mIndex = index;
mPercent = percent;
mImage = image;
mError = error;
}
@Override
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
switch (mType) {
case TYPE_DATA_CHANGED:
if (mIndex < 0) {
mGalleryProviderListener.onDataChanged();
} else {
mGalleryProviderListener.onDataChanged(mIndex);
}
break;
case TYPE_WAIT:
mGalleryProviderListener.onPageWait(mIndex);
break;
case TYPE_PERCENT:
mGalleryProviderListener.onPagePercent(mIndex, mPercent);
break;
case TYPE_SUCCEED:
mGalleryProviderListener.onPageSucceed(mIndex, mImage);
break;
case TYPE_FAILED:
mGalleryProviderListener.onPageFailed(mIndex, mError);
break;
}
// Clean data
mImage = null;
mError = null;
// Push back
mPool.push(this);
return false;
}
}
private static class ImageCache extends LruCache<Integer, ImageWrapper> {
private static final long MAX_CACHE_SIZE = 128 * 1024 * 1024;
private static final long MIN_CACHE_SIZE = 32 * 1024 * 1024;
public ImageCache() {
super((int) MathUtils.clamp(OSUtils.getTotalMemory() / 16, MIN_CACHE_SIZE, MAX_CACHE_SIZE));
}
public void add(Integer key, ImageWrapper value) {
if (value.obtain()) {
put(key, value);
}
}
@Override
protected int sizeOf(Integer key, ImageWrapper value) {
return value.getWidth() * value.getHeight() * 4;
}
@Override
protected void entryRemoved(boolean evicted, Integer key, ImageWrapper oldValue, ImageWrapper newValue) {
if (oldValue != null) {
oldValue.release();
}
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.unifile.UniFile;
public abstract class GalleryProvider2 extends GalleryProvider {
// With dot
public static final String[] SUPPORT_IMAGE_EXTENSIONS = {
".jpg", // Joint Photographic Experts Group
".jpeg",
".png", // Portable Network Graphics
".gif", // Graphics Interchange Format
};
public int getStartPage() {
return 0;
}
public void putStartPage(int page) {}
/**
* @return without extension
*/
@NonNull
public abstract String getImageFilename(int index);
public abstract boolean save(int index, @NonNull UniFile file);
/**
* @param filename without extension
*/
@Nullable
public abstract UniFile save(int index, @NonNull UniFile dir, @NonNull String filename);
}
@@ -1,34 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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;
import com.hippo.glview.image.ImageWrapper;
public interface GalleryProviderListener {
void onDataChanged();
void onPageWait(int index);
void onPagePercent(int index, float percent);
void onPageSucceed(int index, ImageWrapper image);
void onPageFailed(int index, String error);
void onDataChanged(int index);
}
@@ -23,7 +23,7 @@ import android.util.Log;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.gallery.gl.GalleryPageView;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.StringUtils;
@@ -43,7 +43,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
public class ZipGalleryProvider extends GalleryProvider implements Runnable {
public class ZipGalleryProvider extends GalleryProvider2 implements Runnable {
private static final String TAG = ZipGalleryProvider.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
@@ -1,67 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.gl;
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;
}
}
@@ -1,138 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.gl;
import android.content.Context;
import android.hardware.SensorManager;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import com.hippo.glview.anim.Animation;
public abstract class Fling extends Animation {
private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
private static final float START_TENSION = 0.5f;
private static final float END_TENSION = 1.0f;
private static final float P1 = START_TENSION * INFLEXION;
private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
private static final float FLING_FRICTION = ViewConfiguration.getScrollFriction();
private static final int NB_SAMPLES = 100;
private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
private float mPhysicalCoeff;
static {
float x_min = 0.0f;
float y_min = 0.0f;
for (int i = 0; i < NB_SAMPLES; i++) {
final float alpha = (float) i / NB_SAMPLES;
float x_max = 1.0f;
float x, tx, coef;
while (true) {
x = x_min + (x_max - x_min) / 2.0f;
coef = 3.0f * x * (1.0f - x);
tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
if (Math.abs(tx - alpha) < 1E-5) break;
if (tx > alpha) x_max = x;
else x_min = x;
}
SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
float y_max = 1.0f;
float y, dy;
while (true) {
y = y_min + (y_max - y_min) / 2.0f;
coef = 3.0f * y * (1.0f - y);
dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
if (Math.abs(dy - alpha) < 1E-5) break;
if (dy > alpha) y_max = y;
else y_min = y;
}
SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
}
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
}
private static final Interpolator FLING_INTERPOLATOR = new Interpolator() {
@Override
public float getInterpolation(float input) {
final int index = (int) (NB_SAMPLES * input);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (input - t_inf) * velocityCoef;
}
return distanceCoef;
}
};
public Fling(Context context) {
final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi
* 0.84f; // look and feel tuning
setInterpolator(FLING_INTERPOLATOR);
}
private double getSplineDeceleration(int velocity) {
return Math.log(INFLEXION * Math.abs(velocity) / (FLING_FRICTION * mPhysicalCoeff));
}
/* Returns the duration, expressed in milliseconds */
protected int getSplineFlingDuration(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return (int) (1000.0 * Math.exp(l / decelMinusOne));
}
protected double getSplineFlingDistance(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return FLING_FRICTION * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
/**
* Modifies mDuration to the duration it takes to get from start to newFinal using the
* spline interpolation. The previous duration was needed to get to oldFinal.
**/
protected int adjustDuration(int start, int oldFinal, int newFinal, int duration) {
final int oldDistance = oldFinal - start;
final int newDistance = newFinal - start;
final float x = Math.abs((float) newDistance / oldDistance);
final int index = (int) (NB_SAMPLES * x);
if (index < NB_SAMPLES) {
final float x_inf = (float) index / NB_SAMPLES;
final float x_sup = (float) (index + 1) / NB_SAMPLES;
final float t_inf = SPLINE_TIME[index];
final float t_sup = SPLINE_TIME[index + 1];
final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
duration *= timeCoef;
}
return duration;
}
}
@@ -1,189 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.gl;
import android.content.Context;
import com.hippo.glview.glrenderer.BasicTexture;
import com.hippo.glview.glrenderer.Texture;
import com.hippo.glview.image.GLImageMovableTextView;
import com.hippo.glview.image.ImageMovableTextTexture;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.view.Gravity;
import com.hippo.glview.widget.GLFrameLayout;
import com.hippo.glview.widget.GLLinearLayout;
import com.hippo.glview.widget.GLMovableTextView;
import com.hippo.glview.widget.GLProgressView;
import com.hippo.glview.widget.GLTextureView;
import com.hippo.yorozuya.LayoutUtils;
public class GalleryPageView extends GLFrameLayout {
public static final int INVALID_INDEX = -1;
private static final int INFO_INTERVAL = 24;
public static final float PROGRESS_GONE = -1.0f;
public static final float PROGRESS_INDETERMINATE = -2.0f;
private static final int PAGE_MIN_HEIGHT = 256;
private final ImageView mImage;
private final GLLinearLayout mInfo;
private final GLImageMovableTextView mPage;
private final GLTextureView mError;
private final GLProgressView mProgress;
private final int mPageMinHeight;
private int mIndex = INVALID_INDEX;
public GalleryPageView(Context context, ImageMovableTextTexture pageTextTexture,
int progressColor, int progressSize) {
// Add image
mImage = new ImageView();
GravityLayoutParams glp = new GravityLayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
addComponent(mImage, glp);
// Add other panel
mInfo = new GLLinearLayout();
mInfo.setOrientation(GLLinearLayout.VERTICAL);
mInfo.setInterval(LayoutUtils.dp2pix(context, INFO_INTERVAL));
glp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
glp.gravity = Gravity.CENTER;
addComponent(mInfo, glp);
// Add page
mPage = new GLImageMovableTextView();
mPage.setTextTexture(pageTextTexture);
GLLinearLayout.LayoutParams lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mPage, lp);
// Add error
mError = new GLTextureView();
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mError, lp);
// Add progress
mProgress = new GLProgressView();
mProgress.setBgColor(GalleryView.BACKGROUND_COLOR);
mProgress.setColor(progressColor);
mProgress.setMinimumWidth(progressSize);
mProgress.setMinimumHeight(progressSize);
lp = new GLLinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
lp.gravity = Gravity.CENTER_HORIZONTAL;
mInfo.addComponent(mProgress, lp);
mPageMinHeight = LayoutUtils.dp2pix(context, PAGE_MIN_HEIGHT);
}
@Override
protected int getSuggestedMinimumHeight() {
// The height of the actual image may be smaller than mPageMinHeight.
// Set min height as 0 when the image is visible.
// For PageLayoutManager, min height is useless.
if (mImage.getVisibility() == VISIBLE) {
return 0;
} else {
return mPageMinHeight;
}
}
int getIndex() {
return mIndex;
}
void setIndex(int index) {
mIndex = index;
}
public void showImage() {
mImage.setVisibility(VISIBLE);
mInfo.setVisibility(GONE);
}
public void showInfo() {
// For image valid rect
mImage.setVisibility(INVISIBLE);
mInfo.setVisibility(VISIBLE);
}
private void unbindImage() {
ImageTexture texture = mImage.getImageTexture();
if (texture != null) {
mImage.setImageTexture(null);
texture.recycle();
}
}
public void setImage(ImageTexture imageTexture) {
unbindImage();
if (imageTexture != null) {
mImage.setImageTexture(imageTexture);
}
}
public void setPage(int page) {
mPage.setText(Integer.toString(page));
}
public void setProgress(float progress) {
if (progress == PROGRESS_GONE) {
mProgress.setVisibility(GONE);
} else if (progress == PROGRESS_INDETERMINATE) {
mProgress.setVisibility(VISIBLE);
mProgress.setIndeterminate(true);
} else {
mProgress.setVisibility(VISIBLE);
mProgress.setIndeterminate(false);
mProgress.setProgress(progress);
}
}
private void unbindError() {
Texture texture = mError.getTexture();
if (texture != null) {
mError.setTexture(null);
if (texture instanceof BasicTexture) {
((BasicTexture) texture).recycle();
}
}
}
public void setError(String error, GalleryView galleryView) {
unbindError();
if (error == null) {
mError.setVisibility(GONE);
} else {
mError.setVisibility(VISIBLE);
galleryView.bindErrorView(mError, error);
}
}
public ImageView getImageView() {
return mImage;
}
public boolean isLoaded() {
return mImage.getVisibility() == VISIBLE;
}
}
@@ -1,991 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.gl;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.MotionEvent;
import com.hippo.ehviewer.R;
import com.hippo.glview.annotation.RenderThread;
import com.hippo.glview.glrenderer.BasicTexture;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.glrenderer.StringTexture;
import com.hippo.glview.glrenderer.Texture;
import com.hippo.glview.image.ImageMovableTextTexture;
import com.hippo.glview.util.GalleryUtils;
import com.hippo.glview.view.AnimationTime;
import com.hippo.glview.view.GLRoot;
import com.hippo.glview.view.GLView;
import com.hippo.glview.widget.GLEdgeView;
import com.hippo.glview.widget.GLProgressView;
import com.hippo.glview.widget.GLTextureView;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.Pool;
import com.hippo.yorozuya.ResourcesUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class GalleryView extends GLView implements GestureRecognizer.Listener {
@IntDef({LAYOUT_MODE_LEFT_TO_RIGHT, LAYOUT_MODE_RIGHT_TO_LEFT, LAYOUT_MODE_TOP_TO_BOTTOM})
@Retention(RetentionPolicy.SOURCE)
public @interface LayoutMode {}
public static final int BACKGROUND_COLOR = 0xff212121;
private static final int PROGRESS_SIZE_IN_DP = 48;
public static final int LAYOUT_MODE_LEFT_TO_RIGHT = 0;
public static final int LAYOUT_MODE_RIGHT_TO_LEFT = 1;
public static final int LAYOUT_MODE_TOP_TO_BOTTOM = 2;
private static final float[] LEFT_AREA = {0.0f, 0.0f, 1.0f / 3.0f, 1f};
private static final float[] RIGHT_AREA = {2.0f / 3.0f, 0.0f, 1.0f, 1f};
private static final float[] MENU_AREA = {1.0f / 3.0f, 0.0f, 2.0f / 3.0f, 1.0f / 2.0f};
private static final float[] SLIDER_AREA = {1.0f / 3.0f, 1.0f / 2.0f, 2.0f / 3.0f, 1.0f};
private static final int METHOD_ON_SINGLE_TAP_UP = 0;
private static final int METHOD_ON_SINGLE_TAP_CONFIRMED = 1;
private static final int METHOD_ON_DOUBLE_TAP = 2;
private static final int METHOD_ON_DOUBLE_TAP_CONFIRMED = 3;
private static final int METHOD_ON_LONG_PRESS = 4;
private static final int METHOD_ON_SCROLL = 5;
private static final int METHOD_ON_FLING = 6;
private static final int METHOD_ON_SCALE_BEGIN = 7;
private static final int METHOD_ON_SCALE = 8;
private static final int METHOD_ON_SCALE_END = 9;
private static final int METHOD_ON_DOWN = 10;
private static final int METHOD_ON_UP = 11;
private static final int METHOD_ON_POINTER_DOWN = 12;
private static final int METHOD_ON_POINTER_UP = 13;
private static final int METHOD_SET_LAYOUT_MODE = 14;
private static final int METHOD_SET_CURRENT_PAGE = 15;
private static final int METHOD_PAGE_LEFT = 16;
private static final int METHOD_PAGE_RIGHT = 17;
private static final int METHOD_SET_SCALE_MODE = 18;
private static final int METHOD_SET_START_POSITION = 19;
private static final int METHOD_ON_ATTACH_TO_ROOT = 20;
private final Context mContext;
private ImageMovableTextTexture mPageTextTexture;
private final GestureRecognizer mGestureRecognizer;
private Adapter mAdapter;
private PagerLayoutManager mPagerLayoutManager;
private ScrollLayoutManager mScrollLayoutManager;
@Nullable
private LayoutManager mLayoutManager;
private final Pool<GalleryPageView> mGalleryPageViewPool = new Pool<>(5);
private final GLEdgeView mEdgeView;
private GLProgressView mProgressCache;
private GLTextureView mErrorViewCache;
private final int mProgressColor;
private final int mProgressSize;
private final float mErrorSize;
private final int mErrorColor;
private boolean mEnableRequestFill = true;
private boolean mRequestFill = false;
private boolean mWillFill = false;
private boolean mScale = false;
private boolean mScroll = false;
private boolean mFirstScroll = false;
private final Rect mLeftArea = new Rect();
private final Rect mRightArea = new Rect();
private final Rect mMenuArea = new Rect();
private final Rect mSliderArea = new Rect();
private final String mDefaultErrorStr;
private final String mEmptyStr;
@LayoutMode
private int mLayoutMode = LAYOUT_MODE_RIGHT_TO_LEFT;
@ImageView.Scale
private int mScaleMode = ImageView.SCALE_FIT;
@ImageView.StartPosition
private int mStartPosition = ImageView.START_POSITION_TOP_LEFT;
private int mIndex;
private final Listener mListener;
private final List<Integer> mMethodList = new ArrayList<>(5);
private final List<Object[]> mArgsList = new ArrayList<>(5);
private final List<Integer> mMethodListTemp = new ArrayList<>(5);
private final List<Object[]> mArgsListTemp = new ArrayList<>(5);
private final AtomicInteger mCurrentIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
@SuppressWarnings("deprecation")
public GalleryView(@NonNull Context context, @NonNull Adapter adapter,
Listener listener, @LayoutMode int layoutMode, @ImageView.Scale int scaleMode,
@ImageView.StartPosition int startPosition, int startPage) {
mContext = context;
mAdapter = adapter;
mListener = listener;
final int themeColor = ResourcesUtils.getAttrColor(context, R.attr.colorPrimary);
final int edgeColor = (themeColor & 0xffffff) | 0x33000000;
mEdgeView = new GLEdgeView(edgeColor);
mGestureRecognizer = new GestureRecognizer(context, this);
adapter.setGalleryView(this);
mLayoutMode = layoutMode;
mScaleMode = scaleMode;
mStartPosition = startPosition;
mIndex = MathUtils.clamp(startPage, 0, Integer.MAX_VALUE);
mProgressColor = ResourcesUtils.getAttrColor(context, R.attr.colorPrimary);
mProgressSize = LayoutUtils.dp2pix(context, PROGRESS_SIZE_IN_DP);
mErrorSize = context.getResources().getDimension(R.dimen.text_large);
mErrorColor = context.getResources().getColor(R.color.red_500);
mDefaultErrorStr = context.getResources().getString(R.string.error_unknown);
mEmptyStr = context.getResources().getString(R.string.error_empty);
setBackgroundColor(BACKGROUND_COLOR);
}
private void ensurePagerLayoutManager() {
if (mPagerLayoutManager == null) {
mPagerLayoutManager = new PagerLayoutManager(mContext, this, mScaleMode, mStartPosition, 1.0f);
}
}
private void ensureScrollLayoutManager() {
if (mScrollLayoutManager == null) {
mScrollLayoutManager = new ScrollLayoutManager(mContext, this);
}
}
private void attachLayoutManager() {
if (null != mLayoutManager) {
return;
}
switch (mLayoutMode) {
case LAYOUT_MODE_LEFT_TO_RIGHT:
ensurePagerLayoutManager();
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_LEFT_TO_RIGHT);
mPagerLayoutManager.onAttach(mAdapter);
mPagerLayoutManager.setCurrentIndex(mIndex);
mAdapter = null;
mLayoutManager = mPagerLayoutManager;
break;
case LAYOUT_MODE_RIGHT_TO_LEFT:
ensurePagerLayoutManager();
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_RIGHT_TO_LEFT);
mPagerLayoutManager.onAttach(mAdapter);
mPagerLayoutManager.setCurrentIndex(mIndex);
mAdapter = null;
mLayoutManager = mPagerLayoutManager;
break;
case LAYOUT_MODE_TOP_TO_BOTTOM:
ensureScrollLayoutManager();
mScrollLayoutManager.onAttach(mAdapter);
mScrollLayoutManager.setCurrentIndex(mIndex);
mAdapter = null;
mLayoutManager = mScrollLayoutManager;
break;
}
requestFill();
}
private void detachLayoutManager() {
if (null == mLayoutManager) {
return;
}
mIndex = mLayoutManager.getInternalCurrentIndex();
mAdapter = mLayoutManager.onDetach();
mLayoutManager = null;
}
@SuppressWarnings("deprecation")
private void onAttachToRootInternal() {
if (null == mPageTextTexture) {
mPageTextTexture = ImageMovableTextTexture.create(Typeface.DEFAULT,
mContext.getResources().getDimensionPixelSize(R.dimen.gallery_page_text),
mContext.getResources().getColor(R.color.secondary_text_dark),
new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'});
}
attachLayoutManager();
}
@Override
public void onAttachToRoot(GLRoot root) {
super.onAttachToRoot(root);
mEdgeView.onAttachToRoot(root);
postMethod(METHOD_ON_ATTACH_TO_ROOT);
}
@Override
public void onDetachFromRoot() {
// When detached, render() will not be called. So do it here
detachLayoutManager();
if (null != mPageTextTexture) {
mPageTextTexture.recycle();
mPageTextTexture = null;
}
super.onDetachFromRoot();
mEdgeView.onDetachFromRoot();
}
@LayoutMode
public static int sanitizeLayoutMode(int layoutMode) {
if (layoutMode != GalleryView.LAYOUT_MODE_LEFT_TO_RIGHT &&
layoutMode != GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT &&
layoutMode != GalleryView.LAYOUT_MODE_TOP_TO_BOTTOM) {
return GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT;
} else {
return layoutMode;
}
}
public int getLayoutMode() {
return mLayoutMode;
}
public int getCurrentIndex() {
return mCurrentIndex.get();
}
@Override
public void requestLayout() {
// Do not need requestLayout, because the size will not change
requestFill();
}
public void requestFill() {
if (mEnableRequestFill) {
mRequestFill = true;
if (!mWillFill) {
invalidate();
}
}
}
@Override
protected boolean dispatchTouchEvent(MotionEvent event) {
// Do not pass event to component, so handle event here
mGestureRecognizer.onTouchEvent(event);
return true;
}
GLEdgeView getEdgeView() {
return mEdgeView;
}
String getDefaultErrorStr() {
return mDefaultErrorStr;
}
String getEmptyStr() {
return mEmptyStr;
}
boolean isFirstScroll() {
boolean firstScroll = mFirstScroll;
mFirstScroll = false;
return firstScroll;
}
// Make sure method run in render thread to ensure thread safe
private void postMethod(int method, Object... args) {
synchronized (this) {
mMethodList.add(method);
mArgsList.add(args);
}
invalidate();
}
public void setLayoutMode(@LayoutMode int layoutMode) {
postMethod(METHOD_SET_LAYOUT_MODE, layoutMode);
}
public void setCurrentPage(int page) {
postMethod(METHOD_SET_CURRENT_PAGE, page);
}
public void pageLeft() {
postMethod(METHOD_PAGE_LEFT);
}
public void pageRight() {
postMethod(METHOD_PAGE_RIGHT);
}
public void setScaleMode(@ImageView.Scale int scaleMode) {
postMethod(METHOD_SET_SCALE_MODE, scaleMode);
}
public void setStartPosition(@ImageView.StartPosition int startPosition) {
postMethod(METHOD_SET_START_POSITION, startPosition);
}
@Override
public boolean onSingleTapUp(float x, float y) {
postMethod(METHOD_ON_SINGLE_TAP_UP, x, y);
return true;
}
@Override
public boolean onSingleTapConfirmed(float x, float y) {
postMethod(METHOD_ON_SINGLE_TAP_CONFIRMED, x, y);
return true;
}
@Override
public boolean onDoubleTap(float x, float y) {
postMethod(METHOD_ON_DOUBLE_TAP, x, y);
return true;
}
@Override
public boolean onDoubleTapConfirmed(float x, float y) {
postMethod(METHOD_ON_DOUBLE_TAP_CONFIRMED, x, y);
return true;
}
@Override
public void onLongPress(float x, float y) {
if (mLayoutManager != null && !mLayoutManager.isTapOrPressEnable()) {
return;
}
postMethod(METHOD_ON_LONG_PRESS, x, y);
}
@Override
public boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y) {
postMethod(METHOD_ON_SCROLL, dx, dy, totalX, totalY, x, y);
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
postMethod(METHOD_ON_FLING, velocityX, velocityY);
return true;
}
@Override
public boolean onScaleBegin(float focusX, float focusY) {
postMethod(METHOD_ON_SCALE_BEGIN, focusX, focusY);
return true;
}
@Override
public boolean onScale(float focusX, float focusY, float scale) {
postMethod(METHOD_ON_SCALE, focusX, focusY, scale);
return true;
}
@Override
public void onScaleEnd() {
postMethod(METHOD_ON_SCALE_END);
}
@Override
public void onDown(float x, float y) {
postMethod(METHOD_ON_DOWN, x, y);
}
@Override
public void onUp() {
postMethod(METHOD_ON_UP);
}
@Override
public void onPointerDown(float x, float y) {
postMethod(METHOD_ON_POINTER_DOWN, x, y);
}
@Override
public void onPointerUp() {
postMethod(METHOD_ON_POINTER_UP);
}
@Override
protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) {
mEdgeView.layout(left, top, right, bottom);
fill();
if (changeSize) {
int width = right - left;
int height = bottom - top;
mLeftArea.set((int) (LEFT_AREA[0] * width), (int) (LEFT_AREA[1] * height),
(int) (LEFT_AREA[2] * width), (int) (LEFT_AREA[3] * height));
mRightArea.set((int) (RIGHT_AREA[0] * width), (int) (RIGHT_AREA[1] * height),
(int) (RIGHT_AREA[2] * width), (int) (RIGHT_AREA[3] * height));
mMenuArea.set((int) (MENU_AREA[0] * width), (int) (MENU_AREA[1] * height),
(int) (MENU_AREA[2] * width), (int) (MENU_AREA[3] * height));
mSliderArea.set((int) (SLIDER_AREA[0] * width), (int) (SLIDER_AREA[1] * height),
(int) (SLIDER_AREA[2] * width), (int) (SLIDER_AREA[3] * height));
}
}
@RenderThread
public void onDataChanged() {
GalleryUtils.assertInRenderThread();
if (mLayoutManager != null){
mLayoutManager.onDataChanged();
}
}
private void onSingleTapUpInternal(float x, float y) {
}
private void onSingleTapConfirmedInternal(float x, float y) {
if (mLayoutManager == null || !mLayoutManager.isTapOrPressEnable()) {
return;
}
if (mSliderArea.contains((int) x, (int) y)) {
mListener.onTapSliderArea();
} else if (mMenuArea.contains((int) x, (int) y)) {
mListener.onTapMenuArea();
} else if (mLeftArea.contains((int) x, (int) y)) {
mLayoutManager.onPageLeft();
} else if (mRightArea.contains((int) x, (int) y)) {
mLayoutManager.onPageRight();
}
}
private void onDoubleTapInternal(float x, float y) {
}
private void onDoubleTapConfirmedInternal(float x, float y) {
if (mScale) {
return;
}
if (mLayoutManager != null) {
mLayoutManager.onDoubleTapConfirmed(x, y);
}
}
private void onLongPressInternal(float x, float y) {
if (mScale) {
return;
}
if (mLayoutManager == null) {
return;
}
int index = mLayoutManager.getIndexUnder(x, y);
if (index == GalleryPageView.INVALID_INDEX) {
return;
}
mListener.onLongPressPage(index);
}
private void onScrollInternal(float dx, float dy, float totalX, float totalY, float x, float y) {
if (mScale) {
return;
}
mScroll = true;
if (mLayoutManager != null) {
mLayoutManager.onScroll(dx, dy, totalX, totalY, x, y);
}
}
private void onFlingInternal(float velocityX, float velocityY) {
if (mLayoutManager != null) {
mLayoutManager.onFling(velocityX, velocityY);
}
}
private void onScaleBeginInternal(float focusX, float focusY) {
onScaleInternal(focusX, focusY, 1.0f);
}
private void onScaleInternal(float focusX, float focusY, float scale) {
if (mScroll || (mLayoutManager != null && !mLayoutManager.canScale())) {
return;
}
mScale = true;
if (mLayoutManager != null) {
mLayoutManager.onScale(focusX, focusY, scale);
}
}
private void onScaleEndInternal() {
}
private void onDownInternal(float x, float y) {
mScale = false;
mScroll = false;
mFirstScroll = true;
if (mLayoutManager != null) {
mLayoutManager.onDown();
}
}
private void onUpInternal() {
if (mLayoutManager != null) {
mLayoutManager.onUp();
}
}
private void onPointerDownInternal(float x, float y) {
if (!mScroll && (mLayoutManager != null && mLayoutManager.canScale())) {
mScale = true;
}
}
private void onPointerUpInternal() {
}
private void setLayoutModeInternal(int layoutMode) {
if (mLayoutMode == layoutMode) {
return;
}
mLayoutMode = layoutMode;
if (mLayoutManager == null) {
return;
}
switch (mLayoutMode) {
case LAYOUT_MODE_LEFT_TO_RIGHT:
if (mLayoutManager == mPagerLayoutManager) {
// mPagerLayoutManager already attached, just change mode
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_LEFT_TO_RIGHT);
} else {
ensurePagerLayoutManager();
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_LEFT_TO_RIGHT);
int index = mLayoutManager.getInternalCurrentIndex();
mPagerLayoutManager.onAttach(mLayoutManager.onDetach());
mPagerLayoutManager.setCurrentIndex(index);
mLayoutManager = mPagerLayoutManager;
}
break;
case LAYOUT_MODE_RIGHT_TO_LEFT:
if (mLayoutManager == mPagerLayoutManager) {
// mPagerLayoutManager already attached, just change mode
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_RIGHT_TO_LEFT);
} else {
ensurePagerLayoutManager();
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_RIGHT_TO_LEFT);
int index = mLayoutManager.getInternalCurrentIndex();
mPagerLayoutManager.onAttach(mLayoutManager.onDetach());
mPagerLayoutManager.setCurrentIndex(index);
mLayoutManager = mPagerLayoutManager;
}
break;
case LAYOUT_MODE_TOP_TO_BOTTOM:
ensureScrollLayoutManager();
int index = mLayoutManager.getInternalCurrentIndex();
mScrollLayoutManager.onAttach(mLayoutManager.onDetach());
mScrollLayoutManager.setCurrentIndex(index);
mLayoutManager = mScrollLayoutManager;
break;
}
requestFill();
}
private void setCurrentPageInternal(int page) {
if (mLayoutManager != null) {
mLayoutManager.setCurrentIndex(page);
} else {
mIndex = page;
}
}
private void pageLeftInternal() {
if (mLayoutManager != null) {
mLayoutManager.onPageLeft();
}
}
private void pageRightInternal() {
if (mLayoutManager != null) {
mLayoutManager.onPageRight();
}
}
private void setScaleModeInternal(int scaleMode) {
mScaleMode = scaleMode;
if (mPagerLayoutManager != null) {
mPagerLayoutManager.setScaleMode(scaleMode);
}
}
private void setStartPositionInternal(int startPosition) {
mStartPosition = startPosition;
if (mPagerLayoutManager != null) {
mPagerLayoutManager.setStartPosition(startPosition);
}
}
@RenderThread
void forceFill() {
mRequestFill = true;
fill();
}
@RenderThread
private void fill() {
GalleryUtils.assertInRenderThread();
if (!mRequestFill) {
return;
}
// Disable request layout
mEnableRequestFill = false;
if (mLayoutManager != null) {
mLayoutManager.onFill();
}
mEnableRequestFill = true;
mRequestFill = false;
}
private void dispatchMethod() {
List<Integer> methodListTemp = mMethodListTemp;
List<Object[]> argsListTemp = mArgsListTemp;
synchronized (this) {
if (mMethodList.isEmpty()) {
return;
}
methodListTemp.addAll(mMethodList);
argsListTemp.addAll(mArgsList);
mMethodList.clear();
mArgsList.clear();
}
for (int i = 0, n = methodListTemp.size(); i < n; i++) {
int method = methodListTemp.get(i);
Object[] args = argsListTemp.get(i);
switch (method) {
case METHOD_ON_SINGLE_TAP_UP:
onSingleTapUpInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_SINGLE_TAP_CONFIRMED:
onSingleTapConfirmedInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_DOUBLE_TAP:
onDoubleTapInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_DOUBLE_TAP_CONFIRMED:
onDoubleTapConfirmedInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_LONG_PRESS:
onLongPressInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_SCROLL:
onScrollInternal((Float) args[0], (Float) args[1], (Float) args[2],
(Float) args[3], (Float) args[4], (Float) args[5]);
break;
case METHOD_ON_FLING:
onFlingInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_SCALE_BEGIN:
onScaleBeginInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_SCALE:
onScaleInternal((Float) args[0], (Float) args[1], (Float) args[2]);
break;
case METHOD_ON_SCALE_END:
onScaleEndInternal();
break;
case METHOD_ON_DOWN:
onDownInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_UP:
onUpInternal();
break;
case METHOD_ON_POINTER_DOWN:
onPointerDownInternal((Float) args[0], (Float) args[1]);
break;
case METHOD_ON_POINTER_UP:
onPointerUpInternal();
break;
case METHOD_SET_LAYOUT_MODE:
setLayoutModeInternal((Integer) args[0]);
break;
case METHOD_SET_CURRENT_PAGE:
setCurrentPageInternal((Integer) args[0]);
break;
case METHOD_PAGE_LEFT:
pageLeftInternal();
break;
case METHOD_PAGE_RIGHT:
pageRightInternal();
break;
case METHOD_SET_SCALE_MODE:
setScaleModeInternal((Integer) args[0]);
break;
case METHOD_SET_START_POSITION:
setStartPositionInternal((Integer) args[0]);
break;
case METHOD_ON_ATTACH_TO_ROOT:
onAttachToRootInternal();
break;
}
}
methodListTemp.clear();
argsListTemp.clear();
}
@Override
public void render(GLCanvas canvas) {
mWillFill = true;
int oldCurrentIndex = mCurrentIndex.get();
// Dispatch method
dispatchMethod();
long time = AnimationTime.get();
if (mLayoutManager != null && mLayoutManager.onUpdateAnimation(time)) {
invalidate();
}
fill();
mWillFill = false;
super.render(canvas);
mEdgeView.render(canvas);
int newCurrentIndex;
if (mLayoutManager != null) {
newCurrentIndex = mLayoutManager.getCurrentIndex();
} else {
newCurrentIndex = GalleryPageView.INVALID_INDEX;
}
mCurrentIndex.lazySet(newCurrentIndex);
if (oldCurrentIndex != newCurrentIndex) {
mListener.onUpdateCurrentIndex(newCurrentIndex);
}
}
@RenderThread
public GalleryPageView findPageByIndex(int id) {
if (mLayoutManager != null) {
return mLayoutManager.findPageByIndex(id);
} else {
return null;
}
}
GalleryPageView obtainPage() {
GalleryPageView page = mGalleryPageViewPool.pop();
if (page == null) {
page = new GalleryPageView(mContext, mPageTextTexture, mProgressColor, mProgressSize);
}
return page;
}
void releasePage(GalleryPageView page) {
mGalleryPageViewPool.push(page);
}
/**
* Indeterminate GLProgressView
*/
GLProgressView obtainProgress() {
GLProgressView progress;
if (mProgressCache != null) {
progress = mProgressCache;
mProgressCache = null;
} else {
progress = new GLProgressView();
progress.setColor(mProgressColor);
progress.setBgColor(BACKGROUND_COLOR);
progress.setIndeterminate(true);
progress.setMinimumWidth(mProgressSize);
progress.setMinimumHeight(mProgressSize);
}
return progress;
}
/**
* @param progress Indeterminate GLProgressView
*/
void releaseProgress(GLProgressView progress) {
mProgressCache = progress;
}
GLTextureView obtainErrorView() {
GLTextureView errorView;
if (mErrorViewCache != null) {
errorView = mErrorViewCache;
mErrorViewCache = null;
} else {
errorView = new GLTextureView();
}
return errorView;
}
void unbindErrorView(GLTextureView errorView) {
Texture texture = errorView.getTexture();
if (texture != null) {
errorView.setTexture(null);
if (texture instanceof BasicTexture) {
((BasicTexture) texture).recycle();
}
}
}
void bindErrorView(GLTextureView errorView, String error) {
unbindErrorView(errorView);
Texture texture = StringTexture.newInstance(error, mErrorSize, mErrorColor);
errorView.setTexture(texture);
}
void releaseErrorView(GLTextureView errorView) {
unbindErrorView(errorView);
mErrorViewCache = errorView;
}
public static abstract class Adapter {
protected GalleryView mGalleryView;
private void setGalleryView(@NonNull GalleryView galleryView) {
mGalleryView = galleryView;
}
public void bind(GalleryPageView view, int index) {
onBind(view, index);
view.setIndex(index);
}
public void unbind(GalleryPageView view) {
onUnbind(view, view.getIndex());
view.setIndex(GalleryPageView.INVALID_INDEX);
}
public abstract void onBind(GalleryPageView view, int index);
public abstract void onUnbind(GalleryPageView view, int index);
/**
* @return Null for no error
*/
public abstract String getError();
public abstract int size();
}
public static abstract class LayoutManager {
protected GalleryView mGalleryView;
public LayoutManager(@NonNull GalleryView galleryView) {
mGalleryView = galleryView;
}
public abstract void onAttach(Adapter iterator);
public abstract Adapter onDetach();
public abstract void onFill();
public abstract void onDown();
public abstract void onUp();
public abstract void onDoubleTapConfirmed(float x, float y);
public abstract void onLongPress(float x, float y);
public abstract void onScroll(float dx, float dy, float totalX, float totalY, float x, float y);
public abstract void onFling(float velocityX, float velocityY);
public abstract boolean canScale();
public abstract void onScale(float focusX, float focusY, float scale);
public abstract boolean onUpdateAnimation(long time);
public abstract void onDataChanged();
public abstract void onPageLeft();
public abstract void onPageRight();
public abstract boolean isTapOrPressEnable();
public abstract GalleryPageView findPageByIndex(int index);
/**
* @return {@link GalleryPageView#INVALID_INDEX} for error
*/
public abstract int getCurrentIndex();
public abstract void setCurrentIndex(int index);
public abstract int getIndexUnder(float x, float y);
abstract int getInternalCurrentIndex();
protected void placeCenter(GLView view) {
int spec = GLView.MeasureSpec.makeMeasureSpec(GLView.LayoutParams.WRAP_CONTENT,
GLView.LayoutParams.WRAP_CONTENT);
view.measure(spec, spec);
int viewWidth = view.getMeasuredWidth();
int viewHeight = view.getMeasuredHeight();
int viewLeft = mGalleryView.getWidth() / 2 - viewWidth / 2;
int viewTop = mGalleryView.getHeight() / 2 - viewHeight / 2;
view.layout(viewLeft, viewTop, viewLeft + viewWidth, viewTop + viewHeight);
}
}
public interface Listener {
@RenderThread
void onUpdateCurrentIndex(int index);
@RenderThread
void onTapSliderArea();
@RenderThread
void onTapMenuArea();
@RenderThread
void onLongPressPage(int index);
}
}
@@ -1,174 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.gl;
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 onSingleTapConfirmed(float x, float y);
boolean onDoubleTap(float x, float y);
boolean onDoubleTapConfirmed(float x, float y);
void onLongPress(float x, float y);
boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y);
/**
* @param velocityX Finger from top to bottom is positive
* @param velocityY Finger from left to right is positive
*/
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;
MyGestureListener gestureListener = new MyGestureListener();
mGestureDetector = new GestureDetectorCompat(context, gestureListener,
null /* ignoreMultitouch */);
mGestureDetector.setOnDoubleTapListener(gestureListener);
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 onSingleTapConfirmed(MotionEvent e) {
return mListener.onSingleTapConfirmed(e.getX(), e.getY());
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
return mListener.onDoubleTapConfirmed(e.getX(), e.getY());
} else {
return true;
}
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return mListener.onDoubleTap(e.getX(), e.getY());
}
@Override
public void onLongPress(MotionEvent e) {
mListener.onLongPress(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(), e2.getX(), e2.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();
}
}
}
@@ -1,620 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.gl;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.IntDef;
import com.hippo.glview.anim.AlphaAnimation;
import com.hippo.glview.glrenderer.GLCanvas;
import com.hippo.glview.glrenderer.Texture;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.view.GLView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.MathUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
public class ImageView extends GLView implements ImageTexture.Callback {
@IntDef({SCALE_ORIGIN, SCALE_FIT_WIDTH, SCALE_FIT_HEIGHT, SCALE_FIT, SCALE_FIXED})
@Retention(RetentionPolicy.SOURCE)
public @interface Scale {}
@IntDef({START_POSITION_TOP_LEFT, START_POSITION_TOP_RIGHT, START_POSITION_BOTTOM_LEFT,
START_POSITION_BOTTOM_RIGHT, START_POSITION_CENTER})
@Retention(RetentionPolicy.SOURCE)
public @interface StartPosition {}
// TODO adjust scale max and min according to image size and screen size
private static final float SCALE_MIN = 1 / 10.0f;
private static final float SCALE_MAX = 10.0f;
public static final int SCALE_ORIGIN = 0;
public static final int SCALE_FIT_WIDTH = 1;
public static final int SCALE_FIT_HEIGHT = 2;
public static final int SCALE_FIT = 3;
public static final int SCALE_FIXED = 4;
public static final int START_POSITION_TOP_LEFT = 0;
public static final int START_POSITION_TOP_RIGHT = 1;
public static final int START_POSITION_BOTTOM_LEFT = 2;
public static final int START_POSITION_BOTTOM_RIGHT = 3;
public static final int START_POSITION_CENTER = 4;
private static final long ALPHA_ANIMATION_DURING = 300L;
private ImageTexture mImageTexture;
private int mTextureWidth;
private int mTextureHeight;
private final RectF mDst = new RectF();
private final RectF mSrcActual = new RectF();
private final RectF mDstActual = new RectF();
private final Rect mValidRect = new Rect();
private int mScaleMode = SCALE_FIT;
private int mStartPosition = START_POSITION_TOP_RIGHT;
private float mScaleValue = 1.0f;
private float mScale = 1.0f;
private boolean mScaleOffsetDirty = true;
private boolean mPositionInRootDirty = true;
private final AlphaAnimation mAlphaAnimation;
public ImageView() {
mAlphaAnimation = new AlphaAnimation(0.0f, 1.0f);
mAlphaAnimation.setDuration(ALPHA_ANIMATION_DURING);
mAlphaAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
}
@Scale
public static int sanitizeScaleMode(int scaleMode) {
if (scaleMode != SCALE_ORIGIN &&
scaleMode != SCALE_FIT_WIDTH &&
scaleMode != SCALE_FIT_HEIGHT &&
scaleMode != SCALE_FIT &&
scaleMode != SCALE_FIXED) {
return SCALE_FIT;
} else {
return scaleMode;
}
}
@StartPosition
public static int sanitizeStartPosition(int startPosition) {
if (startPosition != START_POSITION_TOP_LEFT &&
startPosition != START_POSITION_TOP_RIGHT &&
startPosition != START_POSITION_BOTTOM_LEFT &&
startPosition != START_POSITION_BOTTOM_RIGHT &&
startPosition != START_POSITION_CENTER) {
return START_POSITION_TOP_RIGHT;
} else {
return startPosition;
}
}
@Override
protected int getSuggestedMinimumWidth() {
return Math.max(super.getSuggestedMinimumWidth(),
mImageTexture == null ? 0 : mTextureWidth);
}
@Override
protected int getSuggestedMinimumHeight() {
return Math.max(super.getSuggestedMinimumHeight(),
mImageTexture == null ? 0 : mTextureHeight);
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mImageTexture == null) {
super.onMeasure(widthSpec, heightSpec);
} else {
float ratio = (float) mTextureWidth / mTextureHeight;
int widthSize = MeasureSpec.getSize(widthSpec);
int heightSize = MeasureSpec.getSize(heightSpec);
int widthMode = MeasureSpec.getMode(widthSpec);
int heightMode = MeasureSpec.getMode(heightSpec);
int measureWidth = -1;
int measureHeight = -1;
if (widthMode == MeasureSpec.EXACTLY) {
measureWidth = widthSize;
if (heightMode == MeasureSpec.EXACTLY) {
measureHeight = heightSize;
} else {
measureHeight = (int) (widthSize / ratio);
if (heightMode == MeasureSpec.AT_MOST) {
measureHeight = Math.min(measureHeight, heightSize);
}
}
} else if (heightMode == MeasureSpec.EXACTLY) {
measureHeight = heightSize;
measureWidth = (int) (heightSize * ratio);
if (widthMode == MeasureSpec.AT_MOST) {
measureWidth = Math.min(measureWidth, widthSize);
}
}
if (measureWidth == -1 || measureHeight == -1) {
super.onMeasure(widthSpec, heightSpec);
} else {
setMeasuredSize(measureWidth, measureHeight);
}
}
}
@Override
protected void onSizeChanged(int newW, int newH, int oldW, int oldH) {
mScaleOffsetDirty = true;
mPositionInRootDirty = true;
}
@Override
protected void onPositionInRootChanged(int x, int y, int oldX, int oldY) {
mPositionInRootDirty = true;
if (mImageTexture != null) {
getValidRect(mValidRect);
if (!mValidRect.isEmpty()) {
mImageTexture.start();
} else {
mImageTexture.stop();
}
}
}
public void getScaleDefault(float[] scaleDefault) {
if (mImageTexture == null) {
return;
}
scaleDefault[0] = 1.0f;
scaleDefault[1] = (float) getWidth() / mTextureWidth;
scaleDefault[2] = (float) getHeight() / mTextureHeight;
scaleDefault[3] = Math.max(scaleDefault[1], scaleDefault[2]) * 2;
scaleDefault[0] = MathUtils.clamp(scaleDefault[0], SCALE_MIN, SCALE_MAX);
scaleDefault[1] = MathUtils.clamp(scaleDefault[1], SCALE_MIN, SCALE_MAX);
scaleDefault[2] = MathUtils.clamp(scaleDefault[2], SCALE_MIN, SCALE_MAX);
scaleDefault[3] = MathUtils.clamp(scaleDefault[3], SCALE_MIN, SCALE_MAX);
Arrays.sort(scaleDefault);
}
public void setImageTexture(ImageTexture imageTexture) {
// Remove callback
if (mImageTexture != null) {
mImageTexture.setCallback(null);
mImageTexture.stop();
}
int oldTextureWidth = mTextureWidth;
int oldTextureHeight = mTextureHeight;
mImageTexture = imageTexture;
if (imageTexture != null) {
imageTexture.setCallback(this);
mTextureWidth = imageTexture.getWidth();
mTextureHeight = imageTexture.getHeight();
// Avoid zero and negative
if (mTextureWidth <= 0) {
mTextureWidth = 1;
}
if (mTextureHeight <= 0) {
mTextureHeight = 1;
}
// Start alpha animation, do not show animation for image has no valid rect
getValidRect(mValidRect);
if (!mValidRect.isEmpty()) {
startAnimation(mAlphaAnimation, true);
mImageTexture.start();
}
} else {
mTextureWidth = 1;
mTextureHeight = 1;
}
mScaleOffsetDirty = true;
mPositionInRootDirty = true;
if (oldTextureWidth != mTextureWidth || oldTextureHeight != mTextureHeight) {
requestLayout();
}
}
public ImageTexture getImageTexture() {
return mImageTexture;
}
public boolean isLoaded() {
return mImageTexture != null;
}
public boolean canFlingVertically() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.top < 0.0f || mDst.bottom > getHeight();
}
public boolean canFlingHorizontally() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.left < 0.0f || mDst.right > getWidth();
}
public boolean canFling() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return false;
}
}
return mDst.left < 0.0f || mDst.top < 0.0f || mDst.right > getWidth() || mDst.bottom > getHeight();
}
public int getMaxDx() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.max(0, -(int) mDst.left);
}
public int getMinDx() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.min(0, getWidth() - (int) mDst.right);
}
public int getMaxDy() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.max(0, -(int) mDst.top);
}
public int getMinDy() {
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
if (mScaleOffsetDirty) {
return 0;
}
}
return Math.min(0, getHeight() - (int) mDst.bottom);
}
public float getScale() {
return mScale;
}
/**
* 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 dst = mDst;
int screenWidth = getWidth();
int screenHeight = getHeight();
float targetWidth = dst.width();
float targetHeight = dst.height();
if (targetWidth > screenWidth) {
float fixXOffset = dst.left;
if (fixXOffset > 0) {
dst.left -= fixXOffset;
dst.right -= fixXOffset;
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
dst.left += fixXOffset;
dst.right += fixXOffset;
}
} else {
float left = (screenWidth - targetWidth) / 2;
dst.offsetTo(left, dst.top);
}
if (targetHeight > screenHeight) {
float fixYOffset = dst.top;
if (fixYOffset > 0) {
dst.top -= fixYOffset;
dst.bottom -= fixYOffset;
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
dst.top += fixYOffset;
dst.bottom += fixYOffset;
}
} else {
float top = (screenHeight - targetHeight) / 2;
dst.offsetTo(dst.left, top);
}
}
public void setScaleOffset(@Scale int scaleMode, @StartPosition int startPosition, float scaleValue) {
mScaleMode = scaleMode;
mStartPosition = startPosition;
mScaleValue = scaleValue;
int screenWidth = getWidth();
int screenHeight = getHeight();
if (mImageTexture == null || screenWidth == 0 || screenHeight == 0) {
mScaleOffsetDirty = true;
return;
}
int textureWidth = mTextureWidth;
int textureHeight = mTextureHeight;
// Set scale
float targetWidth;
float targetHeight;
switch (scaleMode) {
case SCALE_ORIGIN:
mScale = 1.0f;
targetWidth = textureWidth;
targetHeight = textureHeight;
break;
case SCALE_FIT_WIDTH:
mScale = (float) screenWidth / textureWidth;
targetWidth = screenWidth;
targetHeight = textureHeight * mScale;
break;
case SCALE_FIT_HEIGHT:
mScale = (float) screenHeight / textureHeight;
targetWidth = textureWidth * mScale;
targetHeight = screenHeight;
break;
case SCALE_FIT:
float scaleX = (float) screenWidth / textureWidth;
float scaleY = (float) screenHeight / textureHeight;
if (scaleX < scaleY) {
mScale = scaleX;
targetWidth = screenWidth;
targetHeight = textureHeight * scaleX;
} else {
mScale = scaleY;
targetWidth = textureWidth * scaleY;
targetHeight = screenHeight;
break;
}
break;
case SCALE_FIXED:
default:
mScale = scaleValue;
targetWidth = textureWidth * scaleValue;
targetHeight = textureHeight * scaleValue;
break;
}
// adjust scale, not too big, not too small
if (mScale < SCALE_MIN) {
mScale = SCALE_MIN;
targetWidth = textureWidth * SCALE_MIN;
targetHeight = textureHeight * SCALE_MIN;
} else if (mScale > SCALE_MAX) {
mScale = SCALE_MAX;
targetWidth = textureWidth * SCALE_MAX;
targetHeight = textureHeight * SCALE_MAX;
}
// Set mDst.left and mDst.right
RectF dst = mDst;
switch (startPosition) {
case START_POSITION_TOP_LEFT:
dst.left = 0;
dst.top = 0;
break;
case START_POSITION_TOP_RIGHT:
dst.left = screenWidth - targetWidth;
dst.top = 0;
break;
case START_POSITION_BOTTOM_LEFT:
dst.left = 0;
dst.top = screenHeight - targetHeight;
break;
case START_POSITION_BOTTOM_RIGHT:
dst.left = screenWidth - targetWidth;
dst.top = screenHeight - targetHeight;
break;
case START_POSITION_CENTER:
default:
dst.left = (screenWidth - targetWidth) / 2;
dst.top = (screenHeight - targetHeight) / 2;
break;
}
// Set mDst.right and mDst.bottom
dst.right = dst.left + targetWidth;
dst.bottom = dst.top + targetHeight;
// adjust position
adjustPosition();
mScaleOffsetDirty = false;
mPositionInRootDirty = true;
}
public void scroll(int dx, int dy, int[] remain) {
// Only work after layout
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mScaleOffsetDirty) {
remain[0] = dx;
remain[1] = dy;
return;
}
RectF dst = mDst;
int screenWidth = getWidth();
int screenHeight = getHeight();
float targetWidth = dst.width();
float targetHeight = dst.height();
if (targetWidth > screenWidth) {
dst.left -= dx;
dst.right -= dx;
float fixXOffset = dst.left;
if (fixXOffset > 0) {
dst.left -= fixXOffset;
dst.right -= fixXOffset;
remain[0] = -(int) fixXOffset;
} else if ((fixXOffset = screenWidth - dst.right) > 0) {
dst.left += fixXOffset;
dst.right += fixXOffset;
remain[0] = (int) fixXOffset;
} else {
remain[0] = 0;
}
} else {
remain[0] = dx;
}
if (targetHeight > screenHeight) {
dst.top -= dy;
dst.bottom -= dy;
float fixYOffset = dst.top;
if (fixYOffset > 0) {
dst.top -= fixYOffset;
dst.bottom -= fixYOffset;
remain[1] = -(int) fixYOffset;
} else if ((fixYOffset = screenHeight - dst.bottom) > 0) {
dst.top += fixYOffset;
dst.bottom += fixYOffset;
remain[1] = (int) fixYOffset;
} else {
remain[1] = 0;
}
} else {
remain[1] = dy;
}
if (dx != remain[0] || dy != remain[1]) {
mPositionInRootDirty = true;
invalidate();
}
}
public void scale(float focusX, float focusY, float scale) {
// Only work after layout
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mScaleOffsetDirty) {
return;
}
if ((mScale == SCALE_MAX && scale >= 1.0f) || (mScale == SCALE_MIN && scale < 1.0f)) {
return;
}
float newScale = mScale * scale;
newScale = MathUtils.clamp(newScale, SCALE_MIN, SCALE_MAX);
mScale = newScale;
RectF dst = mDst;
float left = (focusX - ((focusX - dst.left) * scale));
float top = (focusY - ((focusY - dst.top) * scale));
dst.set(left, top,
(left + (mImageTexture.getWidth() * newScale)),
(top + (mImageTexture.getHeight() * newScale)));
// adjust position
adjustPosition();
mPositionInRootDirty = true;
invalidate();
}
private void applyPositionInRoot() {
int width = mImageTexture.getWidth();
int height = mImageTexture.getHeight();
RectF dst = mDst;
RectF dstActual = mDstActual;
RectF srcActual = mSrcActual;
dstActual.set(dst);
getValidRect(mValidRect);
if (dstActual.intersect(mValidRect.left, mValidRect.top, mValidRect.right, mValidRect.bottom)) {
srcActual.left = MathUtils.lerp(0, width,
MathUtils.delerp(dst.left, dst.right, dstActual.left));
srcActual.right = MathUtils.lerp(0, width,
MathUtils.delerp(dst.left, dst.right, dstActual.right));
srcActual.top = MathUtils.lerp(0, height,
MathUtils.delerp(dst.top, dst.bottom, dstActual.top));
srcActual.bottom = MathUtils.lerp(0, height,
MathUtils.delerp(dst.top, dst.bottom, dstActual.bottom));
} else {
// Can't be seen, set src and dst empty
srcActual.setEmpty();
dstActual.setEmpty();
}
mPositionInRootDirty = false;
}
@Override
public void onRender(GLCanvas canvas) {
Texture texture = mImageTexture;
if (texture == null) {
return;
}
if (mScaleOffsetDirty) {
setScaleOffset(mScaleMode, mStartPosition, mScaleValue);
}
if (mPositionInRootDirty) {
applyPositionInRoot();
}
if (!mSrcActual.isEmpty()) {
texture.draw(canvas, mSrcActual, mDstActual);
}
}
@Override
public void invalidateImageTexture(ImageTexture who) {
invalidate();
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -27,7 +27,7 @@ import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhCacheKeyFactory;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.gallery.GalleryProvider;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.io.UniFileInputStreamPipe;
import com.hippo.io.UniFileOutputStreamPipe;
import com.hippo.streampipe.InputStreamPipe;
@@ -169,7 +169,7 @@ public final class SpiderDen {
@Nullable
private static UniFile findImageFile(UniFile dir, int index) {
for (String extension : GalleryProvider.SUPPORT_IMAGE_EXTENSIONS) {
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
String filename = generateImageFilename(index, extension);
UniFile file = dir.findFile(filename);
if (file != null) {
@@ -193,10 +193,10 @@ public final class SpiderDen {
* @param extension with dot
*/
private String fixExtension(String extension) {
if (Utilities.contain(GalleryProvider.SUPPORT_IMAGE_EXTENSIONS, extension)) {
if (Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
return extension;
} else {
return GalleryProvider.SUPPORT_IMAGE_EXTENSIONS[0];
return GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
}
}
@@ -275,8 +275,8 @@ public final class SpiderDen {
}
boolean result = false;
for (int i = 0, n = GalleryProvider.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
String filename = generateImageFilename(index, GalleryProvider.SUPPORT_IMAGE_EXTENSIONS[i]);
for (int i = 0, n = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS.length; i < n; i++) {
String filename = generateImageFilename(index, GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[i]);
UniFile file = dir.subFile(filename);
if (file != null) {
result |= file.delete();
@@ -45,8 +45,9 @@ import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.ehviewer.client.parser.GalleryDetailParser;
import com.hippo.ehviewer.client.parser.GalleryPageParser;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.gallery.GalleryProvider;
import com.hippo.ehviewer.gallery.gl.GalleryPageView;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.image.Image;
import com.hippo.streampipe.InputStreamPipe;
import com.hippo.streampipe.OutputStreamPipe;
@@ -1092,8 +1093,8 @@ public final class SpiderQueen implements Runnable {
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(
responseBody.contentType().toString());
// Ensure extension
if (!Utilities.contain(GalleryProvider.SUPPORT_IMAGE_EXTENSIONS, extension)) {
extension = GalleryProvider.SUPPORT_IMAGE_EXTENSIONS[0];
if (!Utilities.contain(GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS, extension)) {
extension = GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS[0];
}
// Get out put pipe
@@ -25,6 +25,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -52,16 +53,15 @@ import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.gallery.DirGalleryProvider;
import com.hippo.ehviewer.gallery.EhGalleryProvider;
import com.hippo.ehviewer.gallery.GalleryProvider;
import com.hippo.ehviewer.gallery.GalleryProviderListener;
import com.hippo.ehviewer.gallery.GalleryProvider2;
import com.hippo.ehviewer.gallery.ZipGalleryProvider;
import com.hippo.ehviewer.gallery.gl.GalleryPageView;
import com.hippo.ehviewer.gallery.gl.GalleryView;
import com.hippo.ehviewer.gallery.gl.ImageView;
import com.hippo.ehviewer.widget.GalleryGuideView;
import com.hippo.ehviewer.widget.ReversibleSeekBar;
import com.hippo.glview.image.ImageTexture;
import com.hippo.glview.image.ImageWrapper;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.glgallery.GalleryView;
import com.hippo.glgallery.SimpleAdapter;
import com.hippo.glgallery.SimpleProviderListener;
import com.hippo.glview.view.GLRootView;
import com.hippo.unifile.UniFile;
import com.hippo.util.SystemUiHelper;
@@ -69,14 +69,14 @@ import com.hippo.widget.ColorView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.ConcurrentPool;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.ResourcesUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.ViewUtils;
import java.io.File;
public class GalleryActivity extends EhActivity
implements GalleryProviderListener, SeekBar.OnSeekBarChangeListener,
public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChangeListener,
GalleryView.Listener {
public static final String ACTION_DIR = "dir";
@@ -103,11 +103,10 @@ public class GalleryActivity extends EhActivity
private GLRootView mGLRootView;
@Nullable
private GalleryView mGalleryView;
@Nullable
private ImageTexture.Uploader mUploader;
private GalleryProvider2 mGalleryProvider;
@Nullable
private GalleryProvider mGalleryProvider;
private ProviderListener mProviderListener;
@Nullable
private SystemUiHelper mSystemUiHelper;
@@ -236,6 +235,7 @@ public class GalleryActivity extends EhActivity
}
@Override
@SuppressWarnings("deprecation")
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (Settings.getReadingFullscreen() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window w = getWindow();
@@ -257,14 +257,8 @@ public class GalleryActivity extends EhActivity
finish();
return;
}
mGalleryProvider.setGalleryProviderListener(this);
mGalleryProvider.start();
setContentView(R.layout.activity_gallery);
mGLRootView = (GLRootView) ViewUtils.$$(this, R.id.gl_root_view);
mGalleryProvider.setGLRoot(mGLRootView);
mUploader = new ImageTexture.Uploader(mGLRootView);
// Get start page
int startPage;
if (savedInstanceState == null) {
startPage = mPage >= 0 ? mPage : mGalleryProvider.getStartPage();
@@ -272,10 +266,39 @@ public class GalleryActivity extends EhActivity
startPage = mCurrentIndex;
}
mGalleryView = new GalleryView(this, new GalleryAdapter(), this,
Settings.getReadingDirection(), Settings.getPageScaling(),
Settings.getStartPosition(), startPage);
setContentView(R.layout.activity_gallery);
mGLRootView = (GLRootView) ViewUtils.$$(this, R.id.gl_root_view);
Resources resources = getResources();
int primaryColor = ResourcesUtils.getAttrColor(this, R.attr.colorPrimary);
mGalleryView = new GalleryView.Builder(this, new SimpleAdapter(mGalleryProvider))
.setListener(this)
.setLayoutMode(Settings.getReadingDirection())
.setScaleMode(Settings.getPageScaling())
.setStartPosition(Settings.getStartPosition())
.setStartPage(startPage)
.setBackgroundColor(resources.getColor(R.color.gallery_background))
.setEdgeColor(primaryColor & 0xffffff | 0x33000000)
.setPagerInterval(resources.getDimensionPixelOffset(R.dimen.gallery_pager_interval))
.setScrollInterval(resources.getDimensionPixelOffset(R.dimen.gallery_scroll_interval))
.setPageMinHeight(resources.getDimensionPixelOffset(R.dimen.gallery_page_min_height))
.setPageInfoInterval(resources.getDimensionPixelOffset(R.dimen.gallery_page_info_interval))
.setProgressColor(primaryColor)
.setProgressSize(resources.getDimensionPixelOffset(R.dimen.gallery_progress_size))
.setPageTextColor(resources.getColor(R.color.secondary_text_default_dark))
.setPageTextSize(resources.getDimensionPixelOffset(R.dimen.gallery_page_text_size))
.setPageTextTypeface(Typeface.DEFAULT)
.setErrorTextColor(resources.getColor(R.color.red_500))
.setErrorTextSize(resources.getDimensionPixelOffset(R.dimen.gallery_error_text_size))
.setDefaultErrorString(resources.getString(R.string.error_unknown))
.setEmptyString(resources.getString(R.string.error_empty))
.build();
mProviderListener = new ProviderListener(mGLRootView, mGalleryView, mGalleryProvider);
mGLRootView.setContentPane(mGalleryView);
mGalleryProvider.setListener(mProviderListener);
mGalleryProvider.setGLRoot(mGLRootView);
mGalleryProvider.start();
// System UI helper
if (Settings.getReadingFullscreen()) {
@@ -347,12 +370,12 @@ public class GalleryActivity extends EhActivity
super.onDestroy();
mGLRootView = null;
mGalleryView = null;
if (mUploader != null) {
mUploader.clear();
mUploader = null;
if (mProviderListener != null) {
mProviderListener.clearUploader();
mProviderListener = null;
}
if (mGalleryProvider != null) {
mGalleryProvider.setGalleryProviderListener(null);
mGalleryProvider.setListener(null);
mGalleryProvider.stop();
mGalleryProvider = null;
}
@@ -409,14 +432,14 @@ public class GalleryActivity extends EhActivity
// Check volume
if (Settings.getVolumePage()) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
if (mLayoutMode == GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageRight();
} else {
mGalleryView.pageLeft();
}
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (mLayoutMode == GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageLeft();
} else {
mGalleryView.pageRight();
@@ -429,7 +452,7 @@ public class GalleryActivity extends EhActivity
switch (keyCode) {
case KeyEvent.KEYCODE_PAGE_UP:
case KeyEvent.KEYCODE_DPAD_UP:
if (mLayoutMode == GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageRight();
} else {
mGalleryView.pageLeft();
@@ -440,7 +463,7 @@ public class GalleryActivity extends EhActivity
return true;
case KeyEvent.KEYCODE_PAGE_DOWN:
case KeyEvent.KEYCODE_DPAD_DOWN:
if (mLayoutMode == GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
mGalleryView.pageLeft();
} else {
mGalleryView.pageRight();
@@ -513,7 +536,7 @@ public class GalleryActivity extends EhActivity
TextView start;
TextView end;
if (mLayoutMode == GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
start = mRightText;
end = mLeftText;
mSeekBar.setReverse(true);
@@ -528,96 +551,11 @@ public class GalleryActivity extends EhActivity
mSeekBar.setProgress(mCurrentIndex);
}
@Override
public void onDataChanged() {
if (mGalleryView != null) {
mGalleryView.onDataChanged();
}
if (mGalleryProvider != null) {
int size = mGalleryProvider.size();
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setData(NotifyTask.KEY_SIZE, size);
SimpleHandler.getInstance().post(task);
}
}
@Override
public void onPageWait(int index) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
page.setPage(index + 1);
page.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
page.setError(null, null);
}
}
@Override
public void onPagePercent(int index, float percent) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
page.showInfo();
page.setImage(null);
page.setPage(index + 1);
page.setProgress(percent);
page.setError(null, null);
}
}
@Override
public void onPageSucceed(int index, ImageWrapper image) {
GalleryPageView page = findPageByIndex(index);
if (page != null) {
if (image.obtain()) {
ImageTexture imageTexture = new ImageTexture(image);
if (mUploader != null) {
mUploader.addTexture(imageTexture);
}
page.showImage();
page.setImage(imageTexture);
page.setPage(index + 1);
page.setProgress(GalleryPageView.PROGRESS_GONE);
page.setError(null, null);
} else {
// The image is recycled, request again.
// TODO request loop ?
if (mGalleryProvider != null) {
mGalleryProvider.request(index);
}
}
}
}
@Override
public void onPageFailed(int index, String error) {
GalleryPageView page = findPageByIndex(index);
if (page != null && mGalleryView != null) {
page.showInfo();
page.setImage(null);
page.setPage(index + 1);
page.setProgress(GalleryPageView.PROGRESS_GONE);
page.setError(error, mGalleryView);
}
}
@Override
public void onDataChanged(int index) {
GalleryPageView page = findPageByIndex(index);
if (page != null && mGalleryProvider != null) {
mGalleryProvider.request(index);
}
}
@Override
@SuppressLint("SetTextI18n")
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
TextView start;
if (mLayoutMode == GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT) {
if (mLayoutMode == GalleryView.LAYOUT_RIGHT_TO_LEFT) {
start = mRightText;
} else {
start = mLeftText;
@@ -816,8 +754,8 @@ public class GalleryActivity extends EhActivity
public void onClick(DialogInterface dialog, int which) {
int screenRotation = mScreenRotation.getSelectedItemPosition();
int layoutMode = GalleryView.sanitizeLayoutMode(mReadingDirection.getSelectedItemPosition());
int scaleMode = ImageView.sanitizeScaleMode(mScaleMode.getSelectedItemPosition());
int startPosition = ImageView.sanitizeStartPosition(mStartPosition.getSelectedItemPosition());
int scaleMode = GalleryView.sanitizeScaleMode(mScaleMode.getSelectedItemPosition());
int startPosition = GalleryView.sanitizeStartPosition(mStartPosition.getSelectedItemPosition());
boolean keepScreenOn = mKeepScreenOn.isChecked();
boolean showClock = mShowClock.isChecked();
boolean showProgress = mShowProgress.isChecked();
@@ -1039,46 +977,26 @@ public class GalleryActivity extends EhActivity
}
}
private class GalleryAdapter extends GalleryView.Adapter {
private class ProviderListener extends SimpleProviderListener {
@Override
public String getError() {
if (mGalleryProvider == null) {
return getString(R.string.error_no_provider);
} else if (mGalleryProvider.size() <= GalleryProvider.STATE_ERROR) {
return mGalleryProvider.getError();
}
return null;
public ProviderListener(@NonNull GLRootView glRootView,
@NonNull GalleryView galleryView, @NonNull GalleryProvider provider) {
super(glRootView, galleryView, provider);
}
@Override
public int size() {
if (mGalleryProvider == null) {
return GalleryProvider.STATE_ERROR;
} else {
return mGalleryProvider.size();
}
}
public void onDataChanged() {
super.onDataChanged();
@Override
public void onBind(GalleryPageView view, int index) {
if (mGalleryProvider != null && mGalleryView != null) {
mGalleryProvider.request(index);
view.showInfo();
view.setImage(null);
view.setPage(index + 1);
view.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
view.setError(null, null);
}
}
@Override
public void onUnbind(GalleryPageView view, int index) {
if (mGalleryProvider != null) {
mGalleryProvider.cancelRequest(index);
int size = mGalleryProvider.size();
NotifyTask task = mNotifyTaskPool.pop();
if (task == null) {
task = new NotifyTask();
}
task.setData(NotifyTask.KEY_SIZE, size);
SimpleHandler.getInstance().post(task);
}
view.setImage(null);
view.setError(null, null);
}
}
}