Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.gallery3d.ui; |
| 18 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 19 | import android.content.Context; |
| 20 | import android.graphics.Rect; |
Owen Lin | c907c32 | 2011-11-17 15:26:56 +0800 | [diff] [blame] | 21 | import android.os.Handler; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 22 | import android.view.GestureDetector; |
| 23 | import android.view.MotionEvent; |
| 24 | import android.view.animation.DecelerateInterpolator; |
| 25 | |
Ray Chen | 73e791c | 2011-10-04 15:19:44 +0800 | [diff] [blame] | 26 | import com.android.gallery3d.anim.Animation; |
| 27 | import com.android.gallery3d.common.Utils; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 28 | |
| 29 | public class SlotView extends GLView { |
| 30 | @SuppressWarnings("unused") |
| 31 | private static final String TAG = "SlotView"; |
| 32 | |
| 33 | private static final boolean WIDE = true; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 34 | private static final int INDEX_NONE = -1; |
| 35 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 36 | public static final int RENDER_MORE_PASS = 1; |
| 37 | public static final int RENDER_MORE_FRAME = 2; |
| 38 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 39 | public interface Listener { |
Chih-Chung Chang | 70a73a7 | 2011-09-19 11:09:39 +0800 | [diff] [blame] | 40 | public void onDown(int index); |
| 41 | public void onUp(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 42 | 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 Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 48 | @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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 59 | } |
| 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 68 | private boolean mMoreAnimation = false; |
| 69 | private MyAnimation mAnimation = null; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 70 | private final Layout mLayout = new Layout(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 71 | 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 Lin | c907c32 | 2011-11-17 15:26:56 +0800 | [diff] [blame] | 76 | private final Handler mHandler; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 77 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 78 | private SlotRenderer mRenderer; |
| 79 | |
| 80 | private int[] mRequestRenderSlots = new int[16]; |
| 81 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 82 | 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 Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 86 | public SlotView(Context context, Spec spec) { |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 87 | mGestureDetector = |
| 88 | new GestureDetector(context, new MyGestureListener()); |
| 89 | mScroller = new ScrollerHelper(context); |
Owen Lin | c907c32 | 2011-11-17 15:26:56 +0800 | [diff] [blame] | 90 | mHandler = new Handler(context.getMainLooper()); |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 91 | setSlotSpec(spec); |
| 92 | } |
| 93 | |
| 94 | public void setSlotRenderer(SlotRenderer slotDrawer) { |
| 95 | mRenderer = slotDrawer; |
| 96 | if (mRenderer != null) { |
| 97 | mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd()); |
| 98 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 99 | } |
| 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 139 | public void setSlotSpec(Spec spec) { |
| 140 | mLayout.setSlotSpec(spec); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | @Override |
| 144 | public void addComponent(GLView view) { |
| 145 | throw new UnsupportedOperationException(); |
| 146 | } |
| 147 | |
| 148 | @Override |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 149 | protected void onLayout(boolean changeSize, int l, int t, int r, int b) { |
| 150 | if (!changeSize) return; |
Chih-Chung Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 151 | |
| 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 157 | mLayout.setSize(r - l, b - t); |
Chih-Chung Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 158 | makeSlotVisible(visibleIndex); |
| 159 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 160 | 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 Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 169 | // TODO: Fix this regression. Transition is disabled in this change |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 170 | public void startTransition(PositionProvider position) { |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 171 | mAnimation = new MyAnimation(); |
| 172 | mAnimation.start(); |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 173 | if (mLayout.mSlotCount != 0) invalidate(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 174 | } |
| 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 192 | 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 Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 205 | case MotionEvent.ACTION_UP: |
| 206 | mPaper.onRelease(); |
| 207 | invalidate(); |
| 208 | break; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 209 | } |
| 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 Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 226 | 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 233 | @Override |
| 234 | protected void render(GLCanvas canvas) { |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 235 | super.render(canvas); |
| 236 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 237 | if (mRenderer == null) return; |
| 238 | mRenderer.prepareDrawing(); |
| 239 | |
Chih-Chung Chang | b3d0196 | 2012-02-17 10:02:27 +0800 | [diff] [blame] | 240 | long animTime = AnimationTime.get(); |
| 241 | boolean more = mScroller.advanceAnimation(animTime); |
Chih-Chung Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 242 | int oldX = mScrollX; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 243 | updateScrollPosition(mScroller.getPosition(), false); |
Chih-Chung Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 244 | |
| 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 Chang | 1b2af5e | 2011-09-27 19:44:36 +0800 | [diff] [blame] | 253 | |
| 254 | // I don't know why, but getCurrVelocity() can return NaN. |
| 255 | if (!Float.isNaN(v)) { |
| 256 | mPaper.edgeReached(v); |
| 257 | } |
Chih-Chung Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 258 | } |
| 259 | paperActive = mPaper.advanceAnimation(); |
| 260 | } |
| 261 | |
| 262 | more |= paperActive; |
| 263 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 264 | float interpolate = 1f; |
| 265 | if (mAnimation != null) { |
Chih-Chung Chang | b3d0196 | 2012-02-17 10:02:27 +0800 | [diff] [blame] | 266 | more |= mAnimation.calculate(animTime); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 267 | interpolate = mAnimation.value; |
| 268 | } |
| 269 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 270 | 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 280 | } |
| 281 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 282 | 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 289 | } |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 290 | requestCount = newCount; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 291 | } |
| 292 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 293 | canvas.translate(mScrollX, mScrollY); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 294 | |
| 295 | if (more) invalidate(); |
Owen Lin | c907c32 | 2011-11-17 15:26:56 +0800 | [diff] [blame] | 296 | |
| 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 305 | } |
| 306 | mMoreAnimation = more; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 307 | } |
| 308 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 309 | private int renderItem(GLCanvas canvas, |
| 310 | int index, int pass, float interpolate, boolean paperActive) { |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 311 | canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX); |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 312 | Rect rect = getSlotRect(index); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 313 | if (paperActive) { |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 314 | canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 315 | } else { |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 316 | canvas.translate(rect.left, rect.top, 0); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 317 | } |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 318 | int result = mRenderer.renderSlot( |
| 319 | canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 320 | canvas.restore(); |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 321 | return result; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 322 | } |
| 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 338 | // 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 356 | } |
| 357 | |
Owen Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 358 | public class Layout { |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 359 | |
| 360 | private int mVisibleStart; |
| 361 | private int mVisibleEnd; |
| 362 | |
| 363 | private int mSlotCount; |
| 364 | private int mSlotWidth; |
| 365 | private int mSlotHeight; |
Chih-Chung Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 366 | private int mSlotGap; |
| 367 | |
| 368 | private Spec mSpec; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 369 | |
| 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 380 | public void setSlotSpec(Spec spec) { |
| 381 | mSpec = spec; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 382 | } |
| 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 402 | int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap); |
| 403 | int y = mVerticalPadding + row * (mSlotHeight + mSlotGap); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 404 | return new Rect(x, y, x + mSlotWidth, y + mSlotHeight); |
| 405 | } |
| 406 | |
Chih-Chung Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 407 | public int getSlotWidth() { |
| 408 | return mSlotWidth; |
| 409 | } |
| 410 | |
| 411 | public int getSlotHeight() { |
| 412 | return mSlotHeight; |
| 413 | } |
| 414 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 415 | // 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 431 | int unitCount = (minorLength + mSlotGap) / (minorUnitSize + mSlotGap); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 432 | 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 437 | int usedMinorLength = availableUnits * minorUnitSize + |
| 438 | (availableUnits - 1) * mSlotGap; |
| 439 | padding[0] = (minorLength - usedMinorLength) / 2; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 440 | |
| 441 | // Then calculate how many columns we need for all slots. |
| 442 | int count = ((mSlotCount + mUnitCount - 1) / mUnitCount); |
Chih-Chung Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 443 | mContentLength = count * majorUnitSize + (count - 1) * mSlotGap; |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 444 | |
| 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 451 | // 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 463 | 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 486 | 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 491 | setVisibleRange(start, end); |
| 492 | } else { |
Chih-Chung Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 493 | 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 498 | 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 Lin | 83a036c | 2012-03-22 14:14:40 +0800 | [diff] [blame] | 516 | if (mRenderer != null) { |
| 517 | mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd); |
| 518 | } |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 519 | } |
| 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 Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 530 | int absoluteX = Math.round(x) + (WIDE ? mScrollPosition : 0); |
| 531 | int absoluteY = Math.round(y) + (WIDE ? 0 : mScrollPosition); |
| 532 | |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 533 | absoluteX -= mHorizontalPadding; |
Chih-Chung Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 534 | absoluteY -= mVerticalPadding; |
| 535 | |
Chih-Chung Chang | 809bff6 | 2011-10-31 11:45:11 +0800 | [diff] [blame] | 536 | if (absoluteX < 0 || absoluteY < 0) { |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 537 | return INDEX_NONE; |
| 538 | } |
| 539 | |
Chih-Chung Chang | 809bff6 | 2011-10-31 11:45:11 +0800 | [diff] [blame] | 540 | 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 548 | return INDEX_NONE; |
| 549 | } |
Chih-Chung Chang | 07069de | 2011-09-14 20:50:28 +0800 | [diff] [blame] | 550 | |
| 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 559 | 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 Chang | 70a73a7 | 2011-09-19 11:09:39 +0800 | [diff] [blame] | 572 | 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 Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 598 | |
| 599 | @Override |
| 600 | public boolean onFling(MotionEvent e1, |
| 601 | MotionEvent e2, float velocityX, float velocityY) { |
Chih-Chung Chang | 70a73a7 | 2011-09-19 11:09:39 +0800 | [diff] [blame] | 602 | cancelDown(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 603 | 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 Chang | 70a73a7 | 2011-09-19 11:09:39 +0800 | [diff] [blame] | 615 | cancelDown(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 616 | float distance = WIDE ? distanceX : distanceY; |
Chih-Chung Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 617 | int overDistance = mScroller.startScroll( |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 618 | Math.round(distance), 0, mLayout.getScrollLimit()); |
Chih-Chung Chang | ca07852 | 2011-09-26 10:40:38 +0800 | [diff] [blame] | 619 | if (mOverscrollEffect == OVERSCROLL_3D && overDistance != 0) { |
| 620 | mPaper.overScroll(overDistance); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 621 | } |
| 622 | invalidate(); |
| 623 | return true; |
| 624 | } |
| 625 | |
| 626 | @Override |
| 627 | public boolean onSingleTapUp(MotionEvent e) { |
Chih-Chung Chang | 70a73a7 | 2011-09-19 11:09:39 +0800 | [diff] [blame] | 628 | cancelDown(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 629 | 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 Chang | 70a73a7 | 2011-09-19 11:09:39 +0800 | [diff] [blame] | 637 | cancelDown(); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 638 | 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 Huang | b8e94ce | 2012-02-13 19:55:58 +0800 | [diff] [blame] | 662 | // Reset the scroll position to avoid scrolling over the updated limit. |
| 663 | setScrollPosition(WIDE ? mScrollX : mScrollY); |
Owen Lin | a2fba68 | 2011-08-17 22:07:43 +0800 | [diff] [blame] | 664 | 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 | } |