SlotView.java revision 8bc5bebba780ca4a322b466b06fc909331697cf4
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;
27dbf4ba12afba115888493caa34937643257e2bb1Ray Chenimport com.android.gallery3d.common.Utils;
28dbf4ba12afba115888493caa34937643257e2bb1Ray Chenimport com.android.gallery3d.ui.PositionRepository.Position;
29dbf4ba12afba115888493caa34937643257e2bb1Ray Chenimport com.android.gallery3d.util.LinkedNode;
30dbf4ba12afba115888493caa34937643257e2bb1Ray Chen
31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.ArrayList;
32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.HashMap;
33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class SlotView extends GLView {
35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @SuppressWarnings("unused")
36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final String TAG = "SlotView";
37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final boolean WIDE = true;
39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int INDEX_NONE = -1;
41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public interface Listener {
43bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public void onDown(int index);
44bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public void onUp();
45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onSingleTapUp(int index);
46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongTap(int index);
47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onScrollPositionChanged(int position, int total);
48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static class SimpleListener implements Listener {
51bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public void onDown(int index) {}
52bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public void onUp() {}
53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onSingleTapUp(int index) {}
54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongTap(int index) {}
55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onScrollPositionChanged(int position, int total) {}
56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final GestureDetector mGestureDetector;
59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final ScrollerHelper mScroller;
60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Paper mPaper = new Paper();
61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private Listener mListener;
63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private UserInteractionListener mUIListener;
64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Use linked hash map to keep the rendering order
66dbf4ba12afba115888493caa34937643257e2bb1Ray Chen    private final HashMap<DisplayItem, ItemEntry> mItems =
67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            new HashMap<DisplayItem, ItemEntry>();
68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public LinkedNode.List<ItemEntry> mItemList = LinkedNode.newList();
70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // This is used for multipass rendering
72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private ArrayList<ItemEntry> mCurrentItems = new ArrayList<ItemEntry>();
73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private ArrayList<ItemEntry> mNextItems = new ArrayList<ItemEntry>();
74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private boolean mMoreAnimation = false;
76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private MyAnimation mAnimation = null;
77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Position mTempPosition = new Position();
78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Layout mLayout = new Layout();
79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private PositionProvider mPositions;
80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mStartIndex = INDEX_NONE;
81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // whether the down action happened while the view is scrolling.
83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private boolean mDownInScrolling;
84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mOverscrollEffect = OVERSCROLL_3D;
858bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin    private final Handler mHandler;
86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_3D = 0;
88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_SYSTEM = 1;
89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_NONE = 2;
90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public SlotView(Context context) {
92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mGestureDetector =
93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                new GestureDetector(context, new MyGestureListener());
94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller = new ScrollerHelper(context);
958bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin        mHandler = new Handler(context.getMainLooper());
96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setCenterIndex(int index) {
99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotCount = mLayout.mSlotCount;
100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (index < 0 || index >= slotCount) {
101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return;
102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Rect rect = mLayout.getSlotRect(index);
104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int position = WIDE
105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                ? (rect.left + rect.right - getWidth()) / 2
106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                : (rect.top + rect.bottom - getHeight()) / 2;
107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        setScrollPosition(position);
108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void makeSlotVisible(int index) {
111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Rect rect = mLayout.getSlotRect(index);
112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleBegin = WIDE ? mScrollX : mScrollY;
113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleLength = WIDE ? getWidth() : getHeight();
114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleEnd = visibleBegin + visibleLength;
115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotBegin = WIDE ? rect.left : rect.top;
116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotEnd = WIDE ? rect.right : rect.bottom;
117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int position = visibleBegin;
119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (visibleLength < slotEnd - slotBegin) {
120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = visibleBegin;
121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (slotBegin < visibleBegin) {
122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = slotBegin;
123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (slotEnd > visibleEnd) {
124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = slotEnd - visibleLength;
125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        setScrollPosition(position);
128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setScrollPosition(int position) {
131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        position = Utils.clamp(position, 0, mLayout.getScrollLimit());
132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller.setPosition(position);
133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(position, false);
134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
1369201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    public void setSlotSpec(Spec spec) {
1379201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        mLayout.setSlotSpec(spec);
138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void addComponent(GLView view) {
142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        throw new UnsupportedOperationException();
143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public boolean removeComponent(GLView view) {
147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        throw new UnsupportedOperationException();
148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (!changeSize) return;
15377dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
15477dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        // Make sure we are still at a resonable scroll position after the size
15577dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        // is changed (like orientation change). We choose to keep the center
15677dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        // visible slot still visible. This is arbitrary but reasonable.
15777dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        int visibleIndex =
15877dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLayout.setSize(r - l, b - t);
16077dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        makeSlotVisible(visibleIndex);
16177dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        onLayoutChanged(r - l, b - t);
163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mOverscrollEffect == OVERSCROLL_3D) {
164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mPaper.setSize(r - l, b - t);
165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onLayoutChanged(int width, int height) {
169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void startTransition(PositionProvider position) {
172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mPositions = position;
173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mAnimation = new MyAnimation();
174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mAnimation.start();
175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mItems.size() != 0) invalidate();
176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void savePositions(PositionRepository repository) {
179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        repository.clear();
180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        LinkedNode.List<ItemEntry> list = mItemList;
181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ItemEntry entry = list.getFirst();
182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Position position = new Position();
183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        while (entry != null) {
184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.set(entry.target);
185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.x -= mScrollX;
186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.y -= mScrollY;
187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            repository.putPosition(entry.item.getIdentity(), position);
188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            entry = list.nextOf(entry);
189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private void updateScrollPosition(int position, boolean force) {
193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (WIDE) {
195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollX = position;
196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollY = position;
198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLayout.setScrollPosition(position);
200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        onScrollPositionChanged(position);
201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onScrollPositionChanged(int newPosition) {
204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int limit = mLayout.getScrollLimit();
205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mListener.onScrollPositionChanged(newPosition, limit);
206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
208f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void putDisplayItem(Position target, Position base, DisplayItem item) {
2099201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        item.setBox(mLayout.getSlotWidth(), mLayout.getSlotHeight());
210f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ItemEntry entry = new ItemEntry(item, target, base);
211f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mItemList.insertLast(entry);
212f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mItems.put(item, entry);
213f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
214f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
215f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void removeDisplayItem(DisplayItem item) {
216f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ItemEntry entry = mItems.remove(item);
217f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (entry != null) entry.remove();
218f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
219f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
220f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public Rect getSlotRect(int slotIndex) {
221f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getSlotRect(slotIndex);
222f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
223f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
224f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
225f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected boolean onTouch(MotionEvent event) {
226f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mUIListener != null) mUIListener.onUserInteraction();
227f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mGestureDetector.onTouchEvent(event);
228f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        switch (event.getAction()) {
229f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case MotionEvent.ACTION_DOWN:
230f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mDownInScrolling = !mScroller.isFinished();
231f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mScroller.forceFinished();
232f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                break;
23377dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            case MotionEvent.ACTION_UP:
23477dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                mPaper.onRelease();
23577dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                invalidate();
23677dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                break;
237f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
238f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return true;
239f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
241f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setListener(Listener listener) {
242f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mListener = listener;
243f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
244f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
245f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setUserInteractionListener(UserInteractionListener listener) {
246f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mUIListener = listener;
247f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
248f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
249f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setOverscrollEffect(int kind) {
250f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mOverscrollEffect = kind;
251f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
252f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
253f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
254f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
255f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void render(GLCanvas canvas) {
256f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        super.render(canvas);
257f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
258f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        long currentTimeMillis = canvas.currentAnimationTimeMillis();
259f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean more = mScroller.advanceAnimation(currentTimeMillis);
26077dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        int oldX = mScrollX;
261f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(mScroller.getPosition(), false);
26277dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
26377dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        boolean paperActive = false;
26477dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        if (mOverscrollEffect == OVERSCROLL_3D) {
26577dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            // Check if an edge is reached and notify mPaper if so.
26677dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            int newX = mScrollX;
26777dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            int limit = mLayout.getScrollLimit();
26877dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) {
26977dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                float v = mScroller.getCurrVelocity();
27077dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                if (newX == limit) v = -v;
271da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang
272da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                // I don't know why, but getCurrVelocity() can return NaN.
273da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                if (!Float.isNaN(v)) {
274da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                    mPaper.edgeReached(v);
275da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                }
27677dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            }
27777dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            paperActive = mPaper.advanceAnimation();
27877dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        }
27977dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
28077dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang        more |= paperActive;
28177dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang
282f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        float interpolate = 1f;
283f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mAnimation != null) {
284f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            more |= mAnimation.calculate(currentTimeMillis);
285f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            interpolate = mAnimation.value;
286f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
287f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
288f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (WIDE) {
289f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(-mScrollX, 0, 0);
290f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
291f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(0, -mScrollY, 0);
292f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
293f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
294f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        LinkedNode.List<ItemEntry> list = mItemList;
295f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        for (ItemEntry entry = list.getLast(); entry != null;) {
296da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang            int r = renderItem(canvas, entry, interpolate, 0, paperActive);
297da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang            if ((r & DisplayItem.RENDER_MORE_PASS) != 0) {
298f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mCurrentItems.add(entry);
299f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
300da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang            more |= ((r & DisplayItem.RENDER_MORE_FRAME) != 0);
301f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            entry = list.previousOf(entry);
302f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
303f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
304f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int pass = 1;
305f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        while (!mCurrentItems.isEmpty()) {
306f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0, n = mCurrentItems.size(); i < n; i++) {
307f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                ItemEntry entry = mCurrentItems.get(i);
308da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                int r = renderItem(canvas, entry, interpolate, pass, paperActive);
309da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                if ((r & DisplayItem.RENDER_MORE_PASS) != 0) {
310f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    mNextItems.add(entry);
311f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                }
312da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang                more |= ((r & DisplayItem.RENDER_MORE_FRAME) != 0);
313f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
314f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mCurrentItems.clear();
315f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // swap mNextItems with mCurrentItems
316f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            ArrayList<ItemEntry> tmp = mNextItems;
317f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mNextItems = mCurrentItems;
318f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mCurrentItems = tmp;
319f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            pass += 1;
320f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
321f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
322f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (WIDE) {
323f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(mScrollX, 0, 0);
324f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
325f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(0, mScrollY, 0);
326f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
327f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
328f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (more) invalidate();
3298bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin
3308bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin        final UserInteractionListener listener = mUIListener;
3318bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin        if (mMoreAnimation && !more && listener != null) {
3328bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin            mHandler.post(new Runnable() {
3338bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                @Override
3348bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                public void run() {
3358bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                    listener.onUserInteractionEnd();
3368bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin                }
3378bc5bebba780ca4a322b466b06fc909331697cf4Owen Lin            });
338f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
339f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mMoreAnimation = more;
340f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
341f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
342da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang    private int renderItem(GLCanvas canvas, ItemEntry entry,
343f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float interpolate, int pass, boolean paperActive) {
344f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
345f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Position position = entry.target;
346f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mPositions != null) {
347f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = mTempPosition;
348f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.set(entry.target);
349f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.x -= mScrollX;
350f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.y -= mScrollY;
351f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            Position source = mPositions
352f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    .getPosition(entry.item.getIdentity(), position);
353f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            source.x += mScrollX;
354f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            source.y += mScrollY;
355f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = mTempPosition;
356f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            Position.interpolate(
357f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    source, entry.target, position, interpolate);
358f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
359f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.multiplyAlpha(position.alpha);
360f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (paperActive) {
361f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.multiplyMatrix(mPaper.getTransform(
362f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    position, entry.base, mScrollX, mScrollY), 0);
363f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
364f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(position.x, position.y, position.z);
365f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
366f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.rotate(position.theta, 0, 0, 1);
367da071d27a1435cce080b5c609d0d833555e5a175Chih-Chung Chang        int more = entry.item.render(canvas, pass);
368f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.restore();
369f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return more;
370f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
371f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
372f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static class MyAnimation extends Animation {
373f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public float value;
374f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
375f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public MyAnimation() {
376f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setInterpolator(new DecelerateInterpolator(4));
377f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setDuration(1500);
378f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
379f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
380f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
381f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        protected void onCalculate(float progress) {
382f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            value = progress;
383f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
384f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
385f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
386f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static class ItemEntry extends LinkedNode {
387f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public DisplayItem item;
388f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public Position target;
389f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public Position base;
390f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
391f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public ItemEntry(DisplayItem item, Position target, Position base) {
392f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            this.item = item;
393f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            this.target = target;
394f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            this.base = base;
395f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
396f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
397f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
3989201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // This Spec class is used to specify the size of each slot in the SlotView.
3999201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // There are two ways to do it:
4009201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //
4019201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // (1) Specify slotWidth and slotHeight: they specify the width and height
4029201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     of each slot. The number of rows and the gap between slots will be
4039201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     determined automatically.
4049201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number
4059201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     of rows in landscape/portrait mode and the gap between slots. The
4069201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //     width and height of each slot is determined automatically.
4079201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    //
4089201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    // The initial value of -1 means they are not specified.
4099201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    public static class Spec {
4109201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int slotWidth = -1;
4119201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int slotHeight = -1;
4129201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
4139201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int rowsLand = -1;
4149201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int rowsPort = -1;
4159201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int slotGap = -1;
4169201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
4179201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        static Spec newWithSize(int width, int height) {
4189201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            Spec s = new Spec();
4199201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            s.slotWidth = width;
4209201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            s.slotHeight = height;
4219201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            return s;
4229201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        }
4239201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
4249201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        static Spec newWithRows(int rowsLand, int rowsPort, int slotGap) {
4259201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            Spec s = new Spec();
4269201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            s.rowsLand = rowsLand;
4279201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            s.rowsPort = rowsPort;
4289201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            s.slotGap = slotGap;
4299201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            return s;
4309201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        }
4319201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang    }
4329201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
433f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static class Layout {
434f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
435f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVisibleStart;
436f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVisibleEnd;
437f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
438f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotCount;
439f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotWidth;
440f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotHeight;
4419201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        private int mSlotGap;
4429201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
4439201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        private Spec mSpec;
444f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
445f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mWidth;
446f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mHeight;
447f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
448f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mUnitCount;
449f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mContentLength;
450f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mScrollPosition;
451f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
452f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVerticalPadding;
453f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mHorizontalPadding;
454f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
4559201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public void setSlotSpec(Spec spec) {
4569201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            mSpec = spec;
457f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
458f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
459f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean setSlotCount(int slotCount) {
460f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mSlotCount = slotCount;
461f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int hPadding = mHorizontalPadding;
462f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int vPadding = mVerticalPadding;
463f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            initLayoutParameters();
464f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
465f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
466f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
467f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public Rect getSlotRect(int index) {
468f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int col, row;
469f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
470f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                col = index / mUnitCount;
471f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                row = index - col * mUnitCount;
472f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
473f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                row = index / mUnitCount;
474f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                col = index - row * mUnitCount;
475f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
476f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
4779201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap);
4789201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int y = mVerticalPadding + row * (mSlotHeight + mSlotGap);
479f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
480f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
481f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
4829201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int getSlotWidth() {
4839201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            return mSlotWidth;
4849201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        }
4859201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
4869201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        public int getSlotHeight() {
4879201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            return mSlotHeight;
4889201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang        }
4899201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
490f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getContentLength() {
491f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mContentLength;
492f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
493f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
494f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // Calculate
495f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (1) mUnitCount: the number of slots we can fit into one column (or row).
496f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (2) mContentLength: the width (or height) we need to display all the
497f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     columns (rows).
498f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (3) padding[]: the vertical and horizontal padding we need in order
499f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     to put the slots towards to the center of the display.
500f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //
501f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // The "major" direction is the direction the user can scroll. The other
502f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // direction is the "minor" direction.
503f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //
504f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // The comments inside this method are the description when the major
505f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // directon is horizontal (X), and the minor directon is vertical (Y).
506f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void initLayoutParameters(
507f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int majorLength, int minorLength,  /* The view width and height */
508f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int majorUnitSize, int minorUnitSize,  /* The slot width and height */
509f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int[] padding) {
5109201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap);
511f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (unitCount == 0) unitCount = 1;
512f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mUnitCount = unitCount;
513f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
514f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // We put extra padding above and below the column.
515f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int availableUnits = Math.min(mUnitCount, mSlotCount);
5169201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int usedMinorLength = availableUnits * minorUnitSize +
5179201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                    (availableUnits - 1) * mSlotGap;
5189201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            padding[0] = (minorLength - usedMinorLength) / 2;
519f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
520f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // Then calculate how many columns we need for all slots.
521f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
5229201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            mContentLength = count * majorUnitSize + (count - 1) * mSlotGap;
523f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
524f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // If the content length is less then the screen width, put
525f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // extra padding in left and right.
526f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
527f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
528f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
529f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void initLayoutParameters() {
5309201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            // Initialize mSlotWidth and mSlotHeight from mSpec
5319201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            if (mSpec.slotWidth != -1) {
5329201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotGap = 0;
5339201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotWidth = mSpec.slotWidth;
5349201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotHeight = mSpec.slotHeight;
5359201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            } else {
5369201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort;
5379201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotGap = mSpec.slotGap;
5389201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows);
5399201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                mSlotWidth = mSlotHeight;
5409201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            }
5419201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
542f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int[] padding = new int[2];
543f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
544f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
545f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVerticalPadding = padding[0];
546f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mHorizontalPadding = padding[1];
547f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
548f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
549f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVerticalPadding = padding[1];
550f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mHorizontalPadding = padding[0];
551f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
552f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            updateVisibleSlotRange();
553f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
554f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
555f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void setSize(int width, int height) {
556f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mWidth = width;
557f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHeight = height;
558f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            initLayoutParameters();
559f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
560f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
561f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void updateVisibleSlotRange() {
562f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = mScrollPosition;
563f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
564f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
5659201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int startCol = position / (mSlotWidth + mSlotGap);
5669201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int start = Math.max(0, mUnitCount * startCol);
5679201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) /
5689201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                        (mSlotWidth + mSlotGap);
5699201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int end = Math.min(mSlotCount, mUnitCount * endCol);
570f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                setVisibleRange(start, end);
571f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
5729201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int startRow = position / (mSlotHeight + mSlotGap);
5739201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int start = Math.max(0, mUnitCount * startRow);
5749201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) /
5759201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                        (mSlotHeight + mSlotGap);
5769201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                int end = Math.min(mSlotCount, mUnitCount * endRow);
577f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                setVisibleRange(start, end);
578f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
579f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
580f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
581f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void setScrollPosition(int position) {
582f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mScrollPosition == position) return;
583f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollPosition = position;
584f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            updateVisibleSlotRange();
585f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
586f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
587f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void setVisibleRange(int start, int end) {
588f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (start == mVisibleStart && end == mVisibleEnd) return;
589f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (start < end) {
590f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleStart = start;
591f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleEnd = end;
592f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
593f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleStart = mVisibleEnd = 0;
594f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
595f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
596f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
597f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getVisibleStart() {
598f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mVisibleStart;
599f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
600f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
601f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getVisibleEnd() {
602f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mVisibleEnd;
603f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
604f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
605f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getSlotIndexByPosition(float x, float y) {
6069201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
6079201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
6089201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
609f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            absoluteX -= mHorizontalPadding;
6109201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            absoluteY -= mVerticalPadding;
6119201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
612003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            if (absoluteX < 0 || absoluteY < 0) {
613003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang                return INDEX_NONE;
614003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            }
615003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang
6169201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int columnIdx = absoluteX / (mSlotWidth + mSlotGap);
6179201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            int rowIdx = absoluteY / (mSlotHeight + mSlotGap);
6189201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
619003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            if (!WIDE && columnIdx >= mUnitCount) {
620f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                return INDEX_NONE;
621f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
622f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
623003328e9d121b31fec76e3599300931bb356e1bdChih-Chung Chang            if (WIDE && rowIdx >= mUnitCount) {
624f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                return INDEX_NONE;
625f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
6269201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
6279201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) {
6289201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                return INDEX_NONE;
6299201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            }
6309201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
6319201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) {
6329201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang                return INDEX_NONE;
6339201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang            }
6349201679ed1c485767f2e334aa618bd733024af03Chih-Chung Chang
635f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int index = WIDE
636f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    ? (columnIdx * mUnitCount + rowIdx)
637f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    : (rowIdx * mUnitCount + columnIdx);
638f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
639f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return index >= mSlotCount ? INDEX_NONE : index;
640f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
641f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
642f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getScrollLimit() {
643f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
644f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return limit <= 0 ? 0 : limit;
645f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
646f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
647f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
648bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang    private class MyGestureListener implements
649bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            GestureDetector.OnGestureListener {
650bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        private boolean isDown;
651bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang
652bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        // We call the listener's onDown() when our onShowPress() is called and
653bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        // call the listener's onUp() when we receive any further event.
654bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        @Override
655bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public void onShowPress(MotionEvent e) {
656bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            if (isDown) return;
657bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
658bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            if (index != INDEX_NONE) {
659bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang                isDown = true;
660bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang                mListener.onDown(index);
661bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            }
662bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        }
663bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang
664bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        private void cancelDown() {
665bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            if (!isDown) return;
666bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            isDown = false;
667bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            mListener.onUp();
668bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        }
669bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang
670bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        @Override
671bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        public boolean onDown(MotionEvent e) {
672bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            return false;
673bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang        }
674f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
675f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
676f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onFling(MotionEvent e1,
677f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                MotionEvent e2, float velocityX, float velocityY) {
678bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            cancelDown();
679f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int scrollLimit = mLayout.getScrollLimit();
680f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (scrollLimit == 0) return false;
681f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float velocity = WIDE ? velocityX : velocityY;
682f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScroller.fling((int) -velocity, 0, scrollLimit);
683f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mUIListener != null) mUIListener.onUserInteractionBegin();
684f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            invalidate();
685f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
686f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
687f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
688f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
689f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onScroll(MotionEvent e1,
690f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                MotionEvent e2, float distanceX, float distanceY) {
691bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            cancelDown();
692f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float distance = WIDE ? distanceX : distanceY;
69377dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            int overDistance = mScroller.startScroll(
694f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    Math.round(distance), 0, mLayout.getScrollLimit());
69577dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang            if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) {
69677dcf07756dcf918c56afb0a8f7605577a7ce8e0Chih-Chung Chang                mPaper.overScroll(overDistance);
697f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
698f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            invalidate();
699f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
700f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
701f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
702f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
703f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onSingleTapUp(MotionEvent e) {
704bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            cancelDown();
705f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mDownInScrolling) return true;
706f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
707f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (index != INDEX_NONE) mListener.onSingleTapUp(index);
708f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
709f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
710f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
711f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
712f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongPress(MotionEvent e) {
713bc2154199aee16762e3758ad0cfed685323d9a9fChih-Chung Chang            cancelDown();
714f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mDownInScrolling) return;
715f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            lockRendering();
716f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            try {
717f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
718f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                if (index != INDEX_NONE) mListener.onLongTap(index);
719f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } finally {
720f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                unlockRendering();
721f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
722f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
723f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
724f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
725f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setStartIndex(int index) {
726f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mStartIndex = index;
727f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
728f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
729f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Return true if the layout parameters have been changed
730f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public boolean setSlotCount(int slotCount) {
731f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean changed = mLayout.setSlotCount(slotCount);
732f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
733f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // mStartIndex is applied the first time setSlotCount is called.
734f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mStartIndex != INDEX_NONE) {
735f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setCenterIndex(mStartIndex);
736f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mStartIndex = INDEX_NONE;
737f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
738f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(WIDE ? mScrollX : mScrollY, true);
739f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return changed;
740f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
741f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
742f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getVisibleStart() {
743f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getVisibleStart();
744f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
745f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
746f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getVisibleEnd() {
747f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getVisibleEnd();
748f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
749f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
750f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getScrollX() {
751f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mScrollX;
752f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
753f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
754f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getScrollY() {
755f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mScrollY;
756f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
757f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin}
758