Improve gallery

This commit is contained in:
seven332 committed 2016-02-21 23:44:10 +08:00
1 parent 56c95740df
commit 9a18ad543a
37 files changed
+2281 -592

No files matched your search

@@ -0,0 +1,176 @@
/*
* 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.drawable;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import com.hippo.yorozuya.MathUtils;
public class BatteryDrawable extends Drawable {
@SuppressWarnings("unused")
private static final String TAG = BatteryDrawable.class.getSimpleName();
public static final int WARN_LIMIT = 15;
private int mColor = Color.WHITE;
private int mWarningColor = Color.RED;
private int mElect = -1;
private final Paint mPaint;
private final Rect mTopRect;
private final Rect mBottomRect;
private final Rect mRightRect;
private final Rect mHeadRect;
private final Rect mElectRect;
private int mStart;
private int mStop;
public BatteryDrawable() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mTopRect = new Rect();
mBottomRect = new Rect();
mRightRect = new Rect();
mHeadRect = new Rect();
mElectRect = new Rect();
updatePaint();
}
@Override
protected void onBoundsChange(Rect bounds) {
int width = bounds.width();
int height = bounds.height();
int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f);
int turn1 = width * 6 / 7;
int turn2 = height / 3;
int secBottom = height - strokeWidth;
mStart = strokeWidth;
mStop = turn1 - strokeWidth;
mTopRect.set(0, 0, turn1, strokeWidth);
mBottomRect.set(0, secBottom, turn1, height);
mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom);
mHeadRect.set(turn1, turn2, width, height - turn2);
mElectRect.set(0, strokeWidth, mStop, secBottom);
}
/**
* How to draw:<br>
* |------------------------------|<br>
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
* |------------------------------|---|<br>
* |/////////////////| |//|\\\|<br>
* |/////////////////| |//|\\\|<br>
* |------------------------------|---|<br>
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
* |------------------------------|
*/
@Override
public void draw(Canvas canvas) {
if (mElect == -1)
return;
mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f);
canvas.drawRect(mTopRect, mPaint);
canvas.drawRect(mBottomRect, mPaint);
canvas.drawRect(mRightRect, mPaint);
canvas.drawRect(mHeadRect, mPaint);
canvas.drawRect(mElectRect, mPaint);
}
private boolean isWarn() {
return mElect <= WARN_LIMIT;
}
public void setColor(int color) {
if (mColor == color) {
return;
}
mColor = color;
if (!isWarn()) {
mPaint.setColor(mColor);
invalidateSelf();
}
}
public void setWarningColor(int color) {
if (mWarningColor == color) {
return;
}
mWarningColor = color;
if (isWarn()) {
mPaint.setColor(mWarningColor);
invalidateSelf();
}
}
public void setElect(int elect) {
if (mElect == elect) {
return;
}
mElect = elect;
updatePaint();
}
public void setElect(int elect, boolean warn) {
if (mElect == elect) {
return;
}
mElect = elect;
updatePaint(warn);
}
private void updatePaint() {
updatePaint(isWarn());
}
private void updatePaint(boolean warn) {
if (warn)
mPaint.setColor(mWarningColor);
else
mPaint.setColor(mColor);
invalidateSelf();
}
@Override
public void setAlpha(int alpha) {
// Empty
}
@Override
public void setColorFilter(ColorFilter cf) {
// Empty
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
@@ -119,6 +119,9 @@ public class Settings {
return sEhConfig;
}
/********************
****** Eh
********************/
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
@@ -126,6 +129,16 @@ public class Settings {
return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE);
}
/********************
****** Read
********************/
private static final String KEY_VOLUME_PAGE = "volume_page";
private static final boolean DEFAULT_VOLUME_PAGE = false;
public static boolean getVolumePage() {
return getBoolean(KEY_VOLUME_PAGE, DEFAULT_VOLUME_PAGE);
}
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
@@ -22,6 +22,7 @@ import android.support.annotation.Nullable;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.spider.SpiderQueen;
import com.hippo.image.Image;
import com.hippo.yorozuya.SimpleHandler;
public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.OnSpiderListener {
@@ -49,7 +50,15 @@ public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.On
if (mSpiderQueen != null) {
mSpiderQueen.removeOnSpiderListener(this);
SpiderQueen.releaseSpiderQueen(mSpiderQueen, SpiderQueen.MODE_READ);
final SpiderQueen spiderQueen = mSpiderQueen;
mSpiderQueen = null;
// Activity recreate may called
SimpleHandler.getInstance().postDelayed(new Runnable() {
@Override
public void run() {
SpiderQueen.releaseSpiderQueen(spiderQueen, SpiderQueen.MODE_READ);
}
}, 3000);
}
}
@@ -22,8 +22,8 @@ import android.support.annotation.UiThread;
import com.hippo.gl.glrenderer.GLCanvas;
import com.hippo.gl.view.GLRoot;
import com.hippo.image.Image;
import com.hippo.yorozuya.ConcurrentPool;
import com.hippo.yorozuya.OSUtils;
import com.hippo.yorozuya.Pool;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -41,7 +41,7 @@ public abstract class GalleryProvider {
".gif", // Graphics Interchange Format
};
private final Pool<NotifyTask> mNotifyTaskPool = new Pool<>(5);
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
private volatile GalleryProviderListener mGalleryProviderListener;
private volatile GLRoot mGLRoot;
@@ -130,8 +130,8 @@ public abstract class GalleryProvider {
public static final int TYPE_SUCCEED = 2;
public static final int TYPE_FAILED = 3;
private GalleryProviderListener mGalleryProviderListener;
private Pool<NotifyTask> mPool;
private final GalleryProviderListener mGalleryProviderListener;
private final ConcurrentPool<NotifyTask> mPool;
@Type
private int mType;
@@ -140,7 +140,7 @@ public abstract class GalleryProvider {
private Image mImage;
private String mError;
public NotifyTask(GalleryProviderListener galleryProviderListener, Pool<NotifyTask> pool) {
public NotifyTask(GalleryProviderListener galleryProviderListener, ConcurrentPool<NotifyTask> pool) {
mGalleryProviderListener = galleryProviderListener;
mPool = pool;
}
@@ -33,6 +33,8 @@ 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;
@@ -43,6 +45,8 @@ public class GalleryPageView extends GLFrameLayout {
private GLTextureView mError;
private GLProgressView mProgress;
private int mIndex = INVALID_INDEX;
public GalleryPageView(Context context, MovableTextTexture pageTextTexture,
int progressColor, int progressSize) {
// Add image
@@ -87,6 +91,14 @@ public class GalleryPageView extends GLFrameLayout {
mInfo.addComponent(mProgress, lp);
}
int getIndex() {
return mIndex;
}
void setIndex(int index) {
mIndex = index;
}
public void showImage() {
mImage.setVisibility(VISIBLE);
mInfo.setVisibility(GONE);
@@ -20,7 +20,6 @@ import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.MotionEvent;
import com.hippo.ehviewer.R;
@@ -38,11 +37,13 @@ import com.hippo.gl.widget.GLEdgeView;
import com.hippo.gl.widget.GLProgressView;
import com.hippo.gl.widget.GLTextureView;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.Pool;
import com.hippo.yorozuya.ResourcesUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Stack;
import java.util.ArrayList;
import java.util.List;
public class GalleryView extends GLView implements GestureRecognizer.Listener {
@@ -60,7 +61,6 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
private @interface LayoutMode {}
public static final int BACKGROUND_COLOR = 0xff212121;
private static final int INTERVAL_IN_DP = 24;
private static final int PROGRESS_SIZE_IN_DP = 48;
private static final int PAGE_MIN_HEIGHT_IN_DP = 256;
@@ -84,37 +84,55 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
private static final float[] RIGHT_AREA = {2.0f / 3.0f, 0.0f, 1.0f, 1f};
private static final float[] CENTER_AREA = {1.0f / 3.0f, 2.0f / 5.0f, 2.0f / 3.0f, 3.0f / 5.0f};
private Context mContext;
private MovableTextTexture mPageTextTexture;
private GestureRecognizer mGestureRecognizer;
private PageIterator mIterator;
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_SCROLL = 4;
private static final int METHOD_ON_FLING = 5;
private static final int METHOD_ON_SCALE_BEGIN = 6;
private static final int METHOD_ON_SCALE = 7;
private static final int METHOD_ON_SCALE_END = 8;
private static final int METHOD_ON_DOWN = 9;
private static final int METHOD_ON_UP = 10;
private static final int METHOD_ON_POINTER_DOWN = 11;
private static final int METHOD_ON_POINTER_UP = 12;
private static final int METHOD_SET_LAYOUT_MODE = 13;
private final Context mContext;
private final MovableTextTexture mPageTextTexture;
private final GestureRecognizer mGestureRecognizer;
private Adapter mAdapter;
private PagerLayoutManager mPagerLayoutManager;
private ScrollLayoutManager mScrollLayoutManager;
private LayoutManager mLayoutManager;
private Recycler mRecycler;
private GLEdgeView mEdgeView;
private final Pool<GalleryPageView> mGalleryPageViewPool = new Pool<>(5);
private final GLEdgeView mEdgeView;
private GLProgressView mProgressCache;
private GLTextureView mErrorViewCache;
private int mProgressColor;
private int mProgressSize;
private float mErrorSize;
private int mErrorColor;
private int mPageMinHeight;
private int mInterval;
private final int mProgressColor;
private final int mProgressSize;
private final float mErrorSize;
private final int mErrorColor;
private final int mPageMinHeight;
private boolean mEnableRequestFill = true;
private boolean mRequestFill = false;
private boolean mScale = false;
private boolean mScroll = false;
private boolean mFirstScroll = false;
private boolean mTouched = false;
private Rect mLeftArea = new Rect();
private Rect mRightArea = new Rect();
private Rect mCenterArea = new Rect();
private final Rect mLeftArea = new Rect();
private final Rect mRightArea = new Rect();
private final Rect mCenterArea = new Rect();
private final String mDefaultErrorStr;
private final String mEmptyStr;
@LayoutMode
private int mLayoutMode = LAYOUT_MODE_RIGHT_TO_LEFT;
@@ -124,21 +142,23 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
@StartPosition
private int mStartPosition = START_POSITION_TOP_LEFT;
private boolean mRequestFill = false;
private final ActionListener mListener;
private ActionListener 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);
public GalleryView(Context context, PageIterator iterator, MovableTextTexture pageTextTexture,
ActionListener listener, @LayoutMode int layoutMode) {
public GalleryView(@NonNull Context context, @NonNull Adapter adapter,
MovableTextTexture pageTextTexture, ActionListener listener, @LayoutMode int layoutMode) {
mContext = context;
mIterator = iterator;
mAdapter = adapter;
mPageTextTexture = pageTextTexture;
mListener = listener;
mEdgeView = new GLEdgeView(context);
mGestureRecognizer = new GestureRecognizer(context, this);
mRecycler = new Recycler();
iterator.setGalleryView(this);
adapter.setGalleryView(this);
mLayoutMode = layoutMode;
mProgressColor = ResourcesUtils.getAttrColor(context, R.attr.colorPrimary);
@@ -146,7 +166,9 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
mErrorSize = context.getResources().getDimension(R.dimen.text_large);
mErrorColor = context.getResources().getColor(R.color.red_500);
mPageMinHeight = LayoutUtils.dp2pix(context, PAGE_MIN_HEIGHT_IN_DP);
mInterval = LayoutUtils.dp2pix(context, INTERVAL_IN_DP);
mDefaultErrorStr = context.getResources().getString(R.string.error_unknown);
mEmptyStr = context.getResources().getString(R.string.error_empty);
setBackgroundColor(BACKGROUND_COLOR);
}
@@ -159,7 +181,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
private void ensureScrollLayoutManager() {
if (mScrollLayoutManager == null) {
mScrollLayoutManager = new ScrollLayoutManager(mContext, this, mInterval);
mScrollLayoutManager = new ScrollLayoutManager(mContext, this);
}
}
@@ -172,24 +194,26 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
case LAYOUT_MODE_LEFT_TO_RIGHT:
ensurePagerLayoutManager();
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_LEFT_TO_RIGHT);
mPagerLayoutManager.onAttach(mIterator);
mIterator = null;
mPagerLayoutManager.onAttach(mAdapter);
mAdapter = null;
mLayoutManager = mPagerLayoutManager;
break;
case LAYOUT_MODE_RIGHT_TO_LEFT:
ensurePagerLayoutManager();
mPagerLayoutManager.setMode(PagerLayoutManager.MODE_RIGHT_TO_LEFT);
mPagerLayoutManager.onAttach(mIterator);
mIterator = null;
mPagerLayoutManager.onAttach(mAdapter);
mAdapter = null;
mLayoutManager = mPagerLayoutManager;
break;
case LAYOUT_MODE_TOP_TO_BOTTOM:
ensureScrollLayoutManager();
mScrollLayoutManager.onAttach(mIterator);
mIterator = null;
mScrollLayoutManager.onAttach(mAdapter);
mAdapter = null;
mLayoutManager = mScrollLayoutManager;
break;
}
requestFill();
}
@Override
@@ -197,7 +221,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
super.onDetachFromRoot();
mEdgeView.onDetachFromRoot();
mIterator = mLayoutManager.onDetach();
mAdapter = mLayoutManager.onDetach();
mLayoutManager = null;
}
@@ -225,13 +249,215 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
return mEdgeView;
}
public boolean isFirstScroll() {
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);
}
@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 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));
mCenterArea.set((int) (CENTER_AREA[0] * width), (int) (CENTER_AREA[1] * height),
(int) (CENTER_AREA[2] * width), (int) (CENTER_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) {
}
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 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) {
mTouched = true;
mScale = false;
mScroll = false;
mFirstScroll = true;
if (mLayoutManager != null) {
mLayoutManager.onDown();
}
}
private void onUpInternal() {
mTouched = false;
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;
}
@@ -270,169 +496,132 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
mLayoutManager = mScrollLayoutManager;
break;
}
requestFill();
}
@Override
public boolean onSingleTapUp(float x, float y) {
return true;
}
@Override
public boolean onSingleTapConfirmed(float x, float y) {
return true;
}
@Override
public boolean onDoubleTap(float x, float y) {
return true;
}
@Override
public boolean onDoubleTapConfirmed(float x, float y) {
if (mScale) {
return false;
}
if (mLayoutManager != null) {
mLayoutManager.onDoubleTapConfirmed(x, y);
}
return true;
}
@Override
public boolean onScroll(float dx, float dy, float totalX, float totalY, float x, float y) {
if (mScale) {
return false;
}
mScroll = true;
if (mLayoutManager != null) {
mLayoutManager.onScroll(dx, dy, totalX, totalY, x, y);
}
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (mLayoutManager != null) {
mLayoutManager.onFling(e1, e2, velocityX, velocityY);
}
return true;
}
@Override
public boolean onScaleBegin(float focusX, float focusY) {
return onScale(focusX, focusY, 1.0f);
}
@Override
public boolean onScale(float focusX, float focusY, float scale) {
if (mScroll || (mLayoutManager != null && !mLayoutManager.canScale())) {
return false;
}
mScale = true;
if (mLayoutManager != null) {
mLayoutManager.onScale(focusX, focusY, scale);
}
return true;
}
@Override
public void onScaleEnd() {
}
@Override
public void onDown(float x, float y) {
mTouched = true;
mScale = false;
mScroll = false;
mFirstScroll = true;
if (mLayoutManager != null) {
mLayoutManager.onDown();
}
}
@Override
public void onUp() {
mTouched = false;
if (mLayoutManager != null) {
mLayoutManager.onUp();
}
}
@Override
public void onPointerDown(float x, float y) {
if (!mScroll && (mLayoutManager != null && mLayoutManager.canScale())) {
mScale = true;
}
}
@Override
public void onPointerUp() {
}
@Override
protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) {
mEdgeView.layout(left, top, right, bottom);
@RenderThread
void forceFill() {
mRequestFill = true;
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));
mCenterArea.set((int) (CENTER_AREA[0] * width), (int) (CENTER_AREA[1] * height),
(int) (CENTER_AREA[2] * width), (int) (CENTER_AREA[3] * height));
}
}
@RenderThread
private void fill() {
// Must be in render thread
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_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;
}
}
methodListTemp.clear();
argsListTemp.clear();
}
@Override
public void render(GLCanvas canvas) {
// Dispatch method
dispatchMethod();
long time = AnimationTime.get();
if (mLayoutManager != null && mLayoutManager.onUpdateAnimation(time)) {
invalidate();
}
// mSmoothScroller may do fill too, so check fill request after scroll animator
if (mRequestFill) {
mRequestFill = false;
fill();
}
fill();
super.render(canvas);
mEdgeView.render(canvas);
}
public GalleryPageView findPageById(int id) {
@RenderThread
public GalleryPageView findPageByIndex(int id) {
GalleryUtils.assertInRenderThread();
if (mLayoutManager != null) {
return mLayoutManager.findPageById(id);
return mLayoutManager.findPageByIndex(id);
} else {
return null;
}
}
public GalleryPageView obtainPage() {
GalleryPageView page = mRecycler.obtain();
GalleryPageView obtainPage() {
GalleryPageView page = mGalleryPageViewPool.pop();
if (page == null) {
page = new GalleryPageView(mContext, mPageTextTexture, mProgressColor, mProgressSize);
page.setMinimumHeight(mPageMinHeight);
@@ -440,14 +629,14 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
return page;
}
public void releasePage(GalleryPageView page) {
mRecycler.release(page);
void releasePage(GalleryPageView page) {
mGalleryPageViewPool.push(page);
}
/**
* Indeterminate GLProgressView
*/
public GLProgressView obtainProgress() {
GLProgressView obtainProgress() {
GLProgressView progress;
if (mProgressCache != null) {
progress = mProgressCache;
@@ -466,11 +655,11 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
/**
* @param progress Indeterminate GLProgressView
*/
public void releaseProgress(GLProgressView progress) {
void releaseProgress(GLProgressView progress) {
mProgressCache = progress;
}
public GLTextureView obtainErrorView() {
GLTextureView obtainErrorView() {
GLTextureView errorView;
if (mErrorViewCache != null) {
errorView = mErrorViewCache;
@@ -481,7 +670,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
return errorView;
}
public void unbindErrorView(GLTextureView errorView) {
void unbindErrorView(GLTextureView errorView) {
Texture texture = errorView.getTexture();
if (texture != null) {
errorView.setTexture(null);
@@ -491,28 +680,19 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
}
}
public void bindErrorView(GLTextureView errorView, String error) {
void bindErrorView(GLTextureView errorView, String error) {
unbindErrorView(errorView);
Texture texture = StringTexture.newInstance(error, mErrorSize, mErrorColor);
errorView.setTexture(texture);
}
public void releaseErrorView(GLTextureView errorView) {
void releaseErrorView(GLTextureView errorView) {
unbindErrorView(errorView);
mErrorViewCache = errorView;
}
@RenderThread
public void onDataChanged() {
GalleryUtils.assertInRenderThread();
if (mLayoutManager != null){
mLayoutManager.onDataChanged();
}
}
public static abstract class PageIterator {
public static abstract class Adapter {
protected GalleryView mGalleryView;
@@ -520,41 +700,26 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
mGalleryView = galleryView;
}
public abstract void mark();
public void bind(GalleryPageView view, int index) {
onBind(view, index);
view.setIndex(index);
}
public abstract void reset();
public void unbind(GalleryPageView view) {
onUnbind(view);
view.setIndex(GalleryPageView.INVALID_INDEX);
}
/**
* @return True for waiting now, layout manager
* should show a progress bar or something like it
*/
public abstract boolean isWaiting();
public abstract void onBind(GalleryPageView view, int index);
public abstract void onUnbind(GalleryPageView view);
/**
* @return Null for no error
*/
public abstract String getError();
public abstract boolean hasNext();
public abstract boolean hasPrevious();
public abstract void next();
public abstract void previous();
public void bind(GalleryPageView view) {
view.setId(onBind(view));
}
public void unbind(GalleryPageView view) {
onUnbind(view);
view.setId(NO_ID);
}
public abstract int onBind(GalleryPageView view);
public abstract void onUnbind(GalleryPageView view);
public abstract int size();
}
public static abstract class LayoutManager {
@@ -565,9 +730,9 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
mGalleryView = galleryView;
}
public abstract void onAttach(PageIterator iterator);
public abstract void onAttach(Adapter iterator);
public abstract PageIterator onDetach();
public abstract Adapter onDetach();
public abstract void onFill();
@@ -579,7 +744,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
public abstract void onScroll(float dx, float dy, float totalX, float totalY, float x, float y);
public abstract void onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
public abstract void onFling(float velocityX, float velocityY);
public abstract boolean canScale();
@@ -589,7 +754,12 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
public abstract void onDataChanged();
public abstract GalleryPageView findPageById(int id);
public abstract GalleryPageView findPageByIndex(int index);
/**
* @return {@link GalleryPageView#INVALID_INDEX} for error
*/
public abstract int getCurrentIndex();
protected void placeCenter(GLView view) {
int spec = GLView.MeasureSpec.makeMeasureSpec(GLView.LayoutParams.WRAP_CONTENT,
@@ -603,36 +773,6 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener {
}
}
private static class Recycler {
private static final int MAX_SIZE = 3;
private int mSize = 0;
private Stack<GalleryPageView> mStack = new Stack<>();
@Nullable
private GalleryPageView obtain() {
if (mSize != 0) {
mSize--;
return mStack.pop();
} else {
return null;
}
}
public void release(@Nullable GalleryPageView page) {
if (page == null) {
return;
}
if (mSize < MAX_SIZE) {
mSize++;
mStack.push(page);
}
}
}
public interface ActionListener {
void onTapCenter();
@@ -19,10 +19,11 @@ package com.hippo.ehviewer.gallery.gl;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.util.Log;
import android.view.animation.Interpolator;
import com.hippo.anani.AnimationUtils;
import com.hippo.ehviewer.gallery.GalleryProvider;
import com.hippo.gl.anim.Animation;
import com.hippo.gl.view.GLView;
import com.hippo.gl.widget.GLEdgeView;
@@ -35,9 +36,10 @@ import com.hippo.yorozuya.MathUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// TODO Not safe ! Ensure change component in render thread
public class PagerLayoutManager extends GalleryView.LayoutManager {
private static final String TAG = PagerLayoutManager.class.getSimpleName();
@IntDef({MODE_LEFT_TO_RIGHT, MODE_RIGHT_TO_LEFT})
@Retention(RetentionPolicy.SOURCE)
private @interface Mode {}
@@ -55,7 +57,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
};
private GalleryView.PageIterator mIterator;
private GalleryView.Adapter mAdapter;
private GLProgressView mProgress;
private String mErrorStr;
@@ -71,19 +73,22 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
private int mDeltaY;
private boolean mCanScrollBetweenPages = false;
private int mInterval;
private final int mInterval;
private int[] mScrollRemain = new int[2];
private float[] mScaleDefault = new float[3];
private final int[] mScrollRemain = new int[2];
private final float[] mScaleDefault = new float[3];
private PageFling mPageFling;
private SmoothScroller mSmoothScroller;
private SmoothScaler mSmoothScaler;
private final PageFling mPageFling;
private final SmoothScroller mSmoothScroller;
private final SmoothScaler mSmoothScaler;
// Current index
private int mIndex;
public PagerLayoutManager(Context context, @NonNull GalleryView galleryView) {
super(galleryView);
mInterval = LayoutUtils.dp2pix(context, INTERVAL);
mInterval = LayoutUtils.dp2pix(context, INTERVAL);
mPageFling = new PageFling(context);
mSmoothScroller = new SmoothScroller();
mSmoothScaler = new SmoothScaler();
@@ -108,7 +113,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
mMode = mode;
if (mIterator != null) {
if (mAdapter != null) {
// It is attached, refill
// Cancel all animations
cancelAllAnimations();
@@ -124,10 +129,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
@Override
public void onAttach(GalleryView.PageIterator iterator) {
AssertUtils.assertEquals("The PagerLayoutManager is attached", mIterator, null);
AssertUtils.assertNotEquals("The iterator is null", iterator, null);
mIterator = iterator;
public void onAttach(GalleryView.Adapter adapter) {
AssertUtils.assertEquals("The PagerLayoutManager is attached", mAdapter, null);
AssertUtils.assertNotEquals("The adapter is null", adapter, null);
mAdapter = adapter;
// Reset parameters
resetParameters();
}
@@ -151,7 +156,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
private void removePage(@NonNull GalleryPageView page) {
mGalleryView.removeComponent(page);
mIterator.unbind(page);
mAdapter.unbind(page);
mGalleryView.releasePage(page);
}
@@ -172,8 +177,8 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
@Override
public GalleryView.PageIterator onDetach() {
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null);
public GalleryView.Adapter onDetach() {
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
// Cancel all animations
cancelAllAnimations();
@@ -184,10 +189,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
removeAllPages();
// Clear iterator
GalleryView.PageIterator iterator = mIterator;
mIterator = null;
GalleryView.Adapter adapter = mAdapter;
mAdapter = null;
return iterator;
return adapter;
}
private GalleryPageView getLeftPage() {
@@ -200,18 +205,6 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
}
private void setLeftPage(GalleryPageView page) {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
mPrevious = page;
break;
default:
case MODE_RIGHT_TO_LEFT:
mNext = page;
break;
}
}
private GalleryPageView getRightPage() {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
@@ -222,29 +215,38 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
}
private void setRightPage(GalleryPageView page) {
switch (mMode) {
case MODE_LEFT_TO_RIGHT:
mNext = page;
break;
default:
case MODE_RIGHT_TO_LEFT:
mPrevious = page;
break;
}
}
@Override
public void onFill() {
GalleryView.PageIterator iterator = mIterator;
GalleryView.Adapter adapter = mAdapter;
GalleryView galleryView = mGalleryView;
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", iterator, null);
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null);
int width = galleryView.getWidth();
int height = galleryView.getHeight();
String errorStr = iterator.getError();
int size = adapter.size();
String errorStr = adapter.getError();
if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar
// Remove error view and all pages
removeErrorView();
removeAllPages();
// Ensure progress
if (mProgress == null) {
mProgress = galleryView.obtainProgress();
galleryView.addComponent(mProgress);
}
// Place progress center
placeCenter(mProgress);
} else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text
// Ensure error is not null
if (0 == size) {
errorStr = galleryView.getEmptyStr();
} else if (null == errorStr) {
errorStr = galleryView.getDefaultErrorStr();
}
if (errorStr != null) {
// Remove progress and all pages
removeProgress();
removeAllPages();
@@ -263,61 +265,66 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
// Place error view center
placeCenter(mErrorView);
} else if (iterator.isWaiting()) {
// Remove error view and all pages
removeErrorView();
removeAllPages();
// Ensure progress
if (mProgress == null) {
mProgress = galleryView.obtainProgress();
galleryView.addComponent(mProgress);
}
// Place progress center
placeCenter(mProgress);
} else {
// Remove progress and error view
removeProgress();
removeErrorView();
// Ensure index in range
int index = mIndex;
if (index < 0) {
index = 0;
mIndex = index;
removeAllPages();
Log.e(TAG, "index < 0, index = " + index);
} else if (index >= size) {
index = size - 1;
mIndex = index;
removeAllPages();
Log.e(TAG, "index >= size, index = " + index + ", size = " + size);
}
// Ensure pages
if (mCurrent == null) {
mCurrent = galleryView.obtainPage();
iterator.bind(mCurrent);
adapter.bind(mCurrent, index);
galleryView.addComponent(mCurrent);
}
if (mPrevious == null && iterator.hasPrevious()) {
iterator.mark();
iterator.previous();
if (mPrevious == null && index > 0) {
mPrevious = galleryView.obtainPage();
iterator.bind(mPrevious);
adapter.bind(mPrevious, index - 1);
galleryView.addComponent(mPrevious);
iterator.reset();
} else if (mPrevious != null && index == 0) {
removePage(mPrevious);
}
if (mNext == null && iterator.hasNext()) {
iterator.mark();
iterator.next();
if (mNext == null && index < size - 1) {
mNext = galleryView.obtainPage();
iterator.bind(mNext);
adapter.bind(mNext, index + 1);
galleryView.addComponent(mNext);
iterator.reset();
} else if (mNext != null && index == size - 1) {
removePage(mNext);
}
GalleryPageView leftPage = getLeftPage();
GalleryPageView rightPage = getRightPage();
// Fix offset
final int min = rightPage == null ? 0 : -width - mInterval + 1;
final int max = leftPage == null ? 0 : width + mInterval - 1;
mOffset = MathUtils.clamp(mOffset, min, max);
// Measure and layout pages
int offset = mOffset;
int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY);
int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY);
final int offset = mOffset;
final int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY);
final int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.EXACTLY);
if (mCurrent != null) {
mCurrent.measure(widthSpec, heightSpec);
mCurrent.layout(offset, 0, width + offset, height);
}
GalleryPageView leftPage = getLeftPage();
if (leftPage != null) {
leftPage.measure(widthSpec, heightSpec);
leftPage.layout(-mInterval - width + offset, 0, -mInterval + offset, height);
}
GalleryPageView rightPage = getRightPage();
if (rightPage != null) {
rightPage.measure(widthSpec, heightSpec);
rightPage.layout(width + mInterval + offset, 0, width + mInterval + width + offset, height);
@@ -381,11 +388,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
private void pagePrevious() {
GalleryView.PageIterator iterator = mIterator;
if (!iterator.hasPrevious()) {
if (mIndex <= 0) {
return;
}
iterator.previous();
mIndex--;
if (mNext != null) {
removePage(mNext);
@@ -394,22 +400,20 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
mCurrent = mPrevious;
mPrevious = null;
if (iterator.hasPrevious()) {
iterator.mark();
iterator.previous();
if (mIndex > 0) {
mPrevious = mGalleryView.obtainPage();
iterator.bind(mPrevious);
mAdapter.bind(mPrevious, mIndex - 1);
mGalleryView.addComponent(mPrevious);
iterator.reset();
}
}
private void pageNext() {
GalleryView.PageIterator iterator = mIterator;
if (!iterator.hasNext()) {
GalleryView.Adapter adapter = mAdapter;
int size = adapter.size();
if (mIndex >= size - 1) {
return;
}
iterator.next();
mIndex++;
if (mPrevious != null) {
removePage(mPrevious);
@@ -418,13 +422,10 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
mCurrent = mNext;
mNext = null;
if (iterator.hasNext()) {
iterator.mark();
iterator.next();
if (mIndex < size - 1) {
mNext = mGalleryView.obtainPage();
iterator.bind(mNext);
adapter.bind(mNext, mIndex + 1);
mGalleryView.addComponent(mNext);
iterator.reset();
}
}
@@ -589,7 +590,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
@Override
public void onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
public void onFling(float velocityX, float velocityY) {
if (mCurrent == null || mOffset != 0 || !mCurrent.getImageView().isLoaded() ||
!mCurrent.getImageView().canFling()) {
return;
@@ -625,7 +626,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
@Override
public void onDataChanged() {
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null);
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
// Cancel all animations
cancelAllAnimations();
@@ -639,19 +640,28 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
}
@Override
public GalleryPageView findPageById(int id) {
if (mCurrent != null && mCurrent.getId() == id) {
public GalleryPageView findPageByIndex(int index) {
if (mCurrent != null && mCurrent.getIndex() == index) {
return mCurrent;
}
if (mPrevious != null && mPrevious.getId() == id) {
if (mPrevious != null && mPrevious.getIndex() == index) {
return mPrevious;
}
if (mNext != null && mNext.getId() == id) {
if (mNext != null && mNext.getIndex() == index) {
return mNext;
}
return null;
}
@Override
public int getCurrentIndex() {
if (mCurrent != null) {
return mCurrent.getIndex();
} else {
return GalleryPageView.INVALID_INDEX;
}
}
class SmoothScroller extends Animation {
private int mDx;
@@ -701,7 +711,7 @@ public class PagerLayoutManager extends GalleryView.LayoutManager {
private int mDy;
private int mLastX;
private int mLastY;
private int[] mTemp = new int[2];
private final int[] mTemp = new int[2];
public PageFling(Context context) {
super(context);
@@ -20,47 +20,57 @@ import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.MotionEvent;
import com.hippo.ehviewer.gallery.GalleryProvider;
import com.hippo.gl.view.GLView;
import com.hippo.gl.widget.GLEdgeView;
import com.hippo.gl.widget.GLProgressView;
import com.hippo.gl.widget.GLTextureView;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
// TODO Not safe ! Ensure change component in render thread
public class ScrollLayoutManager extends GalleryView.LayoutManager {
private static final String TAG = ScrollLayoutManager.class.getSimpleName();
private static final int INTERVAL = 24;
private static final float RESERVATIONS = 0.5f;
private GalleryView.PageIterator mIterator;
private GalleryView.Adapter mAdapter;
private GLProgressView mProgress;
private String mErrorStr;
private GLTextureView mErrorView;
private LinkedList<GalleryPageView> mPages = new LinkedList<>();
private final LinkedList<GalleryPageView> mPages = new LinkedList<>();
private int mOffset;
private int mDeltaX;
private int mDeltaY;
private int mFirstShownLoadedPageIndex = GalleryPageView.INVALID_INDEX;
private int mInterval;
private int mProgressSpec;
private final int mInterval;
private PageFling mPageFling;
private final PageFling mPageFling;
public ScrollLayoutManager(Context context, @NonNull GalleryView galleryView,
int interval) {
// Current index
private int mIndex;
private final BottomState mBottomState = new BottomState();
class BottomState {
int bottom;
boolean hasNext;
}
public ScrollLayoutManager(Context context, @NonNull GalleryView galleryView) {
super(galleryView);
mInterval = interval;
mProgressSpec = GLView.MeasureSpec.makeMeasureSpec(GLView.LayoutParams.WRAP_CONTENT,
GLView.LayoutParams.WRAP_CONTENT);
mInterval = LayoutUtils.dp2pix(context, INTERVAL);
mPageFling = new PageFling(context);
}
@@ -68,6 +78,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
mOffset = 0;
mDeltaX = 0;
mDeltaY = 0;
mFirstShownLoadedPageIndex = GalleryPageView.INVALID_INDEX;
}
private void cancelAllAnimations() {
@@ -75,10 +86,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
}
@Override
public void onAttach(GalleryView.PageIterator iterator) {
AssertUtils.assertEquals("The ScrollLayoutManager is attached", mIterator, null);
AssertUtils.assertNotEquals("The iterator is null", iterator, null);
mIterator = iterator;
public void onAttach(GalleryView.Adapter adapter) {
AssertUtils.assertEquals("The ScrollLayoutManager is attached", mAdapter, null);
AssertUtils.assertNotEquals("The iterator is null", adapter, null);
mAdapter = adapter;
// Reset parameters
resetParameters();
}
@@ -102,7 +113,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
private void removePage(@NonNull GalleryPageView page) {
mGalleryView.removeComponent(page);
mIterator.unbind(page);
mAdapter.unbind(page);
mGalleryView.releasePage(page);
}
@@ -114,8 +125,8 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
}
@Override
public GalleryView.PageIterator onDetach() {
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null);
public GalleryView.Adapter onDetach() {
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
// Cancel all animations
cancelAllAnimations();
@@ -126,17 +137,18 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
removeAllPages();
// Clear iterator
GalleryView.PageIterator iterator = mIterator;
mIterator = null;
GalleryView.Adapter iterator = mAdapter;
mAdapter = null;
return iterator;
}
private void fillPages() {
GalleryView.PageIterator iterator = mIterator;
GalleryView.Adapter adapter = mAdapter;
GalleryView galleryView = mGalleryView;
int width = galleryView.getWidth();
int height = galleryView.getHeight();
int size = adapter.size();
int minY = (int) (-height * RESERVATIONS);
@@ -151,10 +163,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
pages.remove();
// Update offset
mOffset = nextTop;
if (iterator.hasNext()) {
iterator.next();
if (mIndex < size - 1) {
mIndex++;
} else {
Log.e(TAG, "iterator should has next");
Log.e(TAG, "The page is out of show range, but it has no next page");
}
} else {
break;
@@ -167,11 +179,11 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
int widthSpec = GLView.MeasureSpec.makeMeasureSpec(width, GLView.MeasureSpec.EXACTLY);
int heightSpec = GLView.MeasureSpec.makeMeasureSpec(height, GLView.MeasureSpec.UNSPECIFIED);
int pageSize = 0;
while (y >= minY && iterator.hasPrevious()) {
iterator.previous();
while (y >= minY && mIndex > 0) {
mIndex--;
GalleryPageView page = galleryView.obtainPage();
iterator.bind(page);
mAdapter.bind(page, mIndex);
galleryView.addComponent(page);
mPages.add(0, page);
@@ -190,20 +202,27 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
}
mOffset = y;
// Fix offset, ensure it is not positive
if (mOffset > 0) {
int offset = mOffset;
// Update offset
mOffset = 0;
oldOffset -= offset;
// Translate pages
pages = mPages.iterator();
for (int i = 0; i < pageSize; i++) {
pages.next().bounds().offset(0, -offset);
}
}
// Fill from oldOffset
iterator.mark();
// Go to oldOffset
int maxY = (int) (height * (1 + RESERVATIONS));
y = oldOffset;
pages = mPages.iterator();
for (int i = 0; i < pageSize; i++) {
if (iterator.hasNext()) {
iterator.next();
} else {
Log.e(TAG, "iterator should has next");
}
pages.next();
}
int index = mIndex + pageSize;
// Do fill
while (true) {
GalleryPageView page;
@@ -211,7 +230,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
pages = null;
// New page
page = galleryView.obtainPage();
iterator.bind(page);
mAdapter.bind(page, index);
galleryView.addComponent(page);
mPages.add(page);
} else {
@@ -234,13 +253,12 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
y += mInterval;
// Check has next
if (!iterator.hasNext()) {
if (index >= size - 1) {
break;
}
iterator.next();
index++;
}
iterator.reset();
// Remove useless page
while (mPages.size() > pageSize) {
@@ -250,15 +268,36 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
}
@Override
public synchronized void onFill() {
GalleryView.PageIterator iterator = mIterator;
public void onFill() {
GalleryView.Adapter adapter = mAdapter;
GalleryView galleryView = mGalleryView;
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", iterator, null);
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", adapter, null);
int height = galleryView.getHeight();
String errorStr = iterator.getError();
int size = adapter.size();
String errorStr = adapter.getError();
if (size == GalleryProvider.STATE_WAIT) { // Wait here, show progress bar
// Remove error view and all pages
removeErrorView();
removeAllPages();
// Ensure progress
if (mProgress == null) {
mProgress = galleryView.obtainProgress();
galleryView.addComponent(mProgress);
}
// Place progress center
placeCenter(mProgress);
} else if (size <= GalleryProvider.STATE_ERROR || size == 0) { // Get error or empty, show error text
// Ensure error is not null
if (0 == size) {
errorStr = galleryView.getEmptyStr();
} else if (null == errorStr) {
errorStr = galleryView.getDefaultErrorStr();
}
if (errorStr != null) {
// Remove progress and all pages
removeProgress();
removeAllPages();
@@ -277,53 +316,93 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
// Place error view center
placeCenter(mErrorView);
} else if (iterator.isWaiting()) {
// Remove error view and all pages
removeErrorView();
removeAllPages();
// Ensure progress
if (mProgress == null) {
mProgress = galleryView.obtainProgress();
galleryView.addComponent(mProgress);
}
// Place progress center
placeCenter(mProgress);
} else {
LinkedList<GalleryPageView> pages = mPages;
// Remove progress and error view
removeProgress();
removeErrorView();
// Find first shown loaded page
// Ensure index in range
int index = mIndex;
if (index < 0) {
Log.e(TAG, "index < 0, index = " + index);
index = 0;
mIndex = index;
removeAllPages();
} else if (index >= size) {
Log.e(TAG, "index >= size, index = " + index + ", size = " + size);
index = size - 1;
mIndex = index;
removeAllPages();
}
// Find first shown loaded page top
GalleryPageView firstShownLoadedPage = null;
int firstShownLoadedPageTop = mOffset;
for (GalleryPageView page : mPages) {
// Check first shown loaded page
if (page.isLoaded()) {
Rect bound = page.bounds();
int pageTop = bound.top;
int pageBottom = bound.bottom;
if ((pageTop > 0 && pageTop < height) || (pageBottom > 0 && pageBottom < height)) {
if (mFirstShownLoadedPageIndex != GalleryPageView.INVALID_INDEX) {
for (GalleryPageView page : pages) {
// Check first shown loaded page
if (mFirstShownLoadedPageIndex == page.getIndex()) {
firstShownLoadedPage = page;
break;
}
firstShownLoadedPageTop += page.getHeight() + mInterval;
}
firstShownLoadedPageTop += page.getHeight() + mInterval;
}
fillPages();
// TODO Avoid page not fill bottom, but has previous
// Ensure first shown loaded page top is the same
if (firstShownLoadedPage != null && firstShownLoadedPage.isAttachedToRoot()) {
if (firstShownLoadedPage != null && mFirstShownLoadedPageIndex == firstShownLoadedPage.getIndex()) {
int newTop = firstShownLoadedPage.bounds().top;
if (firstShownLoadedPageTop != newTop) {
mOffset += firstShownLoadedPageTop - newTop;
fillPages();
}
}
// Avoid page not fill bottom, but has previous
while (true) {
int bottomDist = height - pages.getLast().bounds().bottom;
int topDist = 0 - pages.getFirst().bounds().top;
if (bottomDist > 0 && topDist > 0) {
int translate = Math.min(bottomDist, topDist);
// Update offset
mOffset += translate;
// Translate pages
for (GalleryPageView page : pages) {
page.bounds().offset(0, translate);
}
// Break if no previous
if (mIndex <= 0) {
break;
}
// Refill pages
fillPages();
// Break if last page bottom fit gallery view bottom
if (bottomDist <= topDist) {
break;
}
} else {
break;
}
}
// Get first shown loaded image
mFirstShownLoadedPageIndex = GalleryPageView.INVALID_INDEX;
for (GalleryPageView page : mPages) {
// Check first shown loaded page
//if (page.isLoaded()) {
Rect bound = page.bounds();
int pageTop = bound.top;
int pageBottom = bound.bottom;
if ((pageTop > 0 && pageTop < height) || (pageBottom > 0 && pageBottom < height)) {
mFirstShownLoadedPageIndex = page.getIndex();
break;
}
//}
}
}
}
@@ -378,13 +457,32 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
}
}
private void getBottomState() {
List<GalleryPageView> pages = mPages;
int bottom = mOffset;
int i = 0;
for (GalleryPageView page : pages) {
if (i != 0) {
bottom += mInterval;
}
bottom += page.getHeight();
i++;
}
boolean hasNext = mIndex + pages.size() < mAdapter.size();
BottomState bottomState = mBottomState;
bottomState.bottom = bottom;
bottomState.hasNext = hasNext;
}
// True for get top or bottom
private boolean scrollInternal(float dx, float dy, boolean fling, float x, float y) {
if (mPages.size() == 0) {
return false;
}
int height = mGalleryView.getHeight();
GalleryView galleryView = mGalleryView;
int height = galleryView.getHeight();
boolean requestFill = false;
int remainY = (int) dy;
boolean result = false;
@@ -392,7 +490,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
while (remainY != 0) {
if (remainY < 0) { // Try to show top
int limit;
if (mIterator.hasPrevious()) {
if (mIndex > 0) {
limit = (int) (-height * RESERVATIONS);
} else {
limit = 0;
@@ -405,10 +503,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
mDeltaX = 0;
mDeltaY = 0;
} else {
if (mIterator.hasPrevious()) {
if (mIndex > 0) {
mOffset = limit;
remainY = remainY + limit - mOffset;
onFill();
galleryView.forceFill();
requestFill = false;
mDeltaX = 0;
mDeltaY = 0;
@@ -425,23 +523,10 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
}
}
} else { // Try to show bottom
int bottom = mOffset;
boolean hasNext = true;
GalleryView.PageIterator iterator = mIterator;
iterator.mark();
for (int i = 0, size = mPages.size(); i < size; i++) {
if (i != 0) {
bottom += mInterval;
}
bottom += mPages.get(i).getHeight();
if (iterator.hasNext()) {
iterator.next();
} else {
hasNext = false;
break;
}
}
iterator.reset();
getBottomState();
BottomState bottomState = mBottomState;
int bottom = bottomState.bottom;
boolean hasNext = bottomState.hasNext;
int limit;
if (hasNext) {
@@ -462,7 +547,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
if (hasNext) {
mOffset -= bottom - limit;
remainY = remainY + limit - bottom;
onFill();
galleryView.forceFill();
requestFill = false;
mDeltaX = 0;
mDeltaY = 0;
@@ -493,60 +578,26 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
scrollInternal(dx, dy, false, x, y);
}
private boolean hasNextPage() {
boolean hasNext = true;
GalleryView.PageIterator iterator = mIterator;
iterator.mark();
for (int i = 0, size = mPages.size(); i < size; i++) {
if (iterator.hasNext()) {
iterator.next();
} else {
hasNext = false;
break;
}
}
iterator.reset();
return hasNext;
}
@Override
public void onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
public void onFling(float velocityX, float velocityY) {
if (mPages.size() <= 0) {
return;
}
int maxY;
if (mIterator.hasPrevious()) {
if (mIndex > 0) {
maxY = Integer.MAX_VALUE;
} else {
maxY = -mOffset;
}
int bottom = mOffset;
boolean hasNext = true;
GalleryView.PageIterator iterator = mIterator;
iterator.mark();
for (int i = 0, size = mPages.size(); i < size; i++) {
if (i != 0) {
bottom += mInterval;
}
bottom += mPages.get(i).getHeight();
if (iterator.hasNext()) {
iterator.next();
} else {
hasNext = false;
break;
}
}
iterator.reset();
getBottomState();
BottomState bottomState = mBottomState;
int minY;
if (hasNext) {
if (bottomState.hasNext) {
minY = Integer.MIN_VALUE;
} else {
minY = mGalleryView.getHeight() - bottom;
minY = mGalleryView.getHeight() - bottomState.bottom;
}
mPageFling.startFling((int) velocityX, 0, 0,
@@ -571,7 +622,7 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
@Override
public void onDataChanged() {
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mIterator, null);
AssertUtils.assertNotEquals("The PagerLayoutManager is not attached", mAdapter, null);
// Cancel all animations
cancelAllAnimations();
@@ -585,16 +636,29 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
}
@Override
public GalleryPageView findPageById(int id) {
for (int i = 0, n = mPages.size(); i < n; i++) {
GalleryPageView page = mPages.get(i);
if (page.getId() == id) {
public GalleryPageView findPageByIndex(int index) {
for (GalleryPageView page : mPages) {
if (page.getIndex() == index) {
return page;
}
}
return null;
}
@Override
public int getCurrentIndex() {
int height = mGalleryView.getHeight();
for (GalleryPageView page : mPages) {
Rect bound = page.bounds();
int pageTop = bound.top;
int pageBottom = bound.bottom;
if ((pageTop > 0 && pageTop < height) || (pageBottom > 0 && pageBottom < height)) {
return page.getIndex();
}
}
return GalleryPageView.INVALID_INDEX;
}
class PageFling extends Fling {
private int mVelocityX;
@@ -661,26 +725,13 @@ public class ScrollLayoutManager extends GalleryView.LayoutManager {
@Override
protected void onFinish() {
boolean topEdge = !mIterator.hasPrevious() && mOffset >= 0;
int index = mIndex;
int bottom = mOffset;
boolean hasNext = true;
GalleryView.PageIterator iterator = mIterator;
iterator.mark();
for (int i = 0, size = mPages.size(); i < size; i++) {
if (i != 0) {
bottom += mInterval;
}
bottom += mPages.get(i).getHeight();
if (iterator.hasNext()) {
iterator.next();
} else {
hasNext = false;
break;
}
}
iterator.reset();
boolean bottomEdge = !hasNext && bottom <= mGalleryView.getHeight();
boolean topEdge = index <= 0 && mOffset >= 0;
getBottomState();
BottomState bottomState = mBottomState;
boolean bottomEdge = !bottomState.hasNext && bottomState.bottom <= mGalleryView.getHeight();
if (topEdge && bottomEdge) {
return;
@@ -22,10 +22,12 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.gallery.DirGalleryProvider;
import com.hippo.ehviewer.gallery.EhGalleryProvider;
@@ -37,13 +39,14 @@ import com.hippo.ehviewer.gallery.gl.GalleryView;
import com.hippo.gl.glrenderer.ImageTexture;
import com.hippo.gl.glrenderer.MovableTextTexture;
import com.hippo.gl.view.GLRootView;
import com.hippo.gl.view.GLView;
import com.hippo.image.Image;
import com.hippo.yorozuya.IntIdGenerator;
import com.hippo.util.SystemUiHelper;
import com.hippo.widget.Slider;
import java.io.File;
public class GalleryActivity extends AppCompatActivity implements GalleryProviderListener {
public class GalleryActivity extends AppCompatActivity
implements GalleryProviderListener, Slider.OnSetProgressListener {
public static final String ACTION_DIR = "dir";
public static final String ACTION_ZIP = "zip";
@@ -52,6 +55,7 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
public static final String KEY_ACTION = "action";
public static final String KEY_FILENAME = "filename";
public static final String KEY_GALLERY_INFO = "gallery_info";
public static final String KEY_SHOWING = "showing";
private String mAction;
private String mFilename;
@@ -66,8 +70,19 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
private ImageTexture.Uploader mUploader;
@Nullable
private GalleryProvider mGalleryProvider;
@Nullable
private SparseIntArray mIndexIdMap;
private SystemUiHelper mSystemUiHelper;
private boolean mSystemUiShowing = false;
@Nullable
private View mSliderPanel;
@Nullable
private TextView mLeftText;
@Nullable
private TextView mRightText;
@Nullable
private Slider mSlider;
private void buildProvider() {
if (mGalleryProvider != null) {
@@ -116,6 +131,7 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
if (mGalleryInfo != null) {
outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo);
}
outState.putBoolean(KEY_SHOWING, mSystemUiShowing);
}
@Override
@@ -139,15 +155,33 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
GLRootView glRootView = (GLRootView) findViewById(R.id.gl_root_view);
mGalleryProvider.setGLRoot(glRootView);
mUploader = new ImageTexture.Uploader(glRootView);
mIndexIdMap = new SparseIntArray();
mPageTextTexture = MovableTextTexture.create(Typeface.DEFAULT,
getResources().getDimensionPixelSize(R.dimen.gallery_page_text),
getResources().getColor(R.color.secondary_text_dark),
new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'});
mGalleryView = new GalleryView(this, new GalleryPageIterator(),
mPageTextTexture, null, GalleryView.LAYOUT_MODE_RIGHT_TO_LEFT);
mGalleryView = new GalleryView(this, new GalleryAdapter(),
mPageTextTexture, null, GalleryView.LAYOUT_MODE_TOP_TO_BOTTOM);
glRootView.setContentPane(mGalleryView);
// System UI helper
mSystemUiHelper = new SystemUiHelper(this, SystemUiHelper.LEVEL_IMMERSIVE,
SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES | SystemUiHelper.FLAG_IMMERSIVE_STICKY);
if (savedInstanceState == null || !savedInstanceState.getBoolean(KEY_SHOWING, false)) {
mSystemUiHelper.hide();
mSystemUiShowing = false;
} else {
mSystemUiHelper.show();
mSystemUiShowing = true;
}
/*
mSliderPanel = findViewById(R.id.slider_panel);
mLeftText = (TextView) mSliderPanel.findViewById(R.id.left);
mRightText = (TextView) mSliderPanel.findViewById(R.id.right);
mSlider = (Slider) mSliderPanel.findViewById(R.id.slider);
mSlider.setOnSetProgressListener(this);
*/
}
@Override
@@ -167,13 +201,29 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
mGalleryProvider.stop();
mGalleryProvider = null;
}
mIndexIdMap = null;
mLeftText = null;
mRightText = null;
mSlider = null;
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && mSystemUiHelper != null) {
if (mSystemUiShowing) {
mSystemUiHelper.show();
} else {
mSystemUiHelper.hide();
}
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check volume
if (false) { // TODO Setting.getVolumePage()
if (Settings.getVolumePage()) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
// TODO do something
return true;
@@ -220,7 +270,7 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Check volume
if (false) { // TODO Setting.getVolumePage()
if (Settings.getVolumePage()) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
return true;
@@ -244,16 +294,25 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
}
private GalleryPageView findPageByIndex(int index) {
if (mIndexIdMap == null || mGalleryView == null) {
if (mGalleryView != null) {
return mGalleryView.findPageByIndex(index);
} else {
return null;
}
}
int id = mIndexIdMap.get(index, GLView.NO_ID);
if (id == GLView.NO_ID) {
return null;
private void updateSlider(int size) {
if (mSlider == null) {
return;
}
return mGalleryView.findPageById(id);
if (size >= 0) {
mSlider.setRange(1, size);
} else {
// TODO hide slider
}
}
@Override
@@ -261,6 +320,16 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
if (mGalleryView != null) {
mGalleryView.onDataChanged();
}
/*
SimpleHandler.getInstance().post(new Runnable() {
@Override
public void run() {
if (mGalleryProvider != null) {
updateSlider(mGalleryProvider.size());
}
}
});
*/
}
@Override
@@ -309,97 +378,49 @@ public class GalleryActivity extends AppCompatActivity implements GalleryProvide
}
}
private class GalleryPageIterator extends GalleryView.PageIterator {
private IntIdGenerator mIdGenerator = new IntIdGenerator();
private int mIndex = 0;
private int mBackup = mIndex;
@Override
public void mark() {
mBackup = mIndex;
@Override
public void onSetProgress(Slider slider, int newProgress, int oldProgress,
boolean byUser, boolean confirm) {
if (confirm && mGalleryView != null) {
//mGalleryView.goToPage(newProgress);
}
}
@Override
public void reset() {
mIndex = mBackup;
}
@Override
public boolean isWaiting() {
return mGalleryProvider != null && mGalleryProvider.size() == GalleryProvider.STATE_WAIT;
}
private class GalleryAdapter extends GalleryView.Adapter {
@Override
public String getError() {
if (mGalleryProvider == null) {
return getString(R.string.error_no_provider);
} else if (mGalleryProvider.size() == GalleryProvider.STATE_ERROR) {
String error = mGalleryProvider.getError();
if (error != null) {
return error;
} else {
return getString(R.string.error_unknown);
}
} else if (mGalleryProvider.size() == 0) {
return getString(R.string.error_empty);
} else if (mGalleryProvider.size() <= GalleryProvider.STATE_ERROR) {
return mGalleryProvider.getError();
}
return null;
}
@Override
public boolean hasNext() {
return mGalleryProvider != null && mIndex < mGalleryProvider.size() - 1;
}
@Override
public boolean hasPrevious() {
return mIndex > 0;
}
@Override
public void next() {
if (mGalleryProvider != null && mIndex >= mGalleryProvider.size() - 1) {
throw new IndexOutOfBoundsException();
public int size() {
if (mGalleryProvider == null) {
return GalleryProvider.STATE_ERROR;
} else {
return mGalleryProvider.size();
}
mIndex++;
}
@Override
public void previous() {
if (mIndex <= 0) {
throw new IndexOutOfBoundsException();
}
mIndex--;
}
@Override
public int onBind(GalleryPageView view) {
public void onBind(GalleryPageView view, int index) {
if (mGalleryProvider != null && mGalleryView != null) {
mGalleryProvider.request(mIndex);
mGalleryProvider.request(index);
view.showInfo();
view.setImage(null);
view.setPage(mIndex + 1);
view.setPage(index + 1);
view.setProgress(GalleryPageView.PROGRESS_INDETERMINATE);
view.setError(null, null);
}
int id = mIdGenerator.nextId();
if (mIndexIdMap != null) {
mIndexIdMap.put(mIndex, id);
}
return id;
}
@Override
public void onUnbind(GalleryPageView view) {
if (mIndexIdMap != null) {
int index = mIndexIdMap.indexOfKey(view.getId());
if (index >= 0) {
mIndexIdMap.removeAt(index);
}
}
view.setImage(null);
view.setError(null, null);
}
@@ -35,6 +35,7 @@ import android.widget.ListAdapter;
import com.hippo.app.AppCompatPreferenceActivity;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.fragment.EhFragment;
import com.hippo.ehviewer.ui.fragment.ReadFragment;
import com.hippo.vector.VectorDrawable;
import java.lang.reflect.Field;
@@ -46,6 +47,7 @@ public final class SettingsActivity extends AppCompatPreferenceActivity {
private static final String[] ENTRY_FRAGMENTS = {
EhFragment.class.getName(),
ReadFragment.class.getName(),
};
private class FakeLayoutInflater extends LayoutInflater {
@@ -0,0 +1,31 @@
/*
* 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.ui.fragment;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import com.hippo.ehviewer.R;
public class ReadFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.read_settings);
}
}
@@ -0,0 +1,287 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.util;
import android.app.Activity;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
/**
* Helper for controlling the visibility of the System UI across the various API levels. To use
* this API, instantiate an instance of this class with the required level. The level specifies the
* extent to which the System UI's visibility is changed when you call {@link #hide()}
* or {@link #toggle()}.
*/
public final class SystemUiHelper {
/**
* In this level, the helper will toggle low profile mode.
*/
public static final int LEVEL_LOW_PROFILE = 0;
/**
* In this level, the helper will toggle the visibility of the status bar.
* If there is a navigation bar, it will toggle low profile mode.
*/
public static final int LEVEL_HIDE_STATUS_BAR = 1;
/**
* In this level, the helper will toggle the visibility of the navigation bar
* (if present and if possible) and status bar. In cases where the navigation
* bar is present but cannot be hidden, it will toggle low profile mode.
*/
public static final int LEVEL_LEAN_BACK = 2;
/**
* In this level, the helper will toggle the visibility of the navigation bar
* (if present and if possible) and status bar, in an immersive mode. This means that the app
* will continue to receive all touch events. The user can reveal the system bars with an
* inward swipe along the region where the system bars normally appear.
*
* <p>The {@link #FLAG_IMMERSIVE_STICKY} flag can be used to control how the system bars are
* displayed.
*/
public static final int LEVEL_IMMERSIVE = 3;
/**
* When this flag is set, the
* {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN}
* flag will be set on older devices, making the status bar "float" on top
* of the activity layout. This is most useful when there are no controls at
* the top of the activity layout.
* <p>
* This flag isn't used on newer devices because the <a
* href="http://developer.android.com/design/patterns/actionbar.html">action
* bar</a>, the most important structural element of an Android app, should
* be visible and not obscured by the system UI.
*/
public static final int FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES = 0x1;
/**
* Used with {@link #LEVEL_IMMERSIVE}. When this flag is set, an inward swipe in the system
* bars areas will cause the system bars to temporarily appear in a semi-transparent state,
* but no flags are cleared, and your system UI visibility change listeners are not triggered.
* The bars automatically hide again after a short delay, or if the user interacts with the
* middle of the screen.
*/
public static final int FLAG_IMMERSIVE_STICKY = 0x2;
private static final String LOG_TAG = SystemUiHelper.class.getSimpleName();
private final SystemUiHelperImpl mImpl;
private final Handler mHandler;
private final Runnable mHideRunnable;
/**
* Construct a new SystemUiHelper.
*
* @param activity The Activity who's system UI should be changed
* @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE},
* {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or
* {@link #LEVEL_IMMERSIVE}
* @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and
* {@link #FLAG_IMMERSIVE_STICKY}
*/
public SystemUiHelper(Activity activity, int level, int flags) {
this(activity, level, flags, null);
}
/**
* Construct a new SystemUiHelper.
*
* @param activity The Activity who's system UI should be changed
* @param level The level of hiding. Should be either {@link #LEVEL_LOW_PROFILE},
* {@link #LEVEL_HIDE_STATUS_BAR}, {@link #LEVEL_LEAN_BACK} or
* {@link #LEVEL_IMMERSIVE}
* @param flags Additional options. See {@link #FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES} and
* {@link #FLAG_IMMERSIVE_STICKY}
* @param listener A listener which is called when the system visibility is changed
*/
public SystemUiHelper(Activity activity, int level, int flags,
OnVisibilityChangeListener listener) {
mHandler = new Handler(Looper.getMainLooper());
mHideRunnable = new HideRunnable();
// Create impl
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mImpl = new SystemUiHelperImplKK(activity, level, flags, listener);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mImpl = new SystemUiHelperImplJB(activity, level, flags, listener);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
mImpl = new SystemUiHelperImplICS(activity, level, flags, listener);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mImpl = new SystemUiHelperImplHC(activity, level, flags, listener);
} else {
mImpl = new SystemUiHelperImplBase(activity, level, flags, listener);
}
}
/**
* @return true if the system UI is currently showing. What this means depends on the mode this
* {@link SystemUiHelper} was instantiated with.
*/
public boolean isShowing() {
return mImpl.isShowing();
}
/**
* Show the system UI. What this means depends on the mode this {@link SystemUiHelper} was
* instantiated with.
*
* <p>Any currently queued delayed hide requests will be removed.
*/
public void show() {
// Ensure that any currently queued hide calls are removed
removeQueuedRunnables();
mImpl.show();
}
/**
* Hide the system UI. What this means depends on the mode this {@link SystemUiHelper} was
* instantiated with.
*
* <p>Any currently queued delayed hide requests will be removed.
*/
public void hide() {
// Ensure that any currently queued hide calls are removed
removeQueuedRunnables();
mImpl.hide();
}
/**
* Request that the system UI is hidden after a delay.
*
* <p>Any currently queued delayed hide requests will be removed.
*
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
*/
public void delayHide(long delayMillis) {
// Ensure that any currently queued hide calls are removed
removeQueuedRunnables();
mHandler.postDelayed(mHideRunnable, delayMillis);
}
/**
* Toggle whether the system UI is displayed.
*/
public void toggle() {
if (mImpl.isShowing()) {
mImpl.hide();
} else {
mImpl.show();
}
}
private void removeQueuedRunnables() {
// Ensure that any currently queued hide calls are removed
mHandler.removeCallbacks(mHideRunnable);
}
/**
* A callback interface used to listen for system UI visibility changes.
*/
public interface OnVisibilityChangeListener {
/**
* Called when the system UI visibility has changed.
*
* @param visible True if the system UI is visible.
*/
public void onVisibilityChange(boolean visible);
}
static abstract class SystemUiHelperImpl {
final Activity mActivity;
final int mLevel;
final int mFlags;
final OnVisibilityChangeListener mOnVisibilityChangeListener;
boolean mIsShowing = true;
SystemUiHelperImpl(Activity activity, int level, int flags,
OnVisibilityChangeListener onVisibilityChangeListener) {
mActivity = activity;
mLevel = level;
mFlags = flags;
mOnVisibilityChangeListener = onVisibilityChangeListener;
}
abstract void show();
abstract void hide();
boolean isShowing() {
return mIsShowing;
}
void setIsShowing(boolean isShowing) {
mIsShowing = isShowing;
if (mOnVisibilityChangeListener != null) {
mOnVisibilityChangeListener.onVisibilityChange(mIsShowing);
}
}
}
/**
* Base implementation. Used on API level 10 and below.
*/
static class SystemUiHelperImplBase extends SystemUiHelperImpl {
SystemUiHelperImplBase(Activity activity, int level, int flags,
OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
if ((mFlags & SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES) != 0) {
mActivity.getWindow().addFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
}
@Override
void show() {
if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) {
mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(true);
}
}
@Override
void hide() {
if (mLevel > SystemUiHelper.LEVEL_LOW_PROFILE) {
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(false);
}
}
}
private class HideRunnable implements Runnable {
@Override
public void run() {
hide();
}
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.util;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
class SystemUiHelperImplHC extends SystemUiHelper.SystemUiHelperImpl
implements View.OnSystemUiVisibilityChangeListener {
final View mDecorView;
SystemUiHelperImplHC(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
mDecorView = activity.getWindow().getDecorView();
mDecorView.setOnSystemUiVisibilityChangeListener(this);
}
@Override
void show() {
mDecorView.setSystemUiVisibility(createShowFlags());
}
@Override
void hide() {
mDecorView.setSystemUiVisibility(createHideFlags());
}
@Override
public final void onSystemUiVisibilityChange(int visibility) {
if ((visibility & createTestFlags()) != 0) {
onSystemUiHidden();
} else {
onSystemUiShown();
}
}
protected void onSystemUiShown() {
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.show();
}
mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(true);
}
protected void onSystemUiHidden() {
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.hide();
}
mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setIsShowing(false);
}
protected int createShowFlags() {
return View.STATUS_BAR_VISIBLE;
}
protected int createHideFlags() {
return View.STATUS_BAR_HIDDEN;
}
protected int createTestFlags() {
return View.STATUS_BAR_HIDDEN;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.util;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.view.View;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
class SystemUiHelperImplICS extends SystemUiHelperImplHC {
SystemUiHelperImplICS(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
}
@Override
protected int createShowFlags() {
return View.SYSTEM_UI_FLAG_VISIBLE;
}
@Override
protected int createTestFlags() {
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
// Intentionally override test flags.
return View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
return View.SYSTEM_UI_FLAG_LOW_PROFILE;
}
@Override
protected int createHideFlags() {
int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE;
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
return flag;
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.util;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.os.Build;
import android.view.View;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
class SystemUiHelperImplJB extends SystemUiHelperImplICS {
SystemUiHelperImplJB(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
}
@Override
protected int createShowFlags() {
int flag = super.createShowFlags();
if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
}
return flag;
}
@Override
protected int createHideFlags() {
int flag = super.createHideFlags();
if (mLevel >= SystemUiHelper.LEVEL_HIDE_STATUS_BAR) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_FULLSCREEN;
if (mLevel >= SystemUiHelper.LEVEL_LEAN_BACK) {
flag |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
}
}
return flag;
}
@Override
protected void onSystemUiShown() {
if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) {
// Manually show the action bar when in low profile mode.
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.show();
}
}
setIsShowing(true);
}
@Override
protected void onSystemUiHidden() {
if (mLevel == SystemUiHelper.LEVEL_LOW_PROFILE) {
// Manually hide the action bar when in low profile mode.
ActionBar ab = mActivity.getActionBar();
if (ab != null) {
ab.hide();
}
}
setIsShowing(false);
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.util;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.view.View;
@TargetApi(Build.VERSION_CODES.KITKAT)
class SystemUiHelperImplKK extends SystemUiHelperImplJB {
SystemUiHelperImplKK(Activity activity, int level, int flags,
SystemUiHelper.OnVisibilityChangeListener onVisibilityChangeListener) {
super(activity, level, flags, onVisibilityChangeListener);
}
@Override
protected int createHideFlags() {
int flag = super.createHideFlags();
if (mLevel == SystemUiHelper.LEVEL_IMMERSIVE) {
// If the client requested immersive mode, and we're on Android 4.4
// or later, add relevant flags. Applying HIDE_NAVIGATION without
// IMMERSIVE prevents the activity from accepting all touch events,
// so we only do this on Android 4.4 and later (where IMMERSIVE is
// present).
flag |= ((mFlags & SystemUiHelper.FLAG_IMMERSIVE_STICKY) != 0)
? View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
: View.SYSTEM_UI_FLAG_IMMERSIVE;
}
return flag;
}
}
@@ -0,0 +1,178 @@
/*
* 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.widget;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.BatteryManager;
import android.util.AttributeSet;
import android.widget.TextView;
import com.hippo.drawable.BatteryDrawable;
import com.hippo.ehviewer.R;
public class BatteryView extends TextView {
private int mColor;
private int mWarningColor;
private int mCurrentColor;
private int mLevel = 0;
private boolean mCharging = false;
private BatteryDrawable mDrawable;
private boolean mAttached = false;
private boolean mIsChargerWorking = false;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@SuppressLint("SetTextI18n")
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
== BatteryManager.BATTERY_STATUS_CHARGING);
if (mLevel != level || mCharging != charging) {
mLevel = level;
mCharging = charging;
if (mCharging && mLevel != 100) {
startCharger();
} else {
stopCharger();
mDrawable.setElect(mLevel);
}
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
setTextColor(mWarningColor);
} else {
setTextColor(mColor);
}
setText(mLevel + "%");
}
}
};
private final Runnable mCharger = new Runnable() {
private int level = 0;
@Override
public void run() {
level += 2;
if (level > 100) {
level = 0;
}
mDrawable.setElect(level, false);
getHandler().postDelayed(mCharger, 200);
}
};
public BatteryView(Context context) {
super(context);
init();
}
public BatteryView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
TypedArray typedArray = context.obtainStyledAttributes(
attrs, R.styleable.BatteryView, defStyleAttr, 0);
mColor = typedArray.getColor(R.styleable.BatteryView_color, Color.WHITE);
mWarningColor = typedArray.getColor(R.styleable.BatteryView_warningColor, Color.RED);
typedArray.recycle();
mDrawable.setColor(mColor);
mDrawable.setWarningColor(mWarningColor);
}
private void init() {
mDrawable = new BatteryDrawable();
int height = (int) getTextSize();
mDrawable.setBounds(0, 0, (int) (height / 0.618f), height);
setCompoundDrawables(mDrawable, null, null, null);
}
@Override
public void setTextColor(int color) {
if (mCurrentColor == color) {
return;
}
mCurrentColor = color;
super.setTextColor(color);
}
private void startCharger() {
if (!mIsChargerWorking) {
getHandler().post(mCharger);
mIsChargerWorking = true;
}
}
private void stopCharger() {
if (mIsChargerWorking) {
getHandler().removeCallbacks(mCharger);
mIsChargerWorking = false;
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mAttached) {
mAttached = true;
registerReceiver();
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAttached) {
unregisterReceiver();
stopCharger();
mAttached = false;
}
}
private void registerReceiver() {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
}
private void unregisterReceiver() {
getContext().unregisterReceiver(mIntentReceiver);
}
}
@@ -0,0 +1,78 @@
/*
* 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.widget;
final class DateUtils {
public static final char QUOTE = '\'';
public static final char SECONDS = 's';
public static boolean hasSeconds(CharSequence inFormat) {
return hasDesignator(inFormat, SECONDS);
}
public static boolean hasDesignator(CharSequence inFormat, char designator) {
if (inFormat == null) return false;
final int length = inFormat.length();
int c;
int count;
for (int i = 0; i < length; i += count) {
count = 1;
c = inFormat.charAt(i);
if (c == QUOTE) {
count = skipQuotedText(inFormat, i, length);
} else if (c == designator) {
return true;
}
}
return false;
}
private static int skipQuotedText(CharSequence s, int i, int len) {
if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
return 2;
}
int count = 1;
// skip leading quote
i++;
while (i < len) {
char c = s.charAt(i);
if (c == QUOTE) {
count++;
// QUOTEQUOTE -> QUOTE
if (i + 1 < len && s.charAt(i + 1) == QUOTE) {
i++;
} else {
break;
}
} else {
i++;
count++;
}
}
return count;
}
}
@@ -0,0 +1,270 @@
/*
* 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.widget;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.ViewDebug.ExportedProperty;
import android.widget.TextView;
import java.util.Calendar;
import java.util.TimeZone;
public class TextClock extends TextView {
public static final CharSequence DEFAULT_FORMAT_12_HOUR = "hh:mm a";
public static final CharSequence DEFAULT_FORMAT_24_HOUR;
static {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
DEFAULT_FORMAT_24_HOUR = "kk:mm";
else
DEFAULT_FORMAT_24_HOUR = "HH:mm";
}
private CharSequence mFormat12;
private CharSequence mFormat24;
@ExportedProperty
private CharSequence mFormat;
@ExportedProperty
private boolean mHasSeconds;
private boolean mAttached;
private Calendar mTime;
private String mTimeZone;
private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
chooseFormat();
onTimeChanged();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
chooseFormat();
onTimeChanged();
}
};
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
}
onTimeChanged();
}
};
private final Runnable mTicker = new Runnable() {
@Override
public void run() {
onTimeChanged();
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
getHandler().postAtTime(mTicker, next);
}
};
public TextClock(Context context) {
this(context, null);
}
public TextClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextClock(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mFormat12 = DEFAULT_FORMAT_12_HOUR;
mFormat24 = DEFAULT_FORMAT_24_HOUR;
mTimeZone = TimeZone.getDefault().getID();
init();
}
private void init() {
createTime(mTimeZone);
// Wait until onAttachedToWindow() to handle the ticker
chooseFormat(false);
}
private void createTime(String timeZone) {
if (timeZone != null) {
mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
} else {
mTime = Calendar.getInstance();
}
}
public CharSequence getFormat12Hour() {
return mFormat12;
}
public void setFormat12Hour(CharSequence format) {
mFormat12 = format;
chooseFormat();
onTimeChanged();
}
public CharSequence getFormat24Hour() {
return mFormat24;
}
public void setFormat24Hour(CharSequence format) {
mFormat24 = format;
chooseFormat();
onTimeChanged();
}
public boolean is24HourModeEnabled() {
return DateFormat.is24HourFormat(getContext());
}
public String getTimeZone() {
return mTimeZone;
}
public void setTimeZone(String timeZone) {
mTimeZone = timeZone;
createTime(timeZone);
onTimeChanged();
}
/**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
* depending on whether the user has selected 24-hour format.
*
* Calling this method does not schedule or unschedule the time ticker.
*/
private void chooseFormat() {
chooseFormat(true);
}
public CharSequence getFormat() {
return mFormat;
}
/**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
* depending on whether the user has selected 24-hour format.
*
* @param handleTicker true if calling this method should schedule/unschedule the
* time ticker, false otherwise
*/
private void chooseFormat(boolean handleTicker) {
final boolean format24Requested = is24HourModeEnabled();
if (format24Requested) {
mFormat = mFormat24;
} else {
mFormat = mFormat12;
}
boolean hadSeconds = mHasSeconds;
mHasSeconds = DateUtils.hasSeconds(mFormat);
if (handleTicker && mAttached && hadSeconds != mHasSeconds) {
if (hadSeconds) getHandler().removeCallbacks(mTicker);
else mTicker.run();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!mAttached) {
mAttached = true;
registerReceiver();
registerObserver();
createTime(mTimeZone);
if (mHasSeconds) {
mTicker.run();
} else {
onTimeChanged();
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mAttached) {
unregisterReceiver();
unregisterObserver();
getHandler().removeCallbacks(mTicker);
mAttached = false;
}
}
private void registerReceiver() {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_TICK);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
}
private void registerObserver() {
final ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver);
}
private void unregisterReceiver() {
getContext().unregisterReceiver(mIntentReceiver);
}
private void unregisterObserver() {
final ContentResolver resolver = getContext().getContentResolver();
resolver.unregisterContentObserver(mFormatChangeObserver);
}
private void onTimeChanged() {
mTime.setTimeInMillis(System.currentTimeMillis());
setText(DateFormat.format(mFormat, mTime));
}
}