summaryrefslogtreecommitdiffstats
path: root/src/com/android/gallery3d/ui/SlotView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/gallery3d/ui/SlotView.java')
-rw-r--r--src/com/android/gallery3d/ui/SlotView.java788
1 files changed, 788 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java
new file mode 100644
index 000000000..bd0ffdc15
--- /dev/null
+++ b/src/com/android/gallery3d/ui/SlotView.java
@@ -0,0 +1,788 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Rect;
+import android.os.Handler;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.gallery3d.anim.Animation;
+import com.android.gallery3d.app.AbstractGalleryActivity;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.GLCanvas;
+
+public class SlotView extends GLView {
+ @SuppressWarnings("unused")
+ private static final String TAG = "SlotView";
+
+ private static final boolean WIDE = true;
+ private static final int INDEX_NONE = -1;
+
+ public static final int RENDER_MORE_PASS = 1;
+ public static final int RENDER_MORE_FRAME = 2;
+
+ public interface Listener {
+ public void onDown(int index);
+ public void onUp(boolean followedByLongPress);
+ public void onSingleTapUp(int index);
+ public void onLongTap(int index);
+ public void onScrollPositionChanged(int position, int total);
+ }
+
+ public static class SimpleListener implements Listener {
+ @Override public void onDown(int index) {}
+ @Override public void onUp(boolean followedByLongPress) {}
+ @Override public void onSingleTapUp(int index) {}
+ @Override public void onLongTap(int index) {}
+ @Override public void onScrollPositionChanged(int position, int total) {}
+ }
+
+ public static interface SlotRenderer {
+ public void prepareDrawing();
+ public void onVisibleRangeChanged(int visibleStart, int visibleEnd);
+ public void onSlotSizeChanged(int width, int height);
+ public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height);
+ }
+
+ private final GestureDetector mGestureDetector;
+ private final ScrollerHelper mScroller;
+ private final Paper mPaper = new Paper();
+
+ private Listener mListener;
+ private UserInteractionListener mUIListener;
+
+ private boolean mMoreAnimation = false;
+ private SlotAnimation mAnimation = null;
+ private final Layout mLayout = new Layout();
+ private int mStartIndex = INDEX_NONE;
+
+ // whether the down action happened while the view is scrolling.
+ private boolean mDownInScrolling;
+ private int mOverscrollEffect = OVERSCROLL_3D;
+ private final Handler mHandler;
+
+ private SlotRenderer mRenderer;
+
+ private int[] mRequestRenderSlots = new int[16];
+
+ public static final int OVERSCROLL_3D = 0;
+ public static final int OVERSCROLL_SYSTEM = 1;
+ public static final int OVERSCROLL_NONE = 2;
+
+ // to prevent allocating memory
+ private final Rect mTempRect = new Rect();
+
+ public SlotView(AbstractGalleryActivity activity, Spec spec) {
+ mGestureDetector = new GestureDetector(activity, new MyGestureListener());
+ mScroller = new ScrollerHelper(activity);
+ mHandler = new SynchronizedHandler(activity.getGLRoot());
+ setSlotSpec(spec);
+ }
+
+ public void setSlotRenderer(SlotRenderer slotDrawer) {
+ mRenderer = slotDrawer;
+ if (mRenderer != null) {
+ mRenderer.onSlotSizeChanged(mLayout.mSlotWidth, mLayout.mSlotHeight);
+ mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd());
+ }
+ }
+
+ public void setCenterIndex(int index) {
+ int slotCount = mLayout.mSlotCount;
+ if (index < 0 || index >= slotCount) {
+ return;
+ }
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
+ int position = WIDE
+ ? (rect.left + rect.right - getWidth()) / 2
+ : (rect.top + rect.bottom - getHeight()) / 2;
+ setScrollPosition(position);
+ }
+
+ public void makeSlotVisible(int index) {
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
+ int visibleBegin = WIDE ? mScrollX : mScrollY;
+ int visibleLength = WIDE ? getWidth() : getHeight();
+ int visibleEnd = visibleBegin + visibleLength;
+ int slotBegin = WIDE ? rect.left : rect.top;
+ int slotEnd = WIDE ? rect.right : rect.bottom;
+
+ int position = visibleBegin;
+ if (visibleLength < slotEnd - slotBegin) {
+ position = visibleBegin;
+ } else if (slotBegin < visibleBegin) {
+ position = slotBegin;
+ } else if (slotEnd > visibleEnd) {
+ position = slotEnd - visibleLength;
+ }
+
+ setScrollPosition(position);
+ }
+
+ public void setScrollPosition(int position) {
+ position = Utils.clamp(position, 0, mLayout.getScrollLimit());
+ mScroller.setPosition(position);
+ updateScrollPosition(position, false);
+ }
+
+ public void setSlotSpec(Spec spec) {
+ mLayout.setSlotSpec(spec);
+ }
+
+ @Override
+ public void addComponent(GLView view) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
+ if (!changeSize) return;
+
+ // Make sure we are still at a resonable scroll position after the size
+ // is changed (like orientation change). We choose to keep the center
+ // visible slot still visible. This is arbitrary but reasonable.
+ int visibleIndex =
+ (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
+ mLayout.setSize(r - l, b - t);
+ makeSlotVisible(visibleIndex);
+ if (mOverscrollEffect == OVERSCROLL_3D) {
+ mPaper.setSize(r - l, b - t);
+ }
+ }
+
+ public void startScatteringAnimation(RelativePosition position) {
+ mAnimation = new ScatteringAnimation(position);
+ mAnimation.start();
+ if (mLayout.mSlotCount != 0) invalidate();
+ }
+
+ public void startRisingAnimation() {
+ mAnimation = new RisingAnimation();
+ mAnimation.start();
+ if (mLayout.mSlotCount != 0) invalidate();
+ }
+
+ private void updateScrollPosition(int position, boolean force) {
+ if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
+ if (WIDE) {
+ mScrollX = position;
+ } else {
+ mScrollY = position;
+ }
+ mLayout.setScrollPosition(position);
+ onScrollPositionChanged(position);
+ }
+
+ protected void onScrollPositionChanged(int newPosition) {
+ int limit = mLayout.getScrollLimit();
+ mListener.onScrollPositionChanged(newPosition, limit);
+ }
+
+ public Rect getSlotRect(int slotIndex) {
+ return mLayout.getSlotRect(slotIndex, new Rect());
+ }
+
+ @Override
+ protected boolean onTouch(MotionEvent event) {
+ if (mUIListener != null) mUIListener.onUserInteraction();
+ mGestureDetector.onTouchEvent(event);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mDownInScrolling = !mScroller.isFinished();
+ mScroller.forceFinished();
+ break;
+ case MotionEvent.ACTION_UP:
+ mPaper.onRelease();
+ invalidate();
+ break;
+ }
+ return true;
+ }
+
+ public void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ public void setUserInteractionListener(UserInteractionListener listener) {
+ mUIListener = listener;
+ }
+
+ public void setOverscrollEffect(int kind) {
+ mOverscrollEffect = kind;
+ mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
+ }
+
+ private static int[] expandIntArray(int array[], int capacity) {
+ while (array.length < capacity) {
+ array = new int[array.length * 2];
+ }
+ return array;
+ }
+
+ @Override
+ protected void render(GLCanvas canvas) {
+ super.render(canvas);
+
+ if (mRenderer == null) return;
+ mRenderer.prepareDrawing();
+
+ long animTime = AnimationTime.get();
+ boolean more = mScroller.advanceAnimation(animTime);
+ more |= mLayout.advanceAnimation(animTime);
+ int oldX = mScrollX;
+ updateScrollPosition(mScroller.getPosition(), false);
+
+ boolean paperActive = false;
+ if (mOverscrollEffect == OVERSCROLL_3D) {
+ // Check if an edge is reached and notify mPaper if so.
+ int newX = mScrollX;
+ int limit = mLayout.getScrollLimit();
+ if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) {
+ float v = mScroller.getCurrVelocity();
+ if (newX == limit) v = -v;
+
+ // I don't know why, but getCurrVelocity() can return NaN.
+ if (!Float.isNaN(v)) {
+ mPaper.edgeReached(v);
+ }
+ }
+ paperActive = mPaper.advanceAnimation();
+ }
+
+ more |= paperActive;
+
+ if (mAnimation != null) {
+ more |= mAnimation.calculate(animTime);
+ }
+
+ canvas.translate(-mScrollX, -mScrollY);
+
+ int requestCount = 0;
+ int requestedSlot[] = expandIntArray(mRequestRenderSlots,
+ mLayout.mVisibleEnd - mLayout.mVisibleStart);
+
+ for (int i = mLayout.mVisibleEnd - 1; i >= mLayout.mVisibleStart; --i) {
+ int r = renderItem(canvas, i, 0, paperActive);
+ if ((r & RENDER_MORE_FRAME) != 0) more = true;
+ if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i;
+ }
+
+ for (int pass = 1; requestCount != 0; ++pass) {
+ int newCount = 0;
+ for (int i = 0; i < requestCount; ++i) {
+ int r = renderItem(canvas,
+ requestedSlot[i], pass, paperActive);
+ if ((r & RENDER_MORE_FRAME) != 0) more = true;
+ if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i;
+ }
+ requestCount = newCount;
+ }
+
+ canvas.translate(mScrollX, mScrollY);
+
+ if (more) invalidate();
+
+ final UserInteractionListener listener = mUIListener;
+ if (mMoreAnimation && !more && listener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ listener.onUserInteractionEnd();
+ }
+ });
+ }
+ mMoreAnimation = more;
+ }
+
+ private int renderItem(
+ GLCanvas canvas, int index, int pass, boolean paperActive) {
+ canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
+ Rect rect = mLayout.getSlotRect(index, mTempRect);
+ if (paperActive) {
+ canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
+ } else {
+ canvas.translate(rect.left, rect.top, 0);
+ }
+ if (mAnimation != null && mAnimation.isActive()) {
+ mAnimation.apply(canvas, index, rect);
+ }
+ int result = mRenderer.renderSlot(
+ canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top);
+ canvas.restore();
+ return result;
+ }
+
+ public static abstract class SlotAnimation extends Animation {
+ protected float mProgress = 0;
+
+ public SlotAnimation() {
+ setInterpolator(new DecelerateInterpolator(4));
+ setDuration(1500);
+ }
+
+ @Override
+ protected void onCalculate(float progress) {
+ mProgress = progress;
+ }
+
+ abstract public void apply(GLCanvas canvas, int slotIndex, Rect target);
+ }
+
+ public static class RisingAnimation extends SlotAnimation {
+ private static final int RISING_DISTANCE = 128;
+
+ @Override
+ public void apply(GLCanvas canvas, int slotIndex, Rect target) {
+ canvas.translate(0, 0, RISING_DISTANCE * (1 - mProgress));
+ }
+ }
+
+ public static class ScatteringAnimation extends SlotAnimation {
+ private int PHOTO_DISTANCE = 1000;
+ private RelativePosition mCenter;
+
+ public ScatteringAnimation(RelativePosition center) {
+ mCenter = center;
+ }
+
+ @Override
+ public void apply(GLCanvas canvas, int slotIndex, Rect target) {
+ canvas.translate(
+ (mCenter.getX() - target.centerX()) * (1 - mProgress),
+ (mCenter.getY() - target.centerY()) * (1 - mProgress),
+ slotIndex * PHOTO_DISTANCE * (1 - mProgress));
+ canvas.setAlpha(mProgress);
+ }
+ }
+
+ // This Spec class is used to specify the size of each slot in the SlotView.
+ // There are two ways to do it:
+ //
+ // (1) Specify slotWidth and slotHeight: they specify the width and height
+ // of each slot. The number of rows and the gap between slots will be
+ // determined automatically.
+ // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number
+ // of rows in landscape/portrait mode and the gap between slots. The
+ // width and height of each slot is determined automatically.
+ //
+ // The initial value of -1 means they are not specified.
+ public static class Spec {
+ public int slotWidth = -1;
+ public int slotHeight = -1;
+ public int slotHeightAdditional = 0;
+
+ public int rowsLand = -1;
+ public int rowsPort = -1;
+ public int slotGap = -1;
+ }
+
+ public class Layout {
+
+ private int mVisibleStart;
+ private int mVisibleEnd;
+
+ private int mSlotCount;
+ private int mSlotWidth;
+ private int mSlotHeight;
+ private int mSlotGap;
+
+ private Spec mSpec;
+
+ private int mWidth;
+ private int mHeight;
+
+ private int mUnitCount;
+ private int mContentLength;
+ private int mScrollPosition;
+
+ private IntegerAnimation mVerticalPadding = new IntegerAnimation();
+ private IntegerAnimation mHorizontalPadding = new IntegerAnimation();
+
+ public void setSlotSpec(Spec spec) {
+ mSpec = spec;
+ }
+
+ public boolean setSlotCount(int slotCount) {
+ if (slotCount == mSlotCount) return false;
+ if (mSlotCount != 0) {
+ mHorizontalPadding.setEnabled(true);
+ mVerticalPadding.setEnabled(true);
+ }
+ mSlotCount = slotCount;
+ int hPadding = mHorizontalPadding.getTarget();
+ int vPadding = mVerticalPadding.getTarget();
+ initLayoutParameters();
+ return vPadding != mVerticalPadding.getTarget()
+ || hPadding != mHorizontalPadding.getTarget();
+ }
+
+ public Rect getSlotRect(int index, Rect rect) {
+ int col, row;
+ if (WIDE) {
+ col = index / mUnitCount;
+ row = index - col * mUnitCount;
+ } else {
+ row = index / mUnitCount;
+ col = index - row * mUnitCount;
+ }
+
+ int x = mHorizontalPadding.get() + col * (mSlotWidth + mSlotGap);
+ int y = mVerticalPadding.get() + row * (mSlotHeight + mSlotGap);
+ rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
+ return rect;
+ }
+
+ public int getSlotWidth() {
+ return mSlotWidth;
+ }
+
+ public int getSlotHeight() {
+ return mSlotHeight;
+ }
+
+ // Calculate
+ // (1) mUnitCount: the number of slots we can fit into one column (or row).
+ // (2) mContentLength: the width (or height) we need to display all the
+ // columns (rows).
+ // (3) padding[]: the vertical and horizontal padding we need in order
+ // to put the slots towards to the center of the display.
+ //
+ // The "major" direction is the direction the user can scroll. The other
+ // direction is the "minor" direction.
+ //
+ // The comments inside this method are the description when the major
+ // directon is horizontal (X), and the minor directon is vertical (Y).
+ private void initLayoutParameters(
+ int majorLength, int minorLength, /* The view width and height */
+ int majorUnitSize, int minorUnitSize, /* The slot width and height */
+ int[] padding) {
+ int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap);
+ if (unitCount == 0) unitCount = 1;
+ mUnitCount = unitCount;
+
+ // We put extra padding above and below the column.
+ int availableUnits = Math.min(mUnitCount, mSlotCount);
+ int usedMinorLength = availableUnits * minorUnitSize +
+ (availableUnits - 1) * mSlotGap;
+ padding[0] = (minorLength - usedMinorLength) / 2;
+
+ // Then calculate how many columns we need for all slots.
+ int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
+ mContentLength = count * majorUnitSize + (count - 1) * mSlotGap;
+
+ // If the content length is less then the screen width, put
+ // extra padding in left and right.
+ padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
+ }
+
+ private void initLayoutParameters() {
+ // Initialize mSlotWidth and mSlotHeight from mSpec
+ if (mSpec.slotWidth != -1) {
+ mSlotGap = 0;
+ mSlotWidth = mSpec.slotWidth;
+ mSlotHeight = mSpec.slotHeight;
+ } else {
+ int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort;
+ mSlotGap = mSpec.slotGap;
+ mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows);
+ mSlotWidth = mSlotHeight - mSpec.slotHeightAdditional;
+ }
+
+ if (mRenderer != null) {
+ mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight);
+ }
+
+ int[] padding = new int[2];
+ if (WIDE) {
+ initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
+ mVerticalPadding.startAnimateTo(padding[0]);
+ mHorizontalPadding.startAnimateTo(padding[1]);
+ } else {
+ initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
+ mVerticalPadding.startAnimateTo(padding[1]);
+ mHorizontalPadding.startAnimateTo(padding[0]);
+ }
+ updateVisibleSlotRange();
+ }
+
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ initLayoutParameters();
+ }
+
+ private void updateVisibleSlotRange() {
+ int position = mScrollPosition;
+
+ if (WIDE) {
+ int startCol = position / (mSlotWidth + mSlotGap);
+ int start = Math.max(0, mUnitCount * startCol);
+ int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) /
+ (mSlotWidth + mSlotGap);
+ int end = Math.min(mSlotCount, mUnitCount * endCol);
+ setVisibleRange(start, end);
+ } else {
+ int startRow = position / (mSlotHeight + mSlotGap);
+ int start = Math.max(0, mUnitCount * startRow);
+ int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) /
+ (mSlotHeight + mSlotGap);
+ int end = Math.min(mSlotCount, mUnitCount * endRow);
+ setVisibleRange(start, end);
+ }
+ }
+
+ public void setScrollPosition(int position) {
+ if (mScrollPosition == position) return;
+ mScrollPosition = position;
+ updateVisibleSlotRange();
+ }
+
+ private void setVisibleRange(int start, int end) {
+ if (start == mVisibleStart && end == mVisibleEnd) return;
+ if (start < end) {
+ mVisibleStart = start;
+ mVisibleEnd = end;
+ } else {
+ mVisibleStart = mVisibleEnd = 0;
+ }
+ if (mRenderer != null) {
+ mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd);
+ }
+ }
+
+ public int getVisibleStart() {
+ return mVisibleStart;
+ }
+
+ public int getVisibleEnd() {
+ return mVisibleEnd;
+ }
+
+ public int getSlotIndexByPosition(float x, float y) {
+ int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
+ int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
+
+ absoluteX -= mHorizontalPadding.get();
+ absoluteY -= mVerticalPadding.get();
+
+ if (absoluteX < 0 || absoluteY < 0) {
+ return INDEX_NONE;
+ }
+
+ int columnIdx = absoluteX / (mSlotWidth + mSlotGap);
+ int rowIdx = absoluteY / (mSlotHeight + mSlotGap);
+
+ if (!WIDE && columnIdx >= mUnitCount) {
+ return INDEX_NONE;
+ }
+
+ if (WIDE && rowIdx >= mUnitCount) {
+ return INDEX_NONE;
+ }
+
+ if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) {
+ return INDEX_NONE;
+ }
+
+ if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) {
+ return INDEX_NONE;
+ }
+
+ int index = WIDE
+ ? (columnIdx * mUnitCount + rowIdx)
+ : (rowIdx * mUnitCount + columnIdx);
+
+ return index >= mSlotCount ? INDEX_NONE : index;
+ }
+
+ public int getScrollLimit() {
+ int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
+ return limit <= 0 ? 0 : limit;
+ }
+
+ public boolean advanceAnimation(long animTime) {
+ // use '|' to make sure both sides will be executed
+ return mVerticalPadding.calculate(animTime) | mHorizontalPadding.calculate(animTime);
+ }
+ }
+
+ private class MyGestureListener implements GestureDetector.OnGestureListener {
+ private boolean isDown;
+
+ // We call the listener's onDown() when our onShowPress() is called and
+ // call the listener's onUp() when we receive any further event.
+ @Override
+ public void onShowPress(MotionEvent e) {
+ GLRoot root = getGLRoot();
+ root.lockRenderThread();
+ try {
+ if (isDown) return;
+ int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
+ if (index != INDEX_NONE) {
+ isDown = true;
+ mListener.onDown(index);
+ }
+ } finally {
+ root.unlockRenderThread();
+ }
+ }
+
+ private void cancelDown(boolean byLongPress) {
+ if (!isDown) return;
+ isDown = false;
+ mListener.onUp(byLongPress);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1,
+ MotionEvent e2, float velocityX, float velocityY) {
+ cancelDown(false);
+ int scrollLimit = mLayout.getScrollLimit();
+ if (scrollLimit == 0) return false;
+ float velocity = WIDE ? velocityX : velocityY;
+ mScroller.fling((int) -velocity, 0, scrollLimit);
+ if (mUIListener != null) mUIListener.onUserInteractionBegin();
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1,
+ MotionEvent e2, float distanceX, float distanceY) {
+ cancelDown(false);
+ float distance = WIDE ? distanceX : distanceY;
+ int overDistance = mScroller.startScroll(
+ Math.round(distance), 0, mLayout.getScrollLimit());
+ if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) {
+ mPaper.overScroll(overDistance);
+ }
+ invalidate();
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ cancelDown(false);
+ if (mDownInScrolling) return true;
+ int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
+ if (index != INDEX_NONE) mListener.onSingleTapUp(index);
+ return true;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ cancelDown(true);
+ if (mDownInScrolling) return;
+ lockRendering();
+ try {
+ int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
+ if (index != INDEX_NONE) mListener.onLongTap(index);
+ } finally {
+ unlockRendering();
+ }
+ }
+ }
+
+ public void setStartIndex(int index) {
+ mStartIndex = index;
+ }
+
+ // Return true if the layout parameters have been changed
+ public boolean setSlotCount(int slotCount) {
+ boolean changed = mLayout.setSlotCount(slotCount);
+
+ // mStartIndex is applied the first time setSlotCount is called.
+ if (mStartIndex != INDEX_NONE) {
+ setCenterIndex(mStartIndex);
+ mStartIndex = INDEX_NONE;
+ }
+ // Reset the scroll position to avoid scrolling over the updated limit.
+ setScrollPosition(WIDE ? mScrollX : mScrollY);
+ return changed;
+ }
+
+ public int getVisibleStart() {
+ return mLayout.getVisibleStart();
+ }
+
+ public int getVisibleEnd() {
+ return mLayout.getVisibleEnd();
+ }
+
+ public int getScrollX() {
+ return mScrollX;
+ }
+
+ public int getScrollY() {
+ return mScrollY;
+ }
+
+ public Rect getSlotRect(int slotIndex, GLView rootPane) {
+ // Get slot rectangle relative to this root pane.
+ Rect offset = new Rect();
+ rootPane.getBoundsOf(this, offset);
+ Rect r = getSlotRect(slotIndex);
+ r.offset(offset.left - getScrollX(),
+ offset.top - getScrollY());
+ return r;
+ }
+
+ private static class IntegerAnimation extends Animation {
+ private int mTarget;
+ private int mCurrent = 0;
+ private int mFrom = 0;
+ private boolean mEnabled = false;
+
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ }
+
+ public void startAnimateTo(int target) {
+ if (!mEnabled) {
+ mTarget = mCurrent = target;
+ return;
+ }
+ if (target == mTarget) return;
+
+ mFrom = mCurrent;
+ mTarget = target;
+ setDuration(180);
+ start();
+ }
+
+ public int get() {
+ return mCurrent;
+ }
+
+ public int getTarget() {
+ return mTarget;
+ }
+
+ @Override
+ protected void onCalculate(float progress) {
+ mCurrent = Math.round(mFrom + progress * (mTarget - mFrom));
+ if (progress == 1f) mEnabled = false;
+ }
+ }
+}