blob: 529a496d36f512a59ec660936e5ff0eff2fde5b9 [file] [log] [blame]
Owen Lina2fba682011-08-17 22:07:43 +08001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.ui;
18
Owen Lina2fba682011-08-17 22:07:43 +080019import android.content.Context;
20import android.graphics.Rect;
Owen Linc907c322011-11-17 15:26:56 +080021import android.os.Handler;
Owen Lina2fba682011-08-17 22:07:43 +080022import android.view.GestureDetector;
23import android.view.MotionEvent;
24import android.view.animation.DecelerateInterpolator;
25
Ray Chen73e791c2011-10-04 15:19:44 +080026import com.android.gallery3d.anim.Animation;
27import com.android.gallery3d.common.Utils;
Owen Lina2fba682011-08-17 22:07:43 +080028
29public class SlotView extends GLView {
30 @SuppressWarnings("unused")
31 private static final String TAG = "SlotView";
32
33 private static final boolean WIDE = true;
Owen Lina2fba682011-08-17 22:07:43 +080034 private static final int INDEX_NONE = -1;
35
Owen Lin83a036c2012-03-22 14:14:40 +080036 public static final int RENDER_MORE_PASS = 1;
37 public static final int RENDER_MORE_FRAME = 2;
38
Owen Lina2fba682011-08-17 22:07:43 +080039 public interface Listener {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +080040 public void onDown(int index);
41 public void onUp();
Owen Lina2fba682011-08-17 22:07:43 +080042 public void onSingleTapUp(int index);
43 public void onLongTap(int index);
44 public void onScrollPositionChanged(int position, int total);
45 }
46
47 public static class SimpleListener implements Listener {
Owen Lin83a036c2012-03-22 14:14:40 +080048 @Override public void onDown(int index) {}
49 @Override public void onUp() {}
50 @Override public void onSingleTapUp(int index) {}
51 @Override public void onLongTap(int index) {}
52 @Override public void onScrollPositionChanged(int position, int total) {}
53 }
54
55 public static interface SlotRenderer {
56 public void prepareDrawing();
57 public void onVisibleRangeChanged(int visibleStart, int visibleEnd);
58 public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height);
Owen Lina2fba682011-08-17 22:07:43 +080059 }
60
61 private final GestureDetector mGestureDetector;
62 private final ScrollerHelper mScroller;
63 private final Paper mPaper = new Paper();
64
65 private Listener mListener;
66 private UserInteractionListener mUIListener;
67
Owen Lina2fba682011-08-17 22:07:43 +080068 private boolean mMoreAnimation = false;
69 private MyAnimation mAnimation = null;
Owen Lina2fba682011-08-17 22:07:43 +080070 private final Layout mLayout = new Layout();
Owen Lina2fba682011-08-17 22:07:43 +080071 private int mStartIndex = INDEX_NONE;
72
73 // whether the down action happened while the view is scrolling.
74 private boolean mDownInScrolling;
75 private int mOverscrollEffect = OVERSCROLL_3D;
Owen Linc907c322011-11-17 15:26:56 +080076 private final Handler mHandler;
Owen Lina2fba682011-08-17 22:07:43 +080077
Owen Lin83a036c2012-03-22 14:14:40 +080078 private SlotRenderer mRenderer;
79
80 private int[] mRequestRenderSlots = new int[16];
81
Owen Lina2fba682011-08-17 22:07:43 +080082 public static final int OVERSCROLL_3D = 0;
83 public static final int OVERSCROLL_SYSTEM = 1;
84 public static final int OVERSCROLL_NONE = 2;
85
Owen Lin83a036c2012-03-22 14:14:40 +080086 public SlotView(Context context, Spec spec) {
Owen Lina2fba682011-08-17 22:07:43 +080087 mGestureDetector =
88 new GestureDetector(context, new MyGestureListener());
89 mScroller = new ScrollerHelper(context);
Owen Linc907c322011-11-17 15:26:56 +080090 mHandler = new Handler(context.getMainLooper());
Owen Lin83a036c2012-03-22 14:14:40 +080091 setSlotSpec(spec);
92 }
93
94 public void setSlotRenderer(SlotRenderer slotDrawer) {
95 mRenderer = slotDrawer;
96 if (mRenderer != null) {
97 mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd());
98 }
Owen Lina2fba682011-08-17 22:07:43 +080099 }
100
101 public void setCenterIndex(int index) {
102 int slotCount = mLayout.mSlotCount;
103 if (index < 0 || index >= slotCount) {
104 return;
105 }
106 Rect rect = mLayout.getSlotRect(index);
107 int position = WIDE
108 ? (rect.left + rect.right - getWidth()) / 2
109 : (rect.top + rect.bottom - getHeight()) / 2;
110 setScrollPosition(position);
111 }
112
113 public void makeSlotVisible(int index) {
114 Rect rect = mLayout.getSlotRect(index);
115 int visibleBegin = WIDE ? mScrollX : mScrollY;
116 int visibleLength = WIDE ? getWidth() : getHeight();
117 int visibleEnd = visibleBegin + visibleLength;
118 int slotBegin = WIDE ? rect.left : rect.top;
119 int slotEnd = WIDE ? rect.right : rect.bottom;
120
121 int position = visibleBegin;
122 if (visibleLength < slotEnd - slotBegin) {
123 position = visibleBegin;
124 } else if (slotBegin < visibleBegin) {
125 position = slotBegin;
126 } else if (slotEnd > visibleEnd) {
127 position = slotEnd - visibleLength;
128 }
129
130 setScrollPosition(position);
131 }
132
133 public void setScrollPosition(int position) {
134 position = Utils.clamp(position, 0, mLayout.getScrollLimit());
135 mScroller.setPosition(position);
136 updateScrollPosition(position, false);
137 }
138
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800139 public void setSlotSpec(Spec spec) {
140 mLayout.setSlotSpec(spec);
Owen Lina2fba682011-08-17 22:07:43 +0800141 }
142
143 @Override
144 public void addComponent(GLView view) {
145 throw new UnsupportedOperationException();
146 }
147
148 @Override
Owen Lina2fba682011-08-17 22:07:43 +0800149 protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
150 if (!changeSize) return;
Chih-Chung Changca078522011-09-26 10:40:38 +0800151
152 // Make sure we are still at a resonable scroll position after the size
153 // is changed (like orientation change). We choose to keep the center
154 // visible slot still visible. This is arbitrary but reasonable.
155 int visibleIndex =
156 (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
Owen Lina2fba682011-08-17 22:07:43 +0800157 mLayout.setSize(r - l, b - t);
Chih-Chung Changca078522011-09-26 10:40:38 +0800158 makeSlotVisible(visibleIndex);
159
Owen Lina2fba682011-08-17 22:07:43 +0800160 onLayoutChanged(r - l, b - t);
161 if (mOverscrollEffect == OVERSCROLL_3D) {
162 mPaper.setSize(r - l, b - t);
163 }
164 }
165
166 protected void onLayoutChanged(int width, int height) {
167 }
168
Owen Lin83a036c2012-03-22 14:14:40 +0800169 // TODO: Fix this regression. Transition is disabled in this change
Owen Lina2fba682011-08-17 22:07:43 +0800170 public void startTransition(PositionProvider position) {
Owen Lina2fba682011-08-17 22:07:43 +0800171 mAnimation = new MyAnimation();
172 mAnimation.start();
Owen Lin83a036c2012-03-22 14:14:40 +0800173 if (mLayout.mSlotCount != 0) invalidate();
Owen Lina2fba682011-08-17 22:07:43 +0800174 }
175
176 private void updateScrollPosition(int position, boolean force) {
177 if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
178 if (WIDE) {
179 mScrollX = position;
180 } else {
181 mScrollY = position;
182 }
183 mLayout.setScrollPosition(position);
184 onScrollPositionChanged(position);
185 }
186
187 protected void onScrollPositionChanged(int newPosition) {
188 int limit = mLayout.getScrollLimit();
189 mListener.onScrollPositionChanged(newPosition, limit);
190 }
191
Owen Lina2fba682011-08-17 22:07:43 +0800192 public Rect getSlotRect(int slotIndex) {
193 return mLayout.getSlotRect(slotIndex);
194 }
195
196 @Override
197 protected boolean onTouch(MotionEvent event) {
198 if (mUIListener != null) mUIListener.onUserInteraction();
199 mGestureDetector.onTouchEvent(event);
200 switch (event.getAction()) {
201 case MotionEvent.ACTION_DOWN:
202 mDownInScrolling = !mScroller.isFinished();
203 mScroller.forceFinished();
204 break;
Chih-Chung Changca078522011-09-26 10:40:38 +0800205 case MotionEvent.ACTION_UP:
206 mPaper.onRelease();
207 invalidate();
208 break;
Owen Lina2fba682011-08-17 22:07:43 +0800209 }
210 return true;
211 }
212
213 public void setListener(Listener listener) {
214 mListener = listener;
215 }
216
217 public void setUserInteractionListener(UserInteractionListener listener) {
218 mUIListener = listener;
219 }
220
221 public void setOverscrollEffect(int kind) {
222 mOverscrollEffect = kind;
223 mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
224 }
225
Owen Lin83a036c2012-03-22 14:14:40 +0800226 private static int[] expandIntArray(int array[], int capacity) {
227 while (array.length < capacity) {
228 array = new int[array.length * 2];
229 }
230 return array;
231 }
232
Owen Lina2fba682011-08-17 22:07:43 +0800233 @Override
234 protected void render(GLCanvas canvas) {
Owen Lina2fba682011-08-17 22:07:43 +0800235 super.render(canvas);
236
Owen Lin83a036c2012-03-22 14:14:40 +0800237 if (mRenderer == null) return;
238 mRenderer.prepareDrawing();
239
Chih-Chung Changb3d01962012-02-17 10:02:27 +0800240 long animTime = AnimationTime.get();
241 boolean more = mScroller.advanceAnimation(animTime);
Chih-Chung Changca078522011-09-26 10:40:38 +0800242 int oldX = mScrollX;
Owen Lina2fba682011-08-17 22:07:43 +0800243 updateScrollPosition(mScroller.getPosition(), false);
Chih-Chung Changca078522011-09-26 10:40:38 +0800244
245 boolean paperActive = false;
246 if (mOverscrollEffect == OVERSCROLL_3D) {
247 // Check if an edge is reached and notify mPaper if so.
248 int newX = mScrollX;
249 int limit = mLayout.getScrollLimit();
250 if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) {
251 float v = mScroller.getCurrVelocity();
252 if (newX == limit) v = -v;
Chih-Chung Chang1b2af5e2011-09-27 19:44:36 +0800253
254 // I don't know why, but getCurrVelocity() can return NaN.
255 if (!Float.isNaN(v)) {
256 mPaper.edgeReached(v);
257 }
Chih-Chung Changca078522011-09-26 10:40:38 +0800258 }
259 paperActive = mPaper.advanceAnimation();
260 }
261
262 more |= paperActive;
263
Owen Lina2fba682011-08-17 22:07:43 +0800264 float interpolate = 1f;
265 if (mAnimation != null) {
Chih-Chung Changb3d01962012-02-17 10:02:27 +0800266 more |= mAnimation.calculate(animTime);
Owen Lina2fba682011-08-17 22:07:43 +0800267 interpolate = mAnimation.value;
268 }
269
Owen Lin83a036c2012-03-22 14:14:40 +0800270 canvas.translate(-mScrollX, -mScrollY);
271
272 int requestCount = 0;
273 int requestedSlot[] = expandIntArray(mRequestRenderSlots,
274 mLayout.mVisibleEnd - mLayout.mVisibleStart);
275
276 for (int i = mLayout.mVisibleStart; i < mLayout.mVisibleEnd; ++i) {
277 int r = renderItem(canvas, i, 0, interpolate, paperActive);
278 if ((r & RENDER_MORE_FRAME) != 0) more = true;
279 if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i;
Owen Lina2fba682011-08-17 22:07:43 +0800280 }
281
Owen Lin83a036c2012-03-22 14:14:40 +0800282 for (int pass = 1; requestCount != 0; ++pass) {
283 int newCount = 0;
284 for (int i = 0; i < requestCount; ++i) {
285 int r = renderItem(canvas,
286 requestedSlot[i], pass, interpolate, paperActive);
287 if ((r & RENDER_MORE_FRAME) != 0) more = true;
288 if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i;
Owen Lina2fba682011-08-17 22:07:43 +0800289 }
Owen Lin83a036c2012-03-22 14:14:40 +0800290 requestCount = newCount;
Owen Lina2fba682011-08-17 22:07:43 +0800291 }
292
Owen Lin83a036c2012-03-22 14:14:40 +0800293 canvas.translate(mScrollX, mScrollY);
Owen Lina2fba682011-08-17 22:07:43 +0800294
295 if (more) invalidate();
Owen Linc907c322011-11-17 15:26:56 +0800296
297 final UserInteractionListener listener = mUIListener;
298 if (mMoreAnimation && !more && listener != null) {
299 mHandler.post(new Runnable() {
300 @Override
301 public void run() {
302 listener.onUserInteractionEnd();
303 }
304 });
Owen Lina2fba682011-08-17 22:07:43 +0800305 }
306 mMoreAnimation = more;
Owen Lina2fba682011-08-17 22:07:43 +0800307 }
308
Owen Lin83a036c2012-03-22 14:14:40 +0800309 private int renderItem(GLCanvas canvas,
310 int index, int pass, float interpolate, boolean paperActive) {
Owen Lina2fba682011-08-17 22:07:43 +0800311 canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
Owen Lin83a036c2012-03-22 14:14:40 +0800312 Rect rect = getSlotRect(index);
Owen Lina2fba682011-08-17 22:07:43 +0800313 if (paperActive) {
Owen Lin83a036c2012-03-22 14:14:40 +0800314 canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
Owen Lina2fba682011-08-17 22:07:43 +0800315 } else {
Owen Lin83a036c2012-03-22 14:14:40 +0800316 canvas.translate(rect.left, rect.top, 0);
Owen Lina2fba682011-08-17 22:07:43 +0800317 }
Owen Lin83a036c2012-03-22 14:14:40 +0800318 int result = mRenderer.renderSlot(
319 canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top);
Owen Lina2fba682011-08-17 22:07:43 +0800320 canvas.restore();
Owen Lin83a036c2012-03-22 14:14:40 +0800321 return result;
Owen Lina2fba682011-08-17 22:07:43 +0800322 }
323
324 public static class MyAnimation extends Animation {
325 public float value;
326
327 public MyAnimation() {
328 setInterpolator(new DecelerateInterpolator(4));
329 setDuration(1500);
330 }
331
332 @Override
333 protected void onCalculate(float progress) {
334 value = progress;
335 }
336 }
337
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800338 // This Spec class is used to specify the size of each slot in the SlotView.
339 // There are two ways to do it:
340 //
341 // (1) Specify slotWidth and slotHeight: they specify the width and height
342 // of each slot. The number of rows and the gap between slots will be
343 // determined automatically.
344 // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number
345 // of rows in landscape/portrait mode and the gap between slots. The
346 // width and height of each slot is determined automatically.
347 //
348 // The initial value of -1 means they are not specified.
349 public static class Spec {
350 public int slotWidth = -1;
351 public int slotHeight = -1;
352
353 public int rowsLand = -1;
354 public int rowsPort = -1;
355 public int slotGap = -1;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800356 }
357
Owen Lin83a036c2012-03-22 14:14:40 +0800358 public class Layout {
Owen Lina2fba682011-08-17 22:07:43 +0800359
360 private int mVisibleStart;
361 private int mVisibleEnd;
362
363 private int mSlotCount;
364 private int mSlotWidth;
365 private int mSlotHeight;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800366 private int mSlotGap;
367
368 private Spec mSpec;
Owen Lina2fba682011-08-17 22:07:43 +0800369
370 private int mWidth;
371 private int mHeight;
372
373 private int mUnitCount;
374 private int mContentLength;
375 private int mScrollPosition;
376
377 private int mVerticalPadding;
378 private int mHorizontalPadding;
379
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800380 public void setSlotSpec(Spec spec) {
381 mSpec = spec;
Owen Lina2fba682011-08-17 22:07:43 +0800382 }
383
384 public boolean setSlotCount(int slotCount) {
385 mSlotCount = slotCount;
386 int hPadding = mHorizontalPadding;
387 int vPadding = mVerticalPadding;
388 initLayoutParameters();
389 return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
390 }
391
392 public Rect getSlotRect(int index) {
393 int col, row;
394 if (WIDE) {
395 col = index / mUnitCount;
396 row = index - col * mUnitCount;
397 } else {
398 row = index / mUnitCount;
399 col = index - row * mUnitCount;
400 }
401
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800402 int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap);
403 int y = mVerticalPadding + row * (mSlotHeight + mSlotGap);
Owen Lina2fba682011-08-17 22:07:43 +0800404 return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
405 }
406
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800407 public int getSlotWidth() {
408 return mSlotWidth;
409 }
410
411 public int getSlotHeight() {
412 return mSlotHeight;
413 }
414
Owen Lina2fba682011-08-17 22:07:43 +0800415 // Calculate
416 // (1) mUnitCount: the number of slots we can fit into one column (or row).
417 // (2) mContentLength: the width (or height) we need to display all the
418 // columns (rows).
419 // (3) padding[]: the vertical and horizontal padding we need in order
420 // to put the slots towards to the center of the display.
421 //
422 // The "major" direction is the direction the user can scroll. The other
423 // direction is the "minor" direction.
424 //
425 // The comments inside this method are the description when the major
426 // directon is horizontal (X), and the minor directon is vertical (Y).
427 private void initLayoutParameters(
428 int majorLength, int minorLength, /* The view width and height */
429 int majorUnitSize, int minorUnitSize, /* The slot width and height */
430 int[] padding) {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800431 int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap);
Owen Lina2fba682011-08-17 22:07:43 +0800432 if (unitCount == 0) unitCount = 1;
433 mUnitCount = unitCount;
434
435 // We put extra padding above and below the column.
436 int availableUnits = Math.min(mUnitCount, mSlotCount);
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800437 int usedMinorLength = availableUnits * minorUnitSize +
438 (availableUnits - 1) * mSlotGap;
439 padding[0] = (minorLength - usedMinorLength) / 2;
Owen Lina2fba682011-08-17 22:07:43 +0800440
441 // Then calculate how many columns we need for all slots.
442 int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800443 mContentLength = count * majorUnitSize + (count - 1) * mSlotGap;
Owen Lina2fba682011-08-17 22:07:43 +0800444
445 // If the content length is less then the screen width, put
446 // extra padding in left and right.
447 padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
448 }
449
450 private void initLayoutParameters() {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800451 // Initialize mSlotWidth and mSlotHeight from mSpec
452 if (mSpec.slotWidth != -1) {
453 mSlotGap = 0;
454 mSlotWidth = mSpec.slotWidth;
455 mSlotHeight = mSpec.slotHeight;
456 } else {
457 int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort;
458 mSlotGap = mSpec.slotGap;
459 mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows);
460 mSlotWidth = mSlotHeight;
461 }
462
Owen Lina2fba682011-08-17 22:07:43 +0800463 int[] padding = new int[2];
464 if (WIDE) {
465 initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
466 mVerticalPadding = padding[0];
467 mHorizontalPadding = padding[1];
468 } else {
469 initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
470 mVerticalPadding = padding[1];
471 mHorizontalPadding = padding[0];
472 }
473 updateVisibleSlotRange();
474 }
475
476 public void setSize(int width, int height) {
477 mWidth = width;
478 mHeight = height;
479 initLayoutParameters();
480 }
481
482 private void updateVisibleSlotRange() {
483 int position = mScrollPosition;
484
485 if (WIDE) {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800486 int startCol = position / (mSlotWidth + mSlotGap);
487 int start = Math.max(0, mUnitCount * startCol);
488 int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) /
489 (mSlotWidth + mSlotGap);
490 int end = Math.min(mSlotCount, mUnitCount * endCol);
Owen Lina2fba682011-08-17 22:07:43 +0800491 setVisibleRange(start, end);
492 } else {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800493 int startRow = position / (mSlotHeight + mSlotGap);
494 int start = Math.max(0, mUnitCount * startRow);
495 int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) /
496 (mSlotHeight + mSlotGap);
497 int end = Math.min(mSlotCount, mUnitCount * endRow);
Owen Lina2fba682011-08-17 22:07:43 +0800498 setVisibleRange(start, end);
499 }
500 }
501
502 public void setScrollPosition(int position) {
503 if (mScrollPosition == position) return;
504 mScrollPosition = position;
505 updateVisibleSlotRange();
506 }
507
508 private void setVisibleRange(int start, int end) {
509 if (start == mVisibleStart && end == mVisibleEnd) return;
510 if (start < end) {
511 mVisibleStart = start;
512 mVisibleEnd = end;
513 } else {
514 mVisibleStart = mVisibleEnd = 0;
515 }
Owen Lin83a036c2012-03-22 14:14:40 +0800516 if (mRenderer != null) {
517 mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd);
518 }
Owen Lina2fba682011-08-17 22:07:43 +0800519 }
520
521 public int getVisibleStart() {
522 return mVisibleStart;
523 }
524
525 public int getVisibleEnd() {
526 return mVisibleEnd;
527 }
528
529 public int getSlotIndexByPosition(float x, float y) {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800530 int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
531 int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
532
Owen Lina2fba682011-08-17 22:07:43 +0800533 absoluteX -= mHorizontalPadding;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800534 absoluteY -= mVerticalPadding;
535
Chih-Chung Chang809bff62011-10-31 11:45:11 +0800536 if (absoluteX < 0 || absoluteY < 0) {
Owen Lina2fba682011-08-17 22:07:43 +0800537 return INDEX_NONE;
538 }
539
Chih-Chung Chang809bff62011-10-31 11:45:11 +0800540 int columnIdx = absoluteX / (mSlotWidth + mSlotGap);
541 int rowIdx = absoluteY / (mSlotHeight + mSlotGap);
542
543 if (!WIDE && columnIdx >= mUnitCount) {
544 return INDEX_NONE;
545 }
546
547 if (WIDE && rowIdx >= mUnitCount) {
Owen Lina2fba682011-08-17 22:07:43 +0800548 return INDEX_NONE;
549 }
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800550
551 if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) {
552 return INDEX_NONE;
553 }
554
555 if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) {
556 return INDEX_NONE;
557 }
558
Owen Lina2fba682011-08-17 22:07:43 +0800559 int index = WIDE
560 ? (columnIdx * mUnitCount + rowIdx)
561 : (rowIdx * mUnitCount + columnIdx);
562
563 return index >= mSlotCount ? INDEX_NONE : index;
564 }
565
566 public int getScrollLimit() {
567 int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
568 return limit <= 0 ? 0 : limit;
569 }
570 }
571
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800572 private class MyGestureListener implements
573 GestureDetector.OnGestureListener {
574 private boolean isDown;
575
576 // We call the listener's onDown() when our onShowPress() is called and
577 // call the listener's onUp() when we receive any further event.
578 @Override
579 public void onShowPress(MotionEvent e) {
580 if (isDown) return;
581 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
582 if (index != INDEX_NONE) {
583 isDown = true;
584 mListener.onDown(index);
585 }
586 }
587
588 private void cancelDown() {
589 if (!isDown) return;
590 isDown = false;
591 mListener.onUp();
592 }
593
594 @Override
595 public boolean onDown(MotionEvent e) {
596 return false;
597 }
Owen Lina2fba682011-08-17 22:07:43 +0800598
599 @Override
600 public boolean onFling(MotionEvent e1,
601 MotionEvent e2, float velocityX, float velocityY) {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800602 cancelDown();
Owen Lina2fba682011-08-17 22:07:43 +0800603 int scrollLimit = mLayout.getScrollLimit();
604 if (scrollLimit == 0) return false;
605 float velocity = WIDE ? velocityX : velocityY;
606 mScroller.fling((int) -velocity, 0, scrollLimit);
607 if (mUIListener != null) mUIListener.onUserInteractionBegin();
608 invalidate();
609 return true;
610 }
611
612 @Override
613 public boolean onScroll(MotionEvent e1,
614 MotionEvent e2, float distanceX, float distanceY) {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800615 cancelDown();
Owen Lina2fba682011-08-17 22:07:43 +0800616 float distance = WIDE ? distanceX : distanceY;
Chih-Chung Changca078522011-09-26 10:40:38 +0800617 int overDistance = mScroller.startScroll(
Owen Lina2fba682011-08-17 22:07:43 +0800618 Math.round(distance), 0, mLayout.getScrollLimit());
Chih-Chung Changca078522011-09-26 10:40:38 +0800619 if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) {
620 mPaper.overScroll(overDistance);
Owen Lina2fba682011-08-17 22:07:43 +0800621 }
622 invalidate();
623 return true;
624 }
625
626 @Override
627 public boolean onSingleTapUp(MotionEvent e) {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800628 cancelDown();
Owen Lina2fba682011-08-17 22:07:43 +0800629 if (mDownInScrolling) return true;
630 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
631 if (index != INDEX_NONE) mListener.onSingleTapUp(index);
632 return true;
633 }
634
635 @Override
636 public void onLongPress(MotionEvent e) {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800637 cancelDown();
Owen Lina2fba682011-08-17 22:07:43 +0800638 if (mDownInScrolling) return;
639 lockRendering();
640 try {
641 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
642 if (index != INDEX_NONE) mListener.onLongTap(index);
643 } finally {
644 unlockRendering();
645 }
646 }
647 }
648
649 public void setStartIndex(int index) {
650 mStartIndex = index;
651 }
652
653 // Return true if the layout parameters have been changed
654 public boolean setSlotCount(int slotCount) {
655 boolean changed = mLayout.setSlotCount(slotCount);
656
657 // mStartIndex is applied the first time setSlotCount is called.
658 if (mStartIndex != INDEX_NONE) {
659 setCenterIndex(mStartIndex);
660 mStartIndex = INDEX_NONE;
661 }
Yuli Huangb8e94ce2012-02-13 19:55:58 +0800662 // Reset the scroll position to avoid scrolling over the updated limit.
663 setScrollPosition(WIDE ? mScrollX : mScrollY);
Owen Lina2fba682011-08-17 22:07:43 +0800664 return changed;
665 }
666
667 public int getVisibleStart() {
668 return mLayout.getVisibleStart();
669 }
670
671 public int getVisibleEnd() {
672 return mLayout.getVisibleEnd();
673 }
674
675 public int getScrollX() {
676 return mScrollX;
677 }
678
679 public int getScrollY() {
680 return mScrollY;
681 }
682}