1f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin/*
2f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Copyright (C) 2010 The Android Open Source Project
3f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
4f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Licensed under the Apache License, Version 2.0 (the "License");
5f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * you may not use this file except in compliance with the License.
6f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * You may obtain a copy of the License at
7f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
8f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *      http://www.apache.org/licenses/LICENSE-2.0
9f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin *
10f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * Unless required by applicable law or agreed to in writing, software
11f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * distributed under the License is distributed on an "AS IS" BASIS,
12f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * See the License for the specific language governing permissions and
14f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin * limitations under the License.
15f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin */
16f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
17f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpackage com.android.gallery3d.ui;
18f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
19f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.Context;
20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.graphics.Rect;
218bc5bebba780ca4a322b466b06fc909331697cf4Owen Linimport android.os.Handler;
22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.GestureDetector;
23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.MotionEvent;
24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.animation.DecelerateInterpolator;
25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
26dbf4ba12afba115888493caa34937643257e2bb1Ray Chenimport com.android.gallery3d.anim.Animation;
2748ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Linimport com.android.gallery3d.app.GalleryActivity;
28dbf4ba12afba115888493caa34937643257e2bb1Ray Chenimport com.android.gallery3d.common.Utils;
29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class SlotView extends GLView {
31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @SuppressWarnings("unused")
32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final String TAG = "SlotView";
33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final boolean WIDE = true;
35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int INDEX_NONE = -1;
36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
377260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    public static final int RENDER_MORE_PASS = 1;
387260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    public static final int RENDER_MORE_FRAME = 2;
397260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public interface Listener {
41bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public void onDown(int index);
4290fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang        public void onUp(boolean followedByLongPress);
43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onSingleTapUp(int index);
44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongTap(int index);
45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onScrollPositionChanged(int position, int total);
46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static class SimpleListener implements Listener {
497260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        @Override public void onDown(int index) {}
5090fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang        @Override public void onUp(boolean followedByLongPress) {}
517260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        @Override public void onSingleTapUp(int index) {}
527260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        @Override public void onLongTap(int index) {}
537260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        @Override public void onScrollPositionChanged(int position, int total) {}
547260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    }
557260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
567260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    public static interface SlotRenderer {
577260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        public void prepareDrawing();
587260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        public void onVisibleRangeChanged(int visibleStart, int visibleEnd);
591a4bd273afe5dd11592f7625c2f19853b6f174e9Owen Lin        public void onSlotSizeChanged(int width, int height);
607260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height);
61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final GestureDetector mGestureDetector;
64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final ScrollerHelper mScroller;
65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Paper mPaper = new Paper();
66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private Listener mListener;
68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private UserInteractionListener mUIListener;
69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private boolean mMoreAnimation = false;
712341c197b0becf99422e8ad305def77df6161714Owen Lin    private SlotAnimation mAnimation = null;
72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Layout mLayout = new Layout();
73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mStartIndex = INDEX_NONE;
74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // whether the down action happened while the view is scrolling.
76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private boolean mDownInScrolling;
77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mOverscrollEffect = OVERSCROLL_3D;
788bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin    private final Handler mHandler;
79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
807260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    private SlotRenderer mRenderer;
817260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
827260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    private int[] mRequestRenderSlots = new int[16];
837260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_3D = 0;
85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_SYSTEM = 1;
86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_NONE = 2;
87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
88d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin    // to prevent allocating memory
89d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin    private final Rect mTempRect = new Rect();
90d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin
9148ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin    public SlotView(GalleryActivity activity, Spec spec) {
9248ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin        mGestureDetector = new GestureDetector(
9348ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                (Context) activity, new MyGestureListener());
9448ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin        mScroller = new ScrollerHelper((Context) activity);
9548ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin        mHandler = new SynchronizedHandler(activity.getGLRoot());
967260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        setSlotSpec(spec);
977260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    }
987260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
997260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    public void setSlotRenderer(SlotRenderer slotDrawer) {
1007260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        mRenderer = slotDrawer;
1017260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        if (mRenderer != null) {
1021a4bd273afe5dd11592f7625c2f19853b6f174e9Owen Lin            mRenderer.onSlotSizeChanged(mLayout.mSlotWidth, mLayout.mSlotHeight);
1037260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd());
1047260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        }
105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setCenterIndex(int index) {
108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotCount = mLayout.mSlotCount;
109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (index < 0 || index >= slotCount) {
110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return;
111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
112d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin        Rect rect = mLayout.getSlotRect(index, mTempRect);
113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int position = WIDE
114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                ? (rect.left + rect.right - getWidth()) / 2
115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                : (rect.top + rect.bottom - getHeight()) / 2;
116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        setScrollPosition(position);
117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void makeSlotVisible(int index) {
120d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin        Rect rect = mLayout.getSlotRect(index, mTempRect);
121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleBegin = WIDE ? mScrollX : mScrollY;
122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleLength = WIDE ? getWidth() : getHeight();
123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleEnd = visibleBegin + visibleLength;
124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotBegin = WIDE ? rect.left : rect.top;
125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotEnd = WIDE ? rect.right : rect.bottom;
126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int position = visibleBegin;
128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (visibleLength < slotEnd - slotBegin) {
129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = visibleBegin;
130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (slotBegin < visibleBegin) {
131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = slotBegin;
132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (slotEnd > visibleEnd) {
133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = slotEnd - visibleLength;
134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        setScrollPosition(position);
137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setScrollPosition(int position) {
140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        position = Utils.clamp(position, 0, mLayout.getScrollLimit());
141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller.setPosition(position);
142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(position, false);
143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1459201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    public void setSlotSpec(Spec spec) {
1469201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        mLayout.setSlotSpec(spec);
147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void addComponent(GLView view) {
151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        throw new UnsupportedOperationException();
152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (!changeSize) return;
15777dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
15877dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        // Make sure we are still at a resonable scroll position after the size
15977dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        // is changed (like orientation change). We choose to keep the center
16077dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        // visible slot still visible. This is arbitrary but reasonable.
16177dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        int visibleIndex =
16277dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLayout.setSize(r - l, b - t);
16477dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        makeSlotVisible(visibleIndex);
165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mOverscrollEffect == OVERSCROLL_3D) {
166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mPaper.setSize(r - l, b - t);
167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1702341c197b0becf99422e8ad305def77df6161714Owen Lin    public void startScatteringAnimation(RelativePosition position) {
1712341c197b0becf99422e8ad305def77df6161714Owen Lin        mAnimation = new ScatteringAnimation(position);
1722341c197b0becf99422e8ad305def77df6161714Owen Lin        mAnimation.start();
1732341c197b0becf99422e8ad305def77df6161714Owen Lin        if (mLayout.mSlotCount != 0) invalidate();
1742341c197b0becf99422e8ad305def77df6161714Owen Lin    }
1752341c197b0becf99422e8ad305def77df6161714Owen Lin
1762341c197b0becf99422e8ad305def77df6161714Owen Lin    public void startRisingAnimation() {
1772341c197b0becf99422e8ad305def77df6161714Owen Lin        mAnimation = new RisingAnimation();
1782341c197b0becf99422e8ad305def77df6161714Owen Lin        mAnimation.start();
1792341c197b0becf99422e8ad305def77df6161714Owen Lin        if (mLayout.mSlotCount != 0) invalidate();
180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private void updateScrollPosition(int position, boolean force) {
183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (WIDE) {
185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollX = position;
186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollY = position;
188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLayout.setScrollPosition(position);
190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        onScrollPositionChanged(position);
191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onScrollPositionChanged(int newPosition) {
194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int limit = mLayout.getScrollLimit();
195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mListener.onScrollPositionChanged(newPosition, limit);
196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public Rect getSlotRect(int slotIndex) {
199d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin        return mLayout.getSlotRect(slotIndex, new Rect());
200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected boolean onTouch(MotionEvent event) {
204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mUIListener != null) mUIListener.onUserInteraction();
205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mGestureDetector.onTouchEvent(event);
206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        switch (event.getAction()) {
207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case MotionEvent.ACTION_DOWN:
208f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mDownInScrolling = !mScroller.isFinished();
209f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mScroller.forceFinished();
210f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                break;
21177dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            case MotionEvent.ACTION_UP:
21277dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                mPaper.onRelease();
21377dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                invalidate();
21477dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                break;
215f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
216f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return true;
217f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
218f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
219f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setListener(Listener listener) {
220f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mListener = listener;
221f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
222f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
223f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setUserInteractionListener(UserInteractionListener listener) {
224f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mUIListener = listener;
225f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
226f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
227f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setOverscrollEffect(int kind) {
228f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mOverscrollEffect = kind;
229f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
230f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
231f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2327260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    private static int[] expandIntArray(int array[], int capacity) {
2337260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        while (array.length < capacity) {
2347260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            array = new int[array.length * 2];
2357260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        }
2367260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        return array;
2377260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    }
2387260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
239f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void render(GLCanvas canvas) {
241f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        super.render(canvas);
242f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2437260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        if (mRenderer == null) return;
2447260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        mRenderer.prepareDrawing();
2457260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
2467d19f7f4281f232b9dceee4a5df390c03e2bd16bChih-Chung Chang        long animTime = AnimationTime.get();
2477d19f7f4281f232b9dceee4a5df390c03e2bd16bChih-Chung Chang        boolean more = mScroller.advanceAnimation(animTime);
24883f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        more |= mLayout.advanceAnimation(animTime);
24977dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        int oldX = mScrollX;
250f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(mScroller.getPosition(), false);
25177dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
25277dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        boolean paperActive = false;
25377dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        if (mOverscrollEffect == OVERSCROLL_3D) {
25477dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            // Check if an edge is reached and notify mPaper if so.
25577dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            int newX = mScrollX;
25677dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            int limit = mLayout.getScrollLimit();
25777dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) {
25877dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                float v = mScroller.getCurrVelocity();
25977dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                if (newX == limit) v = -v;
260da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang
261da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                // I don't know why, but getCurrVelocity() can return NaN.
262da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                if (!Float.isNaN(v)) {
263da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                    mPaper.edgeReached(v);
264da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                }
26577dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            }
26677dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            paperActive = mPaper.advanceAnimation();
26777dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        }
26877dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
26977dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        more |= paperActive;
27077dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
271f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mAnimation != null) {
2727d19f7f4281f232b9dceee4a5df390c03e2bd16bChih-Chung Chang            more |= mAnimation.calculate(animTime);
273f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
274f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2757260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        canvas.translate(-mScrollX, -mScrollY);
276f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2777260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        int requestCount = 0;
2787260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        int requestedSlot[] = expandIntArray(mRequestRenderSlots,
2797260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin                mLayout.mVisibleEnd - mLayout.mVisibleStart);
2807260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin
2812341c197b0becf99422e8ad305def77df6161714Owen Lin        for (int i = mLayout.mVisibleEnd - 1; i >= mLayout.mVisibleStart; --i) {
2822341c197b0becf99422e8ad305def77df6161714Owen Lin            int r = renderItem(canvas, i, 0, paperActive);
2837260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            if ((r & RENDER_MORE_FRAME) != 0) more = true;
2847260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i;
285f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
286f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2877260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        for (int pass = 1; requestCount != 0; ++pass) {
2887260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            int newCount = 0;
2897260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            for (int i = 0; i < requestCount; ++i) {
2907260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin                int r = renderItem(canvas,
2912341c197b0becf99422e8ad305def77df6161714Owen Lin                        requestedSlot[i], pass, paperActive);
2927260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin                if ((r & RENDER_MORE_FRAME) != 0) more = true;
2937260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin                if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i;
294f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
2957260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            requestCount = newCount;
296f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
297f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
2987260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        canvas.translate(mScrollX, mScrollY);
299f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
300f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (more) invalidate();
3018bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin
3028bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin        final UserInteractionListener listener = mUIListener;
3038bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin        if (mMoreAnimation && !more && listener != null) {
3048bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin            mHandler.post(new Runnable() {
3058bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                @Override
3068bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                public void run() {
3078bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                    listener.onUserInteractionEnd();
3088bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                }
3098bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin            });
310f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
311f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mMoreAnimation = more;
312f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
313f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
3142341c197b0becf99422e8ad305def77df6161714Owen Lin    private int renderItem(
3152341c197b0becf99422e8ad305def77df6161714Owen Lin            GLCanvas canvas, int index, int pass, boolean paperActive) {
316f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
317d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin        Rect rect = mLayout.getSlotRect(index, mTempRect);
318f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (paperActive) {
3197260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
320f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
3217260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            canvas.translate(rect.left, rect.top, 0);
322174cac8f92029fc2829c94f274e70793ae948931Chih-Chung Chang        }
3232341c197b0becf99422e8ad305def77df6161714Owen Lin        if (mAnimation != null && mAnimation.isActive()) {
3242341c197b0becf99422e8ad305def77df6161714Owen Lin            mAnimation.apply(canvas, index, rect);
3252341c197b0becf99422e8ad305def77df6161714Owen Lin        }
3267260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        int result = mRenderer.renderSlot(
3277260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin                canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top);
328f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.restore();
3297260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin        return result;
330f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
331f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
3322341c197b0becf99422e8ad305def77df6161714Owen Lin    public static abstract class SlotAnimation extends Animation {
3332341c197b0becf99422e8ad305def77df6161714Owen Lin        protected float mProgress = 0;
334f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
3352341c197b0becf99422e8ad305def77df6161714Owen Lin        public SlotAnimation() {
336f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setInterpolator(new DecelerateInterpolator(4));
337f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setDuration(1500);
338f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
339f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
340f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
341f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        protected void onCalculate(float progress) {
3422341c197b0becf99422e8ad305def77df6161714Owen Lin            mProgress = progress;
3432341c197b0becf99422e8ad305def77df6161714Owen Lin        }
3442341c197b0becf99422e8ad305def77df6161714Owen Lin
3452341c197b0becf99422e8ad305def77df6161714Owen Lin        abstract public void apply(GLCanvas canvas, int slotIndex, Rect target);
3462341c197b0becf99422e8ad305def77df6161714Owen Lin    }
3472341c197b0becf99422e8ad305def77df6161714Owen Lin
3482341c197b0becf99422e8ad305def77df6161714Owen Lin    public static class RisingAnimation extends SlotAnimation {
3492341c197b0becf99422e8ad305def77df6161714Owen Lin        private static final int RISING_DISTANCE = 128;
3502341c197b0becf99422e8ad305def77df6161714Owen Lin
3512341c197b0becf99422e8ad305def77df6161714Owen Lin        @Override
3522341c197b0becf99422e8ad305def77df6161714Owen Lin        public void apply(GLCanvas canvas, int slotIndex, Rect target) {
3532341c197b0becf99422e8ad305def77df6161714Owen Lin            canvas.translate(0, 0, RISING_DISTANCE * (1 - mProgress));
3542341c197b0becf99422e8ad305def77df6161714Owen Lin        }
3552341c197b0becf99422e8ad305def77df6161714Owen Lin    }
3562341c197b0becf99422e8ad305def77df6161714Owen Lin
3572341c197b0becf99422e8ad305def77df6161714Owen Lin    public static class ScatteringAnimation extends SlotAnimation {
3582341c197b0becf99422e8ad305def77df6161714Owen Lin        private int PHOTO_DISTANCE = 1000;
3592341c197b0becf99422e8ad305def77df6161714Owen Lin        private RelativePosition mCenter;
3602341c197b0becf99422e8ad305def77df6161714Owen Lin
3612341c197b0becf99422e8ad305def77df6161714Owen Lin        public ScatteringAnimation(RelativePosition center) {
3622341c197b0becf99422e8ad305def77df6161714Owen Lin            mCenter = center;
3632341c197b0becf99422e8ad305def77df6161714Owen Lin        }
3642341c197b0becf99422e8ad305def77df6161714Owen Lin
3652341c197b0becf99422e8ad305def77df6161714Owen Lin        @Override
3662341c197b0becf99422e8ad305def77df6161714Owen Lin        public void apply(GLCanvas canvas, int slotIndex, Rect target) {
3672341c197b0becf99422e8ad305def77df6161714Owen Lin            canvas.translate(
3682341c197b0becf99422e8ad305def77df6161714Owen Lin                    (mCenter.getX() - target.centerX()) * (1 - mProgress),
3692341c197b0becf99422e8ad305def77df6161714Owen Lin                    (mCenter.getY() - target.centerY()) * (1 - mProgress),
3702341c197b0becf99422e8ad305def77df6161714Owen Lin                    slotIndex * PHOTO_DISTANCE * (1 - mProgress));
3712341c197b0becf99422e8ad305def77df6161714Owen Lin            canvas.setAlpha(mProgress);
3722341c197b0becf99422e8ad305def77df6161714Owen Lin        }
3732341c197b0becf99422e8ad305def77df6161714Owen Lin    }
3742341c197b0becf99422e8ad305def77df6161714Owen Lin
3759201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // This Spec class is used to specify the size of each slot in the SlotView.
3769201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // There are two ways to do it:
3779201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //
3789201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // (1) Specify slotWidth and slotHeight: they specify the width and height
3799201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     of each slot. The number of rows and the gap between slots will be
3809201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     determined automatically.
3819201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number
3829201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     of rows in landscape/portrait mode and the gap between slots. The
3839201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     width and height of each slot is determined automatically.
3849201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //
3859201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // The initial value of -1 means they are not specified.
3869201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    public static class Spec {
3879201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int slotWidth = -1;
3889201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int slotHeight = -1;
3899201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
3909201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int rowsLand = -1;
3919201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int rowsPort = -1;
3929201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int slotGap = -1;
3939201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    }
3949201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
3957260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin    public class Layout {
396f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
397f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVisibleStart;
398f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVisibleEnd;
399f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
400f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotCount;
401f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotWidth;
402f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotHeight;
4039201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        private int mSlotGap;
4049201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
4059201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        private Spec mSpec;
406f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
407f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mWidth;
408f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mHeight;
409f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
410f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mUnitCount;
411f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mContentLength;
412f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mScrollPosition;
413f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
41483f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        private IntegerAnimation mVerticalPadding = new IntegerAnimation();
41583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        private IntegerAnimation mHorizontalPadding = new IntegerAnimation();
416f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
4179201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public void setSlotSpec(Spec spec) {
4189201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            mSpec = spec;
419f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
420f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
421f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean setSlotCount(int slotCount) {
42283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            if (slotCount == mSlotCount) return false;
42383f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            if (mSlotCount != 0) {
42483f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                mHorizontalPadding.setEnabled(true);
42583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                mVerticalPadding.setEnabled(true);
42683f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            }
427f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mSlotCount = slotCount;
42883f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            int hPadding = mHorizontalPadding.getTarget();
42983f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            int vPadding = mVerticalPadding.getTarget();
430f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            initLayoutParameters();
43183f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            return vPadding != mVerticalPadding.getTarget()
43283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                    || hPadding != mHorizontalPadding.getTarget();
433f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
434f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
435d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin        public Rect getSlotRect(int index, Rect rect) {
436f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int col, row;
437f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
438f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                col = index / mUnitCount;
439f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                row = index - col * mUnitCount;
440f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
441f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                row = index / mUnitCount;
442f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                col = index - row * mUnitCount;
443f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
444f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
44583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            int x = mHorizontalPadding.get() + col * (mSlotWidth + mSlotGap);
44683f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            int y = mVerticalPadding.get() + row * (mSlotHeight + mSlotGap);
447d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin            rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
448d8fb81f601830385a2343d08ad5dd171e4c7bfe0Owen Lin            return rect;
449f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
450f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
4519201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int getSlotWidth() {
4529201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            return mSlotWidth;
4539201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        }
4549201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
4559201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int getSlotHeight() {
4569201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            return mSlotHeight;
4579201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        }
4589201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
459f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // Calculate
460f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (1) mUnitCount: the number of slots we can fit into one column (or row).
461f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (2) mContentLength: the width (or height) we need to display all the
462f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     columns (rows).
463f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (3) padding[]: the vertical and horizontal padding we need in order
464f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     to put the slots towards to the center of the display.
465f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //
466f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // The "major" direction is the direction the user can scroll. The other
467f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // direction is the "minor" direction.
468f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //
469f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // The comments inside this method are the description when the major
470f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // directon is horizontal (X), and the minor directon is vertical (Y).
471f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void initLayoutParameters(
472f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int majorLength, int minorLength,  /* The view width and height */
473f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int majorUnitSize, int minorUnitSize,  /* The slot width and height */
474f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int[] padding) {
4759201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap);
476f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (unitCount == 0) unitCount = 1;
477f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mUnitCount = unitCount;
478f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
479f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // We put extra padding above and below the column.
480f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int availableUnits = Math.min(mUnitCount, mSlotCount);
4819201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int usedMinorLength = availableUnits * minorUnitSize +
4829201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                    (availableUnits - 1) * mSlotGap;
4839201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            padding[0] = (minorLength - usedMinorLength) / 2;
484f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
485f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // Then calculate how many columns we need for all slots.
486f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
4879201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            mContentLength = count * majorUnitSize + (count - 1) * mSlotGap;
488f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
489f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // If the content length is less then the screen width, put
490f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // extra padding in left and right.
491f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
492f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
493f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
494f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void initLayoutParameters() {
4959201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            // Initialize mSlotWidth and mSlotHeight from mSpec
4969201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            if (mSpec.slotWidth != -1) {
4979201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotGap = 0;
4989201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotWidth = mSpec.slotWidth;
4999201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotHeight = mSpec.slotHeight;
5009201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            } else {
5019201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort;
5029201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotGap = mSpec.slotGap;
5039201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows);
5049201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotWidth = mSlotHeight;
5059201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            }
5069201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
5071a4bd273afe5dd11592f7625c2f19853b6f174e9Owen Lin            if (mRenderer != null) {
5081a4bd273afe5dd11592f7625c2f19853b6f174e9Owen Lin                mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight);
5091a4bd273afe5dd11592f7625c2f19853b6f174e9Owen Lin            }
5101a4bd273afe5dd11592f7625c2f19853b6f174e9Owen Lin
511f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int[] padding = new int[2];
512f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
513f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
51483f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                mVerticalPadding.startAnimateTo(padding[0]);
51583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                mHorizontalPadding.startAnimateTo(padding[1]);
516f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
517f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
51883f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                mVerticalPadding.startAnimateTo(padding[1]);
51983f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                mHorizontalPadding.startAnimateTo(padding[0]);
520f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
521f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            updateVisibleSlotRange();
522f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
523f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
524f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void setSize(int width, int height) {
525f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mWidth = width;
526f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHeight = height;
527f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            initLayoutParameters();
528f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
529f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
530f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void updateVisibleSlotRange() {
531f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = mScrollPosition;
532f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
533f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
5349201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int startCol = position / (mSlotWidth + mSlotGap);
5359201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int start = Math.max(0, mUnitCount * startCol);
5369201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) /
5379201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                        (mSlotWidth + mSlotGap);
5389201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int end = Math.min(mSlotCount, mUnitCount * endCol);
539f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                setVisibleRange(start, end);
540f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
5419201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int startRow = position / (mSlotHeight + mSlotGap);
5429201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int start = Math.max(0, mUnitCount * startRow);
5439201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) /
5449201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                        (mSlotHeight + mSlotGap);
5459201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int end = Math.min(mSlotCount, mUnitCount * endRow);
546f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                setVisibleRange(start, end);
547f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
548f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
549f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
550f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void setScrollPosition(int position) {
551f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mScrollPosition == position) return;
552f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollPosition = position;
553f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            updateVisibleSlotRange();
554f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
555f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
556f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void setVisibleRange(int start, int end) {
557f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (start == mVisibleStart && end == mVisibleEnd) return;
558f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (start < end) {
559f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleStart = start;
560f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleEnd = end;
561f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
562f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleStart = mVisibleEnd = 0;
563f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
5647260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            if (mRenderer != null) {
5657260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin                mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd);
5667260f6f74d465520e4497b23fe56f98abb0c15a2Owen Lin            }
567f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
568f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
569f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getVisibleStart() {
570f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mVisibleStart;
571f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
572f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
573f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getVisibleEnd() {
574f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mVisibleEnd;
575f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
576f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
577f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getSlotIndexByPosition(float x, float y) {
5789201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
5799201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
5809201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
58183f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            absoluteX -= mHorizontalPadding.get();
58283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            absoluteY -= mVerticalPadding.get();
5839201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
584003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            if (absoluteX < 0 || absoluteY < 0) {
585003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang                return INDEX_NONE;
586003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            }
587003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang
5889201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int columnIdx = absoluteX / (mSlotWidth + mSlotGap);
5899201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int rowIdx = absoluteY / (mSlotHeight + mSlotGap);
5909201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
591003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            if (!WIDE && columnIdx >= mUnitCount) {
592f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                return INDEX_NONE;
593f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
594f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
595003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            if (WIDE && rowIdx >= mUnitCount) {
596f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                return INDEX_NONE;
597f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
5989201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
5999201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) {
6009201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                return INDEX_NONE;
6019201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            }
6029201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
6039201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) {
6049201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                return INDEX_NONE;
6059201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            }
6069201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
607f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int index = WIDE
608f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    ? (columnIdx * mUnitCount + rowIdx)
609f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    : (rowIdx * mUnitCount + columnIdx);
610f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
611f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return index >= mSlotCount ? INDEX_NONE : index;
612f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
613f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
614f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getScrollLimit() {
615f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
616f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return limit <= 0 ? 0 : limit;
617f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
61883f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
61983f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        public boolean advanceAnimation(long animTime) {
62083f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            // use '|' to make sure both sides will be executed
62183f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            return mVerticalPadding.calculate(animTime) | mHorizontalPadding.calculate(animTime);
62283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        }
623f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
624f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
62548ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin    private class MyGestureListener implements GestureDetector.OnGestureListener {
626bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        private boolean isDown;
627bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang
628bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        // We call the listener's onDown() when our onShowPress() is called and
629bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        // call the listener's onUp() when we receive any further event.
630bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        @Override
631bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public void onShowPress(MotionEvent e) {
63248ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin            GLRoot root = getGLRoot();
63348ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin            root.lockRenderThread();
63448ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin            try {
63548ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                if (isDown) return;
63648ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
63748ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                if (index != INDEX_NONE) {
63848ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                    isDown = true;
63948ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                    mListener.onDown(index);
64048ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                }
64148ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin            } finally {
64248ba94ae713dbf57898cfa84ae69517da50cf7a0Owen Lin                root.unlockRenderThread();
643bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            }
644bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        }
645bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang
64690fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang        private void cancelDown(boolean byLongPress) {
647bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            if (!isDown) return;
648bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            isDown = false;
64990fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang            mListener.onUp(byLongPress);
650bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        }
651bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang
652bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        @Override
653bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public boolean onDown(MotionEvent e) {
654bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            return false;
655bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        }
656f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
657f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
658f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onFling(MotionEvent e1,
659f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                MotionEvent e2, float velocityX, float velocityY) {
66090fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang            cancelDown(false);
661f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int scrollLimit = mLayout.getScrollLimit();
662f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (scrollLimit == 0) return false;
663f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float velocity = WIDE ? velocityX : velocityY;
664f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScroller.fling((int) -velocity, 0, scrollLimit);
665f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mUIListener != null) mUIListener.onUserInteractionBegin();
666f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            invalidate();
667f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
668f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
669f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
670f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
671f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onScroll(MotionEvent e1,
672f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                MotionEvent e2, float distanceX, float distanceY) {
67390fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang            cancelDown(false);
674f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float distance = WIDE ? distanceX : distanceY;
67577dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            int overDistance = mScroller.startScroll(
676f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    Math.round(distance), 0, mLayout.getScrollLimit());
67777dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) {
67877dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                mPaper.overScroll(overDistance);
679f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
680f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            invalidate();
681f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
682f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
683f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
684f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
685f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onSingleTapUp(MotionEvent e) {
68690fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang            cancelDown(false);
687f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mDownInScrolling) return true;
688f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
689f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (index != INDEX_NONE) mListener.onSingleTapUp(index);
690f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
691f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
692f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
693f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
694f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongPress(MotionEvent e) {
69590fe70f11b0961e1e5ae353cdf563236a55f502dYuli Huang            cancelDown(true);
696f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mDownInScrolling) return;
697f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            lockRendering();
698f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            try {
699f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
700f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                if (index != INDEX_NONE) mListener.onLongTap(index);
701f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } finally {
702f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                unlockRendering();
703f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
704f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
705f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
706f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
707f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setStartIndex(int index) {
708f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mStartIndex = index;
709f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
710f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
711f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Return true if the layout parameters have been changed
712f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public boolean setSlotCount(int slotCount) {
713f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean changed = mLayout.setSlotCount(slotCount);
714f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
715f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // mStartIndex is applied the first time setSlotCount is called.
716f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mStartIndex != INDEX_NONE) {
717f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setCenterIndex(mStartIndex);
718f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mStartIndex = INDEX_NONE;
719f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
720189e21a31f1c520b2802f734a53a37e5f3b455f4Yuli Huang        // Reset the scroll position to avoid scrolling over the updated limit.
721189e21a31f1c520b2802f734a53a37e5f3b455f4Yuli Huang        setScrollPosition(WIDE ? mScrollX : mScrollY);
722f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return changed;
723f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
724f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
725f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getVisibleStart() {
726f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getVisibleStart();
727f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
728f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
729f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getVisibleEnd() {
730f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getVisibleEnd();
731f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
732f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
733f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getScrollX() {
734f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mScrollX;
735f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
736f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
737f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getScrollY() {
738f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mScrollY;
739f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
74083f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
74183f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin    private static class IntegerAnimation extends Animation {
74283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        private int mTarget;
74383f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        private int mCurrent = 0;
74483f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        private int mFrom = 0;
74583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        private boolean mEnabled = false;
74683f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
74783f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        public void setEnabled(boolean enabled) {
74883f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            mEnabled = enabled;
74983f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        }
75083f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
75183f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        public void startAnimateTo(int target) {
75283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            if (!mEnabled) {
75383f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                mTarget = mCurrent = target;
75483f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin                return;
75583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            }
75683f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            if (target == mTarget) return;
75783f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
75883f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            mFrom = mCurrent;
75983f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            mTarget = target;
76083f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            setDuration(180);
76183f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            start();
76283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        }
76383f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
76483f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        public int get() {
76583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            return mCurrent;
76683f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        }
76783f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
76883f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        public int getTarget() {
76983f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            return mTarget;
77083f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        }
77183f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin
77283f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        @Override
77383f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        protected void onCalculate(float progress) {
77483f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            mCurrent = Math.round(mFrom + progress * (mTarget - mFrom));
77583f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin            if (progress == 1f) mEnabled = false;
77683f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin        }
77783f88ad1c72ae24acc5e78efc93c1e09e1c70c97Owen Lin    }
778f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin}
779