SlotView.java revision f9a0a4306d589b4a4e20554fed512a603426bfa1
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 com.android.gallery3d.anim.Animation;
20f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.common.Utils;
21f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.ui.PositionRepository.Position;
22f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport com.android.gallery3d.util.LinkedNode;
23f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
24f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.content.Context;
25f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.graphics.Rect;
26f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.GestureDetector;
27f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.MotionEvent;
28f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport android.view.animation.DecelerateInterpolator;
29f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
30f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.ArrayList;
31f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linimport java.util.HashMap;
32f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
33f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Linpublic class SlotView extends GLView {
34f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @SuppressWarnings("unused")
35f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final String TAG = "SlotView";
36f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
37f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final boolean WIDE = true;
38f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
39f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static final int INDEX_NONE = -1;
40f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
41f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public interface Listener {
42f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onSingleTapUp(int index);
43f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongTap(int index);
44f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onScrollPositionChanged(int position, int total);
45f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
46f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
47f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static class SimpleListener implements Listener {
48f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onSingleTapUp(int index) {}
49f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongTap(int index) {}
50f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onScrollPositionChanged(int position, int total) {}
51f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
52f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
53f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final GestureDetector mGestureDetector;
54f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final ScrollerHelper mScroller;
55f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Paper mPaper = new Paper();
56f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
57f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private Listener mListener;
58f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private UserInteractionListener mUIListener;
59f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
60f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Use linked hash map to keep the rendering order
61f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private HashMap<DisplayItem, ItemEntry> mItems =
62f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            new HashMap<DisplayItem, ItemEntry>();
63f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
64f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public LinkedNode.List<ItemEntry> mItemList = LinkedNode.newList();
65f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
66f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // This is used for multipass rendering
67f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private ArrayList<ItemEntry> mCurrentItems = new ArrayList<ItemEntry>();
68f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private ArrayList<ItemEntry> mNextItems = new ArrayList<ItemEntry>();
69f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
70f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private boolean mMoreAnimation = false;
71f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private MyAnimation mAnimation = null;
72f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Position mTempPosition = new Position();
73f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private final Layout mLayout = new Layout();
74f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private PositionProvider mPositions;
75f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mStartIndex = INDEX_NONE;
76f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
77f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // whether the down action happened while the view is scrolling.
78f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private boolean mDownInScrolling;
79f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private int mOverscrollEffect = OVERSCROLL_3D;
80f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
81f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_3D = 0;
82f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_SYSTEM = 1;
83f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static final int OVERSCROLL_NONE = 2;
84f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
85f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public SlotView(Context context) {
86f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mGestureDetector =
87f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                new GestureDetector(context, new MyGestureListener());
88f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller = new ScrollerHelper(context);
89f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
90f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
91f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setCenterIndex(int index) {
92f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotCount = mLayout.mSlotCount;
93f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (index < 0 || index >= slotCount) {
94f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return;
95f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
96f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Rect rect = mLayout.getSlotRect(index);
97f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int position = WIDE
98f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                ? (rect.left + rect.right - getWidth()) / 2
99f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                : (rect.top + rect.bottom - getHeight()) / 2;
100f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        setScrollPosition(position);
101f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
102f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
103f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void makeSlotVisible(int index) {
104f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Rect rect = mLayout.getSlotRect(index);
105f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleBegin = WIDE ? mScrollX : mScrollY;
106f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleLength = WIDE ? getWidth() : getHeight();
107f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int visibleEnd = visibleBegin + visibleLength;
108f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotBegin = WIDE ? rect.left : rect.top;
109f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int slotEnd = WIDE ? rect.right : rect.bottom;
110f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
111f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int position = visibleBegin;
112f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (visibleLength < slotEnd - slotBegin) {
113f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = visibleBegin;
114f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (slotBegin < visibleBegin) {
115f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = slotBegin;
116f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else if (slotEnd > visibleEnd) {
117f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = slotEnd - visibleLength;
118f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
119f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
120f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        setScrollPosition(position);
121f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
122f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
123f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setScrollPosition(int position) {
124f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        position = Utils.clamp(position, 0, mLayout.getScrollLimit());
125f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller.setPosition(position);
126f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(position, false);
127f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
128f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
129f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setSlotSize(int slotWidth, int slotHeight) {
130f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLayout.setSlotSize(slotWidth, slotHeight);
131f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
132f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
133f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
134f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void addComponent(GLView view) {
135f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        throw new UnsupportedOperationException();
136f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
137f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
138f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
139f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public boolean removeComponent(GLView view) {
140f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        throw new UnsupportedOperationException();
141f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
142f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
143f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
144f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
145f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (!changeSize) return;
146f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLayout.setSize(r - l, b - t);
147f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        onLayoutChanged(r - l, b - t);
148f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mOverscrollEffect == OVERSCROLL_3D) {
149f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mPaper.setSize(r - l, b - t);
150f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
151f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
152f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
153f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onLayoutChanged(int width, int height) {
154f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
155f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
156f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void startTransition(PositionProvider position) {
157f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mPositions = position;
158f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mAnimation = new MyAnimation();
159f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mAnimation.start();
160f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mItems.size() != 0) invalidate();
161f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
162f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
163f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void savePositions(PositionRepository repository) {
164f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        repository.clear();
165f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        LinkedNode.List<ItemEntry> list = mItemList;
166f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ItemEntry entry = list.getFirst();
167f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Position position = new Position();
168f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        while (entry != null) {
169f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.set(entry.target);
170f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.x -= mScrollX;
171f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.y -= mScrollY;
172f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            repository.putPosition(entry.item.getIdentity(), position);
173f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            entry = list.nextOf(entry);
174f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
175f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
176f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
177f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private void updateScrollPosition(int position, boolean force) {
178f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
179f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (WIDE) {
180f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollX = position;
181f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
182f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollY = position;
183f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
184f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mLayout.setScrollPosition(position);
185f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        onScrollPositionChanged(position);
186f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
187f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
188f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void onScrollPositionChanged(int newPosition) {
189f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int limit = mLayout.getScrollLimit();
190f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mListener.onScrollPositionChanged(newPosition, limit);
191f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
192f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
193f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void putDisplayItem(Position target, Position base, DisplayItem item) {
194f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ItemEntry entry = new ItemEntry(item, target, base);
195f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mItemList.insertLast(entry);
196f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mItems.put(item, entry);
197f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
198f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
199f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void removeDisplayItem(DisplayItem item) {
200f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        ItemEntry entry = mItems.remove(item);
201f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (entry != null) entry.remove();
202f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
203f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
204f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public Rect getSlotRect(int slotIndex) {
205f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getSlotRect(slotIndex);
206f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
207f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
208f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
209f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected boolean onTouch(MotionEvent event) {
210f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mUIListener != null) mUIListener.onUserInteraction();
211f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mGestureDetector.onTouchEvent(event);
212f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        switch (event.getAction()) {
213f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            case MotionEvent.ACTION_DOWN:
214f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mDownInScrolling = !mScroller.isFinished();
215f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mScroller.forceFinished();
216f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                break;
217f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
218f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return true;
219f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
220f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
221f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setListener(Listener listener) {
222f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mListener = listener;
223f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
224f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
225f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setUserInteractionListener(UserInteractionListener listener) {
226f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mUIListener = listener;
227f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
228f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
229f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setOverscrollEffect(int kind) {
230f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mOverscrollEffect = kind;
231f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
232f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
233f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
234f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    @Override
235f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    protected void render(GLCanvas canvas) {
236f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.save(GLCanvas.SAVE_FLAG_CLIP);
237f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.clipRect(0, 0, getWidth(), getHeight());
238f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        super.render(canvas);
239f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
240f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        long currentTimeMillis = canvas.currentAnimationTimeMillis();
241f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean more = mScroller.advanceAnimation(currentTimeMillis);
242f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean paperActive = (mOverscrollEffect == OVERSCROLL_3D)
243f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                && mPaper.advanceAnimation(currentTimeMillis);
244f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(mScroller.getPosition(), false);
245f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        float interpolate = 1f;
246f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mAnimation != null) {
247f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            more |= mAnimation.calculate(currentTimeMillis);
248f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            interpolate = mAnimation.value;
249f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
250f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
251f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        more |= paperActive;
252f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
253f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (WIDE) {
254f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(-mScrollX, 0, 0);
255f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
256f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(0, -mScrollY, 0);
257f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
258f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
259f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        LinkedNode.List<ItemEntry> list = mItemList;
260f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        for (ItemEntry entry = list.getLast(); entry != null;) {
261f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (renderItem(canvas, entry, interpolate, 0, paperActive)) {
262f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mCurrentItems.add(entry);
263f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
264f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            entry = list.previousOf(entry);
265f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
266f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
267f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        int pass = 1;
268f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        while (!mCurrentItems.isEmpty()) {
269f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            for (int i = 0, n = mCurrentItems.size(); i < n; i++) {
270f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                ItemEntry entry = mCurrentItems.get(i);
271f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                if (renderItem(canvas, entry, interpolate, pass, paperActive)) {
272f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    mNextItems.add(entry);
273f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                }
274f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
275f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mCurrentItems.clear();
276f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // swap mNextItems with mCurrentItems
277f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            ArrayList<ItemEntry> tmp = mNextItems;
278f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mNextItems = mCurrentItems;
279f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mCurrentItems = tmp;
280f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            pass += 1;
281f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
282f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
283f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (WIDE) {
284f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(mScrollX, 0, 0);
285f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
286f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(0, mScrollY, 0);
287f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
288f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
289f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (more) invalidate();
290f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mMoreAnimation && !more && mUIListener != null) {
291f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mUIListener.onUserInteractionEnd();
292f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
293f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mMoreAnimation = more;
294f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.restore();
295f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
296f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
297f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private boolean renderItem(GLCanvas canvas, ItemEntry entry,
298f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float interpolate, int pass, boolean paperActive) {
299f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
300f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        Position position = entry.target;
301f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mPositions != null) {
302f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = mTempPosition;
303f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.set(entry.target);
304f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.x -= mScrollX;
305f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position.y -= mScrollY;
306f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            Position source = mPositions
307f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    .getPosition(entry.item.getIdentity(), position);
308f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            source.x += mScrollX;
309f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            source.y += mScrollY;
310f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            position = mTempPosition;
311f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            Position.interpolate(
312f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    source, entry.target, position, interpolate);
313f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
314f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.multiplyAlpha(position.alpha);
315f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (paperActive) {
316f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.multiplyMatrix(mPaper.getTransform(
317f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    position, entry.base, mScrollX, mScrollY), 0);
318f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        } else {
319f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            canvas.translate(position.x, position.y, position.z);
320f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
321f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.rotate(position.theta, 0, 0, 1);
322f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean more = entry.item.render(canvas, pass);
323f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        canvas.restore();
324f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return more;
325f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
326f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
327f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static class MyAnimation extends Animation {
328f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public float value;
329f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
330f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public MyAnimation() {
331f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setInterpolator(new DecelerateInterpolator(4));
332f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setDuration(1500);
333f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
334f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
335f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
336f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        protected void onCalculate(float progress) {
337f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            value = progress;
338f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
339f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
340f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
341f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private static class ItemEntry extends LinkedNode {
342f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public DisplayItem item;
343f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public Position target;
344f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public Position base;
345f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
346f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public ItemEntry(DisplayItem item, Position target, Position base) {
347f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            this.item = item;
348f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            this.target = target;
349f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            this.base = base;
350f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
351f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
352f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
353f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public static class Layout {
354f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
355f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVisibleStart;
356f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVisibleEnd;
357f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
358f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotCount;
359f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotWidth;
360f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mSlotHeight;
361f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
362f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mWidth;
363f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mHeight;
364f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
365f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mUnitCount;
366f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mContentLength;
367f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mScrollPosition;
368f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
369f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mVerticalPadding;
370f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private int mHorizontalPadding;
371f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
372f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void setSlotSize(int slotWidth, int slotHeight) {
373f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mSlotWidth = slotWidth;
374f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mSlotHeight = slotHeight;
375f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
376f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
377f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean setSlotCount(int slotCount) {
378f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mSlotCount = slotCount;
379f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int hPadding = mHorizontalPadding;
380f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int vPadding = mVerticalPadding;
381f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            initLayoutParameters();
382f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
383f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
384f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
385f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public Rect getSlotRect(int index) {
386f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int col, row;
387f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
388f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                col = index / mUnitCount;
389f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                row = index - col * mUnitCount;
390f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
391f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                row = index / mUnitCount;
392f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                col = index - row * mUnitCount;
393f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
394f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
395f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int x = mHorizontalPadding + col * mSlotWidth;
396f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int y = mVerticalPadding + row * mSlotHeight;
397f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
398f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
399f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
400f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getContentLength() {
401f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mContentLength;
402f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
403f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
404f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // Calculate
405f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (1) mUnitCount: the number of slots we can fit into one column (or row).
406f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (2) mContentLength: the width (or height) we need to display all the
407f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     columns (rows).
408f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // (3) padding[]: the vertical and horizontal padding we need in order
409f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //     to put the slots towards to the center of the display.
410f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //
411f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // The "major" direction is the direction the user can scroll. The other
412f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // direction is the "minor" direction.
413f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        //
414f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // The comments inside this method are the description when the major
415f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // directon is horizontal (X), and the minor directon is vertical (Y).
416f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void initLayoutParameters(
417f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int majorLength, int minorLength,  /* The view width and height */
418f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int majorUnitSize, int minorUnitSize,  /* The slot width and height */
419f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int[] padding) {
420f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int unitCount = minorLength / minorUnitSize;
421f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (unitCount == 0) unitCount = 1;
422f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mUnitCount = unitCount;
423f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
424f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // We put extra padding above and below the column.
425f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int availableUnits = Math.min(mUnitCount, mSlotCount);
426f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            padding[0] = (minorLength - availableUnits * minorUnitSize) / 2;
427f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
428f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // Then calculate how many columns we need for all slots.
429f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
430f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mContentLength = count * majorUnitSize;
431f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
432f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // If the content length is less then the screen width, put
433f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            // extra padding in left and right.
434f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
435f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
436f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
437f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void initLayoutParameters() {
438f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int[] padding = new int[2];
439f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
440f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
441f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVerticalPadding = padding[0];
442f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mHorizontalPadding = padding[1];
443f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
444f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
445f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVerticalPadding = padding[1];
446f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mHorizontalPadding = padding[0];
447f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
448f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            updateVisibleSlotRange();
449f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
450f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
451f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void setSize(int width, int height) {
452f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mWidth = width;
453f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mHeight = height;
454f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            initLayoutParameters();
455f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
456f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
457f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void updateVisibleSlotRange() {
458f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int position = mScrollPosition;
459f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
460f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (WIDE) {
461f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int start = Math.max(0, (position / mSlotWidth) * mUnitCount);
462f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int end = Math.min(mSlotCount, mUnitCount
463f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                        * (position + mWidth + mSlotWidth - 1) / mSlotWidth);
464f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                setVisibleRange(start, end);
465f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
466f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int start = Math.max(0, mUnitCount * (position / mSlotHeight));
467f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int end = Math.min(mSlotCount, mUnitCount
468f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                        * (position + mHeight + mSlotHeight - 1) / mSlotHeight);
469f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                setVisibleRange(start, end);
470f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
471f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
472f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
473f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void setScrollPosition(int position) {
474f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mScrollPosition == position) return;
475f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScrollPosition = position;
476f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            updateVisibleSlotRange();
477f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
478f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
479f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        private void setVisibleRange(int start, int end) {
480f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (start == mVisibleStart && end == mVisibleEnd) return;
481f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (start < end) {
482f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleStart = start;
483f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleEnd = end;
484f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } else {
485f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mVisibleStart = mVisibleEnd = 0;
486f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
487f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
488f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
489f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getVisibleStart() {
490f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mVisibleStart;
491f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
492f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
493f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getVisibleEnd() {
494f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return mVisibleEnd;
495f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
496f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
497f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getSlotIndexByPosition(float x, float y) {
498f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float absoluteX = x + (WIDE ? mScrollPosition : 0);
499f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            absoluteX -= mHorizontalPadding;
500f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int columnIdx = (int) (absoluteX + 0.5) / mSlotWidth;
501f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if ((absoluteX - mSlotWidth * columnIdx) < 0
502f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    || (!WIDE && columnIdx >= mUnitCount)) {
503f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                return INDEX_NONE;
504f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
505f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
506f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float absoluteY = y + (WIDE ? 0 : mScrollPosition);
507f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            absoluteY -= mVerticalPadding;
508f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int rowIdx = (int) (absoluteY + 0.5) / mSlotHeight;
509f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (((absoluteY - mSlotHeight * rowIdx) < 0)
510f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    || (WIDE && rowIdx >= mUnitCount)) {
511f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                return INDEX_NONE;
512f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
513f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int index = WIDE
514f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    ? (columnIdx * mUnitCount + rowIdx)
515f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    : (rowIdx * mUnitCount + columnIdx);
516f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
517f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return index >= mSlotCount ? INDEX_NONE : index;
518f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
519f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
520f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public int getScrollLimit() {
521f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
522f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return limit <= 0 ? 0 : limit;
523f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
524f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
525f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
526f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    private class MyGestureListener
527f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            extends GestureDetector.SimpleOnGestureListener {
528f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
529f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
530f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onFling(MotionEvent e1,
531f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                MotionEvent e2, float velocityX, float velocityY) {
532f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int scrollLimit = mLayout.getScrollLimit();
533f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (scrollLimit == 0) return false;
534f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float velocity = WIDE ? velocityX : velocityY;
535f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mScroller.fling((int) -velocity, 0, scrollLimit);
536f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mUIListener != null) mUIListener.onUserInteractionBegin();
537f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            invalidate();
538f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
539f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
540f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
541f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
542f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onScroll(MotionEvent e1,
543f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                MotionEvent e2, float distanceX, float distanceY) {
544f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            float distance = WIDE ? distanceX : distanceY;
545f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            boolean canMove = mScroller.startScroll(
546f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                    Math.round(distance), 0, mLayout.getScrollLimit());
547f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mOverscrollEffect == OVERSCROLL_3D && !canMove) {
548f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                mPaper.overScroll(distance);
549f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
550f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            invalidate();
551f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
552f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
553f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
554f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
555f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public boolean onSingleTapUp(MotionEvent e) {
556f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mDownInScrolling) return true;
557f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
558f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (index != INDEX_NONE) mListener.onSingleTapUp(index);
559f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            return true;
560f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
561f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
562f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        @Override
563f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        public void onLongPress(MotionEvent e) {
564f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            if (mDownInScrolling) return;
565f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            lockRendering();
566f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            try {
567f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
568f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                if (index != INDEX_NONE) mListener.onLongTap(index);
569f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            } finally {
570f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin                unlockRendering();
571f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            }
572f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
573f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
574f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
575f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public void setStartIndex(int index) {
576f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        mStartIndex = index;
577f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
578f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
579f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    // Return true if the layout parameters have been changed
580f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public boolean setSlotCount(int slotCount) {
581f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        boolean changed = mLayout.setSlotCount(slotCount);
582f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
583f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        // mStartIndex is applied the first time setSlotCount is called.
584f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        if (mStartIndex != INDEX_NONE) {
585f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            setCenterIndex(mStartIndex);
586f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin            mStartIndex = INDEX_NONE;
587f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        }
588f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        updateScrollPosition(WIDE ? mScrollX : mScrollY, true);
589f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return changed;
590f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
591f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
592f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getVisibleStart() {
593f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getVisibleStart();
594f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
595f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
596f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getVisibleEnd() {
597f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mLayout.getVisibleEnd();
598f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
599f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
600f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getScrollX() {
601f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mScrollX;
602f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
603f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin
604f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    public int getScrollY() {
605f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin        return mScrollY;
606f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin    }
607f9a0a4306d589b4a4e20554fed512a603426bfa1Owen Lin}
608