Files
EhViewer/src/com/hippo/ehviewer/gallery/GalleryView.java
T
2014-10-08 23:22:38 +08:00

1650 lines
54 KiB
Java

/*
* Copyright (C) 2014 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.content.Context;
import android.graphics.Bitmap;
import android.graphics.Movie;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.view.animation.AnticipateInterpolator;
import com.hippo.ehviewer.AppHandler;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.gallery.data.ImageSet;
import com.hippo.ehviewer.gallery.glrenderer.BasicTexture;
import com.hippo.ehviewer.gallery.glrenderer.BitmapTexture;
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
import com.hippo.ehviewer.gallery.glrenderer.GLPaint;
import com.hippo.ehviewer.gallery.glrenderer.ImageTexture;
import com.hippo.ehviewer.gallery.glrenderer.MovieTexture;
import com.hippo.ehviewer.gallery.glrenderer.StringTexture;
import com.hippo.ehviewer.gallery.image.Image;
import com.hippo.ehviewer.gallery.ui.GLView;
import com.hippo.ehviewer.gallery.ui.GestureRecognizer;
import com.hippo.ehviewer.gallery.util.GalleryUtils;
import com.hippo.ehviewer.util.Config;
import com.hippo.ehviewer.util.Constants;
import com.hippo.ehviewer.util.MathUtils;
import com.hippo.ehviewer.util.TimeRunner;
import com.hippo.ehviewer.util.Ui;
import com.hippo.ehviewer.util.Utils;
import com.hippo.ehviewer.widget.MaterialToast;
public class GalleryView extends GLView
implements ImageSet.ImageListener, Runnable {
@SuppressWarnings("unused")
private static final String TAG = GalleryView.class.getSimpleName();
public static final int INVALID_ID = -1;
private static final int STATE_FIRST = 0x1;
private static final int STATE_LAST = 0x2;
private static final int STATE_NONE = 0x0;
// scale mode
private static final int ORGIN = 0x0;
private static final int FIT_WIDTH = 0x1;
private static final int FIT_HEIGHT = 0x2;
private static final int FIT = 0x3;
private static final int FIXED = 0x4;
// start position mode
private static final int TOP_LEFT = 0x0;
private static final int TOP_RIGHT = 0x1;
private static final int BOTTOM_LEFT = 0x2;
private static final int BOTTOM_RIGHT = 0x3;
private static final int CENTER = 0x4;
// Scroll state
private static final int SCROLL_NONE = 0x0;
private static final int SCROLL_LEFT = 0x1;
private static final int SCROLL_RIGHT = 0x2;
private static final int SCROLL_ANIME_LEFT = 0x3;
private static final int SCROLL_ANIME_RIGHT = 0x4;
private static final int PRE_TARGET_INDEX = 0;
private static final int CUR_TARGET_INDEX = 1;
private static final int NEXT_TARGET_INDEX = 2;
private static final int TARGET_INDEX_SIZE = 3;
// Area
private static final float[] LEFT_AREA_SCALE = {0, 0, 2/7.0f, 1};
private static final float[] TOP_AREA_SCALE = {2/7.0f, 0, 5/7.0f, 3/8.0f};
private static final float[] RIGHT_AREA_SCALE = {5/7.0f, 0, 1, 1};
private static final float[] BOTTOM_AREA_SCALE = {2/7.0f, 5/8.0f, 5/7.0f, 1};
private static final float[] CENTER_AREA_SCALE = {2/7.0f, 3/8.0f, 5/7.0f, 5/8.0f};
private static final float MILLSEC_PER_DIX = 0.2f;
private static final float CHANGE_PAGE_PROPORTION = 0.1f;
private static final float SLIDE_SENSITIVITY = 0;
private static final float BORDER_TIP_SENSITIVITY = Ui.dp2pix(48);
private static final int ANIMATE_MIN = 200;
// TODO 根据图片的大小调整缩放界限
private static final float SCALE_MIN = 1/10.0f;
private static final float SCALE_MAX = 10.0f;
private static final float LINE_WIDTH = Ui.dp2pix(3);
private static final int LINE_COLOR = -1; // White
private static final int TAP_AREA_TEXT_COLOR = -1; // White
private static final int TAP_AREA_TEXT_SIZE = Ui.dp2pix(24);
private static final int TAP_AREA_MASK_COLOR = 0x88000000;
private static final int BACKGROUND_COLOR = 0xff212121;
@SuppressWarnings("unused")
private static final int MASK_COLOR = 0x88000000;
private final GestureRecognizer mGestureRecognizer;
private final Context mContext;
private final ImageSet mImageSet;
private int mState;
// scale and scroll only can choose one
private final int scaleMode;
private final int startMode;
private int mScrollState = SCROLL_NONE;
private int scrollXOffset = 0;
private int scrollYOffset = 0;
private int stopScrollXOffset = 0;
@SuppressWarnings("unused")
private int stopScrollYOffset = 0;
private float mScale = 1;
private int mScreenWidth = -1;
private int mScreenHeight = -1;
// at most keep three item
private volatile ShowItem[] showItems;
private int mCurIndex;
private int mSize;
private boolean isEnsureSize;
private boolean mInit = false;
private int[] leftArea;
private int[] topArea;
private int[] rightArea;
private int[] bottomArea;
private int[] centerArea;
private Text leftText;
private Text topText;
private Text rightText;
private Text bottomText;
private Text centerText;
private boolean mDoubleTapAnimating = false;
private boolean mShowTapArea = false;
private boolean mShowTapAreaTurn = false;
//private boolean mLocked = false;
//private boolean mLocked = false;
private OnTapTextListener mOnTapTextListener;
private GalleryViewListener mGalleryViewListener;
public interface GalleryViewListener {
public void onTapCenter();
public void onPageChanged(int index);
public void onSizeUpdate(int size);
}
public interface OnTapTextListener {
public void onTapText(int index);
public void onTapDoubleText(int index);
}
public void setOnTapTextListener(OnTapTextListener l) {
mOnTapTextListener = l;
}
public void setGalleryViewListener(GalleryViewListener l) {
mGalleryViewListener = l;
}
public GalleryView(Context context, ImageSet imageSet, int startIndex) {
mContext = context;
mImageSet = imageSet;
mCurIndex = startIndex;
showItems = new ShowItem[TARGET_INDEX_SIZE];
checkSize();
// adjust mCurIndex
mCurIndex = MathUtils.clamp(mCurIndex, 0, mSize - 1);
setState();
mGestureRecognizer = new GestureRecognizer(mContext, new MyGestureListener());
setBackgroundColor(GalleryUtils.intColorToFloatARGBArray(BACKGROUND_COLOR));
// Init config
scaleMode = Config.getPageScalingMode();
startMode = Config.getStartPosition();
mImageSet.setImageListener(this);
// Start task to check size
AppHandler.getInstance().postDelayed(this, 500);
// Try to make current index show image
if (mCurIndex != startIndex)
mImageSet.setTargetIndex(mCurIndex);
// First time to load image in onLayout()
}
// The task to update size
@Override
public void run() {
checkSize();
setState();
if (!isEnsureSize && !mImageSet.isStop())
AppHandler.getInstance().postDelayed(this, 500);
}
/**
* Check size
*/
private void checkSize() {
int oldSize = mSize;
int size = mImageSet.getSize();
if (size == -1) {
isEnsureSize = false;
mSize = mImageSet.getEnsureSize();
} else {
isEnsureSize = true;
mSize = size;
}
if (mSize > oldSize && mCurIndex == oldSize - 1 && showItems[NEXT_TARGET_INDEX] == null) {
mState = STATE_NONE;
loadImage(mCurIndex + 1);
}
if (mSize != oldSize && mGalleryViewListener != null)
mGalleryViewListener.onSizeUpdate(mSize);
}
/**
* Set current page state, first page or last page
*/
private void setState() {
mState = STATE_NONE;
if (mCurIndex == 0)
mState = STATE_FIRST;
if (mCurIndex == mSize - 1)
mState = STATE_LAST;
}
private boolean isIndexLoadable(int index) {
int targetIndex = index - mCurIndex + 1;
return targetIndex == CUR_TARGET_INDEX ||
(mState != STATE_FIRST && targetIndex == PRE_TARGET_INDEX) ||
(mState != STATE_LAST && targetIndex == NEXT_TARGET_INDEX);
}
private boolean isIndexValid(int index) {
return index >= 0 && index < mSize;
}
@Override
public void onGetImage(int index) {
if (isIndexLoadable(index)) {
loadImage(index);
}
}
@Override
public synchronized void onDownloading(int index, float percent) {
int targetIndex = index - mCurIndex + 1;
if (isIndexLoadable(index)) {
// Free what do not need
if (showItems[targetIndex] != null)
showItems[targetIndex].recycle();
showItems[targetIndex] = new Text(
String.format(mContext.getString(R.string.loading_percent),
Math.round(percent * 100)));
resetSizePosition(targetIndex);
}
}
@Override
public synchronized void onDecodeOver(int index, Object res) {
int targetIndex = index - mCurIndex + 1;
if (!isIndexLoadable(index)) {
// If it do not need any more, free
if (res != null && res instanceof Bitmap)
((Bitmap)res).recycle();
} else {
// Free what do not need
if (showItems[targetIndex] != null)
showItems[targetIndex].recycle();
if (res == null) {
showItems[targetIndex] = new Text(
mContext.getString(R.string.read_image_error));
} else {
if (res instanceof Bitmap) {
BitmapItem bi = new BitmapItem();
bi.load((Bitmap)res);
showItems[targetIndex] = bi;
} else if(res instanceof Image) {
ImageItem ii = new ImageItem();
ii.load((Image)res);
showItems[targetIndex] = ii;
} else {
MovieItem mi = new MovieItem();
mi.load((Movie)res);
showItems[targetIndex] = mi;
}
}
resetSizePosition(targetIndex);
}
}
public int getCurIndex() {
return mCurIndex;
}
public int getSize() {
return mSize;
}
private void drawTapArea(GLCanvas canvas) {
// Background
canvas.fillRect(0, 0, mScreenWidth, mScreenHeight, TAP_AREA_MASK_COLOR);
drawRect(canvas, leftArea);
drawRect(canvas, topArea);
drawRect(canvas, rightArea);
drawRect(canvas, bottomArea);
drawRect(canvas, centerArea);
leftText.draw(canvas);
topText.draw(canvas);
rightText.draw(canvas);
bottomText.draw(canvas);
centerText.draw(canvas);
}
private void drawRect(GLCanvas canvas, int[] rect) {
int left = rect[0];
int top = rect[1];
int right = rect[2];
int bottom = rect[3];
GLPaint paint = new GLPaint(LINE_COLOR, LINE_WIDTH);
canvas.drawLine(left, top, right, top, paint);
canvas.drawLine(left, bottom, right, bottom, paint);
canvas.drawLine(left, top, left, bottom, paint);
canvas.drawLine(right, top, right, bottom, paint);
}
@Override
protected synchronized void render(GLCanvas canvas) {
// If it is not render thread, do not render
super.render(canvas);
boolean needRefresh = false;
ShowItem item;
switch (mScrollState) {
case SCROLL_NONE:
item = showItems[CUR_TARGET_INDEX];
if (item != null)
item.draw(canvas);
if (item instanceof ImageItem && ((ImageItem)item).isAnimated())
needRefresh |= true;
if (item instanceof MovieItem)
needRefresh |= true;
break;
case SCROLL_LEFT:
case SCROLL_ANIME_LEFT:
item = showItems[PRE_TARGET_INDEX];
if (item != null)
item.draw(canvas, scrollXOffset, scrollYOffset);
if (item instanceof ImageItem && ((ImageItem)item).isAnimated())
needRefresh |= true;
if (item instanceof MovieItem)
needRefresh |= true;
item = showItems[CUR_TARGET_INDEX];
if (item != null)
item.draw(canvas, scrollXOffset, scrollYOffset);
if (item instanceof ImageItem && ((ImageItem)item).isAnimated())
needRefresh |= true;
if (item instanceof MovieItem)
needRefresh |= true;
break;
case SCROLL_RIGHT:
case SCROLL_ANIME_RIGHT:
item = showItems[CUR_TARGET_INDEX];
if (item != null)
item.draw(canvas, scrollXOffset, scrollYOffset);
if (item instanceof ImageItem && ((ImageItem)item).isAnimated())
needRefresh |= true;
if (item instanceof MovieItem)
needRefresh |= true;
item = showItems[NEXT_TARGET_INDEX];
if (item != null)
item.draw(canvas, scrollXOffset, scrollYOffset);
if (item instanceof ImageItem && ((ImageItem)item).isAnimated())
needRefresh |= true;
if (item instanceof MovieItem)
needRefresh |= true;
break;
}
// Add mInit to make show the leftArea or other is not null
if (mShowTapArea && mInit)
drawTapArea(canvas);
// TODO Mask to reduce brightness
if (needRefresh)
invalidate();
}
private void resetSizePosition() {
resetSizePosition(PRE_TARGET_INDEX);
resetSizePosition(CUR_TARGET_INDEX);
resetSizePosition(NEXT_TARGET_INDEX);
}
private void resetSizePosition(int targeIndex) {
ShowItem showItem = showItems[targeIndex];
if (showItem == null)
return;
int sumXOffset;
switch (targeIndex) {
case PRE_TARGET_INDEX:
sumXOffset = -mScreenWidth;
break;
case NEXT_TARGET_INDEX:
sumXOffset = mScreenWidth;
break;
case CUR_TARGET_INDEX:
default:
sumXOffset = 0;
break;
}
if (showItem instanceof Text) {
Text text = (Text)showItem;
int xOffset;
int yOffset;
Rect rect = text.mRect;
xOffset = (mScreenWidth - text.width)/2;
yOffset = (mScreenHeight - text.height)/2;
rect.left = sumXOffset + xOffset;
rect.top = yOffset;
rect.right = rect.left + text.width;
rect.bottom = rect.top + text.height;
} else if (showItem instanceof BasicItem) {
BasicItem image = (BasicItem)showItem;
// Set scale
int showWidth = 0;
int showHeight = 0;
Rect rect = image.mRect;
switch (scaleMode) {
case ORGIN:
image.imageScale = 1;
showWidth = image.width;
showHeight = image.height;
break;
case FIT_WIDTH:
image.imageScale = (float)mScreenWidth/image.width;
showWidth = mScreenWidth;
showHeight = (int)(image.height * image.imageScale);
break;
case FIT_HEIGHT:
image.imageScale = (float)mScreenHeight/image.height;
showWidth = (int)(image.width * image.imageScale);
showHeight = mScreenHeight;
break;
case FIT:
float scaleX = (float)mScreenWidth/image.width;
float scaleY = (float)mScreenHeight/image.height;
if (scaleX < scaleY) {
image.imageScale = scaleX;
showWidth = mScreenWidth;
showHeight = (int)(image.height * image.imageScale);
} else {
image.imageScale = scaleY;
showWidth = (int)(image.width * image.imageScale);
showHeight = mScreenHeight;
break;
}
break;
case FIXED:
default:
image.imageScale = mScale;
showWidth = (int)(image.width * mScale);
showHeight = (int)(image.height * mScale);
break;
}
// adjust scale
if (image.imageScale < SCALE_MIN) {
image.imageScale = SCALE_MIN;
showWidth = (int)(image.width * SCALE_MIN);
showHeight = (int)(image.height * SCALE_MIN);
} else if (image.imageScale > SCALE_MAX) {
image.imageScale = SCALE_MAX;
showWidth = (int)(image.width * SCALE_MAX);
showHeight = (int)(image.height * SCALE_MAX);
}
// set start position
int xOffset;
int yOffset;
switch (startMode) {
case TOP_LEFT:
xOffset = 0;
yOffset = 0;
break;
case TOP_RIGHT:
xOffset = mScreenWidth - showWidth;
yOffset = 0;
break;
case BOTTOM_LEFT:
xOffset = 0;
yOffset = mScreenHeight - showHeight;
break;
case BOTTOM_RIGHT:
xOffset = mScreenWidth - showWidth;
yOffset = mScreenHeight - showHeight;
break;
case CENTER:
default:
xOffset = (mScreenWidth - showWidth)/2;
yOffset = (mScreenHeight - showHeight)/2;
break;
}
rect.left = sumXOffset + xOffset;
rect.right = rect.left + showWidth;
rect.top = yOffset;
rect.bottom = rect.top + showHeight;
// adjust position
adjustPosition(image);
}
invalidate();
}
private void setCenterInArea(int[] area, ShowItem showItem) {
int xOffset = ((area[2] - area[0]) - showItem.width)/2;
int yOffset = ((area[3] - area[1]) - showItem.height)/2;
Rect rect = showItem.mRect;
rect.left = area[0] + xOffset;
rect.right = rect.left + showItem.width;
rect.top = area[1] + yOffset;
rect.bottom = rect.top + showItem.height;
}
private void setTapArea() {
leftArea = new int[]{
(int)(LEFT_AREA_SCALE[0] * mScreenWidth),
(int)(LEFT_AREA_SCALE[1] * mScreenHeight),
(int)(LEFT_AREA_SCALE[2] * mScreenWidth),
(int)(LEFT_AREA_SCALE[3] * mScreenHeight)};
topArea = new int[]{
(int)(TOP_AREA_SCALE[0] * mScreenWidth),
(int)(TOP_AREA_SCALE[1] * mScreenHeight),
(int)(TOP_AREA_SCALE[2] * mScreenWidth),
(int)(TOP_AREA_SCALE[3] * mScreenHeight)};
rightArea = new int[]{
(int)(RIGHT_AREA_SCALE[0] * mScreenWidth),
(int)(RIGHT_AREA_SCALE[1] * mScreenHeight),
(int)(RIGHT_AREA_SCALE[2] * mScreenWidth),
(int)(RIGHT_AREA_SCALE[3] * mScreenHeight)};
bottomArea = new int[]{
(int)(BOTTOM_AREA_SCALE[0] * mScreenWidth),
(int)(BOTTOM_AREA_SCALE[1] * mScreenHeight),
(int)(BOTTOM_AREA_SCALE[2] * mScreenWidth),
(int)(BOTTOM_AREA_SCALE[3] * mScreenHeight)};
centerArea = new int[]{
(int)(CENTER_AREA_SCALE[0] * mScreenWidth),
(int)(CENTER_AREA_SCALE[1] * mScreenHeight),
(int)(CENTER_AREA_SCALE[2] * mScreenWidth),
(int)(CENTER_AREA_SCALE[3] * mScreenHeight)};
if (leftText == null)
leftText = new Text(mContext.getString(R.string.pre_page),
TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR);
if (topText == null)
topText = new Text(mContext.getString(R.string.zoom_in),
TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR);
if (rightText == null)
rightText = new Text(mContext.getString(R.string.next_page),
TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR);
if (bottomText == null)
bottomText = new Text(mContext.getString(R.string.zoom_out),
TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR);
if (centerText == null)
centerText = new Text(mContext.getString(R.string.menu),
TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR);
setCenterInArea(leftArea, leftText);
setCenterInArea(topArea, topText);
setCenterInArea(rightArea, rightText);
setCenterInArea(bottomArea, bottomText);
setCenterInArea(centerArea, centerText);
}
@Override
protected void onLayout(
boolean changeSize, int left, int top, int right, int bottom) {
// Get View size
mScreenWidth = right - left;
mScreenHeight = bottom - top;
if (!mInit) {
mInit = true;
loadImage(mCurIndex);
if ((mState & STATE_LAST) == 0)
loadImage(mCurIndex + 1);
if ((mState & STATE_FIRST) == 0)
loadImage(mCurIndex - 1);
} else {
resetSizePosition();
}
// When screen orientation change, need to reset tap area
setTapArea();
invalidate();
}
/**
* Requset target image from ImageSet,
*
* @param index
*/
private synchronized void loadImage(int index) {
if (!isIndexLoadable(index))
return;
int targetIndex = index - mCurIndex + 1;
Object obj = mImageSet.getImage(index);
if (showItems[targetIndex] != null)
showItems[targetIndex].recycle();
if (obj instanceof Integer) {
int state = (Integer)obj;
if (state == ImageSet.RESULT_DECODE) {
showItems[targetIndex] = null;
} else if (state == ImageSet.RESULT_DOWNLOADING) {
showItems[targetIndex] = new Text(
mContext.getString(R.string.downloading));
} else if (state == ImageSet.RESULT_NONE) {
showItems[targetIndex] = new Text(
mContext.getString(R.string.not_loaded));
}
} else if (obj instanceof Float) {
showItems[targetIndex] = new Text(
String.format(mContext.getString(R.string.loading_percent),
Math.round((Float)obj * 100)));
}
resetSizePosition(targetIndex);
}
/**
* If cur page is first page return true
* @return
*/
private boolean isFirstPage() {
return (mState & STATE_FIRST) != 0;
}
/**
* If cur page is last page return true
* @return
*/
private boolean isLastPage() {
return (mState & STATE_LAST) != 0;
}
/**
*
* @param mode
* true zoom in, false zoom out
* @return
*/
private boolean zoom(boolean mode) {
ShowItem curShowItem;
curShowItem = showItems[CUR_TARGET_INDEX];
if (curShowItem == null || !(curShowItem instanceof BasicItem))
return false;
BasicItem image = (BasicItem)curShowItem;
float newScale;
if (mode) {
newScale = image.imageScale * 1.1f;
if (newScale == SCALE_MAX)
return false;
if (newScale > SCALE_MAX)
newScale = SCALE_MAX;
}
else {
newScale = image.imageScale * 0.9f;
if (newScale == SCALE_MIN)
return false;
if (newScale < SCALE_MIN)
newScale = SCALE_MIN;
}
image.imageScale = newScale;
mScale = newScale;
Rect rect = image.mRect;
int width = (int)(image.width * newScale);
int height = (int)(image.height * newScale);
int xOffset = (width - rect.width())/2;
int yOffset = (height - rect.height())/2;
rect.set(rect.left - xOffset, rect.top - yOffset, rect.right + xOffset, rect.bottom + yOffset);
adjustPosition(curShowItem);
invalidate();
return true;
}
/**
* You'd better resetSizePosition(PRE_TARGET_INDEX) before
* @return
*/
private synchronized boolean goToPrePage() {
if (isFirstPage())
return false;
ShowItem showItem;
if (showItems[NEXT_TARGET_INDEX] != null) {
showItems[NEXT_TARGET_INDEX].recycle();
showItems[NEXT_TARGET_INDEX] = null;
}
showItems[NEXT_TARGET_INDEX] = showItems[CUR_TARGET_INDEX];
showItems[CUR_TARGET_INDEX] = showItems[PRE_TARGET_INDEX];
showItems[PRE_TARGET_INDEX] = null;
// adjust rect
showItem = showItems[NEXT_TARGET_INDEX];
if (showItem != null) {
showItem.mRect.offset(mScreenWidth, 0);
}
showItem = showItems[CUR_TARGET_INDEX];
if (showItem != null) {
showItem.mRect.offset(mScreenWidth, 0);
}
mCurIndex--;
setState();
loadImage(mCurIndex-1);
mImageSet.setCurReadIndex(mCurIndex);
if (mGalleryViewListener != null) {
mGalleryViewListener.onPageChanged(mCurIndex);
}
invalidate();
return true;
}
/**
* You'd better resetSizePosition(NEXT_TARGET_INDEX) before
* @return
*/
private synchronized boolean goToNextPage() {
if (isLastPage())
return false;
ShowItem showItem;
if (showItems[PRE_TARGET_INDEX] != null) {
showItems[PRE_TARGET_INDEX].recycle();
showItems[PRE_TARGET_INDEX] = null;
}
showItems[PRE_TARGET_INDEX] = showItems[CUR_TARGET_INDEX];
showItems[CUR_TARGET_INDEX] = showItems[NEXT_TARGET_INDEX];
showItems[NEXT_TARGET_INDEX] = null;
// adjust rect
showItem = showItems[PRE_TARGET_INDEX];
if (showItem != null) {
showItem.mRect.offset(-mScreenWidth, 0);
}
showItem = showItems[CUR_TARGET_INDEX];
if (showItem != null) {
showItem.mRect.offset(-mScreenWidth, 0);
}
mCurIndex++;
setState();
loadImage(mCurIndex+1);
mImageSet.setCurReadIndex(mCurIndex);
if (mGalleryViewListener != null) {
mGalleryViewListener.onPageChanged(mCurIndex);
}
invalidate();
return true;
}
public synchronized boolean goToPage(int index) {
if (!isIndexValid(index))
return false;
if (index == mCurIndex) {
return true;
} else if (index == mCurIndex - 1) {
return goToPrePage();
} else if (index == mCurIndex + 1) {
return goToNextPage();
} else {
// Free
if (showItems[PRE_TARGET_INDEX] != null) {
showItems[PRE_TARGET_INDEX].recycle();
showItems[PRE_TARGET_INDEX] = null;
}
if (showItems[CUR_TARGET_INDEX] != null) {
showItems[CUR_TARGET_INDEX].recycle();
showItems[CUR_TARGET_INDEX] = null;
}
if (showItems[NEXT_TARGET_INDEX] != null) {
showItems[NEXT_TARGET_INDEX].recycle();
showItems[NEXT_TARGET_INDEX] = null;
}
mCurIndex = index;
setState();
loadImage(mCurIndex-1);
loadImage(mCurIndex);
loadImage(mCurIndex+1);
mImageSet.setCurReadIndex(mCurIndex);
if (mGalleryViewListener != null) {
mGalleryViewListener.onPageChanged(mCurIndex);
}
invalidate();
return true;
}
}
public synchronized void free() {
for (int i = 0; i < TARGET_INDEX_SIZE; i++) {
if (showItems[i] != null) {
showItems[i].recycle();
showItems[i] = null;
}
}
}
@Override
protected boolean onTouch(MotionEvent event) {
mGestureRecognizer.onTouchEvent(event);
return true;
}
private class MyGestureListener implements GestureRecognizer.Listener {
private boolean isScale = false;
private final TimeRunner mToPreTimeRunner = new TimeRunner() {
@Override
protected void onStart() {}
@Override
protected void onRun(float interpolatedTime, int runningTime) {
scrollXOffset = (int)(stopScrollXOffset + ((mScreenWidth - stopScrollXOffset) * interpolatedTime));
invalidate();
}
@Override
protected void onEnd() {
scrollXOffset = 0;
goToPrePage();
mScrollState = SCROLL_NONE;
}
@Override
protected void onCancel() {}
};
private final TimeRunner mToNextTimeRunner = new TimeRunner() {
@Override
protected void onStart() {}
@Override
protected void onRun(float interpolatedTime, int runningTime) {
scrollXOffset = (int)(stopScrollXOffset + ((-mScreenWidth - stopScrollXOffset) * interpolatedTime));
invalidate();
}
@Override
protected void onEnd() {
scrollXOffset = 0;
goToNextPage();
mScrollState = SCROLL_NONE;
}
@Override
protected void onCancel() {}
};
private final TimeRunner mReturnTimeRunner = new TimeRunner() {
@Override
protected void onStart() {}
@Override
protected void onRun(float interpolatedTime, int runningTime) {
scrollXOffset = (int)((1 - interpolatedTime) * stopScrollXOffset);
invalidate();
}
@Override
protected void onEnd() {
scrollXOffset = 0;
mScrollState = SCROLL_NONE;
invalidate();
}
@Override
protected void onCancel() {}
};
private final TimeRunner mDoubleTapRunner = new TimeRunner() {
private BasicItem image;
private float startScale;
private float endScale;
private final Point startPosition = new Point();
private final Point endPosition = new Point();
private boolean chooseFirst(float a, float b, float standard) {
float offset1 = standard - a;
float offset2 = standard - b;
if ((offset1 > 0.0f && offset2 > 0.0f) ||
(offset1 < 0.0f && offset2 < 0.0f)) {
return Math.abs(offset1) < Math.abs(offset2);
} else {
float aa = a / standard;
float bb = b / standard;
aa = aa > 1.0f ? 1 / aa : aa;
bb = bb > 1.0f ? 1 / bb : bb;
return aa < bb;
}
}
@Override
protected void onStart() {
ShowItem showItem = showItems[CUR_TARGET_INDEX];
if (showItem == null || !(showItem instanceof BasicItem)) {
mDoubleTapRunner.cancel();
return;
}
image = (BasicItem)showItem;
int width = image.width;
int height = image.height;
if (width == -1 || height == -1) {
mDoubleTapRunner.cancel();
return;
}
// Init start info
Rect rect = image.mRect;
startScale = image.imageScale;
startPosition.x = (rect.left + rect.right) / 2;
startPosition.y = (rect.top + rect.bottom) / 2;
float fitScreenScale;
float scaleX = (float)mScreenWidth/width;
float scaleY = (float)mScreenHeight/height;
if (scaleX < scaleY)
fitScreenScale = scaleX;
else
fitScreenScale = scaleY;
if (chooseFirst(fitScreenScale, 1, startScale))
// To fix screen
endScale = fitScreenScale;
else
// To origin
endScale = 1;
endPosition.x = mScreenWidth / 2;
endPosition.y = mScreenHeight / 2;
}
@Override
protected void onRun(float interpolatedTime, int runningTime) {
int x = (int)(startPosition.x + (endPosition.x - startPosition.x) * interpolatedTime);
int y = (int)(startPosition.y + (endPosition.y - startPosition.y) * interpolatedTime);
float scale = startScale + (endScale - startScale) * interpolatedTime;
setImageRect(x, y, scale);
invalidate();
}
@Override
protected void onEnd() {
mDoubleTapAnimating = false;
setImageRect(mScreenWidth / 2, mScreenHeight / 2, endScale);
invalidate();
}
@Override
protected void onCancel() {
mDoubleTapAnimating = false;
}
private void setImageRect(int x, int y, float scale) {
image.imageScale = scale;
int showWidth = (int)(image.width * scale);
int showHeight = (int)(image.height * scale);
Rect rect = image.mRect;
rect.left = x - showWidth / 2;
rect.top = y - showHeight / 2;
rect.right = x + showWidth / 2;
rect.bottom = y + showHeight / 2;
}
};
public MyGestureListener() {
mDoubleTapRunner.setDuration(Constants.ANIMATE_TIME);
mDoubleTapRunner.setInterpolator(new AnticipateInterpolator());
}
@Override
public boolean onSingleTapConfirmed(float x, float y) {
if (mScrollState != SCROLL_NONE) {
return false;
}
if (mShowTapAreaTurn) {
mShowTapAreaTurn = false;
return false;
}
if (mDoubleTapAnimating)
return false;
if (showItems[CUR_TARGET_INDEX] == null
|| showItems[CUR_TARGET_INDEX] instanceof Text) {
if (mOnTapTextListener != null)
mOnTapTextListener.onTapText(mCurIndex);
//return true;
}
if (leftArea == null
|| topArea == null
|| rightArea == null
|| bottomArea == null
|| centerArea == null)
return true;
if (Utils.isInArea(leftArea, (int)x, (int)y)) {
// TODO goto bottom first the to pre page
resetSizePosition(PRE_TARGET_INDEX);
if (!goToPrePage()) {
// Get to first page
MaterialToast.showToast(mContext.getString(R.string.first_page));
}
} else if (Utils.isInArea(topArea, (int)x, (int)y)) {
zoom(true);
} else if (Utils.isInArea(rightArea, (int)x, (int)y)) {
// TODO goto bottom first the to pre page
resetSizePosition(NEXT_TARGET_INDEX);
if (!goToNextPage()) {
// Get to last page
MaterialToast.showToast(
mContext.getString(isEnsureSize ? R.string.the_last_page : R.string.wait_for_more));
}
} else if (Utils.isInArea(bottomArea, (int)x, (int)y)) {
zoom(false);
} else if (Utils.isInArea(centerArea, (int)x, (int)y)) {
if (mGalleryViewListener != null)
mGalleryViewListener.onTapCenter();
//mShowTapArea = true;
//mShowTapAreaTurn = true;
//invalidate();
} else {
// Can't catch tap
}
return true;
}
@Override
public boolean onDoubleTap(float x, float y) {
return false;
}
@Override
public boolean onDoubleTapConfirmed(float x, float y) {
if (isScale)
return true;
ShowItem curShowItem = showItems[CUR_TARGET_INDEX];
if (curShowItem == null || !(curShowItem instanceof BasicItem))
return true;
mDoubleTapAnimating = true;
mDoubleTapRunner.start();
return true;
}
@Override
public void onLongPress(MotionEvent e) {
if (isScale)
return;
Utils.vibrator(mContext, 100);
mImageSet.setStartIndex(mCurIndex);
}
@Override
public boolean onScrollBegin(float dx, float dy, float totalX,
float totalY) {
return this.onScroll(dx, dy, totalX, totalY);
}
/**
* dx 和 totalX 符号相反,为啥
*/
@Override
public boolean onScroll(float dx, float dy, float totalX, float totalY) {
if (isScale)
return false;
ShowItem curShowItem = showItems[CUR_TARGET_INDEX];
if (curShowItem == null) // If not load now
return true;
if (mDoubleTapAnimating)
return false;
switch(mScrollState) {
case SCROLL_ANIME_LEFT:
case SCROLL_ANIME_RIGHT:
return false;
case SCROLL_NONE:
boolean changePage = false;
Rect rect = curShowItem.mRect;
// Check change page or not
if (curShowItem == null || !(curShowItem instanceof BasicItem))
changePage = true;
else{
if ( Math.abs(totalX/totalY) > 1 && ((totalX > SLIDE_SENSITIVITY && dx < 0 && rect.left >= 0)
|| (totalX < -SLIDE_SENSITIVITY && dx > 0 && rect.right <= mScreenWidth)))
changePage = true;
}
if (changePage) { // If change page
if (dx < 0) { // Go to left
if (!isFirstPage()) { // Not first page
scrollXOffset = 0;
mScrollState = SCROLL_LEFT;
resetSizePosition(PRE_TARGET_INDEX);
} else {
changePage = false;
}
}
else { // Go to righ
if (!isLastPage()) { // Not last page
scrollXOffset = 0;
mScrollState = SCROLL_RIGHT;
resetSizePosition(NEXT_TARGET_INDEX);
} else {
changePage = false;
}
}
}
if (!changePage){ // Move cur image
int actDx = -(int)dx;
int actDy = -(int)dy;
if (rect.width() <= mScreenWidth)
actDx = 0;
if (rect.height() <= mScreenHeight)
actDy = 0;
rect.offset(actDx, actDy);
// Fix position
adjustPosition(curShowItem);
}
break;
case SCROLL_LEFT:
scrollXOffset -= dx;
if (scrollXOffset <= 0) {
scrollXOffset = 0;
mScrollState = SCROLL_NONE;
}
break;
case SCROLL_RIGHT:
scrollXOffset -= dx;
if (scrollXOffset >= 0) {
scrollXOffset = 0;
mScrollState = SCROLL_NONE;
}
break;
}
invalidate();
return true;
}
@Override
public boolean onScrollEnd(float totalX, float totalY) {
if (isScale)
return false;
if (mDoubleTapAnimating)
return false;
switch(mScrollState) {
case SCROLL_ANIME_LEFT:
case SCROLL_ANIME_RIGHT:
return false;
case SCROLL_LEFT:
stopScrollXOffset = scrollXOffset;
stopScrollYOffset = scrollYOffset;
mScrollState = SCROLL_ANIME_LEFT;
if (stopScrollXOffset > mScreenWidth * CHANGE_PAGE_PROPORTION) { // Go to pre page
mToPreTimeRunner.setDuration(Math.max((int) (MILLSEC_PER_DIX * (mScreenWidth - stopScrollXOffset)), ANIMATE_MIN));
mToPreTimeRunner.start();
} else {
mReturnTimeRunner.setDuration(Math.max((int) (MILLSEC_PER_DIX * stopScrollXOffset), ANIMATE_MIN));
mReturnTimeRunner.start();
}
break;
case SCROLL_RIGHT:
stopScrollXOffset = scrollXOffset;
stopScrollYOffset = scrollYOffset;
mScrollState = SCROLL_ANIME_RIGHT;
if (-stopScrollXOffset > mScreenWidth * CHANGE_PAGE_PROPORTION) { // Go to next page
mToNextTimeRunner.setDuration(Math.max((int) (MILLSEC_PER_DIX * (mScreenWidth + stopScrollXOffset)), ANIMATE_MIN));
mToNextTimeRunner.start();
} else {
mReturnTimeRunner.setDuration(Math.max((int) (MILLSEC_PER_DIX * (-stopScrollXOffset)), ANIMATE_MIN));
mReturnTimeRunner.start();
}
break;
case SCROLL_NONE:
ShowItem showItem = showItems[CUR_TARGET_INDEX];
if (showItem != null && Math.abs(totalX) - BORDER_TIP_SENSITIVITY > 0) {
if (isFirstPage() && showItem.mRect.left >= 0)
MaterialToast.showToast(mContext.getString(R.string.first_page));
else if (isLastPage() && showItem.mRect.right <= mScreenWidth)
MaterialToast.showToast(
mContext.getString(isEnsureSize ? R.string.the_last_page : R.string.wait_for_more));
}
}
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return true;
}
@Override
public boolean onScaleBegin(float focusX, float focusY) {
if (mDoubleTapAnimating)
return false;
isScale = true;
return true;
}
@Override
public boolean onScale(float focusX, float focusY, float scale) {
if (mDoubleTapAnimating)
return false;
if (mScrollState == SCROLL_ANIME_LEFT || mScrollState == SCROLL_ANIME_RIGHT)
return true;
if (mScrollState == SCROLL_LEFT || mScrollState == SCROLL_RIGHT) {
scrollXOffset = 0;
scrollYOffset = 0;
mScrollState = SCROLL_NONE;
}
ShowItem curShowItem = showItems[CUR_TARGET_INDEX];
if (curShowItem == null || !(curShowItem instanceof BasicItem))
return true;
BasicItem image = (BasicItem)curShowItem;
float newScale = image.imageScale * scale;
if (newScale > SCALE_MAX || newScale < SCALE_MIN)
return true;
image.imageScale = newScale;
mScale = newScale;
Rect rect = image.mRect;
int left = rect.left;
int top = rect.top;
left = (int)(focusX - ((focusX - left) * scale));
top = (int)(focusY - ((focusY - top) * scale));
rect.set(left, top, (int)(left + (image.width * image.imageScale)), (int)(top + (image.height * image.imageScale)));
// adjust
adjustPosition(image);
invalidate();
return true;
}
@Override
public void onScaleEnd() {
isScale = false;
}
@Override
public void onDown(float x, float y) {
mShowTapArea = false;
invalidate();
}
@Override
public void onUp() {
// if use onSingleTapUp, use below
/*
* if (mShowTapAreaTurn) {
mShowTapAreaTurn = false;
*/
}
}
/**
* If side is shorter then parent's, make it in parent's center
* If side is longer then parent's, make sure it fill parent
*
* @param showItem
*/
private void adjustPosition(ShowItem showItem) {
Rect rect = showItem.mRect;
int showWidth = rect.width();
int showHeight = rect.height();
int sumXOffset;
int sumYOffset = 0;
int targetIndex = getTargetIndex(showItem);
switch (targetIndex) {
case PRE_TARGET_INDEX:
sumXOffset = -mScreenWidth;
break;
case NEXT_TARGET_INDEX:
sumXOffset = mScreenWidth;
break;
case CUR_TARGET_INDEX:
default:
sumXOffset = 0;
}
if (showWidth > mScreenWidth) {
int fixXOffset = rect.left - sumXOffset;
if (fixXOffset > 0) {
rect.left -= fixXOffset;
rect.right -= fixXOffset;
} else if ((fixXOffset = sumXOffset + mScreenWidth - rect.right) > 0) {
rect.left += fixXOffset;
rect.right += fixXOffset;
}
} else {
int left = sumXOffset + (mScreenWidth - showWidth) / 2;
rect.offsetTo(left, rect.top);
}
if (showHeight > mScreenHeight) {
int fixYOffset = rect.top - sumYOffset;
if (fixYOffset > 0) {
rect.top -= fixYOffset;
rect.bottom -= fixYOffset;
} else if ((fixYOffset = sumYOffset + mScreenHeight - rect.bottom) > 0) {
rect.top += fixYOffset;
rect.bottom += fixYOffset;
}
} else {
int top = sumYOffset + (mScreenHeight - showHeight) / 2;
rect.offsetTo(rect.left, top);
}
}
public int getTargetIndex(ShowItem showItem) {
if (mImageSet == null)
return INVALID_ID;
for (int i = 0; i < TARGET_INDEX_SIZE; i++) {
if (showItem == showItems[i])
return i;
}
return INVALID_ID;
}
private abstract class ShowItem {
protected int width = -1;
protected int height = -1;
public Rect mRect = new Rect();
public void draw(GLCanvas canvas) {
this.draw(canvas, 0, 0);
}
public abstract void draw(GLCanvas canvas, int xOffset, int yOffset);
public abstract void recycle();
}
private abstract class BasicItem extends ShowItem{
private BasicTexture mTexture;
public float imageScale = 1;
private final RectF source = new RectF();
private final RectF target = new RectF();
/**
* You must call init before draw
* @param texture
*/
public void init(BasicTexture texture) {
mTexture = texture;
imageScale = 1;
}
@Override
public void draw(GLCanvas canvas, int xOffset, int yOffset) {
if (mTexture == null)
return;
int targetIndex;
if ((targetIndex = getTargetIndex(this)) == INVALID_ID)
mTexture.draw(canvas, mRect.left + xOffset, mRect.top + yOffset, mRect.width(), mRect.height());
else {
int leftBound = mScreenWidth;
int topBound = 0;
int rightBound = 2 * mScreenWidth;
int bottomBound = mScreenHeight;
switch (targetIndex) {
case PRE_TARGET_INDEX:
leftBound = -leftBound;
rightBound = 0;
break;
case CUR_TARGET_INDEX:
rightBound = leftBound;
leftBound = 0;
break;
case NEXT_TARGET_INDEX:
default:
break;
}
int left = mRect.left;
int top = mRect.top;
int right = mRect.right;
int bottom = mRect.bottom;
// Only show what in the own box
// TODO only what can be seen
if (left < leftBound || top < topBound || right > rightBound || bottom > bottomBound) {
if (left < leftBound) {
target.left = leftBound;
source.left = (leftBound - left) / imageScale;
} else {
target.left = left;
source.left = 0;
}
if (top < topBound) {
target.top = topBound;
source.top = (topBound - top) / imageScale;
} else {
target.top = top;
source.top = 0;
}
if (right > rightBound) {
target.right = rightBound;
source.right = width - ((right - rightBound) / imageScale);
} else {
target.right = right;
source.right = width;
}
if (bottom > bottomBound) {
target.bottom = bottomBound;
source.bottom = height - ((bottom - bottomBound) / imageScale);
} else {
target.bottom = bottom;
source.bottom = height;
}
target.left += xOffset;
target.top += yOffset;
target.right += xOffset;
target.bottom += yOffset;
mTexture.draw(canvas, source, target);
} else {
mTexture.draw(canvas, left + xOffset, top + yOffset, right - left, bottom - top);
}
}
}
}
private class BitmapItem extends BasicItem{
private BitmapTexture mTexture;
private Bitmap mContextBmp;
public void load(Bitmap bmp) {
if (mTexture != null)
recycle();
mContextBmp = bmp;
mTexture = new BitmapTexture(bmp);
width = mContextBmp.getWidth();
height = mContextBmp.getHeight();
super.init(mTexture);
}
@Override
public void recycle() {
if (mTexture == null)
return;
mTexture.recycle();
mContextBmp.recycle();
mTexture = null;
mContextBmp = null;
}
}
private class ImageItem extends BasicItem {
private ImageTexture mTexture;
public void load(Image image) {
mTexture = new ImageTexture(image);
width = mTexture.getWidth();
height = mTexture.getHeight();
super.init(mTexture);
}
@Override
public void draw(GLCanvas canvas, int xOffset, int yOffset) {
super.draw(canvas, xOffset, yOffset);
}
@Override
public void recycle() {
mTexture.recycle();
}
public boolean isAnimated() {
return mTexture.isAnimated();
}
}
private class MovieItem extends BasicItem {
private MovieTexture mTexture;
private Movie mContextMovie;
public void load(Movie movie) {
if (mTexture != null)
recycle();
mContextMovie = movie;
mTexture = MovieTexture.newInstance(mContextMovie);
width = mContextMovie.width();
height = mContextMovie.height();
super.init(mTexture);
}
@Override
public void recycle() {
if (mTexture == null)
return;
mTexture.recycle();
mTexture = null;
mContextMovie = null;
}
}
private class Text extends ShowItem{
private StringTexture mTexture;
public Text(String str) {
load(str);
}
public Text(String str, float size, int color) {
load(str, size, color);
}
public void load(String str) {
load(str, 100, 0xdeffffff);
}
public void load(String str, float size, int color) {
if (mTexture != null)
recycle();
mTexture = StringTexture.newInstance(str, size, color);
width = mTexture.getWidth();
height = mTexture.getHeight();
}
@Override
public void draw(GLCanvas canvas, int xOffset, int yOffset) {
if (mTexture != null)
mTexture.draw(canvas, mRect.left + xOffset, mRect.top + yOffset);
}
@Override
public void recycle() {
if (mTexture == null)
return;
mTexture.recycle();
}
}
}