blob: bd0ffdc158a7cc789609896caeb9fd2f2f16361f [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.graphics.Rect;
Owen Linc907c322011-11-17 15:26:56 +080020import android.os.Handler;
Owen Lina2fba682011-08-17 22:07:43 +080021import android.view.GestureDetector;
22import android.view.MotionEvent;
23import android.view.animation.DecelerateInterpolator;
24
Ray Chen73e791c2011-10-04 15:19:44 +080025import com.android.gallery3d.anim.Animation;
Owen Line681d652012-08-24 12:25:57 +080026import com.android.gallery3d.app.AbstractGalleryActivity;
Ray Chen73e791c2011-10-04 15:19:44 +080027import com.android.gallery3d.common.Utils;
John Reck8cb165a2012-12-11 14:42:27 -080028import com.android.gallery3d.glrenderer.GLCanvas;
Owen Lina2fba682011-08-17 22:07:43 +080029
30public class SlotView extends GLView {
31 @SuppressWarnings("unused")
32 private static final String TAG = "SlotView";
33
34 private static final boolean WIDE = true;
Owen Lina2fba682011-08-17 22:07:43 +080035 private static final int INDEX_NONE = -1;
36
Owen Lin83a036c2012-03-22 14:14:40 +080037 public static final int RENDER_MORE_PASS = 1;
38 public static final int RENDER_MORE_FRAME = 2;
39
Owen Lina2fba682011-08-17 22:07:43 +080040 public interface Listener {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +080041 public void onDown(int index);
Yuli Huang4072ab92012-04-24 15:20:28 +080042 public void onUp(boolean followedByLongPress);
Owen Lina2fba682011-08-17 22:07:43 +080043 public void onSingleTapUp(int index);
44 public void onLongTap(int index);
45 public void onScrollPositionChanged(int position, int total);
46 }
47
48 public static class SimpleListener implements Listener {
Owen Lin83a036c2012-03-22 14:14:40 +080049 @Override public void onDown(int index) {}
Yuli Huang4072ab92012-04-24 15:20:28 +080050 @Override public void onUp(boolean followedByLongPress) {}
Owen Lin83a036c2012-03-22 14:14:40 +080051 @Override public void onSingleTapUp(int index) {}
52 @Override public void onLongTap(int index) {}
53 @Override public void onScrollPositionChanged(int position, int total) {}
54 }
55
56 public static interface SlotRenderer {
57 public void prepareDrawing();
58 public void onVisibleRangeChanged(int visibleStart, int visibleEnd);
Owen Lin81234442012-03-26 18:28:48 +080059 public void onSlotSizeChanged(int width, int height);
Owen Lin83a036c2012-03-22 14:14:40 +080060 public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height);
Owen Lina2fba682011-08-17 22:07:43 +080061 }
62
63 private final GestureDetector mGestureDetector;
64 private final ScrollerHelper mScroller;
65 private final Paper mPaper = new Paper();
66
67 private Listener mListener;
68 private UserInteractionListener mUIListener;
69
Owen Lina2fba682011-08-17 22:07:43 +080070 private boolean mMoreAnimation = false;
Owen Linf52afa92012-03-23 14:59:14 +080071 private SlotAnimation mAnimation = null;
Owen Lina2fba682011-08-17 22:07:43 +080072 private final Layout mLayout = new Layout();
Owen Lina2fba682011-08-17 22:07:43 +080073 private int mStartIndex = INDEX_NONE;
74
75 // whether the down action happened while the view is scrolling.
76 private boolean mDownInScrolling;
77 private int mOverscrollEffect = OVERSCROLL_3D;
Owen Linc907c322011-11-17 15:26:56 +080078 private final Handler mHandler;
Owen Lina2fba682011-08-17 22:07:43 +080079
Owen Lin83a036c2012-03-22 14:14:40 +080080 private SlotRenderer mRenderer;
81
82 private int[] mRequestRenderSlots = new int[16];
83
Owen Lina2fba682011-08-17 22:07:43 +080084 public static final int OVERSCROLL_3D = 0;
85 public static final int OVERSCROLL_SYSTEM = 1;
86 public static final int OVERSCROLL_NONE = 2;
87
Owen Lind7055452012-03-29 14:27:58 +080088 // to prevent allocating memory
89 private final Rect mTempRect = new Rect();
90
Owen Line681d652012-08-24 12:25:57 +080091 public SlotView(AbstractGalleryActivity activity, Spec spec) {
92 mGestureDetector = new GestureDetector(activity, new MyGestureListener());
93 mScroller = new ScrollerHelper(activity);
Owen Lin3c217512012-04-17 15:38:36 +080094 mHandler = new SynchronizedHandler(activity.getGLRoot());
Owen Lin83a036c2012-03-22 14:14:40 +080095 setSlotSpec(spec);
96 }
97
98 public void setSlotRenderer(SlotRenderer slotDrawer) {
99 mRenderer = slotDrawer;
100 if (mRenderer != null) {
Owen Lin81234442012-03-26 18:28:48 +0800101 mRenderer.onSlotSizeChanged(mLayout.mSlotWidth, mLayout.mSlotHeight);
Owen Lin83a036c2012-03-22 14:14:40 +0800102 mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd());
103 }
Owen Lina2fba682011-08-17 22:07:43 +0800104 }
105
106 public void setCenterIndex(int index) {
107 int slotCount = mLayout.mSlotCount;
108 if (index < 0 || index >= slotCount) {
109 return;
110 }
Owen Lind7055452012-03-29 14:27:58 +0800111 Rect rect = mLayout.getSlotRect(index, mTempRect);
Owen Lina2fba682011-08-17 22:07:43 +0800112 int position = WIDE
113 ? (rect.left + rect.right - getWidth()) / 2
114 : (rect.top + rect.bottom - getHeight()) / 2;
115 setScrollPosition(position);
116 }
117
118 public void makeSlotVisible(int index) {
Owen Lind7055452012-03-29 14:27:58 +0800119 Rect rect = mLayout.getSlotRect(index, mTempRect);
Owen Lina2fba682011-08-17 22:07:43 +0800120 int visibleBegin = WIDE ? mScrollX : mScrollY;
121 int visibleLength = WIDE ? getWidth() : getHeight();
122 int visibleEnd = visibleBegin + visibleLength;
123 int slotBegin = WIDE ? rect.left : rect.top;
124 int slotEnd = WIDE ? rect.right : rect.bottom;
125
126 int position = visibleBegin;
127 if (visibleLength < slotEnd - slotBegin) {
128 position = visibleBegin;
129 } else if (slotBegin < visibleBegin) {
130 position = slotBegin;
131 } else if (slotEnd > visibleEnd) {
132 position = slotEnd - visibleLength;
133 }
134
135 setScrollPosition(position);
136 }
137
138 public void setScrollPosition(int position) {
139 position = Utils.clamp(position, 0, mLayout.getScrollLimit());
140 mScroller.setPosition(position);
141 updateScrollPosition(position, false);
142 }
143
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800144 public void setSlotSpec(Spec spec) {
145 mLayout.setSlotSpec(spec);
Owen Lina2fba682011-08-17 22:07:43 +0800146 }
147
148 @Override
149 public void addComponent(GLView view) {
150 throw new UnsupportedOperationException();
151 }
152
153 @Override
Owen Lina2fba682011-08-17 22:07:43 +0800154 protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
155 if (!changeSize) return;
Chih-Chung Changca078522011-09-26 10:40:38 +0800156
157 // Make sure we are still at a resonable scroll position after the size
158 // is changed (like orientation change). We choose to keep the center
159 // visible slot still visible. This is arbitrary but reasonable.
160 int visibleIndex =
161 (mLayout.getVisibleStart() + mLayout.getVisibleEnd()) / 2;
Owen Lina2fba682011-08-17 22:07:43 +0800162 mLayout.setSize(r - l, b - t);
Chih-Chung Changca078522011-09-26 10:40:38 +0800163 makeSlotVisible(visibleIndex);
Owen Lina2fba682011-08-17 22:07:43 +0800164 if (mOverscrollEffect == OVERSCROLL_3D) {
165 mPaper.setSize(r - l, b - t);
166 }
167 }
168
Owen Linf52afa92012-03-23 14:59:14 +0800169 public void startScatteringAnimation(RelativePosition position) {
170 mAnimation = new ScatteringAnimation(position);
171 mAnimation.start();
172 if (mLayout.mSlotCount != 0) invalidate();
Owen Lina2fba682011-08-17 22:07:43 +0800173 }
174
Owen Linf52afa92012-03-23 14:59:14 +0800175 public void startRisingAnimation() {
176 mAnimation = new RisingAnimation();
177 mAnimation.start();
178 if (mLayout.mSlotCount != 0) invalidate();
179 }
180
Owen Lina2fba682011-08-17 22:07:43 +0800181 private void updateScrollPosition(int position, boolean force) {
182 if (!force && (WIDE ? position == mScrollX : position == mScrollY)) return;
183 if (WIDE) {
184 mScrollX = position;
185 } else {
186 mScrollY = position;
187 }
188 mLayout.setScrollPosition(position);
189 onScrollPositionChanged(position);
190 }
191
192 protected void onScrollPositionChanged(int newPosition) {
193 int limit = mLayout.getScrollLimit();
194 mListener.onScrollPositionChanged(newPosition, limit);
195 }
196
Owen Lina2fba682011-08-17 22:07:43 +0800197 public Rect getSlotRect(int slotIndex) {
Owen Lind7055452012-03-29 14:27:58 +0800198 return mLayout.getSlotRect(slotIndex, new Rect());
Owen Lina2fba682011-08-17 22:07:43 +0800199 }
200
201 @Override
202 protected boolean onTouch(MotionEvent event) {
203 if (mUIListener != null) mUIListener.onUserInteraction();
204 mGestureDetector.onTouchEvent(event);
205 switch (event.getAction()) {
206 case MotionEvent.ACTION_DOWN:
207 mDownInScrolling = !mScroller.isFinished();
208 mScroller.forceFinished();
209 break;
Chih-Chung Changca078522011-09-26 10:40:38 +0800210 case MotionEvent.ACTION_UP:
211 mPaper.onRelease();
212 invalidate();
213 break;
Owen Lina2fba682011-08-17 22:07:43 +0800214 }
215 return true;
216 }
217
218 public void setListener(Listener listener) {
219 mListener = listener;
220 }
221
222 public void setUserInteractionListener(UserInteractionListener listener) {
223 mUIListener = listener;
224 }
225
226 public void setOverscrollEffect(int kind) {
227 mOverscrollEffect = kind;
228 mScroller.setOverfling(kind == OVERSCROLL_SYSTEM);
229 }
230
Owen Lin83a036c2012-03-22 14:14:40 +0800231 private static int[] expandIntArray(int array[], int capacity) {
232 while (array.length < capacity) {
233 array = new int[array.length * 2];
234 }
235 return array;
236 }
237
Owen Lina2fba682011-08-17 22:07:43 +0800238 @Override
239 protected void render(GLCanvas canvas) {
Owen Lina2fba682011-08-17 22:07:43 +0800240 super.render(canvas);
241
Owen Lin83a036c2012-03-22 14:14:40 +0800242 if (mRenderer == null) return;
243 mRenderer.prepareDrawing();
244
Chih-Chung Changb3d01962012-02-17 10:02:27 +0800245 long animTime = AnimationTime.get();
246 boolean more = mScroller.advanceAnimation(animTime);
Owen Linc88fabf2012-05-09 11:32:32 +0800247 more |= mLayout.advanceAnimation(animTime);
Chih-Chung Changca078522011-09-26 10:40:38 +0800248 int oldX = mScrollX;
Owen Lina2fba682011-08-17 22:07:43 +0800249 updateScrollPosition(mScroller.getPosition(), false);
Chih-Chung Changca078522011-09-26 10:40:38 +0800250
251 boolean paperActive = false;
252 if (mOverscrollEffect == OVERSCROLL_3D) {
253 // Check if an edge is reached and notify mPaper if so.
254 int newX = mScrollX;
255 int limit = mLayout.getScrollLimit();
256 if (oldX > 0 && newX == 0 || oldX < limit && newX == limit) {
257 float v = mScroller.getCurrVelocity();
258 if (newX == limit) v = -v;
Chih-Chung Chang1b2af5e2011-09-27 19:44:36 +0800259
260 // I don't know why, but getCurrVelocity() can return NaN.
261 if (!Float.isNaN(v)) {
262 mPaper.edgeReached(v);
263 }
Chih-Chung Changca078522011-09-26 10:40:38 +0800264 }
265 paperActive = mPaper.advanceAnimation();
266 }
267
268 more |= paperActive;
269
Owen Lina2fba682011-08-17 22:07:43 +0800270 if (mAnimation != null) {
Chih-Chung Changb3d01962012-02-17 10:02:27 +0800271 more |= mAnimation.calculate(animTime);
Owen Lina2fba682011-08-17 22:07:43 +0800272 }
273
Owen Lin83a036c2012-03-22 14:14:40 +0800274 canvas.translate(-mScrollX, -mScrollY);
275
276 int requestCount = 0;
277 int requestedSlot[] = expandIntArray(mRequestRenderSlots,
278 mLayout.mVisibleEnd - mLayout.mVisibleStart);
279
Owen Linf52afa92012-03-23 14:59:14 +0800280 for (int i = mLayout.mVisibleEnd - 1; i >= mLayout.mVisibleStart; --i) {
281 int r = renderItem(canvas, i, 0, paperActive);
Owen Lin83a036c2012-03-22 14:14:40 +0800282 if ((r & RENDER_MORE_FRAME) != 0) more = true;
283 if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i;
Owen Lina2fba682011-08-17 22:07:43 +0800284 }
285
Owen Lin83a036c2012-03-22 14:14:40 +0800286 for (int pass = 1; requestCount != 0; ++pass) {
287 int newCount = 0;
288 for (int i = 0; i < requestCount; ++i) {
289 int r = renderItem(canvas,
Owen Linf52afa92012-03-23 14:59:14 +0800290 requestedSlot[i], pass, paperActive);
Owen Lin83a036c2012-03-22 14:14:40 +0800291 if ((r & RENDER_MORE_FRAME) != 0) more = true;
292 if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i;
Owen Lina2fba682011-08-17 22:07:43 +0800293 }
Owen Lin83a036c2012-03-22 14:14:40 +0800294 requestCount = newCount;
Owen Lina2fba682011-08-17 22:07:43 +0800295 }
296
Owen Lin83a036c2012-03-22 14:14:40 +0800297 canvas.translate(mScrollX, mScrollY);
Owen Lina2fba682011-08-17 22:07:43 +0800298
299 if (more) invalidate();
Owen Linc907c322011-11-17 15:26:56 +0800300
301 final UserInteractionListener listener = mUIListener;
302 if (mMoreAnimation && !more && listener != null) {
303 mHandler.post(new Runnable() {
304 @Override
305 public void run() {
306 listener.onUserInteractionEnd();
307 }
308 });
Owen Lina2fba682011-08-17 22:07:43 +0800309 }
310 mMoreAnimation = more;
Owen Lina2fba682011-08-17 22:07:43 +0800311 }
312
Owen Linf52afa92012-03-23 14:59:14 +0800313 private int renderItem(
314 GLCanvas canvas, int index, int pass, boolean paperActive) {
Owen Lina2fba682011-08-17 22:07:43 +0800315 canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
Owen Lind7055452012-03-29 14:27:58 +0800316 Rect rect = mLayout.getSlotRect(index, mTempRect);
Owen Lina2fba682011-08-17 22:07:43 +0800317 if (paperActive) {
Owen Lin83a036c2012-03-22 14:14:40 +0800318 canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
Owen Lina2fba682011-08-17 22:07:43 +0800319 } else {
Owen Lin83a036c2012-03-22 14:14:40 +0800320 canvas.translate(rect.left, rect.top, 0);
Owen Lina2fba682011-08-17 22:07:43 +0800321 }
Owen Linf52afa92012-03-23 14:59:14 +0800322 if (mAnimation != null && mAnimation.isActive()) {
323 mAnimation.apply(canvas, index, rect);
324 }
Owen Lin83a036c2012-03-22 14:14:40 +0800325 int result = mRenderer.renderSlot(
326 canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top);
Owen Lina2fba682011-08-17 22:07:43 +0800327 canvas.restore();
Owen Lin83a036c2012-03-22 14:14:40 +0800328 return result;
Owen Lina2fba682011-08-17 22:07:43 +0800329 }
330
Owen Linf52afa92012-03-23 14:59:14 +0800331 public static abstract class SlotAnimation extends Animation {
332 protected float mProgress = 0;
Owen Lina2fba682011-08-17 22:07:43 +0800333
Owen Linf52afa92012-03-23 14:59:14 +0800334 public SlotAnimation() {
Owen Lina2fba682011-08-17 22:07:43 +0800335 setInterpolator(new DecelerateInterpolator(4));
336 setDuration(1500);
337 }
338
339 @Override
340 protected void onCalculate(float progress) {
Owen Linf52afa92012-03-23 14:59:14 +0800341 mProgress = progress;
342 }
343
344 abstract public void apply(GLCanvas canvas, int slotIndex, Rect target);
345 }
346
347 public static class RisingAnimation extends SlotAnimation {
348 private static final int RISING_DISTANCE = 128;
349
350 @Override
351 public void apply(GLCanvas canvas, int slotIndex, Rect target) {
352 canvas.translate(0, 0, RISING_DISTANCE * (1 - mProgress));
353 }
354 }
355
356 public static class ScatteringAnimation extends SlotAnimation {
357 private int PHOTO_DISTANCE = 1000;
358 private RelativePosition mCenter;
359
360 public ScatteringAnimation(RelativePosition center) {
361 mCenter = center;
362 }
363
364 @Override
365 public void apply(GLCanvas canvas, int slotIndex, Rect target) {
366 canvas.translate(
367 (mCenter.getX() - target.centerX()) * (1 - mProgress),
368 (mCenter.getY() - target.centerY()) * (1 - mProgress),
369 slotIndex * PHOTO_DISTANCE * (1 - mProgress));
370 canvas.setAlpha(mProgress);
371 }
372 }
373
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800374 // This Spec class is used to specify the size of each slot in the SlotView.
375 // There are two ways to do it:
376 //
377 // (1) Specify slotWidth and slotHeight: they specify the width and height
378 // of each slot. The number of rows and the gap between slots will be
379 // determined automatically.
380 // (2) Specify rowsLand, rowsPort, and slotGap: they specify the number
381 // of rows in landscape/portrait mode and the gap between slots. The
382 // width and height of each slot is determined automatically.
383 //
384 // The initial value of -1 means they are not specified.
385 public static class Spec {
386 public int slotWidth = -1;
387 public int slotHeight = -1;
Bobby Georgescu93d87ff2012-08-23 13:05:53 -0700388 public int slotHeightAdditional = 0;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800389
390 public int rowsLand = -1;
391 public int rowsPort = -1;
392 public int slotGap = -1;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800393 }
394
Owen Lin83a036c2012-03-22 14:14:40 +0800395 public class Layout {
Owen Lina2fba682011-08-17 22:07:43 +0800396
397 private int mVisibleStart;
398 private int mVisibleEnd;
399
400 private int mSlotCount;
401 private int mSlotWidth;
402 private int mSlotHeight;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800403 private int mSlotGap;
404
405 private Spec mSpec;
Owen Lina2fba682011-08-17 22:07:43 +0800406
407 private int mWidth;
408 private int mHeight;
409
410 private int mUnitCount;
411 private int mContentLength;
412 private int mScrollPosition;
413
Owen Linc88fabf2012-05-09 11:32:32 +0800414 private IntegerAnimation mVerticalPadding = new IntegerAnimation();
415 private IntegerAnimation mHorizontalPadding = new IntegerAnimation();
Owen Lina2fba682011-08-17 22:07:43 +0800416
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800417 public void setSlotSpec(Spec spec) {
418 mSpec = spec;
Owen Lina2fba682011-08-17 22:07:43 +0800419 }
420
421 public boolean setSlotCount(int slotCount) {
Owen Linc88fabf2012-05-09 11:32:32 +0800422 if (slotCount == mSlotCount) return false;
423 if (mSlotCount != 0) {
424 mHorizontalPadding.setEnabled(true);
425 mVerticalPadding.setEnabled(true);
426 }
Owen Lina2fba682011-08-17 22:07:43 +0800427 mSlotCount = slotCount;
Owen Linc88fabf2012-05-09 11:32:32 +0800428 int hPadding = mHorizontalPadding.getTarget();
429 int vPadding = mVerticalPadding.getTarget();
Owen Lina2fba682011-08-17 22:07:43 +0800430 initLayoutParameters();
Owen Linc88fabf2012-05-09 11:32:32 +0800431 return vPadding != mVerticalPadding.getTarget()
432 || hPadding != mHorizontalPadding.getTarget();
Owen Lina2fba682011-08-17 22:07:43 +0800433 }
434
Owen Lind7055452012-03-29 14:27:58 +0800435 public Rect getSlotRect(int index, Rect rect) {
Owen Lina2fba682011-08-17 22:07:43 +0800436 int col, row;
437 if (WIDE) {
438 col = index / mUnitCount;
439 row = index - col * mUnitCount;
440 } else {
441 row = index / mUnitCount;
442 col = index - row * mUnitCount;
443 }
444
Owen Linc88fabf2012-05-09 11:32:32 +0800445 int x = mHorizontalPadding.get() + col * (mSlotWidth + mSlotGap);
446 int y = mVerticalPadding.get() + row * (mSlotHeight + mSlotGap);
Owen Lind7055452012-03-29 14:27:58 +0800447 rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
448 return rect;
Owen Lina2fba682011-08-17 22:07:43 +0800449 }
450
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800451 public int getSlotWidth() {
452 return mSlotWidth;
453 }
454
455 public int getSlotHeight() {
456 return mSlotHeight;
457 }
458
Owen Lina2fba682011-08-17 22:07:43 +0800459 // Calculate
460 // (1) mUnitCount: the number of slots we can fit into one column (or row).
461 // (2) mContentLength: the width (or height) we need to display all the
462 // columns (rows).
463 // (3) padding[]: the vertical and horizontal padding we need in order
464 // to put the slots towards to the center of the display.
465 //
466 // The "major" direction is the direction the user can scroll. The other
467 // direction is the "minor" direction.
468 //
469 // The comments inside this method are the description when the major
470 // directon is horizontal (X), and the minor directon is vertical (Y).
471 private void initLayoutParameters(
472 int majorLength, int minorLength, /* The view width and height */
473 int majorUnitSize, int minorUnitSize, /* The slot width and height */
474 int[] padding) {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800475 int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap);
Owen Lina2fba682011-08-17 22:07:43 +0800476 if (unitCount == 0) unitCount = 1;
477 mUnitCount = unitCount;
478
479 // We put extra padding above and below the column.
480 int availableUnits = Math.min(mUnitCount, mSlotCount);
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800481 int usedMinorLength = availableUnits * minorUnitSize +
482 (availableUnits - 1) * mSlotGap;
483 padding[0] = (minorLength - usedMinorLength) / 2;
Owen Lina2fba682011-08-17 22:07:43 +0800484
485 // Then calculate how many columns we need for all slots.
486 int count = ((mSlotCount + mUnitCount - 1) / mUnitCount);
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800487 mContentLength = count * majorUnitSize + (count - 1) * mSlotGap;
Owen Lina2fba682011-08-17 22:07:43 +0800488
489 // If the content length is less then the screen width, put
490 // extra padding in left and right.
491 padding[1] = Math.max(0, (majorLength - mContentLength) / 2);
492 }
493
494 private void initLayoutParameters() {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800495 // Initialize mSlotWidth and mSlotHeight from mSpec
496 if (mSpec.slotWidth != -1) {
497 mSlotGap = 0;
498 mSlotWidth = mSpec.slotWidth;
499 mSlotHeight = mSpec.slotHeight;
500 } else {
501 int rows = (mWidth > mHeight) ? mSpec.rowsLand : mSpec.rowsPort;
502 mSlotGap = mSpec.slotGap;
503 mSlotHeight = Math.max(1, (mHeight - (rows - 1) * mSlotGap) / rows);
Bobby Georgescu93d87ff2012-08-23 13:05:53 -0700504 mSlotWidth = mSlotHeight - mSpec.slotHeightAdditional;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800505 }
506
Owen Lin81234442012-03-26 18:28:48 +0800507 if (mRenderer != null) {
508 mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight);
509 }
510
Owen Lina2fba682011-08-17 22:07:43 +0800511 int[] padding = new int[2];
512 if (WIDE) {
513 initLayoutParameters(mWidth, mHeight, mSlotWidth, mSlotHeight, padding);
Owen Linc88fabf2012-05-09 11:32:32 +0800514 mVerticalPadding.startAnimateTo(padding[0]);
515 mHorizontalPadding.startAnimateTo(padding[1]);
Owen Lina2fba682011-08-17 22:07:43 +0800516 } else {
517 initLayoutParameters(mHeight, mWidth, mSlotHeight, mSlotWidth, padding);
Owen Linc88fabf2012-05-09 11:32:32 +0800518 mVerticalPadding.startAnimateTo(padding[1]);
519 mHorizontalPadding.startAnimateTo(padding[0]);
Owen Lina2fba682011-08-17 22:07:43 +0800520 }
521 updateVisibleSlotRange();
522 }
523
524 public void setSize(int width, int height) {
525 mWidth = width;
526 mHeight = height;
527 initLayoutParameters();
528 }
529
530 private void updateVisibleSlotRange() {
531 int position = mScrollPosition;
532
533 if (WIDE) {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800534 int startCol = position / (mSlotWidth + mSlotGap);
535 int start = Math.max(0, mUnitCount * startCol);
536 int endCol = (position + mWidth + mSlotWidth + mSlotGap - 1) /
537 (mSlotWidth + mSlotGap);
538 int end = Math.min(mSlotCount, mUnitCount * endCol);
Owen Lina2fba682011-08-17 22:07:43 +0800539 setVisibleRange(start, end);
540 } else {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800541 int startRow = position / (mSlotHeight + mSlotGap);
542 int start = Math.max(0, mUnitCount * startRow);
543 int endRow = (position + mHeight + mSlotHeight + mSlotGap - 1) /
544 (mSlotHeight + mSlotGap);
545 int end = Math.min(mSlotCount, mUnitCount * endRow);
Owen Lina2fba682011-08-17 22:07:43 +0800546 setVisibleRange(start, end);
547 }
548 }
549
550 public void setScrollPosition(int position) {
551 if (mScrollPosition == position) return;
552 mScrollPosition = position;
553 updateVisibleSlotRange();
554 }
555
556 private void setVisibleRange(int start, int end) {
557 if (start == mVisibleStart && end == mVisibleEnd) return;
558 if (start < end) {
559 mVisibleStart = start;
560 mVisibleEnd = end;
561 } else {
562 mVisibleStart = mVisibleEnd = 0;
563 }
Owen Lin83a036c2012-03-22 14:14:40 +0800564 if (mRenderer != null) {
565 mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd);
566 }
Owen Lina2fba682011-08-17 22:07:43 +0800567 }
568
569 public int getVisibleStart() {
570 return mVisibleStart;
571 }
572
573 public int getVisibleEnd() {
574 return mVisibleEnd;
575 }
576
577 public int getSlotIndexByPosition(float x, float y) {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800578 int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0);
579 int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition);
580
Owen Linc88fabf2012-05-09 11:32:32 +0800581 absoluteX -= mHorizontalPadding.get();
582 absoluteY -= mVerticalPadding.get();
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800583
Chih-Chung Chang809bff62011-10-31 11:45:11 +0800584 if (absoluteX < 0 || absoluteY < 0) {
Owen Lina2fba682011-08-17 22:07:43 +0800585 return INDEX_NONE;
586 }
587
Chih-Chung Chang809bff62011-10-31 11:45:11 +0800588 int columnIdx = absoluteX / (mSlotWidth + mSlotGap);
589 int rowIdx = absoluteY / (mSlotHeight + mSlotGap);
590
591 if (!WIDE && columnIdx >= mUnitCount) {
592 return INDEX_NONE;
593 }
594
595 if (WIDE && rowIdx >= mUnitCount) {
Owen Lina2fba682011-08-17 22:07:43 +0800596 return INDEX_NONE;
597 }
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800598
599 if (absoluteX % (mSlotWidth + mSlotGap) >= mSlotWidth) {
600 return INDEX_NONE;
601 }
602
603 if (absoluteY % (mSlotHeight + mSlotGap) >= mSlotHeight) {
604 return INDEX_NONE;
605 }
606
Owen Lina2fba682011-08-17 22:07:43 +0800607 int index = WIDE
608 ? (columnIdx * mUnitCount + rowIdx)
609 : (rowIdx * mUnitCount + columnIdx);
610
611 return index >= mSlotCount ? INDEX_NONE : index;
612 }
613
614 public int getScrollLimit() {
615 int limit = WIDE ? mContentLength - mWidth : mContentLength - mHeight;
616 return limit <= 0 ? 0 : limit;
617 }
Owen Linc88fabf2012-05-09 11:32:32 +0800618
619 public boolean advanceAnimation(long animTime) {
620 // use '|' to make sure both sides will be executed
621 return mVerticalPadding.calculate(animTime) | mHorizontalPadding.calculate(animTime);
622 }
Owen Lina2fba682011-08-17 22:07:43 +0800623 }
624
Owen Lin3c217512012-04-17 15:38:36 +0800625 private class MyGestureListener implements GestureDetector.OnGestureListener {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800626 private boolean isDown;
627
628 // We call the listener's onDown() when our onShowPress() is called and
629 // call the listener's onUp() when we receive any further event.
630 @Override
631 public void onShowPress(MotionEvent e) {
Owen Lin3c217512012-04-17 15:38:36 +0800632 GLRoot root = getGLRoot();
633 root.lockRenderThread();
634 try {
635 if (isDown) return;
636 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
637 if (index != INDEX_NONE) {
638 isDown = true;
639 mListener.onDown(index);
640 }
641 } finally {
642 root.unlockRenderThread();
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800643 }
644 }
645
Yuli Huang4072ab92012-04-24 15:20:28 +0800646 private void cancelDown(boolean byLongPress) {
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800647 if (!isDown) return;
648 isDown = false;
Yuli Huang4072ab92012-04-24 15:20:28 +0800649 mListener.onUp(byLongPress);
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800650 }
651
652 @Override
653 public boolean onDown(MotionEvent e) {
654 return false;
655 }
Owen Lina2fba682011-08-17 22:07:43 +0800656
657 @Override
658 public boolean onFling(MotionEvent e1,
659 MotionEvent e2, float velocityX, float velocityY) {
Yuli Huang4072ab92012-04-24 15:20:28 +0800660 cancelDown(false);
Owen Lina2fba682011-08-17 22:07:43 +0800661 int scrollLimit = mLayout.getScrollLimit();
662 if (scrollLimit == 0) return false;
663 float velocity = WIDE ? velocityX : velocityY;
664 mScroller.fling((int) -velocity, 0, scrollLimit);
665 if (mUIListener != null) mUIListener.onUserInteractionBegin();
666 invalidate();
667 return true;
668 }
669
670 @Override
671 public boolean onScroll(MotionEvent e1,
672 MotionEvent e2, float distanceX, float distanceY) {
Yuli Huang4072ab92012-04-24 15:20:28 +0800673 cancelDown(false);
Owen Lina2fba682011-08-17 22:07:43 +0800674 float distance = WIDE ? distanceX : distanceY;
Chih-Chung Changca078522011-09-26 10:40:38 +0800675 int overDistance = mScroller.startScroll(
Owen Lina2fba682011-08-17 22:07:43 +0800676 Math.round(distance), 0, mLayout.getScrollLimit());
Chih-Chung Changca078522011-09-26 10:40:38 +0800677 if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) {
678 mPaper.overScroll(overDistance);
Owen Lina2fba682011-08-17 22:07:43 +0800679 }
680 invalidate();
681 return true;
682 }
683
684 @Override
685 public boolean onSingleTapUp(MotionEvent e) {
Yuli Huang4072ab92012-04-24 15:20:28 +0800686 cancelDown(false);
Owen Lina2fba682011-08-17 22:07:43 +0800687 if (mDownInScrolling) return true;
688 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
689 if (index != INDEX_NONE) mListener.onSingleTapUp(index);
690 return true;
691 }
692
693 @Override
694 public void onLongPress(MotionEvent e) {
Yuli Huang4072ab92012-04-24 15:20:28 +0800695 cancelDown(true);
Owen Lina2fba682011-08-17 22:07:43 +0800696 if (mDownInScrolling) return;
697 lockRendering();
698 try {
699 int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
700 if (index != INDEX_NONE) mListener.onLongTap(index);
701 } finally {
702 unlockRendering();
703 }
704 }
705 }
706
707 public void setStartIndex(int index) {
708 mStartIndex = index;
709 }
710
711 // Return true if the layout parameters have been changed
712 public boolean setSlotCount(int slotCount) {
713 boolean changed = mLayout.setSlotCount(slotCount);
714
715 // mStartIndex is applied the first time setSlotCount is called.
716 if (mStartIndex != INDEX_NONE) {
717 setCenterIndex(mStartIndex);
718 mStartIndex = INDEX_NONE;
719 }
Yuli Huangb8e94ce2012-02-13 19:55:58 +0800720 // Reset the scroll position to avoid scrolling over the updated limit.
721 setScrollPosition(WIDE ? mScrollX : mScrollY);
Owen Lina2fba682011-08-17 22:07:43 +0800722 return changed;
723 }
724
725 public int getVisibleStart() {
726 return mLayout.getVisibleStart();
727 }
728
729 public int getVisibleEnd() {
730 return mLayout.getVisibleEnd();
731 }
732
733 public int getScrollX() {
734 return mScrollX;
735 }
736
737 public int getScrollY() {
738 return mScrollY;
739 }
Owen Linc88fabf2012-05-09 11:32:32 +0800740
Bobby Georgescuee96fa82012-09-13 15:46:40 -0700741 public Rect getSlotRect(int slotIndex, GLView rootPane) {
742 // Get slot rectangle relative to this root pane.
743 Rect offset = new Rect();
744 rootPane.getBoundsOf(this, offset);
745 Rect r = getSlotRect(slotIndex);
746 r.offset(offset.left - getScrollX(),
747 offset.top - getScrollY());
748 return r;
749 }
750
Owen Linc88fabf2012-05-09 11:32:32 +0800751 private static class IntegerAnimation extends Animation {
752 private int mTarget;
753 private int mCurrent = 0;
754 private int mFrom = 0;
755 private boolean mEnabled = false;
756
757 public void setEnabled(boolean enabled) {
758 mEnabled = enabled;
759 }
760
761 public void startAnimateTo(int target) {
762 if (!mEnabled) {
763 mTarget = mCurrent = target;
764 return;
765 }
766 if (target == mTarget) return;
767
768 mFrom = mCurrent;
769 mTarget = target;
770 setDuration(180);
771 start();
772 }
773
774 public int get() {
775 return mCurrent;
776 }
777
778 public int getTarget() {
779 return mTarget;
780 }
781
782 @Override
783 protected void onCalculate(float progress) {
784 mCurrent = Math.round(mFrom + progress * (mTarget - mFrom));
785 if (progress == 1f) mEnabled = false;
786 }
787 }
Owen Lina2fba682011-08-17 22:07:43 +0800788}