From 36424b11a9fd9bfa1abe338b099c2e4525b8bf83 Mon Sep 17 00:00:00 2001 From: seven332 Date: Sun, 9 Aug 2015 00:11:51 +0800 Subject: [PATCH] Improve gallery activity --- .../fonts/{number.ttf => gallery_index.ttf} | Bin .../main/assets/fonts/gallery_progress.ttf | Bin 0 -> 12680 bytes .../hippo/ehviewer/gallery/SpiderInfo.java | 8 +- .../hippo/ehviewer/gallery/SpiderQueen.java | 3 +- .../ehviewer/gallery/anim/Animation.java | 13 +- .../ehviewer/gallery/anim/FloatAnimation.java | 11 +- .../anim/StateTransitionAnimation.java | 176 -------------- .../gallery/glrenderer/CanvasTexture.java | 5 +- .../gallery/glrenderer/TextTexture.java | 49 +++- .../glrenderer/XmlResourceTexture.java | 79 ++++++ .../ui/{FrameView.java => FrameLayout.java} | 2 +- .../hippo/ehviewer/gallery/ui/GLRootView.java | 30 +++ .../com/hippo/ehviewer/gallery/ui/GLView.java | 128 +++++++--- .../ehviewer/gallery/ui/GalleryLayout.java | 89 +++++++ .../ehviewer/gallery/ui/GalleryPageView.java | 14 +- .../ehviewer/gallery/ui/GalleryPanel.java | 228 ++++++++++++++++++ .../ehviewer/gallery/ui/GalleryView.java | 96 ++++++-- .../hippo/ehviewer/gallery/ui/Gravity.java | 40 +++ .../ehviewer/gallery/ui/LinearLayout.java | 208 ++++++++++++++++ .../hippo/ehviewer/gallery/ui/LinearView.java | 74 ------ .../ehviewer/gallery/ui/ProgressView.java | 30 ++- .../com/hippo/ehviewer/gallery/ui/Slider.java | 144 +++++++++++ .../hippo/ehviewer/gallery/ui/TextView.java | 25 +- .../ehviewer/gallery/ui/XmlResourceView.java | 56 +++++ .../ehviewer/gallery/util/GalleryUtils.java | 4 + .../hippo/ehviewer/ui/GalleryActivity.java | 131 ++++++++-- .../hippo/scene/ElementsSharedCurtain.java | 2 +- .../java/com/hippo/scene/SceneManager.java | 3 +- .../res/drawable/ic_file_image_dark_x48.xml | 42 ++++ .../main/res/drawable/ic_refresh_dark_x48.xml | 39 +++ .../drawable/ic_screen_rotation_dark_x48.xml | 39 +++ .../main/res/drawable/ic_share_dark_x48.xml | 40 +++ app/src/main/res/drawable/ic_url_dark_x48.xml | 41 ++++ app/src/main/res/layout/activity_gallery.xml | 3 +- app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/pathdata.xml | 3 + 36 files changed, 1492 insertions(+), 364 deletions(-) rename app/src/main/assets/fonts/{number.ttf => gallery_index.ttf} (100%) create mode 100644 app/src/main/assets/fonts/gallery_progress.ttf delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/anim/StateTransitionAnimation.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/XmlResourceTexture.java rename app/src/main/java/com/hippo/ehviewer/gallery/ui/{FrameView.java => FrameLayout.java} (98%) create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPanel.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearLayout.java delete mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/XmlResourceView.java create mode 100644 app/src/main/res/drawable/ic_file_image_dark_x48.xml create mode 100644 app/src/main/res/drawable/ic_refresh_dark_x48.xml create mode 100644 app/src/main/res/drawable/ic_screen_rotation_dark_x48.xml create mode 100644 app/src/main/res/drawable/ic_share_dark_x48.xml create mode 100644 app/src/main/res/drawable/ic_url_dark_x48.xml diff --git a/app/src/main/assets/fonts/number.ttf b/app/src/main/assets/fonts/gallery_index.ttf similarity index 100% rename from app/src/main/assets/fonts/number.ttf rename to app/src/main/assets/fonts/gallery_index.ttf diff --git a/app/src/main/assets/fonts/gallery_progress.ttf b/app/src/main/assets/fonts/gallery_progress.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5c430d452571f86f15de81d20a0f7e1aee539152 GIT binary patch literal 12680 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 0f) { - applyOldTexture(view, canvas, mCurrentBackgroundAlpha, mCurrentBackgroundScale, true); - } - } - - public void applyContentTransform(GLView view, GLCanvas canvas) { - int xOffset = view.getWidth() / 2; - int yOffset = view.getHeight() / 2; - canvas.translate(xOffset, yOffset); - canvas.scale(mCurrentContentScale, mCurrentContentScale, 1); - canvas.translate(-xOffset, -yOffset); - canvas.setAlpha(mCurrentContentAlpha); - } - - public void applyOverlay(GLView view, GLCanvas canvas) { - if (mCurrentOverlayAlpha > 0f) { - applyOldTexture(view, canvas, mCurrentOverlayAlpha, mCurrentOverlayScale, false); - } - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java index de31d21d1..40ee91bbe 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java @@ -24,7 +24,6 @@ import android.graphics.Canvas; // The subclasses should override onDraw() to draw on the bitmap. // By default CanvasTexture is not opaque. abstract class CanvasTexture extends UploadedTexture { - protected Canvas mCanvas; private final Config mConfig; public CanvasTexture(int width, int height) { @@ -36,8 +35,8 @@ abstract class CanvasTexture extends UploadedTexture { @Override protected Bitmap onGetBitmap() { Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig); - mCanvas = new Canvas(bitmap); - onDraw(mCanvas, bitmap); + Canvas canvas = new Canvas(bitmap); + onDraw(canvas, bitmap); return bitmap; } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextTexture.java index b3846245f..0ab94decd 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextTexture.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextTexture.java @@ -23,6 +23,19 @@ public class TextTexture extends SpriteTexture { mMaxWidth = maxWidth; } + public int[] getTextIndexes(String text) { + char[] characters = mCharacters; + + int length = text.length(); + int[] indexes = new int[length]; + for (int i = 0; i < length; i++) { + char ch = text.charAt(i); + indexes[i] = Arrays.binarySearch(characters, ch); + } + + return indexes; + } + public float getTextWidth(String text) { char[] characters = mCharacters; float[] widths = mWidths; @@ -41,7 +54,27 @@ public class TextTexture extends SpriteTexture { return width; } - public float getTextHeight(String text) { + public float getTextWidth(int[] indexes) { + float[] widths = mWidths; + float width = 0.0f; + int length = indexes.length; + for (int i = 0; i < length; i++) { + int index = indexes[i]; + if (index >= 0) { + width += widths[index]; + } else { + width += mMaxWidth; + } + } + + return width; + } + + public float getMaxWidth() { + return mMaxWidth; + } + + public float getTextHeight() { return mHeight; } @@ -61,6 +94,20 @@ public class TextTexture extends SpriteTexture { } } + public void drawText(GLCanvas canvas, int[] indexes, int x, int y) { + float[] widths = mWidths; + + for (int i = 0, n = indexes.length; i < n; i++) { + int index = indexes[i]; + if (index >= 0) { + drawSprite(canvas, index, x, y); + x += widths[index]; + } else { + x += mMaxWidth; + } + } + } + /** * Create a TextTexture to draw text * diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/XmlResourceTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/XmlResourceTexture.java new file mode 100644 index 000000000..97755a405 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/XmlResourceTexture.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.support.v4.content.ContextCompat; + +public class XmlResourceTexture extends UploadedTexture { + + private Context mContext; + private int mResId; + private int mWidth; + private int mHeight; + + public XmlResourceTexture(Context context, @DrawableRes int resId) { + mContext = context; + mResId = resId; + setOpaque(false); + } + + public void setBound(int width, int height) { + mWidth = width; + mHeight = height; + invalidateContent(); + } + + @Override + protected Bitmap onGetBitmap() { + Drawable drawable = ContextCompat.getDrawable(mContext, mResId); + + int width = mWidth; + int height = mHeight; + if (mWidth <= 0) { + width = drawable.getIntrinsicWidth(); + } + if (mHeight <= 0) { + height = drawable.getIntrinsicHeight(); + } + if (mWidth <= 0) { + width = 1; + } + if (mHeight <= 0) { + height = 1; + } + + drawable.setBounds(0, 0, width, height); + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.draw(canvas); + + return bitmap; + } + + @Override + protected void onFreeBitmap(Bitmap bitmap) { + if (!inFinalizer()) { + bitmap.recycle(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/FrameView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/FrameLayout.java similarity index 98% rename from app/src/main/java/com/hippo/ehviewer/gallery/ui/FrameView.java rename to app/src/main/java/com/hippo/ehviewer/gallery/ui/FrameLayout.java index db8d07740..60ca1b5e3 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/FrameView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/FrameLayout.java @@ -1,6 +1,6 @@ package com.hippo.ehviewer.gallery.ui; -public class FrameView extends GLView { +public class FrameLayout extends GLView { @Override protected void onMeasure(int widthSpec, int heightSpec) { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java index 9e34a46a4..2354a0ea4 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java @@ -19,10 +19,12 @@ package com.hippo.ehviewer.gallery.ui; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Matrix; +import android.graphics.Rect; import android.opengl.GLSurfaceView; import android.os.Build; import android.os.Process; import android.os.SystemClock; +import android.support.annotation.NonNull; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; @@ -36,6 +38,7 @@ import com.hippo.ehviewer.gallery.glrenderer.UploadedTexture; import com.hippo.ehviewer.gallery.util.ApiHelper; import com.hippo.ehviewer.gallery.util.GalleryUtils; import com.hippo.ehviewer.gallery.util.MotionEventHelper; +import com.hippo.widget.FitPaddingImpl; import com.hippo.yorozuya.AssertUtils; import java.util.ArrayDeque; @@ -609,6 +612,33 @@ public class GLRootView extends GLSurfaceView } } + @SuppressWarnings("deprecation") + @Override + protected boolean fitSystemWindows(@NonNull Rect insets) { + int fitPaddingLeft = insets.left; + int fitPaddingTop = insets.top; + int fitPaddingRight = insets.right; + int fitPaddingBottom = insets.bottom; + + GLView contentView = mContentView; + if (contentView != null) { + for (int i = 0, n = contentView.getComponentCount(); i < n; i++) { + GLView component = contentView.getComponent(i); + if (component instanceof FitPaddingImpl) { + ((FitPaddingImpl) component).onFitPadding(fitPaddingLeft ,fitPaddingTop, + fitPaddingRight, fitPaddingBottom); + } + } + } + + insets.left = 0; + insets.top = 0; + insets.right = 0; + insets.bottom = 0; + + return super.fitSystemWindows(insets); + } + private static abstract class BaseConfigChooser implements EGLConfigChooser { public BaseConfigChooser(int[] configSpec) { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java index f59a09059..253935791 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java @@ -23,7 +23,6 @@ import android.util.Log; import android.view.MotionEvent; import com.hippo.ehviewer.gallery.anim.CanvasAnimation; -import com.hippo.ehviewer.gallery.anim.StateTransitionAnimation; import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; import com.hippo.ehviewer.gallery.glrenderer.GLPaint; import com.hippo.yorozuya.AssertUtils; @@ -45,10 +44,12 @@ import java.util.ArrayList; // from main thread (like a Handler) in your GLView, you need to call // lockRendering() if the rendering thread should not run at the same time. // +// TODO Add store state +// TODO Add click listener public class GLView { private static final String TAG = "GLView"; - private static final boolean DEBUG_DRAW_BOUNDS = false; + private static final boolean DEBUG_DRAW_BOUNDS = true; public static final int VISIBLE = 0; public static final int INVISIBLE = 1; @@ -107,8 +108,7 @@ public class GLView { private LayoutParams mLayoutParams; - private float [] mBackgroundColor; - private StateTransitionAnimation mTransition; + private int mBackgroundColor = Color.TRANSPARENT; public void startAnimation(CanvasAnimation animation) { GLRoot root = getGLRoot(); @@ -195,7 +195,9 @@ public class GLView { } // Ensure params is valid - if (!checkLayoutParams(params)) { + if (params == null) { + params = generateDefaultLayoutParams(); + } else if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } @@ -319,17 +321,16 @@ public class GLView { } protected void render(GLCanvas canvas) { - boolean transitionActive = false; - if (mTransition != null && mTransition.calculate(AnimationTime.get())) { - invalidate(); - transitionActive = mTransition.isActive(); + // render background color + if (Color.alpha(mBackgroundColor) != 0) { + canvas.fillRect(0, 0, getWidth(), getHeight(), mBackgroundColor); } - renderBackground(canvas); + + // render content onRender(canvas); + + // render child canvas.save(); - if (transitionActive) { - mTransition.applyContentTransform(this, canvas); - } for (int i = 0, n = getComponentCount(); i < n; ++i) { GLView component = getComponent(i); if (Rect.intersects(mRenderCheckBounds, component.mBounds)) { @@ -338,40 +339,19 @@ public class GLView { } canvas.restore(); + // render bounds if (DEBUG_DRAW_BOUNDS) { canvas.drawRect(0, 0, getWidth(), getHeight(), mDrawBoundsPaint); } - - if (transitionActive) { - mTransition.applyOverlay(this, canvas); - } } protected void onRender(GLCanvas canvas) { } - public void setIntroAnimation(StateTransitionAnimation intro) { - mTransition = intro; - if (mTransition != null) mTransition.start(); - } - - public float [] getBackgroundColor() { - return mBackgroundColor; - } - - public void setBackgroundColor(float [] color) { + public void setBackgroundColor(int color) { mBackgroundColor = color; } - protected void renderBackground(GLCanvas view) { - if (mBackgroundColor != null) { - view.clearBuffer(mBackgroundColor); - } - if (mTransition != null && mTransition.isActive()) { - mTransition.applyBackground(this, view); - } - } - protected void renderChild(GLCanvas canvas, GLView component) { if (component.getVisibility() != GLView.VISIBLE && component.mAnimation == null) return; @@ -449,6 +429,26 @@ public class GLView { return onTouch(event); } + public void setPaddings(int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { + mPaddings.set(paddingLeft, paddingTop, paddingRight, paddingBottom); + } + + public void setPaddingLeft(int paddingLeft) { + mPaddings.left = paddingLeft; + } + + public void setPaddingTop(int paddingTop) { + mPaddings.top = paddingTop; + } + + public void setPaddingRight(int paddingRight) { + mPaddings.right = paddingRight; + } + + public void setPaddingBottom(int paddingBottom) { + mPaddings.bottom = paddingBottom; + } + public Rect getPaddings() { return mPaddings; } @@ -498,11 +498,11 @@ public class GLView { } protected int getSuggestedMinimumHeight() { - return getMinimumHeight(); + return getMinimumHeight() + mPaddings.top + mPaddings.bottom; } protected int getSuggestedMinimumWidth() { - return getMinimumWidth(); + return getMinimumWidth() + mPaddings.left + mPaddings.right; } /** @@ -574,6 +574,28 @@ public class GLView { boolean changeSize, int left, int top, int right, int bottom) { } + /** + * Utility to return a default begin. Uses the supplied number as left or top. + * + * @param size the parent size + * @param specSize the child size + * @param paddingBeign the padding begin + * @param paddingFinish the padding finish + * @param position the {@link Gravity#POSITION_BEGIN} or {@link Gravity#POSITION_CENTER} or + * {@link Gravity#POSITION_FINISH} + * @return the begin size + */ + public static int getDefaultBegin(int size, int specSize, int paddingBeign, int paddingFinish, + @Gravity.PositionMode int position) { + if (position == Gravity.POSITION_FINISH) { + return size - paddingFinish - specSize; + } else if (position == Gravity.POSITION_CENTER) { + return ((size - paddingBeign - paddingFinish) / 2) - (specSize / 2) + paddingBeign; + } else { + return paddingBeign; + } + } + /** * Utility to return a default size. Uses the supplied size if the * MeasureSpec imposed no constraints. Will get larger if allowed @@ -601,6 +623,32 @@ public class GLView { return result; } + /** + * Utility to return a max size. Uses the supplied size if the + * MeasureSpec imposed no constraints. Will get larger if allowed + * by the MeasureSpec. + * + * @param size Default size for this view + * @param measureSpec Constraints imposed by the parent + * @return The size this view should be. + */ + public static int getMaxSize(int size, int measureSpec) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + result = size; + break; + case MeasureSpec.EXACTLY: + case MeasureSpec.AT_MOST: + result = specSize; + break; + } + return result; + } + public static int getComponentSpec(int spec, int childSize) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); @@ -969,6 +1017,10 @@ public class GLView { public GravityLayoutParams(GLView.LayoutParams source) { super(source); + + if (source instanceof GravityLayoutParams) { + gravity = ((GravityLayoutParams) source).gravity; + } } public GravityLayoutParams(int width, int height) { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryLayout.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryLayout.java new file mode 100644 index 000000000..470559bb5 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryLayout.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.content.Context; + +import com.hippo.ehviewer.R; +import com.hippo.yorozuya.LayoutUtils; + +public class GalleryLayout extends FrameLayout implements GalleryView.ActionListener, GalleryPanel.ActionListener { + + private GalleryView mGalleryView; + private GalleryPanel mGalleryPanel; + + private ActionListener mActionListener; + + public GalleryLayout(Context context, ActionListener listener) { + mActionListener = listener; + setBackgroundColor(context.getResources().getColor(R.color.gallery_background)); + + mGalleryView = new GalleryView(context, 0, this); + mGalleryView.setProgressSize(LayoutUtils.dp2pix(context, 56)); + mGalleryView.setInterval(LayoutUtils.dp2pix(context, 48)); + + mGalleryPanel = new GalleryPanel(context, this); + + addComponent(mGalleryView, new GravityLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + addComponent(mGalleryPanel, new GravityLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + } + + public GalleryView getGalleryView() { + return mGalleryView; + } + + public GalleryPanel getGalleryPanel() { + return mGalleryPanel; + } + + @Override + public void onTapCenter() { + mGalleryPanel.setShown(true); + mActionListener.onShowGalleryPanel(); + } + + @Override + public void onSetMode(GalleryView.Mode mode) { + mGalleryPanel.setRange(1, mGalleryView.getPages()); + } + + @Override + public void onScrollToPage(int page, boolean internal) { + if (internal) { + mGalleryPanel.setProgress(page + 1); + } + } + + @Override + public void onSetProgress(int newProgress, int oldProgress, boolean byUser) { + if (byUser) { + mGalleryView.scrollToPage(newProgress - 1); + } + } + + @Override + public void onHide() { + mActionListener.onHideGalleryPanel(); + } + + public interface ActionListener { + + void onShowGalleryPanel(); + + void onHideGalleryPanel(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPageView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPageView.java index 30d4f28d7..7c9f58bb1 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPageView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPageView.java @@ -6,10 +6,10 @@ import android.graphics.Rect; import com.hippo.ehviewer.gallery.glrenderer.TextTexture; import com.hippo.yorozuya.LayoutUtils; -public class GalleryPageView extends FrameView { +public class GalleryPageView extends FrameLayout { public ScaledImageView mImageView; - public LinearView mLinearView; + public LinearLayout mLinearLayout; public TextView mIndexView; public ProgressView mProgressView; @@ -23,8 +23,8 @@ public class GalleryPageView extends FrameView { public GalleryPageView(Context context) { mImageView = new ScaledImageView(); - mLinearView = new LinearView(); - mLinearView.setInterval(LayoutUtils.dp2pix(context, 24)); // 12dp + mLinearLayout = new LinearLayout(); + mLinearLayout.setInterval(LayoutUtils.dp2pix(context, 24)); // 12dp mIndexView = new TextView(sTextTexture); mProgressView = new ProgressView(); mProgressView.setMinimumWidth(LayoutUtils.dp2pix(context, 56)); // 56dp @@ -33,11 +33,11 @@ public class GalleryPageView extends FrameView { GravityLayoutParams lp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER_HORIZONTAL; - mLinearView.addComponent(mIndexView, lp); + mLinearLayout.addComponent(mIndexView, lp); lp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER_HORIZONTAL; - mLinearView.addComponent(mProgressView, lp); + mLinearLayout.addComponent(mProgressView, lp); lp = new GravityLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); @@ -46,7 +46,7 @@ public class GalleryPageView extends FrameView { lp = new GravityLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER; - addComponent(mLinearView, lp); + addComponent(mLinearLayout, lp); } private void updateSeen() { diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPanel.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPanel.java new file mode 100644 index 000000000..48ca2d425 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryPanel.java @@ -0,0 +1,228 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.content.Context; +import android.view.MotionEvent; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.gallery.anim.FloatAnimation; +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; +import com.hippo.ehviewer.gallery.glrenderer.TextTexture; +import com.hippo.ehviewer.gallery.glrenderer.XmlResourceTexture; +import com.hippo.util.AnimationUtils; +import com.hippo.widget.FitPaddingImpl; +import com.hippo.yorozuya.LayoutUtils; + +public class GalleryPanel extends GLView implements FitPaddingImpl, Slider.OnSetProgressListener { + + private Context mContext; + + private LinearLayout mRightView; + private XmlResourceView mScreenRotation; + private XmlResourceView mRefresh; + private XmlResourceView mShare; + + private LinearLayout mBottomView; + private TextView mProgressTextView; + private TextView mPagesTextView; + private Slider mSlider; + + private float mShowPercent = 0.0f; + + private int mEnd = 0; + + private ActionListener mListener; + + private FloatAnimation mAnimation; + private boolean mShown = false; + + private static TextTexture sTextTexture; + + public static void setTextTexture(TextTexture textTexture) { + sTextTexture = textTexture; + } + + public GalleryPanel(Context context, ActionListener listener) { + mContext = context; + mListener = listener; + + int background = context.getResources().getColor(R.color.gallery_panel_background); + LinearLayout.LayoutParams lp; + + int dp8 = LayoutUtils.dp2pix(context, 8); + mRightView = new LinearLayout(); + mRightView.setOrientation(LinearLayout.VERTICAL); + mRightView.setBackgroundColor(background); + mRightView.setInterval(dp8); + mRightView.setPaddings(dp8, dp8, dp8, dp8); + mScreenRotation = new XmlResourceView(); + XmlResourceTexture screenRotationTexture = new XmlResourceTexture(context, R.drawable.ic_screen_rotation_dark_x48); + mScreenRotation.setTexture(screenRotationTexture); + mRefresh = new XmlResourceView(); + XmlResourceTexture refreshTexture = new XmlResourceTexture(context, R.drawable.ic_refresh_dark_x48); + mRefresh.setTexture(refreshTexture); + mShare = new XmlResourceView(); + XmlResourceTexture shareTexture = new XmlResourceTexture(context, R.drawable.ic_share_dark_x48); + mShare.setTexture(shareTexture); + + int actionSize = LayoutUtils.dp2pix(context, 48); + lp = new LinearLayout.LayoutParams( + actionSize, actionSize); + mRightView.addComponent(mScreenRotation, lp); + lp = new LinearLayout.LayoutParams( + actionSize, actionSize); + mRightView.addComponent(mRefresh, lp); + lp = new LinearLayout.LayoutParams( + actionSize, actionSize); + mRightView.addComponent(mShare, lp); + + mBottomView = new LinearLayout(); + mBottomView.setOrientation(LinearLayout.HORIZONTAL); + mBottomView.setBackgroundColor(background); + mBottomView.setPaddingLeft(LayoutUtils.dp2pix(context, 8)); + mBottomView.setPaddingRight(LayoutUtils.dp2pix(context, 8)); + mProgressTextView = new TextView(sTextTexture); + mProgressTextView.setGravity(Gravity.CENTER); + mPagesTextView = new TextView(sTextTexture); + mPagesTextView.setGravity(Gravity.CENTER); + mSlider = new Slider(context); + mSlider.setPaddings(LayoutUtils.dp2pix(context, 8), LayoutUtils.dp2pix(context, 18), + LayoutUtils.dp2pix(context, 8), LayoutUtils.dp2pix(context, 18)); + mSlider.setColor(context.getResources().getColor(R.color.theme_primary)); + mSlider.setOnSetProgressListener(this); + + lp = new LinearLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_VERTICAL; + mBottomView.addComponent(mProgressTextView, lp); + lp = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_VERTICAL; + lp.weight = 1; + mBottomView.addComponent(mSlider, lp); + lp = new LinearLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_VERTICAL; + mBottomView.addComponent(mPagesTextView, lp); + + addComponent(mRightView, new LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); + addComponent(mBottomView, new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + + mAnimation = new FloatAnimation() { + @Override + protected void onCalculate(float progress) { + super.onCalculate(progress); + mShowPercent = get(); + mRightView.offsetLeftAndRight(getWidth() - (int) (mRightView.getWidth() * mShowPercent) - mRightView.bounds().left); + mBottomView.offsetTopAndBottom(getHeight() - (int) (mBottomView.getMeasuredHeight() * mShowPercent) - mBottomView.bounds().top); + } + }; + mAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR); + } + + protected void setShown(boolean shown) { + if (mShown != shown) { + mShown = shown; + + mAnimation.setRange(mShowPercent, shown ? 1.0f : 0.0f); + mAnimation.setDuration(300); // TODO + mAnimation.forceReset(); + invalidate(); + + if (!shown) { + mListener.onHide(); + } + } + } + + @Override + protected boolean onTouch(MotionEvent event) { + if (mShown && event.getAction() == MotionEvent.ACTION_UP) { + setShown(false); + } + return mShown; + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + measureComponent(mRightView, widthSpec, heightSpec); + measureComponent(mBottomView, widthSpec, heightSpec); + super.onMeasure(widthSpec, heightSpec); + } + + @Override + protected void onLayout( + boolean changeSize, int left, int top, int right, int bottom) { + if (changeSize) { + int width = getWidth(); + int height = getHeight(); + + int l = width - (int) (mRightView.getMeasuredWidth() * mShowPercent); + mRightView.layout(l, height / 2 - mRightView.getMeasuredHeight() / 2, + l + mRightView.getMeasuredWidth(), height / 2 + mRightView.getMeasuredHeight() / 2); + + int t = height - (int) (mBottomView.getMeasuredHeight() * mShowPercent); + mBottomView.layout(0, t, width, t + mBottomView.getMeasuredHeight()); + } + } + + public void setRange(int start, int end) { + mSlider.setRange(start, end); + mEnd = end; + + String pages = Integer.toString(end); + mProgressTextView.setMinimumWidth((int) (sTextTexture.getMaxWidth() * pages.length())); + mPagesTextView.setMinimumWidth((int) (sTextTexture.getMaxWidth() * pages.length())); + mPagesTextView.setText(pages); + } + + public void setProgress(int progress) { + mSlider.setProgress(progress); + mProgressTextView.setText(Integer.toString(progress)); + } + + @Override + public void onFitPadding(int left, int top, int right, int bottom) { + mRightView.setPaddingRight(LayoutUtils.dp2pix(mContext, 8) + right); + mBottomView.setPaddingBottom(bottom); + mBottomView.setPaddingRight(LayoutUtils.dp2pix(mContext, 8) + right); + } + + @Override + public void onSetProgress(int newProgress, int oldProgress, boolean byUser) { + mListener.onSetProgress(newProgress, oldProgress, byUser); + mProgressTextView.setText(Integer.toString(newProgress)); + } + + @Override + protected void onRender(GLCanvas canvas) { + if (mAnimation.calculate(AnimationTime.get())) { + invalidate(); + } + + super.onRender(canvas); + } + + public interface ActionListener { + void onSetProgress(int newProgress, int oldProgress, boolean byUser); + + void onHide(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java index 5950e2a49..f4f23284d 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GalleryView.java @@ -1,12 +1,12 @@ package com.hippo.ehviewer.gallery.ui; import android.content.Context; +import android.graphics.Rect; import android.util.SparseArray; import android.view.MotionEvent; import android.view.animation.Interpolator; import android.view.animation.OvershootInterpolator; -import com.hippo.ehviewer.R; import com.hippo.ehviewer.gallery.anim.Animation; import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; import com.hippo.ehviewer.gallery.util.GalleryUtils; @@ -40,6 +40,10 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { } }; + private static final float[] LEFT_AREA = {0.0f, 0.0f, 1.0f / 3.0f, 1f}; + private static final float[] RIGHT_AREA = {2.0f / 3.0f, 0.0f, 1.0f, 1f}; + private static final float[] CENTER_AREA = {1.0f / 3.0f, 2.0f / 5.0f, 2.0f / 3.0f, 3.0f / 5.0f}; + private static final Interpolator SMOOTH_SCALER_INTERPOLATOR = new OvershootInterpolator(); private boolean mEnableRequestLayout = true; @@ -63,6 +67,10 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { private boolean mScroll = false; private boolean mTouched = false; + private Rect mLeftArea = new Rect(); + private Rect mRightArea = new Rect(); + private Rect mCenterArea = new Rect(); + private int mDeltaX; private int[] mScrollRemain = new int[2]; @@ -84,6 +92,8 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { private boolean mRequestFill = false; + private ActionListener mListener; + public enum Mode { NONE, // Just a progress view LEFT_TO_RIGHT, @@ -107,7 +117,9 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { CENTER } - public GalleryView(Context context) { + public GalleryView(Context context, int firstShownIndex, ActionListener listener) { + mFirstShownIndex = firstShownIndex; + mListener = listener; mEdgeView = new EdgeView(context); mGestureRecognizer = new GestureRecognizer(context, this); mSmoothScroller = new SmoothScroller(); @@ -115,8 +127,6 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mSmoothScaler = new SmoothScaler(); mSmoothScaler.setInterpolator(SMOOTH_SCALER_INTERPOLATOR); mRecycler = new Recycler(); - - setBackgroundColor(GalleryUtils.intColorToFloatARGBArray(context.getResources().getColor(R.color.gallery_background))); } @Override @@ -135,8 +145,15 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { public void setMode(Mode mode) { if (mMode != mode) { + Mode oldMode = mMode; mMode = mode; superRequestLayout(); + + mListener.onSetMode(mode); + if (oldMode == Mode.NONE) { + mFirstShownIndex = MathUtils.clamp(mFirstShownIndex, 0, mAdapter.getPages() - 1); + mListener.onScrollToPage(mFirstShownIndex, true); + } } } @@ -154,6 +171,10 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { superRequestLayout(); } + public int getPages() { + return mAdapter.getPages(); + } + public void superRequestLayout() { super.requestLayout(); } @@ -168,6 +189,10 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { } public void scrollToPage(int page) { + scrollToPageInternal(page, false); + } + + private void scrollToPageInternal(int page, boolean internal) { mSmoothScroller.forceStop(); mSmoothScaler.forceStop(); @@ -178,6 +203,8 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mFirstShownIndex = page; mLayoutOffset = 0; requestFill(); + + mListener.onScrollToPage(page, internal); } public void smoothScrollToPage(int page) { @@ -196,6 +223,7 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { boolean reverse = mMode == Mode.RIGHT_TO_LEFT; // Fix TODO What if offset is very large + int oldFirstShownIndex = mFirstShownIndex; int width = getWidth(); if (!mHaveChangePage) { if (mLayoutOffset > mInterval) { @@ -221,6 +249,10 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { } } + if (mFirstShownIndex != oldFirstShownIndex) { + mListener.onScrollToPage(mFirstShownIndex, true); + } + requestFill(); } @@ -238,21 +270,37 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { @Override public boolean onSingleTapConfirmed(float x, float y) { - if ((mMode == Mode.LEFT_TO_RIGHT || mMode == Mode.RIGHT_TO_LEFT) && mLayoutOffset == 0) { - - if (x < getWidth() / 2) { - if (mFirstShownIndex > 0) { - scrollToPage(mFirstShownIndex - 1); + if (mLeftArea.contains((int) x, (int) y)) { + if (mMode == Mode.LEFT_TO_RIGHT) { + if (mFirstShownIndex > 0) { + scrollToPageInternal(mFirstShownIndex - 1, true); + } else { + mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.LEFT); + } } else { - mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.LEFT); + if (mFirstShownIndex < mAdapter.getPages() - 1) { + scrollToPageInternal(mFirstShownIndex + 1, true); + } else { + mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.LEFT); + } } - } else { - if (mFirstShownIndex < mAdapter.getPages() - 1) { - scrollToPage(mFirstShownIndex + 1); + } else if (mRightArea.contains((int) x, (int) y)) { + if (mMode == Mode.LEFT_TO_RIGHT) { + if (mFirstShownIndex < mAdapter.getPages() - 1) { + scrollToPageInternal(mFirstShownIndex + 1, true); + } else { + mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.RIGHT); + } } else { - mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.RIGHT); + if (mFirstShownIndex > 0) { + scrollToPageInternal(mFirstShownIndex - 1, true); + } else { + mEdgeView.onPull(getWidth() * 2, (int) y, EdgeView.RIGHT); + } } + } else if (mCenterArea.contains((int) x, (int) y)) { + mListener.onTapCenter(); } } @@ -533,6 +581,17 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { mLayoutOffset = 0; mHaveChangePage = false; 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)); + } } public GalleryPageView getPage(int index) { @@ -922,4 +981,13 @@ public class GalleryView extends GLView implements GestureRecognizer.Listener { } } } + + public interface ActionListener { + + void onTapCenter(); + + void onSetMode(Mode mode); + + void onScrollToPage(int page, boolean internal); + } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/Gravity.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/Gravity.java index d3c2a80e3..dd9a8f639 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/Gravity.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/Gravity.java @@ -1,7 +1,20 @@ package com.hippo.ehviewer.gallery.ui; +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public class Gravity { + @IntDef({POSITION_BEGIN, POSITION_CENTER, POSITION_FINISH}) + @Retention(RetentionPolicy.SOURCE) + public @interface PositionMode {} + + @IntDef({HORIZONTAL, VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface DirectionMode {} + public static final int NO_GRAVITY = 0x0000; public static final int LEFT = 0x0001; @@ -16,6 +29,13 @@ public class Gravity { public static final int HORIZONTAL_MASK = CENTER_HORIZONTAL; public static final int VERTICAL_MASK = CENTER_VERTICAL; + public static final int POSITION_BEGIN = 0; + public static final int POSITION_CENTER = 1; + public static final int POSITION_FINISH = 2; + + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + public static boolean centerHorizontal(int gravity) { return (gravity & HORIZONTAL_MASK) == CENTER_HORIZONTAL; } @@ -39,4 +59,24 @@ public class Gravity { public static boolean bottom(int gravity) { return (gravity & VERTICAL_MASK) == BOTTOM; } + + public static @PositionMode int getPosition(int gravity, @DirectionMode int direction) { + if (direction == HORIZONTAL) { + if (right(gravity)) { + return POSITION_FINISH; + } else if (centerHorizontal(gravity)) { + return POSITION_CENTER; + } else { + return POSITION_BEGIN; + } + } else { + if (bottom(gravity)) { + return POSITION_FINISH; + } else if (centerVertical(gravity)) { + return POSITION_CENTER; + } else { + return POSITION_BEGIN; + } + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearLayout.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearLayout.java new file mode 100644 index 000000000..b857e127a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearLayout.java @@ -0,0 +1,208 @@ +package com.hippo.ehviewer.gallery.ui; + +import android.graphics.Rect; +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +public class LinearLayout extends GLView { + + @IntDef({HORIZONTAL, VERTICAL}) + @Retention(RetentionPolicy.SOURCE) + public @interface OrientationMode {} + + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + private int mInterval = 0; + private int mOrientation = VERTICAL; + + private List mTempList = new ArrayList<>(); + + public void setInterval(int interval) { + if (mInterval != interval) { + mInterval = interval; + requestLayout(); + } + } + + /** + * Should the layout be a column or a row. + * @param orientation Pass {@link #HORIZONTAL} or {@link #VERTICAL}. Default + * value is {@link #HORIZONTAL}. + */ + public void setOrientation(@OrientationMode int orientation) { + if (mOrientation != orientation) { + mOrientation = orientation; + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + int layoutWidthSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthSpec) - mPaddings.left - mPaddings.right, + MeasureSpec.getMode(widthSpec)); + int layoutHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightSpec) - mPaddings.top - mPaddings.bottom, + MeasureSpec.getMode(heightSpec)); + + if (mOrientation == HORIZONTAL && MeasureSpec.getMode(widthSpec) == MeasureSpec.EXACTLY) { + int width = MeasureSpec.getSize(layoutWidthSpec); + float sumWeight = 0.0f; + + for (int i = 0, n = getComponentCount(); i < n; i++) { + final GLView component = getComponent(i); + final LayoutParams lp = (LayoutParams) component.getLayoutParams(); + + if (lp.weight > 0.0f) { + sumWeight += lp.weight; + mTempList.add(component); + } else { + measureComponent(component, layoutWidthSpec, layoutHeightSpec); + width -= component.getMeasuredWidth(); + } + } + + for (GLView component : mTempList) { + final LayoutParams lp = (LayoutParams) component.getLayoutParams(); + final int componentWidthSpec = MeasureSpec.makeMeasureSpec( + (int) (width * lp.weight / sumWeight), MeasureSpec.EXACTLY); + final int componentHeightSpec = getComponentSpec(layoutHeightSpec, lp.height); + component.measure(componentWidthSpec, componentHeightSpec); + } + + mTempList.clear(); + } else if (mOrientation == VERTICAL && MeasureSpec.getMode(heightSpec) == MeasureSpec.EXACTLY) { + int height = MeasureSpec.getSize(layoutHeightSpec); + float sumWeight = 0.0f; + + for (int i = 0, n = getComponentCount(); i < n; i++) { + final GLView component = getComponent(i); + final LayoutParams lp = (LayoutParams) component.getLayoutParams(); + + if (lp.weight > 0.0f) { + sumWeight += lp.weight; + mTempList.add(component); + } else { + measureComponent(component, layoutWidthSpec, layoutHeightSpec); + height -= component.getMeasuredHeight(); + } + } + + for (GLView component : mTempList) { + final LayoutParams lp = (LayoutParams) component.getLayoutParams(); + final int componentWidthSpec = getComponentSpec(layoutWidthSpec, lp.width); + final int componentHeightSpec = MeasureSpec.makeMeasureSpec( + (int) (height * lp.weight / sumWeight), MeasureSpec.EXACTLY); + component.measure(componentWidthSpec, componentHeightSpec); + } + + mTempList.clear(); + } else { + measureAllComponents(this, layoutWidthSpec, layoutHeightSpec); + } + + int maxWidth = 0; + int maxHeight = 0; + for (int i = 0, n = getComponentCount(); i < n; i++) { + GLView component = getComponent(i); + + if (mOrientation == VERTICAL) { + maxWidth = Math.max(maxWidth, component.getMeasuredWidth()); + if (i != 0) { + maxHeight += mInterval; + } + maxHeight += component.getMeasuredHeight(); + } else if (mOrientation == HORIZONTAL) { + maxHeight = Math.max(maxHeight, component.getMeasuredHeight()); + if (i != 0) { + maxWidth += mInterval; + } + maxWidth += component.getMeasuredWidth(); + } + } + + Rect paddings = getPaddings(); + maxWidth = maxWidth + paddings.left + paddings.right; + maxHeight = maxHeight + paddings.top + paddings.bottom; + + setMeasuredSize(getDefaultSize(maxWidth, widthSpec), getDefaultSize(maxHeight, heightSpec)); + } + + @Override + protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) { + int width = getWidth(); + int height = getHeight(); + Rect paddings = getPaddings(); + int componentLeft; + int componentTop; + + if (mOrientation == VERTICAL) { + componentTop = paddings.top; + + for (int i = 0, n = getComponentCount(); i < n; i++) { + GLView component = getComponent(i); + int measureWidth = component.getMeasuredWidth(); + int measureHeight = component.getMeasuredHeight(); + + LayoutParams lp = (LayoutParams) component.getLayoutParams(); + componentLeft = getDefaultBegin(width, measureWidth, paddings.left, paddings.right, + Gravity.getPosition(lp.gravity, Gravity.HORIZONTAL)); + component.layout(componentLeft, componentTop, + componentLeft + measureWidth, componentTop + measureHeight); + + componentTop += measureHeight + mInterval; + } + } else if (mOrientation == HORIZONTAL) { + componentLeft = paddings.left; + + for (int i = 0, n = getComponentCount(); i < n; i++) { + GLView component = getComponent(i); + int measureWidth = component.getMeasuredWidth(); + int measureHeight = component.getMeasuredHeight(); + + LayoutParams lp = (LayoutParams) component.getLayoutParams(); + componentTop = getDefaultBegin(height, measureHeight, paddings.top, paddings.bottom, + Gravity.getPosition(lp.gravity, Gravity.VERTICAL)); + component.layout(componentLeft, componentTop, + componentLeft + measureWidth, componentTop + measureHeight); + + componentLeft += measureWidth + mInterval; + } + } + } + + @Override + protected boolean checkLayoutParams(GLView.LayoutParams p) { + return p instanceof LayoutParams; + } + + @Override + protected GLView.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + + @Override + protected GLView.LayoutParams generateLayoutParams(GLView.LayoutParams p) { + return p == null ? generateDefaultLayoutParams() : new LayoutParams(p); + } + + public static class LayoutParams extends GravityLayoutParams { + + public float weight = 0.0f; + + public LayoutParams(GLView.LayoutParams source) { + super(source); + + if (source instanceof LayoutParams) { + weight = ((LayoutParams) source).weight; + } + } + + public LayoutParams(int width, int height) { + super(width, height); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearView.java deleted file mode 100644 index 63015be8b..000000000 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/LinearView.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.hippo.ehviewer.gallery.ui; - -public class LinearView extends GLView { - - public int mInterval; - - public void setInterval(int interval) { - mInterval = interval; - requestLayout(); - } - - // TODO - @Override - protected void onMeasure(int widthSpec, int heightSpec) { - measureAllComponents(this, widthSpec, heightSpec); - - int maxWidth = 0; - int maxHeight = 0; - int n = getComponentCount(); - for (int i = 0; i < n; i++) { - GLView component = getComponent(i); - maxWidth = Math.max(maxWidth, component.getMeasuredWidth()); - if (i != 0) { - maxHeight += mInterval; - } - maxHeight += component.getMeasuredHeight(); - } - - setMeasuredSize(getDefaultSize(maxWidth, widthSpec), getDefaultSize(maxHeight, heightSpec)); - } - - @Override - protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) { - int width = getWidth(); - int componentLeft; - int componentTop = 0; - - for (int i = 0, n = getComponentCount(); i < n; i++) { - GLView component = getComponent(i); - int measureWidth = component.getMeasuredWidth(); - int measureHeight = component.getMeasuredHeight(); - - GravityLayoutParams lp = (GravityLayoutParams) component.getLayoutParams(); - int gravity = lp.gravity; - if (Gravity.centerHorizontal(gravity)) { - componentLeft = (width / 2) - (measureWidth / 2); - } else if (Gravity.right(gravity)) { - componentLeft = width - measureWidth; - } else { - componentLeft = 0; - } - - component.layout(componentLeft, componentTop, - componentLeft + measureWidth, componentTop + measureHeight); - - componentTop += measureHeight + mInterval; - } - } - - @Override - protected boolean checkLayoutParams(GLView.LayoutParams p) { - return p instanceof GravityLayoutParams; - } - - @Override - protected LayoutParams generateDefaultLayoutParams() { - return new GravityLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - } - - @Override - protected LayoutParams generateLayoutParams(GLView.LayoutParams p) { - return p == null ? generateDefaultLayoutParams() : new GravityLayoutParams(p); - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/ProgressView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/ProgressView.java index 660150ec8..f89ec7b8a 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/ProgressView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/ProgressView.java @@ -22,10 +22,10 @@ import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import com.hippo.ehviewer.gallery.anim.Animation; +import com.hippo.ehviewer.gallery.anim.FloatAnimation; import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; import com.hippo.ehviewer.gallery.glrenderer.GLPaint; import com.hippo.vectorold.animation.PathInterpolator; -import com.hippo.yorozuya.MathUtils; import java.util.ArrayList; import java.util.List; @@ -76,42 +76,50 @@ public class ProgressView extends GLView { } public void setupAnimations() { - Animation trimStart = new Animation() { + FloatAnimation trimStart = new FloatAnimation() { @Override protected void onCalculate(float progress) { - mTrimStart = MathUtils.lerp(0.0f, 0.75f, progress); + super.onCalculate(progress); + mTrimStart = get(); } }; + trimStart.setRange(0.0f, 0.75f); trimStart.setDuration(1333L); trimStart.setInterpolator(TRIM_START_INTERPOLATOR); trimStart.setRepeatCount(Animation.INFINITE); - Animation trimEnd = new Animation() { + FloatAnimation trimEnd = new FloatAnimation() { @Override protected void onCalculate(float progress) { - mTrimEnd = MathUtils.lerp(0.0f, 0.75f, progress); + super.onCalculate(progress); + mTrimEnd = get(); } }; + trimEnd.setRange(0.0f, 0.75f); trimEnd.setDuration(1333L); trimEnd.setInterpolator(TRIM_END_INTERPOLATOR); trimEnd.setRepeatCount(Animation.INFINITE); - Animation trimOffset = new Animation() { + FloatAnimation trimOffset = new FloatAnimation() { @Override protected void onCalculate(float progress) { - mTrimOffset = MathUtils.lerp(0.0f, 0.25f, progress); + super.onCalculate(progress); + mTrimOffset = get(); } }; + trimOffset.setRange(0.0f, 0.25f); trimOffset.setDuration(1333L); trimOffset.setInterpolator(LINEAR_INTERPOLATOR); trimOffset.setRepeatCount(Animation.INFINITE); - Animation trimRotation = new Animation() { + FloatAnimation trimRotation = new FloatAnimation() { @Override protected void onCalculate(float progress) { - mTrimRotation = MathUtils.lerp(0.0f, 720.0f, progress); + super.onCalculate(progress); + mTrimRotation = get(); } }; + trimRotation.setRange(0.0f, 720.0f); trimRotation.setDuration(6665L); trimRotation.setInterpolator(LINEAR_INTERPOLATOR); trimRotation.setRepeatCount(Animation.INFINITE); @@ -125,7 +133,7 @@ public class ProgressView extends GLView { private void startAnimations() { List animations = mAnimations; for (int i = 0, n = animations.size(); i < n; i++) { - animations.get(i).start(); + animations.get(i).forceReset(); } } @@ -191,7 +199,7 @@ public class ProgressView extends GLView { int cy = height / 2; float startAngle = (mTrimStart + mTrimOffset) * 360.0f - 90; - float sweepAngle = (mTrimEnd - mTrimStart) * 360.0f; + float sweepAngle = Math.max(6.0f, (mTrimEnd - mTrimStart) * 360.0f); float rotation = mTrimRotation + startAngle; canvas.save(); diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java new file mode 100644 index 000000000..6f7731449 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/Slider.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Rect; +import android.view.MotionEvent; + +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; +import com.hippo.yorozuya.LayoutUtils; +import com.hippo.yorozuya.MathUtils; + +public class Slider extends GLView { + + private int mStart; + private int mEnd; + private int mProgress; + // 0.0f to 1.0f + private float mDrawProgress = 0.0f; + + private int m2dp; + private int m5dp; + private int m6dp; + private int m12dp; + + private int mColor = Color.WHITE; + + private OnSetProgressListener mListener; + + public Slider(Context context) { + m2dp = LayoutUtils.dp2pix(context, 2); + m5dp = LayoutUtils.dp2pix(context, 5); + m6dp = LayoutUtils.dp2pix(context, 6); + m12dp = LayoutUtils.dp2pix(context, 12); + + setMinimumHeight(m12dp); + setMinimumWidth(512); + } + + public void setColor(int color) { + mColor = color; + } + + public void setRange(int start, int end) { + mStart = start; + mEnd = end; + setProgress(mProgress); + } + + public void setProgress(int progress) { + progress = MathUtils.clamp(progress, mStart, mEnd); + if (mProgress != progress) { + int oldProgress = mProgress; + mProgress = progress; + mDrawProgress = MathUtils.delerp(mStart, mEnd, mProgress); + if (mListener != null) { + mListener.onSetProgress(progress, oldProgress, false); + } + invalidate(); + } + } + + public void setOnSetProgressListener(OnSetProgressListener listener) { + mListener = listener; + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + setMeasuredSize(getMaxSize(getSuggestedMinimumWidth(), widthSpec), + getDefaultSize(getSuggestedMinimumHeight(), heightSpec)); + } + + @Override + protected void onRender(GLCanvas canvas) { + super.onRender(canvas); + + int width = getWidth(); + if (width < m12dp * 2) { + canvas.fillRect(0, 0, width, getHeight(), mColor); + } else { + Rect padding = mPaddings; + int dp6 = m6dp; + // Draw bar + canvas.fillRect(padding.left + dp6, padding.top + m5dp, + width - dp6 - dp6 - padding.left - padding.right, m2dp, mColor); + // Draw controllor + canvas.fillOval(padding.left + dp6 + (width - dp6 - dp6 - padding.left - padding.right) * mDrawProgress, + padding.top + dp6, dp6, dp6, mColor); + } + } + + @Override + protected boolean onTouch(MotionEvent event) { + + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + float x = event.getX(); + int progress = Math.round(MathUtils.lerp((float) mStart, (float) mEnd, + MathUtils.clamp((x - m6dp) / (getWidth() - m6dp - m6dp), 0.0f, 1.0f))); + if (progress != mProgress) { + mDrawProgress = MathUtils.delerp(mStart, mEnd, progress); + invalidate(); + } + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (progress != mProgress) { + int oldProgress = mProgress; + mProgress = progress; + if (mListener != null) { + mListener.onSetProgress(progress, oldProgress, true); + } + } + } + + break; + } + + return true; + } + + public interface OnSetProgressListener { + + void onSetProgress(int newProgress, int oldProgress, boolean byUser); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/TextView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/TextView.java index b579240b0..a89b8c332 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ui/TextView.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/TextView.java @@ -1,13 +1,19 @@ package com.hippo.ehviewer.gallery.ui; +import android.graphics.Rect; + import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; import com.hippo.ehviewer.gallery.glrenderer.TextTexture; +import com.hippo.yorozuya.ArrayUtils; public class TextView extends GLView { TextTexture mTextTexture; private String mText = ""; + private int[] mIndexes = ArrayUtils.EMPTY_INT_ARRAY; + + private int mGravity = Gravity.NO_GRAVITY; public TextView(TextTexture textTexture) { mTextTexture = textTexture; @@ -19,18 +25,26 @@ public class TextView extends GLView { } if (!text.equals(mText)) { mText = text; + mIndexes = mTextTexture.getTextIndexes(text); requestLayout(); } } + public void setGravity(int gravity) { + if (mGravity != gravity) { + mGravity = gravity; + invalidate(); + } + } + @Override protected void onMeasure(int widthSpec, int heightSpec) { int textWidth = 0; int textHight = 0; if (mTextTexture != null) { - textWidth = (int) mTextTexture.getTextWidth(mText); - textHight = (int) mTextTexture.getTextHeight(mText); + textWidth = (int) mTextTexture.getTextWidth(mIndexes); + textHight = (int) mTextTexture.getTextHeight(); } setMeasuredSize(getDefaultSize(Math.max(getSuggestedMinimumWidth(), textWidth), widthSpec), @@ -39,6 +53,11 @@ public class TextView extends GLView { @Override protected void onRender(GLCanvas canvas) { - mTextTexture.drawText(canvas, mText, 0, 0); + Rect paddings = getPaddings(); + int x = getDefaultBegin(getWidth(), (int) mTextTexture.getTextWidth(mIndexes), + paddings.left, paddings.right, Gravity.getPosition(mGravity, Gravity.HORIZONTAL)); + int y = getDefaultBegin(getHeight(), (int) mTextTexture.getTextHeight(), + paddings.top, paddings.bottom, Gravity.getPosition(mGravity, Gravity.VERTICAL)); + mTextTexture.drawText(canvas, mIndexes, x, y); } } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/XmlResourceView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/XmlResourceView.java new file mode 100644 index 000000000..24b74c27f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/XmlResourceView.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; +import com.hippo.ehviewer.gallery.glrenderer.XmlResourceTexture; + +public class XmlResourceView extends GLView { + + private XmlResourceTexture mTexture; + + public void setTexture(XmlResourceTexture texture) { + mTexture = texture; + + if (texture != null) { + int width = getWidth(); + int height = getHeight(); + if (width > 0 && height > 0) { + texture.setBound(width, height); + invalidate(); + } + } + } + + @Override + protected void onLayout(boolean changeSize, int left, int top, int right, int bottom) { + super.onLayout(changeSize, left, top, right, bottom); + + if (changeSize && mTexture != null) { + mTexture.setBound(right - left, bottom - top); + } + } + + @Override + protected void onRender(GLCanvas canvas) { + super.onRender(canvas); + + if (mTexture != null) { + canvas.drawTexture(mTexture, 0, 0, getWidth(), getHeight()); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java b/app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java index 626160dd7..8bee1cef7 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java @@ -33,6 +33,10 @@ public class GalleryUtils { }; } + public static int floatARGBArrayTointColor(float[] from) { + return Color.argb((int) (from[0] * 255), (int) (from[1] * 255), (int) (from[2] * 255), (int) (from[3] * 255)); + } + // Below are used the detect using database in the render thread. It only // works most of the time, but that's ok because it's for debugging only. diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java index 1ee6879d2..98730655e 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.os.Process; import android.support.annotation.Nullable; import android.util.Pair; +import android.util.SparseArray; import com.hippo.app.StatsActivity; import com.hippo.conaco.BitmapPool; @@ -43,17 +44,20 @@ import com.hippo.ehviewer.gallery.glrenderer.TextTexture; import com.hippo.ehviewer.gallery.glrenderer.TiledTexture; import com.hippo.ehviewer.gallery.ui.GLRootView; import com.hippo.ehviewer.gallery.ui.GLView; +import com.hippo.ehviewer.gallery.ui.GalleryLayout; import com.hippo.ehviewer.gallery.ui.GalleryPageView; +import com.hippo.ehviewer.gallery.ui.GalleryPanel; import com.hippo.ehviewer.gallery.ui.GalleryView; import com.hippo.util.SystemUiHelper; -import com.hippo.yorozuya.LayoutUtils; import com.hippo.yorozuya.PriorityThreadFactory; +import com.hippo.yorozuya.SimpleHandler; import java.io.File; import java.io.IOException; import java.util.concurrent.LinkedBlockingQueue; -public class GalleryActivity extends StatsActivity implements TiledTexture.OnFreeBitmapListener, GalleryProviderListener { +public class GalleryActivity extends StatsActivity implements TiledTexture.OnFreeBitmapListener, + GalleryProviderListener, GalleryLayout.ActionListener { public static final String ACTION_GALLERY_FROM_GALLERY_BASE = "com.hippo.ehviewer.gallery.action.GALLERY_FROM_GALLERY_BASE"; public static final String ACTION_GALLERY_FROM_ARCHIVE = "com.hippo.ehviewer.gallery.action.GALLERY_FROM_ARCHIVE"; @@ -61,6 +65,10 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre public static final String KEY_GALLERY_BASE = "gallery_base"; public static final String KEY_ARCHIVE_URI = "archive_uri"; + public static final String KEY_HASH_CODE = "hash_code"; + public static final SparseArray sGalleryProviderMap = new SparseArray<>(); + public static final SparseArray sCloseGalleryProviderTaskMap = new SparseArray<>(); + private SystemUiHelper mSystemUiHelper; private Resources mResources; @@ -70,10 +78,12 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre private GLRootView mGLRootView; private GalleryView mGalleryView; + private GalleryPanel mGalleryPanel; private GalleryAdapter mAdapter; - private TextTexture mTextTexture; + private TextTexture mGalleryIndexTextTexture; + private TextTexture mGalleryProgressTextTexture; private GalleryProvider mGalleryProvider; @@ -120,7 +130,20 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (!getGalleryProvider(getIntent())) { + if (savedInstanceState != null) { + int hashCode = savedInstanceState.getInt(KEY_HASH_CODE, 0); + if (hashCode != 0) { + mGalleryProvider = sGalleryProviderMap.get(hashCode); + sGalleryProviderMap.remove(hashCode); + Runnable runnable = sCloseGalleryProviderTaskMap.get(hashCode); + sCloseGalleryProviderTaskMap.remove(hashCode); + if (runnable != null) { + SimpleHandler.getInstance().removeCallbacks(runnable); + } + } + } + + if (mGalleryProvider == null && !getGalleryProvider(getIntent())) { mDieYoung = true; finish(); return; @@ -130,9 +153,26 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre SystemUiHelper.FLAG_LAYOUT_IN_SCREEN_OLDER_DEVICES | SystemUiHelper.FLAG_IMMERSIVE_STICKY); mSystemUiHelper.hide(); + mResources = getResources(); + + // Gallery index typeface + Typeface galleryIndexTf = Typeface.createFromAsset(mResources.getAssets(), "fonts/gallery_index.ttf"); + mGalleryIndexTextTexture = TextTexture.create(galleryIndexTf, + mResources.getDimensionPixelSize(R.dimen.gallery_index_text), + mResources.getColor(R.color.secondary_text_dark), + new char[]{'.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}); + GalleryPageView.setTextTexture(mGalleryIndexTextTexture); + // Gallery progress typeface + Typeface galleryProgressTf = Typeface.createFromAsset(mResources.getAssets(), "fonts/gallery_progress.ttf"); + mGalleryProgressTextTexture = TextTexture.create(galleryProgressTf, + mResources.getDimensionPixelSize(R.dimen.text_little_small), + mResources.getColor(R.color.primary_text_dark), + new char[]{'/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}); + GalleryPanel.setTextTexture(mGalleryProgressTextTexture); + setContentView(R.layout.activity_gallery); mGLRootView = (GLRootView) findViewById(R.id.gl_root_view); - mResources = getResources(); + // Prepare TiledTexture.prepareResources(); TiledTexture.setOnFreeBitmapListener(this); @@ -142,19 +182,15 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre GifTexture.initialize(mUploader, mThreadExecutor); mGifBitmapPool = new BitmapPool(); GifDecoder.setBitmapPool(mGifBitmapPool); - Typeface tf = Typeface.createFromAsset(mResources.getAssets(), "fonts/number.ttf"); - mTextTexture = TextTexture.create(tf, - mResources.getDimensionPixelSize(R.dimen.gallery_index_text), - mResources.getColor(R.color.secondary_text_dark), - new char[]{'.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}); - mUploader.addTexture(mTextTexture); - GalleryPageView.setTextTexture(mTextTexture); - mGalleryView = new GalleryView(this); + mUploader.addTexture(mGalleryIndexTextTexture); + mUploader.addTexture(mGalleryProgressTextTexture); + + GalleryLayout galleryLayout = new GalleryLayout(this, this); + mGalleryView = galleryLayout.getGalleryView(); + mGalleryPanel = galleryLayout.getGalleryPanel(); mAdapter = new GalleryAdapter(); mGalleryView.setAdapter(mAdapter); - mGalleryView.setProgressSize(LayoutUtils.dp2pix(this, 56)); - mGalleryView.setInterval(LayoutUtils.dp2pix(this, 48)); mSize = mGalleryProvider.size(); if (mSize <= 0) { @@ -164,7 +200,7 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre } mGalleryProvider.addGalleryProviderListener(this); - mGLRootView.setContentPane(mGalleryView); + mGLRootView.setContentPane(galleryLayout); } private void setMode(GalleryView.Mode mode) { @@ -172,6 +208,25 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre mGalleryView.setMode(mode); } + @Override + public void finish() { + super.finish(); + } + + private static void closeGalleryProvider(GalleryProvider galleryProvider) { + if (galleryProvider instanceof GallerySpider) { + GallerySpider.release((GallerySpider) galleryProvider); + } else if (galleryProvider instanceof ZipGalleryProvider) { + ((ZipGalleryProvider) galleryProvider).stop(); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(KEY_HASH_CODE, hashCode()); + } + @Override protected void onDestroy() { super.onDestroy(); @@ -179,18 +234,21 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre if (!mDieYoung) { GalleryPageView.setTextTexture(null); mUploader.clear(); - mTextTexture.recycle(); + mGalleryIndexTextTexture.recycle(); + mGalleryProgressTextTexture.recycle(); + GifDecoder.setBitmapPool(null); GifTexture.uninitialize(); TiledTexture.setOnFreeBitmapListener(null); TiledTexture.freeResources(); mGalleryProvider.removeGalleryProviderListener(this); - if (mGalleryProvider instanceof GallerySpider) { - GallerySpider.release((GallerySpider) mGalleryProvider); - } else if (mGalleryProvider instanceof ZipGalleryProvider) { - ((ZipGalleryProvider) mGalleryProvider).stop(); - } + // Post close task + int hashCode = hashCode(); + CloseGalleryProviderTask task = new CloseGalleryProviderTask(mGalleryProvider, hashCode); + sGalleryProviderMap.put(hashCode, mGalleryProvider); + sCloseGalleryProviderTaskMap.put(hashCode, task); + SimpleHandler.getInstance().postDelayed(task, 3000); mGalleryProvider = null; @@ -382,6 +440,16 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre } } + @Override + public void onShowGalleryPanel() { + mSystemUiHelper.show(); + } + + @Override + public void onHideGalleryPanel() { + mSystemUiHelper.hide(); + } + class GalleryAdapter extends GalleryView.Adapter { @Override @@ -407,4 +475,23 @@ public class GalleryActivity extends StatsActivity implements TiledTexture.OnFre clearTiledTextureInPage(view); } } + + static class CloseGalleryProviderTask implements Runnable { + + private GalleryProvider mGalleryProvider; + private int mHashCode; + + public CloseGalleryProviderTask(GalleryProvider galleryProvider, int hashCode) { + mGalleryProvider = galleryProvider; + mHashCode = hashCode; + } + + @Override + public void run() { + closeGalleryProvider(mGalleryProvider); + mGalleryProvider = null; + sGalleryProviderMap.remove(mHashCode); + sCloseGalleryProviderTaskMap.remove(mHashCode); + } + } } diff --git a/app/src/main/java/com/hippo/scene/ElementsSharedCurtain.java b/app/src/main/java/com/hippo/scene/ElementsSharedCurtain.java index b10731d5b..1e6b0fe6b 100644 --- a/app/src/main/java/com/hippo/scene/ElementsSharedCurtain.java +++ b/app/src/main/java/com/hippo/scene/ElementsSharedCurtain.java @@ -250,7 +250,7 @@ public class ElementsSharedCurtain extends Curtain { View sceneContentView = exit.getSceneView().getChildAt(0); if (sceneContentView == null) { Say.w(TAG, "Can't get exit scene content view"); - dispatchOpenFinished(enter, exit); + dispatchCloseFinished(enter, exit); return; } diff --git a/app/src/main/java/com/hippo/scene/SceneManager.java b/app/src/main/java/com/hippo/scene/SceneManager.java index c914b9fb0..78a24371f 100644 --- a/app/src/main/java/com/hippo/scene/SceneManager.java +++ b/app/src/main/java/com/hippo/scene/SceneManager.java @@ -202,6 +202,7 @@ public class SceneManager { } if (previousState != null) { + previousState.endCurtainAnimation(); previousState.pause(); previousState.setState(Scene.SCENE_STATE_PAUSE); } @@ -319,7 +320,7 @@ public class SceneManager { } private void checkSceneState(Scene scene, int expected) { - AssertUtils.assertEquals("The scene state is " + scene.getState() + ", but it should be " + expected, + AssertUtils.assertEquals(this + " state is " + scene.getState() + ", but it should be " + expected, expected, scene.getState()); } diff --git a/app/src/main/res/drawable/ic_file_image_dark_x48.xml b/app/src/main/res/drawable/ic_file_image_dark_x48.xml new file mode 100644 index 000000000..f0d3a8d01 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_image_dark_x48.xml @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_refresh_dark_x48.xml b/app/src/main/res/drawable/ic_refresh_dark_x48.xml new file mode 100644 index 000000000..9cc41d971 --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_dark_x48.xml @@ -0,0 +1,39 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_screen_rotation_dark_x48.xml b/app/src/main/res/drawable/ic_screen_rotation_dark_x48.xml new file mode 100644 index 000000000..22270140d --- /dev/null +++ b/app/src/main/res/drawable/ic_screen_rotation_dark_x48.xml @@ -0,0 +1,39 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_share_dark_x48.xml b/app/src/main/res/drawable/ic_share_dark_x48.xml new file mode 100644 index 000000000..47f112dcb --- /dev/null +++ b/app/src/main/res/drawable/ic_share_dark_x48.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_url_dark_x48.xml b/app/src/main/res/drawable/ic_url_dark_x48.xml new file mode 100644 index 000000000..f4ba2664a --- /dev/null +++ b/app/src/main/res/drawable/ic_url_dark_x48.xml @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_gallery.xml b/app/src/main/res/layout/activity_gallery.xml index b4cc500c7..ef5b21f23 100644 --- a/app/src/main/res/layout/activity_gallery.xml +++ b/app/src/main/res/layout/activity_gallery.xml @@ -22,6 +22,7 @@ + android:layout_height="match_parent" + android:fitsSystemWindows="true"/> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 2174e4f48..5fdb99f01 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -110,5 +110,6 @@ @color/material_deep_Orange_400 #ff212121 + #ff333333 diff --git a/app/src/main/res/values/pathdata.xml b/app/src/main/res/values/pathdata.xml index f1d43c5f4..0fcfb16df 100644 --- a/app/src/main/res/values/pathdata.xml +++ b/app/src/main/res/values/pathdata.xml @@ -43,5 +43,8 @@ M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z + M8.3,20c-2.8-1.3-4.6-4-5-7.2H2C2.4,18,6.8,22,12,22h0.5l-3.3-3.1 M14.4,19.7L4.4,9.7l5.4-5.3l10,10.1 M10.5,3.5C10,3,9.3,3,8.8,3.5L3.4,8.8c-0.5,0.5-0.5,1.3,0,1.8l10,10.1c0.5,0.5,1.3,0.5,1.8,0l5.4-5.3c0.5-0.5,0.5-1.3,0-1.8L10.5,3.5M15.8,4.1c2.8,1.3,4.6,4,5,7.2H22C21.6,6,17.3,2,12,2h-0.5l3.3,3.1L15.8,4.1z + M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z + M11.6,7v7.1c0,1-0.3,1.8-0.9,2.4c-0.6,0.6-1.5,1-2.5,1.1l-0.4,0c-1.1,0-2.1-0.3-2.7-0.9c-0.7-0.6-1-1.5-1-2.6V7h1.4v7.1c0,0.8,0.2,1.3,0.6,1.8c0.4,0.4,1,0.6,1.8,0.6c0.8,0,1.4-0.2,1.8-0.6c0.4-0.4,0.6-1,0.6-1.8V7H11.6M17.3,10.9c-0.2,0-0.4-0.1-0.7-0.1c-0.9,0-1.5,0.4-1.8,1.1v5.5h-1.3V9.7h1.3l0,0.9c0.4-0.7,1.1-1,1.9-1c0.3,0,0.5,0,0.6,0.1V10.9M20,17.5h-1.3V6.4H20V17.5z