• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.gallery3d.ui;
18 
19 import android.graphics.Rect;
20 import android.os.Handler;
21 import android.view.GestureDetector;
22 import android.view.MotionEvent;
23 import android.view.animation.DecelerateInterpolator;
24 
25 import com.android.gallery3d.anim.Animation;
26 import com.android.gallery3d.app.AbstractGalleryActivity;
27 import com.android.gallery3d.common.Utils;
28 
29 public class SlotView extends GLView {
30     @SuppressWarnings("unused")
31     private static final String TAG = "SlotView";
32 
33     private static final boolean WIDE = true;
34     private static final int INDEX_NONE = -1;
35 
36     public static final int RENDER_MORE_PASS = 1;
37     public static final int RENDER_MORE_FRAME = 2;
38 
39     public interface Listener {
onDown(int index)40         public void onDown(int index);
onUp(boolean followedByLongPress)41         public void onUp(boolean followedByLongPress);
onSingleTapUp(int index)42         public void onSingleTapUp(int index);
onLongTap(int index)43         public void onLongTap(int index);
onScrollPositionChanged(int position, int total)44         public void onScrollPositionChanged(int position, int total);
45     }
46 
47     public static class SimpleListener implements Listener {
onDown(int index)48         @Override public void onDown(int index) {}
onUp(boolean followedByLongPress)49         @Override public void onUp(boolean followedByLongPress) {}
onSingleTapUp(int index)50         @Override public void onSingleTapUp(int index) {}
onLongTap(int index)51         @Override public void onLongTap(int index) {}
onScrollPositionChanged(int position, int total)52         @Override public void onScrollPositionChanged(int position, int total) {}
53     }
54 
55     public static interface SlotRenderer {
prepareDrawing()56         public void prepareDrawing();
onVisibleRangeChanged(int visibleStart, int visibleEnd)57         public void onVisibleRangeChanged(int visibleStart, int visibleEnd);
onSlotSizeChanged(int width, int height)58         public void onSlotSizeChanged(int width, int height);
renderSlot(GLCanvas canvas, int index, int pass, int width, int height)59         public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height);
60     }
61 
62     private final GestureDetector mGestureDetector;
63     private final ScrollerHelper mScroller;
64     private final Paper mPaper = new Paper();
65 
66     private Listener mListener;
67     private UserInteractionListener mUIListener;
68 
69     private boolean mMoreAnimation = false;
70     private SlotAnimation mAnimation = null;
71     private final Layout mLayout = new Layout();
72     private int mStartIndex = INDEX_NONE;
73 
74     // whether the down action happened while the view is scrolling.
75     private boolean mDownInScrolling;
76     private int mOverscrollEffect = OVERSCROLL_3D;
77     private final Handler mHandler;
78 
79     private SlotRenderer mRenderer;
80 
81     private int[] mRequestRenderSlots = new int[16];
82 
83     public static final int OVERSCROLL_3D = 0;
84     public static final int OVERSCROLL_SYSTEM = 1;
85     public static final int OVERSCROLL_NONE = 2;
86 
87     // to prevent allocating memory
88     private final Rect mTempRect = new Rect();
89 
SlotView(AbstractGalleryActivity activity, Spec spec)90     public SlotView(AbstractGalleryActivity activity, Spec spec) {
91         mGestureDetector = new GestureDetector(activity, new MyGestureListener());
92         mScroller = new ScrollerHelper(activity);
93         mHandler = new SynchronizedHandler(activity.getGLRoot());
94         setSlotSpec(spec);
95     }
96 
setSlotRenderer(SlotRenderer slotDrawer)97     public void setSlotRenderer(SlotRenderer slotDrawer) {
98         mRenderer = slotDrawer;
99         if (mRenderer != null) {
100             mRenderer.onSlotSizeChanged(mLayout.mSlotWidth, mLayout.mSlotHeight);
101             mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd());
102         }
103     }
104 
setCenterIndex(int index)105     public void setCenterIndex(int index) {
106         int slotCount = mLayout.mSlotCount;
107         if (index < 0 || index >= slotCount) {
108             return;
109         }
110         Rect rect = mLayout.getSlotRect(index, mTempRect);
111         int position = WIDE
112                 ? (rect.left + rect.right - getWidth()) / 2
113                 : (rect.top + rect.bottom - getHeight()) / 2;
114         setScrollPosition(position);
115     }
116 
makeSlotVisible(int index)117     public void makeSlotVisible(int index) {
118         Rect rect = mLayout.getSlotRect(index, mTempRect);
119         int visibleBegin = WIDE ? mScrollX : mScrollY;
120         int visibleLength = WIDE ? getWidth() : getHeight();
121         int visibleEnd = visibleBegin + visibleLength;
122         int slotBegin = WIDE ? rect.left : rect.top;
123         int slotEnd = WIDE ? rect.right : rect.bottom;
124 
125         int position = visibleBegin;
126         if (visibleLength < slotEnd - slotBegin) {
127             position = visibleBegin;
128         } else if (slotBegin < visibleBegin) {
129             position = slotBegin;
130         } else if (slotEnd > visibleEnd) {
131             position = slotEnd - visibleLength;
132         }
133 
134         setScrollPosition(position);
135     }
136 
setScrollPosition(int position)137     public void setScrollPosition(int position) {
138         position = Utils.clamp(position, 0, mLayout.getScrollLimit());
139         mScroller.setPosition(position);
140         updateScrollPosition(position, false);
141     }
142 
setSlotSpec(Spec spec)143     public void setSlotSpec(Spec spec) {
144         mLayout.setSlotSpec(spec);
145     }
146 
147     @Override
addComponent(GLView view)148     public void addComponent(GLView view) {
149         throw new UnsupportedOperationException();
150     }
151 
152     @Override
onLayout(boolean changeSize, int l, int t, int r, int b)153     protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
154         if (!changeSize) return;
155 
156         // Make sure we are still at a resonable scroll position after the size
157         // is changed (like orientation change). We choose to keep the center
158         // visible slot still visible. This is arbitrary but reasonable.
159         int visibleIndex =
160                 (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
161         mLayout.setSize(r - l, b - t);
162         makeSlotVisible(visibleIndex);
163         if (mOverscrollEffect == OVERSCROLL_3D) {
164             mPaper.setSize(r - l, b - t);
165         }
166     }
167 
startScatteringAnimation(RelativePosition position)168     public void startScatteringAnimation(RelativePosition position) {
169         mAnimation = new ScatteringAnimation(position);
170         mAnimation.start();
171         if (mLayout.mSlotCount != 0) invalidate();
172     }
173 
startRisingAnimation()174     public void startRisingAnimation() {
175         mAnimation = new RisingAnimation();
176         mAnimation.start();
177         if (mLayout.mSlotCount != 0) invalidate();
178     }
179 
updateScrollPosition(int position, boolean force)180     private void updateScrollPosition(int position, boolean force) {
181         if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
182         if (WIDE) {
183             mScrollX = position;
184         } else {
185             mScrollY = position;
186         }
187         mLayout.setScrollPosition(position);
188         onScrollPositionChanged(position);
189     }
190 
onScrollPositionChanged(int newPosition)191     protected void onScrollPositionChanged(int newPosition) {
192         int limit = mLayout.getScrollLimit();
193         mListener.onScrollPositionChanged(newPosition, limit);
194     }
195 
getSlotRect(int slotIndex)196     public Rect getSlotRect(int slotIndex) {
197         return mLayout.getSlotRect(slotIndex, new Rect());
198     }
199 
200     @Override
onTouch(MotionEvent event)201     protected boolean onTouch(MotionEvent event) {
202         if (mUIListener != null) mUIListener.onUserInteraction();
203         mGestureDetector.onTouchEvent(event);
204         switch (event.getAction()) {
205             case MotionEvent.ACTION_DOWN:
206                 mDownInScrolling = !mScroller.isFinished();
207                 mScroller.forceFinished();
208                 break;
209             case MotionEvent.ACTION_UP:
210                 mPaper.onRelease();
211                 invalidate();
212                 break;
213         }
214         return true;
215     }
216 
setListener(Listener listener)217     public void setListener(Listener listener) {
218         mListener = listener;
219     }
220 
setUserInteractionListener(UserInteractionListener listener)221     public void setUserInteractionListener(UserInteractionListener listener) {
222         mUIListener = listener;
223     }
224 
setOverscrollEffect(int kind)225     public void setOverscrollEffect(int kind) {
226         mOverscrollEffect = kind;
227         mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
228     }
229 
expandIntArray(int array[], int capacity)230     private static int[] expandIntArray(int array[], int capacity) {
231         while (array.length < capacity) {
232             array = new int[array.length * 2];
233         }
234         return array;
235     }
236 
237     @Override
render(GLCanvas canvas)238     protected void render(GLCanvas canvas) {
239         super.render(canvas);
240 
241         if (mRenderer == null) return;
242         mRenderer.prepareDrawing();
243 
244         long animTime = AnimationTime.get();
245         boolean more = mScroller.advanceAnimation(animTime);
246         more |= mLayout.advanceAnimation(animTime);
247         int oldX = mScrollX;
248         updateScrollPosition(mScroller.getPosition(), false);
249 
250         boolean paperActive = false;
251         if (mOverscrollEffect == OVERSCROLL_3D) {
252             // Check if an edge is reached and notify mPaper if so.
253             int newX = mScrollX;
254             int limit = mLayout.getScrollLimit();
255             if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) {
256                 float v = mScroller.getCurrVelocity();
257                 if (newX == limit) v = -v;
258 
259                 // I don't know why, but getCurrVelocity() can return NaN.
260                 if (!Float.isNaN(v)) {
261                     mPaper.edgeReached(v);
262                 }
263             }
264             paperActive = mPaper.advanceAnimation();
265         }
266 
267         more |= paperActive;
268 
269         if (mAnimation != null) {
270             more |= mAnimation.calculate(animTime);
271         }
272 
273         canvas.translate(-mScrollX, -mScrollY);
274 
275         int requestCount = 0;
276         int requestedSlot[] = expandIntArray(mRequestRenderSlots,
277                 mLayout.mVisibleEnd - mLayout.mVisibleStart);
278 
279         for (int i = mLayout.mVisibleEnd - 1; i >= mLayout.mVisibleStart; --i) {
280             int r = renderItem(canvas, i, 0, paperActive);
281             if ((r & RENDER_MORE_FRAME) != 0) more = true;
282             if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i;
283         }
284 
285         for (int pass = 1; requestCount != 0; ++pass) {
286             int newCount = 0;
287             for (int i = 0; i < requestCount; ++i) {
288                 int r = renderItem(canvas,
289                         requestedSlot[i], pass, paperActive);
290                 if ((r & RENDER_MORE_FRAME) != 0) more = true;
291                 if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i;
292             }
293             requestCount = newCount;
294         }
295 
296         canvas.translate(mScrollX, mScrollY);
297 
298         if (more) invalidate();
299 
300         final UserInteractionListener listener = mUIListener;
301         if (mMoreAnimation && !more && listener != null) {
302             mHandler.post(new Runnable() {
303                 @Override
304                 public void run() {
305                     listener.onUserInteractionEnd();
306                 }
307             });
308         }
309         mMoreAnimation = more;
310     }
311 
renderItem( GLCanvas canvas, int index, int pass, boolean paperActive)312     private int renderItem(
313             GLCanvas canvas, int index, int pass, boolean paperActive) {
314         canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
315         Rect rect = mLayout.getSlotRect(index, mTempRect);
316         if (paperActive) {
317             canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
318         } else {
319             canvas.translate(rect.left, rect.top, 0);
320         }
321         if (mAnimation != null && mAnimation.isActive()) {
322             mAnimation.apply(canvas, index, rect);
323         }
324         int result = mRenderer.renderSlot(
325                 canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top);
326         canvas.restore();
327         return result;
328     }
329 
330     public static abstract class SlotAnimation extends Animation {
331         protected float mProgress = 0;
332 
SlotAnimation()333         public SlotAnimation() {
334             setInterpolator(new DecelerateInterpolator(4));
335             setDuration(1500);
336         }
337 
338         @Override
onCalculate(float progress)339         protected void onCalculate(float progress) {
340             mProgress = progress;
341         }
342 
apply(GLCanvas canvas, int slotIndex, Rect target)343         abstract public void apply(GLCanvas canvas, int slotIndex, Rect target);
344     }
345 
346     public static class RisingAnimation extends SlotAnimation {
347         private static final int RISING_DISTANCE = 128;
348 
349         @Override
apply(GLCanvas canvas, int slotIndex, Rect target)350         public void apply(GLCanvas canvas, int slotIndex, Rect target) {
351             canvas.translate(0, 0, RISING_DISTANCE * (1 - mProgress));
352         }
353     }
354 
355     public static class ScatteringAnimation extends SlotAnimation {
356         private int PHOTO_DISTANCE = 1000;
357         private RelativePosition mCenter;
358 
ScatteringAnimation(RelativePosition center)359         public ScatteringAnimation(RelativePosition center) {
360             mCenter = center;
361         }
362 
363         @Override
apply(GLCanvas canvas, int slotIndex, Rect target)364         public void apply(GLCanvas canvas, int slotIndex, Rect target) {
365             canvas.translate(
366                     (mCenter.getX() - target.centerX()) * (1 - mProgress),
367                     (mCenter.getY() - target.centerY()) * (1 - mProgress),
368                     slotIndex * PHOTO_DISTANCE * (1 - mProgress));
369             canvas.setAlpha(mProgress);
370         }
371     }
372 
373     // This Spec class is used to specify the size of each slot in the SlotView.
374     // There are two ways to do it:
375     //
376     // (1) Specify slotWidth and slotHeight: they specify the width and height
377     //     of each slot. The number of rows and the gap between slots will be
378     //     determined automatically.
379     // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number
380     //     of rows in landscape/portrait mode and the gap between slots. The
381     //     width and height of each slot is determined automatically.
382     //
383     // The initial value of -1 means they are not specified.
384     public static class Spec {
385         public int slotWidth = -1;
386         public int slotHeight = -1;
387         public int slotHeightAdditional = 0;
388 
389         public int rowsLand = -1;
390         public int rowsPort = -1;
391         public int slotGap = -1;
392     }
393 
394     public class Layout {
395 
396         private int mVisibleStart;
397         private int mVisibleEnd;
398 
399         private int mSlotCount;
400         private int mSlotWidth;
401         private int mSlotHeight;
402         private int mSlotGap;
403 
404         private Spec mSpec;
405 
406         private int mWidth;
407         private int mHeight;
408 
409         private int mUnitCount;
410         private int mContentLength;
411         private int mScrollPosition;
412 
413         private IntegerAnimation mVerticalPadding = new IntegerAnimation();
414         private IntegerAnimation mHorizontalPadding = new IntegerAnimation();
415 
setSlotSpec(Spec spec)416         public void setSlotSpec(Spec spec) {
417             mSpec = spec;
418         }
419 
setSlotCount(int slotCount)420         public boolean setSlotCount(int slotCount) {
421             if (slotCount == mSlotCount) return false;
422             if (mSlotCount != 0) {
423                 mHorizontalPadding.setEnabled(true);
424                 mVerticalPadding.setEnabled(true);
425             }
426             mSlotCount = slotCount;
427             int hPadding = mHorizontalPadding.getTarget();
428             int vPadding = mVerticalPadding.getTarget();
429             initLayoutParameters();
430             return vPadding != mVerticalPadding.getTarget()
431                     || hPadding != mHorizontalPadding.getTarget();
432         }
433 
getSlotRect(int index, Rect rect)434         public Rect getSlotRect(int index, Rect rect) {
435             int col, row;
436             if (WIDE) {
437                 col = index / mUnitCount;
438                 row = index - col * mUnitCount;
439             } else {
440                 row = index / mUnitCount;
441                 col = index - row * mUnitCount;
442             }
443 
444             int x = mHorizontalPadding.get() + col * (mSlotWidth + mSlotGap);
445             int y = mVerticalPadding.get() + row * (mSlotHeight + mSlotGap);
446             rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
447             return rect;
448         }
449 
getSlotWidth()450         public int getSlotWidth() {
451             return mSlotWidth;
452         }
453 
getSlotHeight()454         public int getSlotHeight() {
455             return mSlotHeight;
456         }
457 
458         // Calculate
459         // (1) mUnitCount: the number of slots we can fit into one column (or row).
460         // (2) mContentLength: the width (or height) we need to display all the
461         //     columns (rows).
462         // (3) padding[]: the vertical and horizontal padding we need in order
463         //     to put the slots towards to the center of the display.
464         //
465         // The "major" direction is the direction the user can scroll. The other
466         // direction is the "minor" direction.
467         //
468         // The comments inside this method are the description when the major
469         // directon is horizontal (X), and the minor directon is vertical (Y).
initLayoutParameters( int majorLength, int minorLength, int majorUnitSize, int minorUnitSize, int[] padding)470         private void initLayoutParameters(
471                 int majorLength, int minorLength,  /* The view width and height */
472                 int majorUnitSize, int minorUnitSize,  /* The slot width and height */
473                 int[] padding) {
474             int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap);
475             if (unitCount == 0) unitCount = 1;
476             mUnitCount = unitCount;
477 
478             // We put extra padding above and below the column.
479             int availableUnits = Math.min(mUnitCount, mSlotCount);
480             int usedMinorLength = availableUnits * minorUnitSize +
481                     (availableUnits - 1) * mSlotGap;
482             padding[0] = (minorLength - usedMinorLength) / 2;
483 
484             // Then calculate how many columns we need for all slots.
485             int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
486             mContentLength = count * majorUnitSize + (count - 1) * mSlotGap;
487 
488             // If the content length is less then the screen width, put
489             // extra padding in left and right.
490             padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
491         }
492 
initLayoutParameters()493         private void initLayoutParameters() {
494             // Initialize mSlotWidth and mSlotHeight from mSpec
495             if (mSpec.slotWidth != -1) {
496                 mSlotGap = 0;
497                 mSlotWidth = mSpec.slotWidth;
498                 mSlotHeight = mSpec.slotHeight;
499             } else {
500                 int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort;
501                 mSlotGap = mSpec.slotGap;
502                 mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows);
503                 mSlotWidth = mSlotHeight - mSpec.slotHeightAdditional;
504             }
505 
506             if (mRenderer != null) {
507                 mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight);
508             }
509 
510             int[] padding = new int[2];
511             if (WIDE) {
512                 initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
513                 mVerticalPadding.startAnimateTo(padding[0]);
514                 mHorizontalPadding.startAnimateTo(padding[1]);
515             } else {
516                 initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
517                 mVerticalPadding.startAnimateTo(padding[1]);
518                 mHorizontalPadding.startAnimateTo(padding[0]);
519             }
520             updateVisibleSlotRange();
521         }
522 
setSize(int width, int height)523         public void setSize(int width, int height) {
524             mWidth = width;
525             mHeight = height;
526             initLayoutParameters();
527         }
528 
updateVisibleSlotRange()529         private void updateVisibleSlotRange() {
530             int position = mScrollPosition;
531 
532             if (WIDE) {
533                 int startCol = position / (mSlotWidth + mSlotGap);
534                 int start = Math.max(0, mUnitCount * startCol);
535                 int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) /
536                         (mSlotWidth + mSlotGap);
537                 int end = Math.min(mSlotCount, mUnitCount * endCol);
538                 setVisibleRange(start, end);
539             } else {
540                 int startRow = position / (mSlotHeight + mSlotGap);
541                 int start = Math.max(0, mUnitCount * startRow);
542                 int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) /
543                         (mSlotHeight + mSlotGap);
544                 int end = Math.min(mSlotCount, mUnitCount * endRow);
545                 setVisibleRange(start, end);
546             }
547         }
548 
setScrollPosition(int position)549         public void setScrollPosition(int position) {
550             if (mScrollPosition == position) return;
551             mScrollPosition = position;
552             updateVisibleSlotRange();
553         }
554 
setVisibleRange(int start, int end)555         private void setVisibleRange(int start, int end) {
556             if (start == mVisibleStart && end == mVisibleEnd) return;
557             if (start < end) {
558                 mVisibleStart = start;
559                 mVisibleEnd = end;
560             } else {
561                 mVisibleStart = mVisibleEnd = 0;
562             }
563             if (mRenderer != null) {
564                 mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd);
565             }
566         }
567 
getVisibleStart()568         public int getVisibleStart() {
569             return mVisibleStart;
570         }
571 
getVisibleEnd()572         public int getVisibleEnd() {
573             return mVisibleEnd;
574         }
575 
getSlotIndexByPosition(float x, float y)576         public int getSlotIndexByPosition(float x, float y) {
577             int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
578             int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
579 
580             absoluteX -= mHorizontalPadding.get();
581             absoluteY -= mVerticalPadding.get();
582 
583             if (absoluteX < 0 || absoluteY < 0) {
584                 return INDEX_NONE;
585             }
586 
587             int columnIdx = absoluteX / (mSlotWidth + mSlotGap);
588             int rowIdx = absoluteY / (mSlotHeight + mSlotGap);
589 
590             if (!WIDE && columnIdx >= mUnitCount) {
591                 return INDEX_NONE;
592             }
593 
594             if (WIDE && rowIdx >= mUnitCount) {
595                 return INDEX_NONE;
596             }
597 
598             if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) {
599                 return INDEX_NONE;
600             }
601 
602             if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) {
603                 return INDEX_NONE;
604             }
605 
606             int index = WIDE
607                     ? (columnIdx * mUnitCount + rowIdx)
608                     : (rowIdx * mUnitCount + columnIdx);
609 
610             return index >= mSlotCount ? INDEX_NONE : index;
611         }
612 
getScrollLimit()613         public int getScrollLimit() {
614             int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
615             return limit <= 0 ? 0 : limit;
616         }
617 
advanceAnimation(long animTime)618         public boolean advanceAnimation(long animTime) {
619             // use '|' to make sure both sides will be executed
620             return mVerticalPadding.calculate(animTime) | mHorizontalPadding.calculate(animTime);
621         }
622     }
623 
624     private class MyGestureListener implements GestureDetector.OnGestureListener {
625         private boolean isDown;
626 
627         // We call the listener's onDown() when our onShowPress() is called and
628         // call the listener's onUp() when we receive any further event.
629         @Override
onShowPress(MotionEvent e)630         public void onShowPress(MotionEvent e) {
631             GLRoot root = getGLRoot();
632             root.lockRenderThread();
633             try {
634                 if (isDown) return;
635                 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
636                 if (index != INDEX_NONE) {
637                     isDown = true;
638                     mListener.onDown(index);
639                 }
640             } finally {
641                 root.unlockRenderThread();
642             }
643         }
644 
cancelDown(boolean byLongPress)645         private void cancelDown(boolean byLongPress) {
646             if (!isDown) return;
647             isDown = false;
648             mListener.onUp(byLongPress);
649         }
650 
651         @Override
onDown(MotionEvent e)652         public boolean onDown(MotionEvent e) {
653             return false;
654         }
655 
656         @Override
onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)657         public boolean onFling(MotionEvent e1,
658                 MotionEvent e2, float velocityX, float velocityY) {
659             cancelDown(false);
660             int scrollLimit = mLayout.getScrollLimit();
661             if (scrollLimit == 0) return false;
662             float velocity = WIDE ? velocityX : velocityY;
663             mScroller.fling((int) -velocity, 0, scrollLimit);
664             if (mUIListener != null) mUIListener.onUserInteractionBegin();
665             invalidate();
666             return true;
667         }
668 
669         @Override
onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)670         public boolean onScroll(MotionEvent e1,
671                 MotionEvent e2, float distanceX, float distanceY) {
672             cancelDown(false);
673             float distance = WIDE ? distanceX : distanceY;
674             int overDistance = mScroller.startScroll(
675                     Math.round(distance), 0, mLayout.getScrollLimit());
676             if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) {
677                 mPaper.overScroll(overDistance);
678             }
679             invalidate();
680             return true;
681         }
682 
683         @Override
onSingleTapUp(MotionEvent e)684         public boolean onSingleTapUp(MotionEvent e) {
685             cancelDown(false);
686             if (mDownInScrolling) return true;
687             int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
688             if (index != INDEX_NONE) mListener.onSingleTapUp(index);
689             return true;
690         }
691 
692         @Override
onLongPress(MotionEvent e)693         public void onLongPress(MotionEvent e) {
694             cancelDown(true);
695             if (mDownInScrolling) return;
696             lockRendering();
697             try {
698                 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
699                 if (index != INDEX_NONE) mListener.onLongTap(index);
700             } finally {
701                 unlockRendering();
702             }
703         }
704     }
705 
setStartIndex(int index)706     public void setStartIndex(int index) {
707         mStartIndex = index;
708     }
709 
710     // Return true if the layout parameters have been changed
setSlotCount(int slotCount)711     public boolean setSlotCount(int slotCount) {
712         boolean changed = mLayout.setSlotCount(slotCount);
713 
714         // mStartIndex is applied the first time setSlotCount is called.
715         if (mStartIndex != INDEX_NONE) {
716             setCenterIndex(mStartIndex);
717             mStartIndex = INDEX_NONE;
718         }
719         // Reset the scroll position to avoid scrolling over the updated limit.
720         setScrollPosition(WIDE ? mScrollX : mScrollY);
721         return changed;
722     }
723 
getVisibleStart()724     public int getVisibleStart() {
725         return mLayout.getVisibleStart();
726     }
727 
getVisibleEnd()728     public int getVisibleEnd() {
729         return mLayout.getVisibleEnd();
730     }
731 
getScrollX()732     public int getScrollX() {
733         return mScrollX;
734     }
735 
getScrollY()736     public int getScrollY() {
737         return mScrollY;
738     }
739 
getSlotRect(int slotIndex, GLView rootPane)740     public Rect getSlotRect(int slotIndex, GLView rootPane) {
741         // Get slot rectangle relative to this root pane.
742         Rect offset = new Rect();
743         rootPane.getBoundsOf(this, offset);
744         Rect r = getSlotRect(slotIndex);
745         r.offset(offset.left - getScrollX(),
746                 offset.top - getScrollY());
747         return r;
748     }
749 
750     private static class IntegerAnimation extends Animation {
751         private int mTarget;
752         private int mCurrent = 0;
753         private int mFrom = 0;
754         private boolean mEnabled = false;
755 
setEnabled(boolean enabled)756         public void setEnabled(boolean enabled) {
757             mEnabled = enabled;
758         }
759 
startAnimateTo(int target)760         public void startAnimateTo(int target) {
761             if (!mEnabled) {
762                 mTarget = mCurrent = target;
763                 return;
764             }
765             if (target == mTarget) return;
766 
767             mFrom = mCurrent;
768             mTarget = target;
769             setDuration(180);
770             start();
771         }
772 
get()773         public int get() {
774             return mCurrent;
775         }
776 
getTarget()777         public int getTarget() {
778             return mTarget;
779         }
780 
781         @Override
onCalculate(float progress)782         protected void onCalculate(float progress) {
783             mCurrent = Math.round(mFrom + progress * (mTarget - mFrom));
784             if (progress == 1f) mEnabled = false;
785         }
786     }
787 }
788