Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 1 | /* |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 2 | * Copyright (c) 2015, The Linux Foundation. All rights reserved. |
| 3 | * Not a Contribution |
| 4 | * |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 5 | * Copyright (C) 2010 The Android Open Source Project |
| 6 | * |
| 7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | * you may not use this file except in compliance with the License. |
| 9 | * You may obtain a copy of the License at |
| 10 | * |
| 11 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | * |
| 13 | * Unless required by applicable law or agreed to in writing, software |
| 14 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | * See the License for the specific language governing permissions and |
| 17 | * limitations under the License. |
| 18 | */ |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 19 | package com.android.gallery3d.ui; |
| 20 | |
| 21 | import android.graphics.Rect; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 22 | import android.text.TextUtils; |
| 23 | import android.view.GestureDetector; |
| 24 | import android.view.MotionEvent; |
| 25 | import android.view.View; |
| 26 | import android.view.animation.DecelerateInterpolator; |
| 27 | |
| 28 | import com.android.gallery3d.anim.Animation; |
| 29 | import com.android.gallery3d.app.AbstractGalleryActivity; |
Likai Ding | 711aee7 | 2016-05-18 13:47:56 +0800 | [diff] [blame] | 30 | import com.android.gallery3d.common.ApiHelper.SystemProperties; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 31 | import com.android.gallery3d.common.Utils; |
| 32 | import com.android.gallery3d.glrenderer.GLCanvas; |
| 33 | |
| 34 | import java.util.Locale; |
| 35 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 36 | public class TimeLineSlotView extends GLView { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 37 | @SuppressWarnings("unused") |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 38 | private static final String TAG = "TimeLineSlotView"; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 39 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 40 | public static final int INDEX_NONE = -1; |
| 41 | private static final int mainKey = SystemProperties.getInt("qemu.hw.mainkeys", 1); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 42 | |
| 43 | public static final int RENDER_MORE_PASS = 1; |
| 44 | public static final int RENDER_MORE_FRAME = 2; |
| 45 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 46 | private int mWidth = 0; |
| 47 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 48 | public interface Listener { |
| 49 | public void onDown(int index); |
| 50 | public void onUp(boolean followedByLongPress); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 51 | public void onSingleTapUp(int index, boolean isTitle); |
| 52 | public void onLongTap(int index, boolean isTitle); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 53 | public void onScrollPositionChanged(int position, int total); |
| 54 | } |
| 55 | |
| 56 | public static class SimpleListener implements Listener { |
| 57 | @Override public void onDown(int index) {} |
| 58 | @Override public void onUp(boolean followedByLongPress) {} |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 59 | @Override public void onSingleTapUp(int index, boolean isTitle) {} |
| 60 | @Override public void onLongTap(int index, boolean isTitle) {} |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 61 | @Override public void onScrollPositionChanged(int position, int total) {} |
| 62 | } |
| 63 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 64 | private final GestureDetector mGestureDetector; |
| 65 | private final ScrollerHelper mScroller; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 66 | |
| 67 | private Listener mListener; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 68 | private SlotAnimation mAnimation = null; |
| 69 | private final Layout mLayout = new Layout(); |
| 70 | private int mStartIndex = INDEX_NONE; |
| 71 | |
| 72 | // whether the down action happened while the view is scrolling. |
| 73 | private boolean mDownInScrolling; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 74 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 75 | private TimeLineSlotRenderer mRenderer; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 76 | |
| 77 | private int[] mRequestRenderSlots = new int[16]; |
| 78 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 79 | // Flag to check whether it is come from Photo Page. |
| 80 | private boolean isFromPhotoPage = false; |
| 81 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 82 | public TimeLineSlotView(AbstractGalleryActivity activity, Spec spec) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 83 | mGestureDetector = new GestureDetector(activity, new MyGestureListener()); |
| 84 | mScroller = new ScrollerHelper(activity); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 85 | setSlotSpec(spec); |
| 86 | } |
| 87 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 88 | public void setSlotRenderer(TimeLineSlotRenderer slotDrawer) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 89 | mRenderer = slotDrawer; |
| 90 | if (mRenderer != null) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 91 | mRenderer.onVisibleRangeChanged(getVisibleStart(), getVisibleEnd()); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | public void setCenterIndex(int index) { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 96 | int size = mLayout.getSlotSize(); |
| 97 | if (index < 0 || index >= size) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 98 | return; |
| 99 | } |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 100 | Rect rect = mLayout.getSlotRect(index); |
zdi | 1162ee9 | 2016-03-30 17:28:17 +0800 | [diff] [blame] | 101 | if (rect != null) { |
| 102 | int position = (rect.top + rect.bottom - getHeight()) / 2; |
| 103 | setScrollPosition(position); |
| 104 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 105 | } |
| 106 | |
| 107 | public void makeSlotVisible(int index) { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 108 | Rect rect = mLayout.getSlotRect(index); |
| 109 | if (rect == null) return; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 110 | int visibleBegin = mScrollY; |
| 111 | int visibleLength = getHeight(); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 112 | int visibleEnd = visibleBegin + visibleLength; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 113 | int slotBegin = rect.top; |
| 114 | int slotEnd = rect.bottom; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 115 | |
| 116 | int position = visibleBegin; |
| 117 | if (visibleLength < slotEnd - slotBegin) { |
| 118 | position = visibleBegin; |
| 119 | } else if (slotBegin < visibleBegin) { |
| 120 | position = slotBegin; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 121 | } else if (slotEnd > visibleEnd && mainKey == 1) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 122 | position = slotEnd - visibleLength; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 123 | } else if (slotBegin > visibleEnd && mainKey == 0) { |
| 124 | position = slotBegin - visibleLength; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | setScrollPosition(position); |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * Set the flag which used for check whether it is come from Photo Page. |
| 132 | */ |
| 133 | public void setIsFromPhotoPage(boolean flag) { |
| 134 | isFromPhotoPage = flag; |
| 135 | } |
| 136 | |
| 137 | public void setScrollPosition(int position) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 138 | position = Utils.clamp(position, 0, mLayout.getScrollLimit()); |
| 139 | mScroller.setPosition(position); |
| 140 | updateScrollPosition(position, false); |
| 141 | } |
| 142 | |
| 143 | public void setSlotSpec(Spec spec) { |
| 144 | mLayout.setSlotSpec(spec); |
| 145 | } |
| 146 | |
| 147 | @Override |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 148 | protected void onLayout(boolean changeSize, int l, int t, int r, int b) { |
| 149 | if (!changeSize) return; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 150 | mWidth = r - l; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 151 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 152 | // Make sure we are still at a reasonable scroll position after the size |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 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; |
| 157 | mLayout.setSize(r - l, b - t); |
| 158 | makeSlotVisible(visibleIndex); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 159 | } |
| 160 | |
| 161 | public void startScatteringAnimation(RelativePosition position) { |
| 162 | mAnimation = new ScatteringAnimation(position); |
| 163 | mAnimation.start(); |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 164 | if (mLayout.getSlotSize() != 0) invalidate(); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | public void startRisingAnimation() { |
| 168 | mAnimation = new RisingAnimation(); |
| 169 | mAnimation.start(); |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 170 | if (mLayout.getSlotSize() != 0) invalidate(); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 171 | } |
| 172 | |
| 173 | private void updateScrollPosition(int position, boolean force) { |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 174 | if (!force && (position == mScrollY)) return; |
| 175 | mScrollY = position; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 176 | mLayout.setScrollPosition(position); |
| 177 | onScrollPositionChanged(position); |
| 178 | } |
| 179 | |
| 180 | protected void onScrollPositionChanged(int newPosition) { |
| 181 | int limit = mLayout.getScrollLimit(); |
| 182 | mListener.onScrollPositionChanged(newPosition, limit); |
| 183 | } |
| 184 | |
| 185 | public Rect getSlotRect(int slotIndex) { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 186 | return mLayout.getSlotRect(slotIndex); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 187 | } |
| 188 | |
| 189 | @Override |
| 190 | protected boolean onTouch(MotionEvent event) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 191 | mGestureDetector.onTouchEvent(event); |
| 192 | switch (event.getAction()) { |
| 193 | case MotionEvent.ACTION_DOWN: |
| 194 | mDownInScrolling = !mScroller.isFinished(); |
| 195 | mScroller.forceFinished(); |
| 196 | break; |
| 197 | case MotionEvent.ACTION_UP: |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 198 | invalidate(); |
| 199 | break; |
| 200 | } |
| 201 | return true; |
| 202 | } |
| 203 | |
| 204 | public void setListener(Listener listener) { |
| 205 | mListener = listener; |
| 206 | } |
| 207 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 208 | private static int[] expandIntArray(int array[], int capacity) { |
| 209 | while (array.length < capacity) { |
| 210 | array = new int[array.length * 2]; |
| 211 | } |
| 212 | return array; |
| 213 | } |
| 214 | |
| 215 | @Override |
| 216 | protected void render(GLCanvas canvas) { |
| 217 | super.render(canvas); |
| 218 | |
| 219 | if (mRenderer == null) return; |
| 220 | mRenderer.prepareDrawing(); |
| 221 | |
| 222 | long animTime = AnimationTime.get(); |
| 223 | boolean more = mScroller.advanceAnimation(animTime); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 224 | int oldX = mScrollX; |
| 225 | updateScrollPosition(mScroller.getPosition(), false); |
| 226 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 227 | if (mAnimation != null) { |
| 228 | more |= mAnimation.calculate(animTime); |
| 229 | } |
| 230 | |
| 231 | canvas.translate(-mScrollX, -mScrollY); |
| 232 | |
| 233 | int requestCount = 0; |
| 234 | int requestedSlot[] = expandIntArray(mRequestRenderSlots, |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 235 | mLayout.getVisibleEnd() - mLayout.getVisibleStart()); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 236 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 237 | for (int i = mLayout.getVisibleEnd() - 1; i >= mLayout.getVisibleStart(); --i) { |
Michael Bestas | a4866ae | 2016-06-11 00:46:15 +0300 | [diff] [blame^] | 238 | int r = renderItem(canvas, i, 0); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 239 | if ((r & RENDER_MORE_FRAME) != 0) more = true; |
| 240 | if ((r & RENDER_MORE_PASS) != 0) requestedSlot[requestCount++] = i; |
| 241 | } |
| 242 | |
| 243 | for (int pass = 1; requestCount != 0; ++pass) { |
| 244 | int newCount = 0; |
| 245 | for (int i = 0; i < requestCount; ++i) { |
Michael Bestas | a4866ae | 2016-06-11 00:46:15 +0300 | [diff] [blame^] | 246 | int r = renderItem(canvas, requestedSlot[i], pass); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 247 | if ((r & RENDER_MORE_FRAME) != 0) more = false; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 248 | if ((r & RENDER_MORE_PASS) != 0) requestedSlot[newCount++] = i; |
| 249 | } |
| 250 | requestCount = newCount; |
| 251 | } |
| 252 | |
| 253 | canvas.translate(mScrollX, mScrollY); |
| 254 | |
| 255 | if (more) invalidate(); |
| 256 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 257 | } |
| 258 | |
Michael Bestas | a4866ae | 2016-06-11 00:46:15 +0300 | [diff] [blame^] | 259 | private int renderItem(GLCanvas canvas, int index, int pass) { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 260 | Rect rect = mLayout.getSlotRect(index); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 261 | if (rect == null) return 0; |
huiyan | 5a3b443 | 2016-02-03 16:09:04 +0800 | [diff] [blame] | 262 | canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 263 | canvas.translate(rect.left, rect.top, 0); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 264 | if (mAnimation != null && mAnimation.isActive()) { |
| 265 | mAnimation.apply(canvas, index, rect); |
| 266 | } |
| 267 | int result = mRenderer.renderSlot( |
| 268 | canvas, index, pass, rect.right - rect.left, rect.bottom - rect.top); |
| 269 | canvas.restore(); |
| 270 | return result; |
| 271 | } |
| 272 | |
| 273 | public static abstract class SlotAnimation extends Animation { |
| 274 | protected float mProgress = 0; |
| 275 | |
| 276 | public SlotAnimation() { |
| 277 | setInterpolator(new DecelerateInterpolator(4)); |
| 278 | setDuration(1500); |
| 279 | } |
| 280 | |
| 281 | @Override |
| 282 | protected void onCalculate(float progress) { |
| 283 | mProgress = progress; |
| 284 | } |
| 285 | |
| 286 | abstract public void apply(GLCanvas canvas, int slotIndex, Rect target); |
| 287 | } |
| 288 | |
| 289 | public static class RisingAnimation extends SlotAnimation { |
| 290 | private static final int RISING_DISTANCE = 128; |
| 291 | |
| 292 | @Override |
| 293 | public void apply(GLCanvas canvas, int slotIndex, Rect target) { |
| 294 | canvas.translate(0, 0, RISING_DISTANCE * (1 - mProgress)); |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | public static class ScatteringAnimation extends SlotAnimation { |
| 299 | private int PHOTO_DISTANCE = 1000; |
| 300 | private RelativePosition mCenter; |
| 301 | |
| 302 | public ScatteringAnimation(RelativePosition center) { |
| 303 | mCenter = center; |
| 304 | } |
| 305 | |
| 306 | @Override |
| 307 | public void apply(GLCanvas canvas, int slotIndex, Rect target) { |
| 308 | canvas.translate( |
| 309 | (mCenter.getX() - target.centerX()) * (1 - mProgress), |
| 310 | (mCenter.getY() - target.centerY()) * (1 - mProgress), |
| 311 | slotIndex * PHOTO_DISTANCE * (1 - mProgress)); |
| 312 | canvas.setAlpha(mProgress); |
| 313 | } |
| 314 | } |
| 315 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 316 | private class MyGestureListener implements GestureDetector.OnGestureListener { |
| 317 | private boolean isDown; |
| 318 | |
| 319 | // We call the listener's onDown() when our onShowPress() is called and |
| 320 | // call the listener's onUp() when we receive any further event. |
| 321 | @Override |
| 322 | public void onShowPress(MotionEvent e) { |
| 323 | GLRoot root = getGLRoot(); |
| 324 | root.lockRenderThread(); |
| 325 | try { |
| 326 | if (isDown) return; |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 327 | Slot slot = mLayout.getSlotByPosition(e.getX(), e.getY()); |
| 328 | if (slot != null) { |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 329 | isDown = true; |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 330 | mListener.onDown(slot.index); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 331 | } |
| 332 | } finally { |
| 333 | root.unlockRenderThread(); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | private void cancelDown(boolean byLongPress) { |
| 338 | if (!isDown) return; |
| 339 | isDown = false; |
| 340 | mListener.onUp(byLongPress); |
| 341 | } |
| 342 | |
| 343 | @Override |
| 344 | public boolean onDown(MotionEvent e) { |
| 345 | return false; |
| 346 | } |
| 347 | |
| 348 | @Override |
| 349 | public boolean onFling(MotionEvent e1, |
| 350 | MotionEvent e2, float velocityX, float velocityY) { |
| 351 | cancelDown(false); |
| 352 | int scrollLimit = mLayout.getScrollLimit(); |
| 353 | if (scrollLimit == 0) return false; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 354 | mScroller.fling((int) -velocityY, 0, scrollLimit); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 355 | invalidate(); |
| 356 | return true; |
| 357 | } |
| 358 | |
| 359 | @Override |
| 360 | public boolean onScroll(MotionEvent e1, |
| 361 | MotionEvent e2, float distanceX, float distanceY) { |
| 362 | cancelDown(false); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 363 | int overDistance = mScroller.startScroll( |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 364 | Math.round(distanceY), 0, mLayout.getScrollLimit()); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 365 | invalidate(); |
| 366 | return true; |
| 367 | } |
| 368 | |
| 369 | @Override |
| 370 | public boolean onSingleTapUp(MotionEvent e) { |
| 371 | cancelDown(false); |
| 372 | if (mDownInScrolling) return true; |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 373 | Slot slot = mLayout.getSlotByPosition(e.getX(), e.getY()); |
| 374 | if (slot != null) { |
| 375 | mListener.onSingleTapUp(slot.index, slot.isTitle); |
| 376 | } |
| 377 | return true; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 378 | } |
| 379 | |
| 380 | @Override |
| 381 | public void onLongPress(MotionEvent e) { |
| 382 | cancelDown(true); |
| 383 | if (mDownInScrolling) return; |
| 384 | lockRendering(); |
| 385 | try { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 386 | Slot slot = mLayout.getSlotByPosition(e.getX(), e.getY()); |
| 387 | if (slot != null) { |
| 388 | mListener.onLongTap(slot.index, slot.isTitle); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 389 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 390 | } finally { |
| 391 | unlockRendering(); |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | public void setStartIndex(int index) { |
| 397 | mStartIndex = index; |
| 398 | } |
| 399 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 400 | public void setSlotCount(int[] count) { |
| 401 | mLayout.setSlotCount(count); |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 402 | |
| 403 | // mStartIndex is applied the first time setSlotCount is called. |
| 404 | if (mStartIndex != INDEX_NONE) { |
| 405 | setCenterIndex(mStartIndex); |
| 406 | mStartIndex = INDEX_NONE; |
| 407 | } |
| 408 | // Reset the scroll position to avoid scrolling over the updated limit. |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 409 | setScrollPosition(mScrollY); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 410 | } |
| 411 | |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 412 | public int getVisibleStart() { |
| 413 | return mLayout.getVisibleStart(); |
| 414 | } |
| 415 | |
| 416 | public int getVisibleEnd() { |
| 417 | return mLayout.getVisibleEnd(); |
| 418 | } |
| 419 | |
| 420 | public int getScrollX() { |
| 421 | return mScrollX; |
| 422 | } |
| 423 | |
| 424 | public int getScrollY() { |
| 425 | return mScrollY; |
| 426 | } |
| 427 | |
| 428 | public Rect getSlotRect(int slotIndex, GLView rootPane) { |
| 429 | // Get slot rectangle relative to this root pane. |
| 430 | Rect offset = new Rect(); |
| 431 | rootPane.getBoundsOf(this, offset); |
| 432 | Rect r = getSlotRect(slotIndex); |
huiyan | 5379627 | 2016-05-05 11:33:07 +0800 | [diff] [blame] | 433 | if (r != null) { |
| 434 | r.offset(offset.left - getScrollX(), |
| 435 | offset.top - getScrollY()); |
| 436 | return r; |
| 437 | } |
| 438 | return offset; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 439 | } |
| 440 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 441 | public int getTitleWidth() { |
| 442 | return mWidth; |
| 443 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 444 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 445 | // This Spec class is used to specify the size of each slot in the SlotView. |
| 446 | // There are two ways to do it: |
| 447 | // |
| 448 | // Specify colsLand, colsPort, and slotGap: they specify the number |
| 449 | // of rows in landscape/portrait mode and the gap between slots. The |
| 450 | // width and height of each slot is determined automatically. |
| 451 | // |
| 452 | // The initial value of -1 means they are not specified. |
| 453 | public static class Spec { |
| 454 | public int colsLand = -1; |
| 455 | public int colsPort = -1; |
| 456 | public int titleHeight = -1; |
| 457 | public int slotGapPort = -1; |
| 458 | public int slotGapLand = -1; |
| 459 | } |
| 460 | |
| 461 | public class Layout { |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 462 | private int mVisibleStart; |
| 463 | private int mVisibleEnd; |
| 464 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 465 | public int mSlotSize; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 466 | private int mSlotWidth; |
| 467 | private int mSlotHeight; |
| 468 | private int mSlotGap; |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 469 | private int[] mSlotCount; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 470 | |
| 471 | private Spec mSpec; |
| 472 | |
| 473 | private int mWidth; |
| 474 | private int mHeight; |
| 475 | |
| 476 | private int mUnitCount; |
| 477 | private int mContentLength; |
| 478 | private int mScrollPosition; |
| 479 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 480 | public void setSlotSpec(TimeLineSlotView.Spec spec) { |
| 481 | mSpec = spec; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 482 | } |
| 483 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 484 | public void setSlotCount(int[] count) { |
| 485 | mSlotCount = count; |
| 486 | if (mHeight != 0) { |
| 487 | initLayoutParameters(); |
| 488 | createSlots(); |
| 489 | } |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 490 | } |
| 491 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 492 | public int getSlotSize() { |
| 493 | return mSlotSize; |
| 494 | } |
| 495 | |
| 496 | public Rect getSlotRect(int index) { |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 497 | if (index >= mVisibleStart && index < mVisibleEnd && mVisibleEnd != 0) { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 498 | int height = 0, base = 0, top = 0; |
| 499 | for (int count : mSlotCount) { |
| 500 | if (index == base) { |
| 501 | return getSlotRect(getSlot(true, index, index, top)); |
| 502 | } |
| 503 | top += mSpec.titleHeight; |
| 504 | ++base; |
| 505 | |
| 506 | if (index >= base && index < base + count) { |
| 507 | return getSlotRect(getSlot(false, index, base, top)); |
| 508 | } |
| 509 | int rows = (count + mUnitCount - 1) / mUnitCount; |
| 510 | top += mSlotHeight * rows + mSlotGap * (rows > 0 ? rows - 1 : 0); |
| 511 | base += count; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 512 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 513 | } |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 514 | return null; |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 515 | } |
| 516 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 517 | private void initLayoutParameters() { |
| 518 | mUnitCount = (mWidth > mHeight) ? mSpec.colsLand : mSpec.colsPort; |
| 519 | mSlotGap = (mWidth > mHeight) ? mSpec.slotGapLand: mSpec.slotGapPort; |
| 520 | mSlotWidth = Math.round((mWidth - (mUnitCount - 1) * mSlotGap) / mUnitCount); |
| 521 | mSlotHeight = mSlotWidth; |
| 522 | if (mRenderer != null) { |
| 523 | mRenderer.onSlotSizeChanged(mSlotWidth, mSlotHeight); |
| 524 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 525 | } |
| 526 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 527 | private void setSize(int width, int height) { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 528 | if (width != mWidth || height != mHeight) { |
| 529 | mWidth = width; |
| 530 | mHeight = height; |
| 531 | initLayoutParameters(); |
| 532 | createSlots(); |
| 533 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 534 | } |
| 535 | |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 536 | public void setScrollPosition(int position) { |
| 537 | if (mScrollPosition == position) return; |
| 538 | mScrollPosition = position; |
| 539 | updateVisibleSlotRange(); |
| 540 | } |
| 541 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 542 | private Rect getSlotRect(Slot slot) { |
| 543 | int x, y, w, h; |
| 544 | if (slot.isTitle) { |
| 545 | x = 0; |
| 546 | y = slot.top; |
| 547 | w = mWidth; |
| 548 | h = mSpec.titleHeight; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 549 | } else { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 550 | x = slot.col * (mSlotWidth + mSlotGap); |
| 551 | y = slot.top; |
| 552 | w = mSlotWidth; |
| 553 | h = mSlotHeight; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 554 | } |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 555 | return new Rect(x, y, x + w, y + h); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 556 | } |
| 557 | |
| 558 | private synchronized void updateVisibleSlotRange() { |
| 559 | int position = mScrollPosition; |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 560 | if (mSlotCount != null) { |
| 561 | Slot begin = getSlotByPosition(0, mScrollPosition, true, false), |
| 562 | end = getSlotByPosition(0, mScrollPosition + mHeight, true, true); |
Kedi Xu | f91a4b2 | 2016-09-08 11:11:11 +0800 | [diff] [blame] | 563 | if (begin == null && end != null && end.index == 0) { |
| 564 | setVisibleRange(0, 0); |
| 565 | } else if (begin != null && end != null) { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 566 | setVisibleRange(begin.index, end.index); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 567 | } |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 568 | } |
| 569 | } |
| 570 | |
| 571 | private void setVisibleRange(int start, int end) { |
| 572 | if (start == mVisibleStart && end == mVisibleEnd) return; |
| 573 | if (start < end) { |
| 574 | mVisibleStart = start; |
| 575 | mVisibleEnd = end; |
| 576 | } else { |
| 577 | mVisibleStart = mVisibleEnd = 0; |
| 578 | } |
| 579 | if (mRenderer != null) { |
| 580 | mRenderer.onVisibleRangeChanged(mVisibleStart, mVisibleEnd); |
| 581 | } |
| 582 | } |
| 583 | |
| 584 | public int getVisibleStart() { |
| 585 | return mVisibleStart; |
| 586 | } |
| 587 | |
| 588 | public int getVisibleEnd() { |
| 589 | return mVisibleEnd; |
| 590 | } |
| 591 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 592 | private Slot getSlot(boolean isTitle, int index, int indexBase, int top) { |
| 593 | if (isTitle) { |
| 594 | return new Slot(true, index, 0, top); |
| 595 | } else { |
| 596 | int row = (index - indexBase) / mUnitCount; |
| 597 | return new Slot(false, index, (index - indexBase) % mUnitCount, |
| 598 | top + row * (mSlotHeight + mSlotGap)); |
| 599 | } |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 600 | } |
| 601 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 602 | public Slot getSlotByPosition(float x, float y, boolean rowStart, boolean roundUp) { |
| 603 | if (x < 0 || y < 0 || mSlotCount == null) { |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 604 | return null; |
| 605 | } |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 606 | int pos = (int) y, index = 0, top = 0; |
| 607 | for (int count : mSlotCount) { |
| 608 | int h = mSpec.titleHeight; |
| 609 | if (pos < top + h) { |
| 610 | if (roundUp) { |
| 611 | return getSlot(false, index + 1, index, top + h); |
| 612 | } else { |
| 613 | return getSlot(true, index, index, top); |
yongga | f84c053 | 2016-01-11 18:36:35 +0800 | [diff] [blame] | 614 | } |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 615 | } |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 616 | top += h; |
| 617 | ++index; |
| 618 | |
| 619 | int rows = (count + mUnitCount - 1) / mUnitCount; |
| 620 | h = mSlotHeight * rows + mSlotGap * (rows > 0 ? rows - 1 : 0); |
| 621 | if (pos < top + h) { |
| 622 | int row = ((int) pos - top) / (mSlotHeight + mSlotGap); |
| 623 | int col = 0; |
| 624 | if (roundUp) { |
| 625 | int idx = (row + 1) * mUnitCount; |
| 626 | if (idx > count) |
| 627 | idx = count + 1; |
| 628 | return getSlot(false, index + idx, index, top + mSlotHeight); |
| 629 | } |
| 630 | if (!rowStart) { |
| 631 | col = ((int) x) / (mSlotWidth + mSlotGap); |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 632 | if (row * mUnitCount + col >= count) { |
| 633 | break; |
| 634 | } |
| 635 | } |
| 636 | return getSlot(false, index + row * mUnitCount + col, index, top); |
| 637 | } |
| 638 | top += h; |
| 639 | index += count; |
| 640 | } |
| 641 | if (roundUp) { |
| 642 | return getSlot(false, index, index, top); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 643 | } |
| 644 | return null; |
| 645 | } |
| 646 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 647 | public Slot getSlotByPosition(float x, float y) { |
| 648 | return getSlotByPosition(x, mScrollPosition + y, false, false); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 649 | } |
| 650 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 651 | public int getScrollLimit() { |
| 652 | return Math.max(0, mContentLength - mHeight); |
| 653 | } |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 654 | |
| 655 | public void createSlots() { |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 656 | int height = 0; |
| 657 | int size = 0; |
| 658 | if (mSlotCount != null) { |
| 659 | for (int count : mSlotCount) { |
| 660 | int rows = (count + mUnitCount - 1) / mUnitCount; |
| 661 | height += mSlotHeight * rows + mSlotGap * (rows > 0 ? rows - 1 : 0); |
| 662 | size += 1 + count; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 663 | } |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 664 | height += mSpec.titleHeight * mSlotCount.length; |
| 665 | mContentLength = height; |
| 666 | mSlotSize = size; |
| 667 | updateVisibleSlotRange(); |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 668 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 669 | } |
| 670 | } |
| 671 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 672 | private static class Slot { |
| 673 | public boolean isTitle; |
| 674 | public int index; |
| 675 | public int col; |
| 676 | public int top; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 677 | |
Likai Ding | 941b459 | 2016-03-16 14:45:27 +0800 | [diff] [blame] | 678 | public Slot(boolean isTitle, int index, int col, int top) { |
| 679 | this.isTitle = isTitle; |
| 680 | this.index = index; |
| 681 | this.col = col; |
| 682 | this.top = top; |
Ravi Banuri | c963ee8 | 2015-10-20 19:41:58 +0530 | [diff] [blame] | 683 | } |
Ravi Banuri | 7a41c37 | 2015-10-19 16:03:03 +0530 | [diff] [blame] | 684 | } |
| 685 | } |