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