blob: fb84777118965929a9662506080847641434c94a [file] [log] [blame]
Angus Kong49b9ba22013-05-13 13:42:21 -07001/*
2 * Copyright (C) 2013 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
Angus Kong01054e92013-12-10 11:06:18 -080017package com.android.camera.widget;
Angus Kong49b9ba22013-05-13 13:42:21 -070018
19import android.animation.Animator;
Doris Liud70f0fb2013-10-04 13:22:09 -070020import android.animation.AnimatorSet;
Angus Konge78541f2013-05-28 12:30:33 -070021import android.animation.TimeInterpolator;
Angus Kong49b9ba22013-05-13 13:42:21 -070022import android.animation.ValueAnimator;
23import android.content.Context;
24import android.graphics.Canvas;
Angus Kongc195e7a2014-02-20 16:56:37 -080025import android.graphics.Point;
Angus Kong49b9ba22013-05-13 13:42:21 -070026import android.graphics.Rect;
Angus Konga6a6a732013-06-18 16:47:57 -070027import android.graphics.RectF;
Doris Liu8de13112013-08-23 13:35:24 -070028import android.net.Uri;
Angus Kongb21215a2013-09-20 18:41:46 -070029import android.os.Handler;
Sam Juddbcd11522014-03-12 15:48:18 -070030import android.os.SystemClock;
Angus Kong49b9ba22013-05-13 13:42:21 -070031import android.util.AttributeSet;
Doris Liu3179f6a2013-10-08 16:54:22 -070032import android.util.DisplayMetrics;
Sam Judd43bf03f2014-03-17 11:27:03 -070033import android.util.SparseArray;
Angus Kong49b9ba22013-05-13 13:42:21 -070034import android.view.MotionEvent;
35import android.view.View;
36import android.view.ViewGroup;
37import android.view.animation.DecelerateInterpolator;
38import android.widget.Scroller;
39
Sascha Haeberling88ef7662013-08-15 17:19:22 -070040import com.android.camera.CameraActivity;
Angus Kong2bca2102014-03-11 16:27:30 -070041import com.android.camera.debug.Log;
Angus Kong01054e92013-12-10 11:06:18 -080042import com.android.camera.filmstrip.DataAdapter;
Angus Kong62848152013-11-08 17:25:29 -080043import com.android.camera.filmstrip.FilmstripController;
Angus Kong01054e92013-12-10 11:06:18 -080044import com.android.camera.filmstrip.ImageData;
Angus Kong01054e92013-12-10 11:06:18 -080045import com.android.camera.ui.FilmstripGestureRecognizer;
46import com.android.camera.ui.ZoomView;
Angus Kongc195e7a2014-02-20 16:56:37 -080047import com.android.camera.util.CameraUtil;
Sascha Haeberling8e963a52013-08-06 11:43:02 -070048import com.android.camera2.R;
Sascha Haeberlingf1f51862013-07-31 11:28:21 -070049
Sam Judd43bf03f2014-03-17 11:27:03 -070050import java.util.ArrayDeque;
51import java.util.ArrayList;
Alan Newberger3f969c12013-08-23 10:10:30 -070052import java.util.Arrays;
Sam Judd43bf03f2014-03-17 11:27:03 -070053import java.util.List;
54import java.util.Queue;
Alan Newberger3f969c12013-08-23 10:10:30 -070055
Angus Kongb2510252013-12-10 22:58:15 -080056public class FilmstripView extends ViewGroup {
Angus Kong2bca2102014-03-11 16:27:30 -070057 private static final Log.Tag TAG = new Log.Tag("FilmstripView");
Angus Kongaf24d282013-05-20 16:27:02 -070058
Angus Kong49b9ba22013-05-13 13:42:21 -070059 private static final int BUFFER_SIZE = 5;
Angus Kong734598c2013-08-21 15:25:05 -070060 private static final int GEOMETRY_ADJUST_TIME_MS = 400;
Angus Kong8969a672013-08-19 23:25:48 -070061 private static final int SNAP_IN_CENTER_TIME_MS = 600;
Doris Liud70f0fb2013-10-04 13:22:09 -070062 private static final float FLING_COASTING_DURATION_S = 0.05f;
Doris Liu121022c2013-09-03 16:03:16 -070063 private static final int ZOOM_ANIMATION_DURATION_MS = 200;
Angus Kong7d2388d2013-09-18 23:56:01 -070064 private static final int CAMERA_PREVIEW_SWIPE_THRESHOLD = 300;
Angus Kong43596cd2013-12-04 23:38:00 -080065 private static final float FILM_STRIP_SCALE = 0.7f;
Angus Kong4ff5a1a2013-08-14 16:07:54 -070066 private static final float FULL_SCREEN_SCALE = 1f;
Doris Liu121022c2013-09-03 16:03:16 -070067
Sam Juddbcd11522014-03-12 15:48:18 -070068 // The min velocity at which the user must have moved their finger in
69 // pixels per millisecond to count a vertical gesture as a promote/demote
70 // at short vertical distances.
71 private static final float PROMOTE_VELOCITY = 3.5f;
72 // The min distance relative to this view's height the user must have
73 // moved their finger to count a vertical gesture as a promote/demote if
74 // they moved their finger at least at PROMOTE_VELOCITY.
75 private static final float VELOCITY_PROMOTE_HEIGHT_RATIO = 1/10f;
76 // The min distance relative to this view's height the user must have
77 // moved their finger to count a vertical gesture as a promote/demote if
78 // they moved their finger at less than PROMOTE_VELOCITY.
79 private static final float PROMOTE_HEIGHT_RATIO = 1/2f;
80
Doris Liu121022c2013-09-03 16:03:16 -070081 private static final float TOLERANCE = 0.1f;
Doris Liufb57df12013-05-14 14:37:46 -070082 // Only check for intercepting touch events within first 500ms
83 private static final int SWIPE_TIME_OUT = 500;
Doris Liud70f0fb2013-10-04 13:22:09 -070084 private static final int DECELERATION_FACTOR = 4;
Angus Kong49b9ba22013-05-13 13:42:21 -070085
Sascha Haeberling88ef7662013-08-15 17:19:22 -070086 private CameraActivity mActivity;
Angus Kong166e36f2013-12-03 08:54:42 -080087 private FilmstripGestureRecognizer mGestureRecognizer;
88 private FilmstripGestureRecognizer.Listener mGestureListener;
Angus Kong01054e92013-12-10 11:06:18 -080089 private DataAdapter mDataAdapter;
Angus Kongfaaee012013-12-07 00:38:46 -080090 private int mViewGapInPixel;
Angus Kong49b9ba22013-05-13 13:42:21 -070091 private final Rect mDrawArea = new Rect();
92
Angus Kong8969a672013-08-19 23:25:48 -070093 private final int mCurrentItem = (BUFFER_SIZE - 1) / 2;
Angus Kong49b9ba22013-05-13 13:42:21 -070094 private float mScale;
Angus Kong6d23f4c2013-05-22 16:26:07 -070095 private MyController mController;
Angus Kongaf24d282013-05-20 16:27:02 -070096 private int mCenterX = -1;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -080097 private final ViewItem[] mViewItem = new ViewItem[BUFFER_SIZE];
Angus Kong49b9ba22013-05-13 13:42:21 -070098
Angus Kong01054e92013-12-10 11:06:18 -080099 private FilmstripController.FilmstripListener mListener;
Doris Liu8de13112013-08-23 13:35:24 -0700100 private ZoomView mZoomView = null;
Angus Kong87f9a622013-05-16 16:59:39 -0700101
Doris Liufb57df12013-05-14 14:37:46 -0700102 private MotionEvent mDown;
103 private boolean mCheckToIntercept = true;
Doris Liufb57df12013-05-14 14:37:46 -0700104 private int mSlop;
Angus Konge78541f2013-05-28 12:30:33 -0700105 private TimeInterpolator mViewAnimInterpolator;
106
Angus Kong47721fa2013-08-14 15:24:00 -0700107 // This is true if and only if the user is scrolling,
108 private boolean mIsUserScrolling;
Angus Kong7d2388d2013-09-18 23:56:01 -0700109 private int mDataIdOnUserScrolling;
Doris Liu3179f6a2013-10-08 16:54:22 -0700110 private float mOverScaleFactor = 1f;
Angus Kong47721fa2013-08-14 15:24:00 -0700111
Angus Kong45671602014-01-13 15:06:17 -0800112 private boolean mFullScreenUIHidden = false;
Sam Judd43bf03f2014-03-17 11:27:03 -0700113 private SparseArray<Queue<View>> recycledViews = new SparseArray<Queue<View>>();
114
Angus Kong45671602014-01-13 15:06:17 -0800115
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700116 /**
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700117 * A helper class to tract and calculate the view coordination.
118 */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800119 private class ViewItem {
Angus Kong563a2892013-09-09 17:15:01 -0700120 private int mDataId;
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700121 /** The position of the left of the view in the whole filmstrip. */
Angus Kong49b9ba22013-05-13 13:42:21 -0700122 private int mLeftPosition;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800123 private final View mView;
Angus Kong1f9db2d2014-01-09 00:56:35 -0800124 private final ImageData mData;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800125 private final RectF mViewArea;
Andy Huiberscaca8c72013-12-13 15:53:43 -0800126 private boolean mMaximumBitmapRequested;
Angus Kong1f9db2d2014-01-09 00:56:35 -0800127
128 private ValueAnimator mTranslationXAnimator;
129 private ValueAnimator mTranslationYAnimator;
130 private ValueAnimator mAlphaAnimator;
Angus Kongedbba622013-09-12 14:49:48 -0700131
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700132 /**
133 * Constructor.
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700134 *
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800135 * @param id The id of the data from
Angus Kong01054e92013-12-10 11:06:18 -0800136 * {@link com.android.camera.filmstrip.DataAdapter}.
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700137 * @param v The {@code View} representing the data.
138 */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800139 public ViewItem(int id, View v, ImageData data) {
Angus Kong49b9ba22013-05-13 13:42:21 -0700140 v.setPivotX(0f);
141 v.setPivotY(0f);
Angus Kong563a2892013-09-09 17:15:01 -0700142 mDataId = id;
Angus Kong1f9db2d2014-01-09 00:56:35 -0800143 mData = data;
Angus Kong49b9ba22013-05-13 13:42:21 -0700144 mView = v;
Andy Huiberscaca8c72013-12-13 15:53:43 -0800145 mMaximumBitmapRequested = false;
Angus Kong49b9ba22013-05-13 13:42:21 -0700146 mLeftPosition = -1;
Angus Konga6a6a732013-06-18 16:47:57 -0700147 mViewArea = new RectF();
Angus Kong49b9ba22013-05-13 13:42:21 -0700148 }
149
Andy Huiberscaca8c72013-12-13 15:53:43 -0800150 public boolean isMaximumBitmapRequested() {
151 return mMaximumBitmapRequested;
152 }
153
154 public void setMaximumBitmapRequested() {
155 mMaximumBitmapRequested = true;
156 }
157
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800158 /**
159 * Returns the data id from
Angus Kong01054e92013-12-10 11:06:18 -0800160 * {@link com.android.camera.filmstrip.DataAdapter}.
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800161 */
Angus Kong563a2892013-09-09 17:15:01 -0700162 public int getId() {
163 return mDataId;
Angus Kong49b9ba22013-05-13 13:42:21 -0700164 }
165
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800166 /**
167 * Sets the data id from
Angus Kong01054e92013-12-10 11:06:18 -0800168 * {@link com.android.camera.filmstrip.DataAdapter}.
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800169 */
Angus Kong563a2892013-09-09 17:15:01 -0700170 public void setId(int id) {
171 mDataId = id;
Angus Kong87f9a622013-05-16 16:59:39 -0700172 }
173
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700174 /** Sets the left position of the view in the whole filmstrip. */
Angus Kong49b9ba22013-05-13 13:42:21 -0700175 public void setLeftPosition(int pos) {
176 mLeftPosition = pos;
177 }
178
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700179 /** Returns the left position of the view in the whole filmstrip. */
Angus Kong49b9ba22013-05-13 13:42:21 -0700180 public int getLeftPosition() {
181 return mLeftPosition;
182 }
183
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700184 /** Returns the translation of Y regarding the view scale. */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800185 public float getTranslationY() {
186 return mView.getTranslationY() / mScale;
Angus Kong49b9ba22013-05-13 13:42:21 -0700187 }
188
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700189 /** Returns the translation of X regarding the view scale. */
Doris Liu8de13112013-08-23 13:35:24 -0700190 public float getTranslationX() {
Angus Kong1f9db2d2014-01-09 00:56:35 -0800191 return mView.getTranslationX() / mScale;
Doris Liu8de13112013-08-23 13:35:24 -0700192 }
193
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700194 /** Sets the translation of Y regarding the view scale. */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800195 public void setTranslationY(float transY) {
196 mView.setTranslationY(transY * mScale);
Angus Kong87f9a622013-05-16 16:59:39 -0700197 }
198
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700199 /** Sets the translation of X regarding the view scale. */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800200 public void setTranslationX(float transX) {
201 mView.setTranslationX(transX * mScale);
Angus Kong49b9ba22013-05-13 13:42:21 -0700202 }
203
Angus Kong1f9db2d2014-01-09 00:56:35 -0800204 /** Forwarding of {@link android.view.View#setAlpha(float)}. */
205 public void setAlpha(float alpha) {
206 mView.setAlpha(alpha);
207 }
208
209 /** Forwarding of {@link android.view.View#getAlpha()}. */
210 public float getAlpha() {
211 return mView.getAlpha();
212 }
213
214 /** Forwarding of {@link android.view.View#getMeasuredWidth()}. */
215 public int getMeasuredWidth() {
216 return mView.getMeasuredWidth();
217 }
218
219 /**
220 * Animates the X translation of the view. Note: the animated value is
221 * not set directly by {@link android.view.View#setTranslationX(float)}
222 * because the value might be changed during in {@code onLayout()}.
223 * The animated value of X translation is specially handled in {@code
224 * layoutIn()}.
225 *
226 * @param targetX The final value.
227 * @param duration_ms The duration of the animation.
228 * @param interpolator Time interpolator.
229 */
Angus Kongedbba622013-09-12 14:49:48 -0700230 public void animateTranslationX(
231 float targetX, long duration_ms, TimeInterpolator interpolator) {
Angus Kong1f9db2d2014-01-09 00:56:35 -0800232 if (mTranslationXAnimator == null) {
233 mTranslationXAnimator = new ValueAnimator();
234 mTranslationXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
235 @Override
236 public void onAnimationUpdate(ValueAnimator valueAnimator) {
237 // We invalidate the filmstrip view instead of setting the
238 // translation X because the translation X of the view is
239 // touched in onLayout(). See the documentation of
240 // animateTranslationX().
241 invalidate();
242 }
243 });
244 }
245 runAnimation(mTranslationXAnimator, getTranslationX(), targetX, duration_ms,
246 interpolator);
247 }
248
249 /**
250 * Animates the Y translation of the view.
251 *
252 * @param targetY The final value.
253 * @param duration_ms The duration of the animation.
254 * @param interpolator Time interpolator.
255 */
256 public void animateTranslationY(
257 float targetY, long duration_ms, TimeInterpolator interpolator) {
258 if (mTranslationYAnimator == null) {
259 mTranslationYAnimator = new ValueAnimator();
260 mTranslationYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
261 @Override
262 public void onAnimationUpdate(ValueAnimator valueAnimator) {
263 setTranslationY((Float) valueAnimator.getAnimatedValue());
264 }
265 });
266 }
267 runAnimation(mTranslationYAnimator, getTranslationY(), targetY, duration_ms,
268 interpolator);
269 }
270
271 /**
272 * Animates the alpha value of the view.
273 *
274 * @param targetAlpha The final value.
275 * @param duration_ms The duration of the animation.
276 * @param interpolator Time interpolator.
277 */
278 public void animateAlpha(float targetAlpha, long duration_ms,
279 TimeInterpolator interpolator) {
280 if (mAlphaAnimator == null) {
281 mAlphaAnimator = new ValueAnimator();
282 mAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
283 @Override
284 public void onAnimationUpdate(ValueAnimator valueAnimator) {
285 ViewItem.this.setAlpha((Float) valueAnimator.getAnimatedValue());
286 }
287 });
288 }
289 runAnimation(mAlphaAnimator, getAlpha(), targetAlpha, duration_ms, interpolator);
290 }
291
292 private void runAnimation(final ValueAnimator animator, final float startValue,
293 final float targetValue, final long duration_ms,
294 final TimeInterpolator interpolator) {
295 if (startValue == targetValue) {
296 return;
297 }
298 animator.setInterpolator(interpolator);
299 animator.setDuration(duration_ms);
300 animator.setFloatValues(startValue, targetValue);
301 animator.start();
Angus Kongedbba622013-09-12 14:49:48 -0700302 }
303
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700304 /** Adjusts the translation of X regarding the view scale. */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800305 public void translateXScaledBy(float transX) {
306 setTranslationX(getTranslationX() + transX * mScale);
307 }
308
309 /**
310 * Forwarding of {@link android.view.View#getHitRect(android.graphics.Rect)}.
311 */
312 public void getHitRect(Rect rect) {
313 mView.getHitRect(rect);
Angus Kong9f02c872013-05-20 13:46:14 -0700314 }
315
Angus Kong49b9ba22013-05-13 13:42:21 -0700316 public int getCenterX() {
Angus Kong7d2388d2013-09-18 23:56:01 -0700317 return mLeftPosition + mView.getMeasuredWidth() / 2;
Angus Kong49b9ba22013-05-13 13:42:21 -0700318 }
319
Angus Kong1f9db2d2014-01-09 00:56:35 -0800320 /** Forwarding of {@link android.view.View#getVisibility()}. */
321 public int getVisibility() {
322 return mView.getVisibility();
323 }
324
325 /** Forwarding of {@link android.view.View#setVisibility(int)}. */
326 public void setVisibility(int visibility) {
327 mView.setVisibility(visibility);
328 }
329
330 /**
331 * Notifies the {@link com.android.camera.filmstrip.DataAdapter} to
332 * resize the view.
333 */
334 public void resizeView(Context context, int w, int h) {
335 mDataAdapter.resizeView(context, mDataId, mView, w, h);
336 }
337
338 /**
339 * Adds the view of the data to the view hierarchy if necessary.
340 */
341 public void addViewToHierarchy() {
342 if (indexOfChild(mView) < 0) {
343 mData.prepare();
344 addView(mView);
345 } else {
346 setVisibility(View.VISIBLE);
347 setAlpha(1f);
348 setTranslationX(0);
349 setTranslationY(0);
350 }
351 }
352
353 /**
354 * Removes from the hierarchy. Keeps the view in the view hierarchy if
355 * view type is {@code VIEW_TYPE_STICKY} and set to invisible instead.
356 *
357 * @param force {@code true} to remove the view from the hierarchy
358 * regardless of the view type.
359 */
360 public void removeViewFromHierarchy(boolean force) {
361 if (force || mData.getViewType() != ImageData.VIEW_TYPE_STICKY) {
362 removeView(mView);
Sam Judd4021c892014-03-17 12:57:50 -0700363 mData.recycle(mView);
Sam Judd43bf03f2014-03-17 11:27:03 -0700364 recycleView(mView, mDataId);
Angus Kong1f9db2d2014-01-09 00:56:35 -0800365 } else {
366 setVisibility(View.INVISIBLE);
367 }
368 }
369
370 /**
371 * Brings the view to front by
372 * {@link #bringChildToFront(android.view.View)}
373 */
374 public void bringViewToFront() {
375 bringChildToFront(mView);
Angus Kong49b9ba22013-05-13 13:42:21 -0700376 }
377
Doris Liu8de13112013-08-23 13:35:24 -0700378 /**
379 * The visual x position of this view, in pixels.
380 */
381 public float getX() {
382 return mView.getX();
383 }
384
385 /**
386 * The visual y position of this view, in pixels.
387 */
388 public float getY() {
389 return mView.getY();
390 }
391
Angus Kong1f9db2d2014-01-09 00:56:35 -0800392 /**
393 * Forwarding of {@link android.view.View#measure(int, int)}.
394 */
395 public void measure(int widthSpec, int heightSpec) {
396 mView.measure(widthSpec, heightSpec);
397 }
398
Angus Kong49b9ba22013-05-13 13:42:21 -0700399 private void layoutAt(int left, int top) {
400 mView.layout(left, top, left + mView.getMeasuredWidth(),
401 top + mView.getMeasuredHeight());
402 }
403
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700404 /**
Doris Liu8de13112013-08-23 13:35:24 -0700405 * The bounding rect of the view.
406 */
407 public RectF getViewRect() {
408 RectF r = new RectF();
409 r.left = mView.getX();
410 r.top = mView.getY();
411 r.right = r.left + mView.getWidth() * mView.getScaleX();
412 r.bottom = r.top + mView.getHeight() * mView.getScaleY();
413 return r;
414 }
415
416 /**
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700417 * Layouts the view in the area assuming the center of the area is at a
418 * specific point of the whole filmstrip.
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700419 *
420 * @param drawArea The area when filmstrip will show in.
421 * @param refCenter The absolute X coordination in the whole filmstrip
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700422 * of the center of {@code drawArea}.
Angus Kong1f9db2d2014-01-09 00:56:35 -0800423 * @param scale The scale of the view on the filmstrip.
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700424 */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800425 public void layoutWithTranslationX(Rect drawArea, int refCenter, float scale) {
426 final float translationX =
427 ((mTranslationXAnimator != null && mTranslationXAnimator.isRunning()) ?
428 (Float) mTranslationXAnimator.getAnimatedValue() : 0);
429 int left =
430 (int) (drawArea.centerX() + (mLeftPosition - refCenter + translationX) * scale);
Angus Kong87f9a622013-05-16 16:59:39 -0700431 int top = (int) (drawArea.centerY() - (mView.getMeasuredHeight() / 2) * scale);
Angus Kong49b9ba22013-05-13 13:42:21 -0700432 layoutAt(left, top);
433 mView.setScaleX(scale);
434 mView.setScaleY(scale);
Angus Konga6a6a732013-06-18 16:47:57 -0700435
436 // update mViewArea for touch detection.
437 int l = mView.getLeft();
438 int t = mView.getTop();
439 mViewArea.set(l, t,
Angus Kongf1582c92013-10-12 18:52:33 -0700440 l + mView.getMeasuredWidth() * scale,
441 t + mView.getMeasuredHeight() * scale);
Angus Konga6a6a732013-06-18 16:47:57 -0700442 }
443
Angus Kong4ff5a1a2013-08-14 16:07:54 -0700444 /** Returns true if the point is in the view. */
Angus Konga6a6a732013-06-18 16:47:57 -0700445 public boolean areaContains(float x, float y) {
446 return mViewArea.contains(x, y);
Angus Kong49b9ba22013-05-13 13:42:21 -0700447 }
Angus Kong8969a672013-08-19 23:25:48 -0700448
Doris Liu8de13112013-08-23 13:35:24 -0700449 /**
450 * Return the width of the view.
451 */
452 public int getWidth() {
453 return mView.getWidth();
454 }
455
Sam Judd29d9b3c2014-03-12 13:22:37 -0700456 /**
457 * Returns the position of the left edge of the view area content is drawn in.
458 */
459 public int getDrawAreaLeft() {
460 return Math.round(mViewArea.left);
461 }
462
Angus Kong1f9db2d2014-01-09 00:56:35 -0800463 public void copyAttributes(ViewItem item) {
Angus Kong8969a672013-08-19 23:25:48 -0700464 setLeftPosition(item.getLeftPosition());
Angus Kong1f9db2d2014-01-09 00:56:35 -0800465 // X
466 setTranslationX(item.getTranslationX());
467 if (item.mTranslationXAnimator != null) {
468 mTranslationXAnimator = item.mTranslationXAnimator;
469 mTranslationXAnimator.removeAllUpdateListeners();
470 mTranslationXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
471 @Override
472 public void onAnimationUpdate(ValueAnimator valueAnimator) {
473 // We invalidate the filmstrip view instead of setting the
474 // translation X because the translation X of the view is
475 // touched in onLayout(). See the documentation of
476 // animateTranslationX().
477 invalidate();
478 }
479 });
480 }
481 // Y
482 setTranslationY(item.getTranslationY());
483 if (item.mTranslationYAnimator != null) {
484 mTranslationYAnimator = item.mTranslationYAnimator;
485 mTranslationYAnimator.removeAllUpdateListeners();
486 mTranslationYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
487 @Override
488 public void onAnimationUpdate(ValueAnimator valueAnimator) {
489 setTranslationY((Float) valueAnimator.getAnimatedValue());
490 }
491 });
492 }
493 // Alpha
494 setAlpha(item.getAlpha());
495 if (item.mAlphaAnimator != null) {
496 mAlphaAnimator = item.mAlphaAnimator;
497 mAlphaAnimator.removeAllUpdateListeners();
498 mAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
499 @Override
500 public void onAnimationUpdate(ValueAnimator valueAnimator) {
501 ViewItem.this.setAlpha((Float) valueAnimator.getAnimatedValue());
502 }
503 });
504 }
Angus Kong8969a672013-08-19 23:25:48 -0700505 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800506
Doris Liu121022c2013-09-03 16:03:16 -0700507 /**
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800508 * Apply a scale factor (i.e. {@code postScale}) on top of current scale at
509 * pivot point ({@code focusX}, {@code focusY}). Visually it should be the
Doris Liu121022c2013-09-03 16:03:16 -0700510 * same as post concatenating current view's matrix with specified scale.
511 */
512 void postScale(float focusX, float focusY, float postScale, int viewportWidth,
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800513 int viewportHeight) {
Angus Kong1f9db2d2014-01-09 00:56:35 -0800514 float transX = mView.getTranslationX();
515 float transY = mView.getTranslationY();
Doris Liu121022c2013-09-03 16:03:16 -0700516 // Pivot point is top left of the view, so we need to translate
517 // to scale around focus point
518 transX -= (focusX - getX()) * (postScale - 1f);
519 transY -= (focusY - getY()) * (postScale - 1f);
520 float scaleX = mView.getScaleX() * postScale;
521 float scaleY = mView.getScaleY() * postScale;
522 updateTransform(transX, transY, scaleX, scaleY, viewportWidth,
523 viewportHeight);
524 }
Doris Liu8de13112013-08-23 13:35:24 -0700525
526 void updateTransform(float transX, float transY, float scaleX, float scaleY,
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800527 int viewportWidth, int viewportHeight) {
Doris Liu121022c2013-09-03 16:03:16 -0700528 float left = transX + mView.getLeft();
529 float top = transY + mView.getTop();
530 RectF r = ZoomView.adjustToFitInBounds(new RectF(left, top,
531 left + mView.getWidth() * scaleX,
532 top + mView.getHeight() * scaleY),
Doris Liu8de13112013-08-23 13:35:24 -0700533 viewportWidth, viewportHeight);
534 mView.setScaleX(scaleX);
535 mView.setScaleY(scaleY);
536 transX = r.left - mView.getLeft();
537 transY = r.top - mView.getTop();
538 mView.setTranslationX(transX);
539 mView.setTranslationY(transY);
540 }
541
Doris Liuf9ddb602013-08-30 16:20:58 -0700542 void resetTransform() {
543 mView.setScaleX(FULL_SCREEN_SCALE);
544 mView.setScaleY(FULL_SCREEN_SCALE);
545 mView.setTranslationX(0f);
546 mView.setTranslationY(0f);
547 }
Angus Kongf1582c92013-10-12 18:52:33 -0700548
549 @Override
550 public String toString() {
551 return "DataID = " + mDataId + "\n\t left = " + mLeftPosition
552 + "\n\t viewArea = " + mViewArea
553 + "\n\t centerX = " + getCenterX()
554 + "\n\t view MeasuredSize = "
555 + mView.getMeasuredWidth() + ',' + mView.getMeasuredHeight()
556 + "\n\t view Size = " + mView.getWidth() + ',' + mView.getHeight()
557 + "\n\t view scale = " + mView.getScaleX();
558 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700559 }
560
Angus Kong62848152013-11-08 17:25:29 -0800561 /** Constructor. */
562 public FilmstripView(Context context) {
Angus Kong49b9ba22013-05-13 13:42:21 -0700563 super(context);
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700564 init((CameraActivity) context);
Angus Kong49b9ba22013-05-13 13:42:21 -0700565 }
566
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700567 /** Constructor. */
Angus Kong62848152013-11-08 17:25:29 -0800568 public FilmstripView(Context context, AttributeSet attrs) {
Angus Kong49b9ba22013-05-13 13:42:21 -0700569 super(context, attrs);
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700570 init((CameraActivity) context);
Angus Kong49b9ba22013-05-13 13:42:21 -0700571 }
572
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700573 /** Constructor. */
Angus Kong62848152013-11-08 17:25:29 -0800574 public FilmstripView(Context context, AttributeSet attrs, int defStyle) {
Angus Kong49b9ba22013-05-13 13:42:21 -0700575 super(context, attrs, defStyle);
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700576 init((CameraActivity) context);
Angus Kong49b9ba22013-05-13 13:42:21 -0700577 }
578
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700579 private void init(CameraActivity cameraActivity) {
Angus Kong49b9ba22013-05-13 13:42:21 -0700580 setWillNotDraw(false);
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700581 mActivity = cameraActivity;
Angus Kong49b9ba22013-05-13 13:42:21 -0700582 mScale = 1.0f;
Angus Kong7d2388d2013-09-18 23:56:01 -0700583 mDataIdOnUserScrolling = 0;
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700584 mController = new MyController(cameraActivity);
Angus Konga6a6a732013-06-18 16:47:57 -0700585 mViewAnimInterpolator = new DecelerateInterpolator();
Doris Liu8de13112013-08-23 13:35:24 -0700586 mZoomView = new ZoomView(cameraActivity);
587 mZoomView.setVisibility(GONE);
588 addView(mZoomView);
589
Angus Kong166e36f2013-12-03 08:54:42 -0800590 mGestureListener = new MyGestureReceiver();
Angus Kong49b9ba22013-05-13 13:42:21 -0700591 mGestureRecognizer =
Angus Kong166e36f2013-12-03 08:54:42 -0800592 new FilmstripGestureRecognizer(cameraActivity, mGestureListener);
Doris Liufb57df12013-05-14 14:37:46 -0700593 mSlop = (int) getContext().getResources().getDimension(R.dimen.pie_touch_slop);
Doris Liu3179f6a2013-10-08 16:54:22 -0700594 DisplayMetrics metrics = new DisplayMetrics();
595 mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
596 // Allow over scaling because on high density screens, pixels are too
597 // tiny to clearly see the details at 1:1 zoom. We should not scale
598 // beyond what 1:1 would look like on a medium density screen, as
599 // scaling beyond that would only yield blur.
Andy Huiberscaca8c72013-12-13 15:53:43 -0800600 mOverScaleFactor = (float) metrics.densityDpi / (float) DisplayMetrics.DENSITY_HIGH;
Doris Liu3179f6a2013-10-08 16:54:22 -0700601 if (mOverScaleFactor < 1f) {
602 mOverScaleFactor = 1f;
603 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700604 }
605
Sam Judd43bf03f2014-03-17 11:27:03 -0700606 private void recycleView(View view, int dataId) {
607 final int viewType = mDataAdapter.getItemViewType(dataId);
608 Queue<View> recycledViewsForType = recycledViews.get(viewType);
609 if (recycledViewsForType == null) {
610 recycledViewsForType = new ArrayDeque<View>();
611 recycledViews.put(viewType, recycledViewsForType);
612 }
613 recycledViewsForType.offer(view);
614 }
615
616 private View getRecycledView(int dataId) {
617 final int viewType = mDataAdapter.getItemViewType(dataId);
618 Queue<View> recycledViewsForType = recycledViews.get(viewType);
619 View result = null;
620 if (recycledViewsForType != null) {
621 result = recycledViewsForType.poll();
622 }
623 return result;
624 }
625
Angus Kong8e5e4ee2013-07-30 11:36:00 -0700626 /**
627 * Returns the controller.
628 *
629 * @return The {@code Controller}.
630 */
Angus Kong62848152013-11-08 17:25:29 -0800631 public FilmstripController getController() {
Angus Kong6d23f4c2013-05-22 16:26:07 -0700632 return mController;
633 }
634
Sam Judd29d9b3c2014-03-12 13:22:37 -0700635 /**
636 * Returns the draw area width of the current item.
637 */
638 public int getCurrentItemLeft() {
639 return mViewItem[mCurrentItem].getDrawAreaLeft();
640 }
641
Angus Kong01054e92013-12-10 11:06:18 -0800642 private void setListener(FilmstripController.FilmstripListener l) {
Angus Kong87f9a622013-05-16 16:59:39 -0700643 mListener = l;
644 }
645
Angus Kong62848152013-11-08 17:25:29 -0800646 private void setViewGap(int viewGap) {
Angus Kongfaaee012013-12-07 00:38:46 -0800647 mViewGapInPixel = viewGap;
Angus Konge78541f2013-05-28 12:30:33 -0700648 }
649
Angus Kong17e669d2013-09-05 16:12:22 -0700650 /**
651 * Checks if the data is at the center.
652 *
653 * @param id The id of the data to check.
654 * @return {@code True} if the data is currently at the center.
655 */
Erin Dahlgren3044d8c2013-10-10 18:23:45 -0700656 private boolean isDataAtCenter(int id) {
Angus Kong8969a672013-08-19 23:25:48 -0700657 if (mViewItem[mCurrentItem] == null) {
Angus Kong3d0b6912013-08-09 13:05:55 -0700658 return false;
659 }
Angus Kong563a2892013-09-09 17:15:01 -0700660 if (mViewItem[mCurrentItem].getId() == id
Angus Kongec2fb472013-12-10 01:06:43 -0800661 && isCurrentItemCentered()) {
Angus Kong49b9ba22013-05-13 13:42:21 -0700662 return true;
663 }
664 return false;
665 }
666
Angus Kong653c43b2013-08-21 18:28:43 -0700667 private void measureViewItem(ViewItem item, int boundWidth, int boundHeight) {
Angus Kong563a2892013-09-09 17:15:01 -0700668 int id = item.getId();
Angus Kong01054e92013-12-10 11:06:18 -0800669 ImageData imageData = mDataAdapter.getImageData(id);
670 if (imageData == null) {
Erin Dahlgrenf9ef1ae2013-10-14 17:35:23 -0700671 Log.e(TAG, "trying to measure a null item");
672 return;
673 }
674
Angus Kongc195e7a2014-02-20 16:56:37 -0800675 Point dim = CameraUtil.resizeToFill(imageData.getWidth(), imageData.getHeight(),
676 imageData.getRotation(), boundWidth, boundHeight);
Angus Kong653c43b2013-08-21 18:28:43 -0700677
Angus Kongc195e7a2014-02-20 16:56:37 -0800678 item.measure(MeasureSpec.makeMeasureSpec(dim.x, MeasureSpec.EXACTLY),
679 MeasureSpec.makeMeasureSpec(dim.y, MeasureSpec.EXACTLY));
Angus Kong653c43b2013-08-21 18:28:43 -0700680 }
681
Angus Kong49b9ba22013-05-13 13:42:21 -0700682 @Override
683 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
684 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
685
686 int boundWidth = MeasureSpec.getSize(widthMeasureSpec);
687 int boundHeight = MeasureSpec.getSize(heightMeasureSpec);
Angus Kongb477b422013-07-09 09:30:32 -0700688 if (boundWidth == 0 || boundHeight == 0) {
689 // Either width or height is unknown, can't measure children yet.
690 return;
691 }
692
Angus Kong8969a672013-08-19 23:25:48 -0700693 for (ViewItem item : mViewItem) {
Angus Kong653c43b2013-08-21 18:28:43 -0700694 if (item != null) {
695 measureViewItem(item, boundWidth, boundHeight);
Sascha Haeberling88ef7662013-08-15 17:19:22 -0700696 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700697 }
Angus Kong7d2388d2013-09-18 23:56:01 -0700698 clampCenterX();
Doris Liu8de13112013-08-23 13:35:24 -0700699 // Measure zoom view
Angus Kongc02b13a2013-11-12 11:50:57 -0800700 mZoomView.measure(MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY),
701 MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.EXACTLY));
Angus Kong49b9ba22013-05-13 13:42:21 -0700702 }
703
704 private int findTheNearestView(int pointX) {
705
706 int nearest = 0;
Angus Kong8969a672013-08-19 23:25:48 -0700707 // Find the first non-null ViewItem.
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700708 while (nearest < BUFFER_SIZE
Angus Kong8969a672013-08-19 23:25:48 -0700709 && (mViewItem[nearest] == null || mViewItem[nearest].getLeftPosition() == -1)) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700710 nearest++;
711 }
Angus Kong8969a672013-08-19 23:25:48 -0700712 // No existing available ViewItem
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700713 if (nearest == BUFFER_SIZE) {
714 return -1;
715 }
Erin Dahlgren3044d8c2013-10-10 18:23:45 -0700716
Angus Kong8969a672013-08-19 23:25:48 -0700717 int min = Math.abs(pointX - mViewItem[nearest].getCenterX());
Angus Kong49b9ba22013-05-13 13:42:21 -0700718
Angus Kong8969a672013-08-19 23:25:48 -0700719 for (int itemID = nearest + 1; itemID < BUFFER_SIZE && mViewItem[itemID] != null; itemID++) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700720 // Not measured yet.
Angus Kong8969a672013-08-19 23:25:48 -0700721 if (mViewItem[itemID].getLeftPosition() == -1)
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700722 continue;
Angus Kong49b9ba22013-05-13 13:42:21 -0700723
Angus Kong8969a672013-08-19 23:25:48 -0700724 int c = mViewItem[itemID].getCenterX();
Angus Kong49b9ba22013-05-13 13:42:21 -0700725 int dist = Math.abs(pointX - c);
726 if (dist < min) {
727 min = dist;
Angus Kong8969a672013-08-19 23:25:48 -0700728 nearest = itemID;
Angus Kong49b9ba22013-05-13 13:42:21 -0700729 }
730 }
731 return nearest;
732 }
733
Angus Kong8969a672013-08-19 23:25:48 -0700734 private ViewItem buildItemFromData(int dataID) {
Angus Kong01054e92013-12-10 11:06:18 -0800735 ImageData data = mDataAdapter.getImageData(dataID);
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700736 if (data == null) {
737 return null;
738 }
Andy Huiberscaca8c72013-12-13 15:53:43 -0800739
Sam Judd4021c892014-03-17 12:57:50 -0700740 int width = Math.round(mScale * getWidth());
741 int height = Math.round(mScale * getHeight());
742 mDataAdapter.suggestViewSizeBound(width, height);
743
Angus Kong1f9db2d2014-01-09 00:56:35 -0800744 data.prepare();
Sam Judd43bf03f2014-03-17 11:27:03 -0700745 View recycled = getRecycledView(dataID);
746 View v = mDataAdapter.getView(mActivity, recycled, dataID);
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700747 if (v == null) {
748 return null;
749 }
Angus Kong1f9db2d2014-01-09 00:56:35 -0800750 ViewItem item = new ViewItem(dataID, v, data);
751 item.addViewToHierarchy();
Angus Kong8969a672013-08-19 23:25:48 -0700752 return item;
Angus Kong49b9ba22013-05-13 13:42:21 -0700753 }
754
Andy Huiberscaca8c72013-12-13 15:53:43 -0800755 private void checkItemAtMaxSize() {
756 ViewItem item = mViewItem[mCurrentItem];
757 if (item.isMaximumBitmapRequested()) {
758 return;
759 };
760 item.setMaximumBitmapRequested();
761 // Request full size bitmap, or max that DataAdapter will create.
762 int id = item.getId();
763 int h = mDataAdapter.getImageData(id).getHeight();
764 int w = mDataAdapter.getImageData(id).getWidth();
Angus Kong1f9db2d2014-01-09 00:56:35 -0800765 item.resizeView(mActivity, w, h);
Andy Huiberscaca8c72013-12-13 15:53:43 -0800766 }
767
Angus Kong8969a672013-08-19 23:25:48 -0700768 private void removeItem(int itemID) {
769 if (itemID >= mViewItem.length || mViewItem[itemID] == null) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700770 return;
771 }
Angus Kong01054e92013-12-10 11:06:18 -0800772 ImageData data = mDataAdapter.getImageData(mViewItem[itemID].getId());
Erin Dahlgrenf9ef1ae2013-10-14 17:35:23 -0700773 if (data == null) {
774 Log.e(TAG, "trying to remove a null item");
775 return;
776 }
Angus Kong1f9db2d2014-01-09 00:56:35 -0800777 mViewItem[itemID].removeViewFromHierarchy(false);
Angus Kong8969a672013-08-19 23:25:48 -0700778 mViewItem[itemID] = null;
Angus Kong750e8ec2013-05-06 10:42:28 -0700779 }
780
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700781 /**
782 * We try to keep the one closest to the center of the screen at position
Angus Kong8969a672013-08-19 23:25:48 -0700783 * mCurrentItem.
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700784 */
Angus Kong49b9ba22013-05-13 13:42:21 -0700785 private void stepIfNeeded() {
Angus Kong62848152013-11-08 17:25:29 -0800786 if (!inFilmstrip() && !inFullScreen()) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700787 // The good timing to step to the next view is when everything is
Sascha Haeberling37f36112013-08-06 14:31:52 -0700788 // not in transition.
Angus Konga6a6a732013-06-18 16:47:57 -0700789 return;
790 }
Angus Kong53aedc02013-10-10 16:46:04 -0700791 final int nearest = findTheNearestView(mCenterX);
Angus Kong49b9ba22013-05-13 13:42:21 -0700792 // no change made.
Angus Kong53aedc02013-10-10 16:46:04 -0700793 if (nearest == -1 || nearest == mCurrentItem) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700794 return;
Angus Kong53aedc02013-10-10 16:46:04 -0700795 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700796
Angus Kongfaaee012013-12-07 00:38:46 -0800797 int prevDataId = (mViewItem[mCurrentItem] == null ? -1 : mViewItem[mCurrentItem].getId());
Angus Kong53aedc02013-10-10 16:46:04 -0700798 final int adjust = nearest - mCurrentItem;
Angus Kong49b9ba22013-05-13 13:42:21 -0700799 if (adjust > 0) {
800 for (int k = 0; k < adjust; k++) {
Angus Kong8969a672013-08-19 23:25:48 -0700801 removeItem(k);
Angus Kong49b9ba22013-05-13 13:42:21 -0700802 }
803 for (int k = 0; k + adjust < BUFFER_SIZE; k++) {
Angus Kong8969a672013-08-19 23:25:48 -0700804 mViewItem[k] = mViewItem[k + adjust];
Angus Kong49b9ba22013-05-13 13:42:21 -0700805 }
806 for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) {
Angus Kong8969a672013-08-19 23:25:48 -0700807 mViewItem[k] = null;
808 if (mViewItem[k - 1] != null) {
Angus Kong563a2892013-09-09 17:15:01 -0700809 mViewItem[k] = buildItemFromData(mViewItem[k - 1].getId() + 1);
Angus Kong750e8ec2013-05-06 10:42:28 -0700810 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700811 }
Angus Kong53aedc02013-10-10 16:46:04 -0700812 adjustChildZOrder();
Angus Kong49b9ba22013-05-13 13:42:21 -0700813 } else {
814 for (int k = BUFFER_SIZE - 1; k >= BUFFER_SIZE + adjust; k--) {
Angus Kong8969a672013-08-19 23:25:48 -0700815 removeItem(k);
Angus Kong49b9ba22013-05-13 13:42:21 -0700816 }
817 for (int k = BUFFER_SIZE - 1; k + adjust >= 0; k--) {
Angus Kong8969a672013-08-19 23:25:48 -0700818 mViewItem[k] = mViewItem[k + adjust];
Angus Kong49b9ba22013-05-13 13:42:21 -0700819 }
820 for (int k = -1 - adjust; k >= 0; k--) {
Angus Kong8969a672013-08-19 23:25:48 -0700821 mViewItem[k] = null;
822 if (mViewItem[k + 1] != null) {
Angus Kong563a2892013-09-09 17:15:01 -0700823 mViewItem[k] = buildItemFromData(mViewItem[k + 1].getId() - 1);
Angus Kong750e8ec2013-05-06 10:42:28 -0700824 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700825 }
826 }
Angus Kong53aedc02013-10-10 16:46:04 -0700827 invalidate();
Sascha Haeberling37f36112013-08-06 14:31:52 -0700828 if (mListener != null) {
Angus Kongfaaee012013-12-07 00:38:46 -0800829 mListener.onDataFocusChanged(prevDataId, mViewItem[mCurrentItem].getId());
Sam Juddde3e9ab2014-03-17 13:07:22 -0700830 final int firstVisible = mViewItem[mCurrentItem].getId() - 2;
831 final int visibleItemCount = firstVisible + BUFFER_SIZE;
832 final int totalItemCount = mDataAdapter.getTotalNumber();
833 mListener.onScroll(firstVisible, visibleItemCount, totalItemCount);
Sascha Haeberling37f36112013-08-06 14:31:52 -0700834 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700835 }
836
Angus Kong7d2388d2013-09-18 23:56:01 -0700837 /**
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800838 * Check the bounds of {@code mCenterX}. Always call this function after: 1.
839 * Any changes to {@code mCenterX}. 2. Any size change of the view items.
Angus Kong1f36cf12013-10-01 16:58:52 -0700840 *
841 * @return Whether clamp happened.
Angus Kong7d2388d2013-09-18 23:56:01 -0700842 */
Angus Kong1f36cf12013-10-01 16:58:52 -0700843 private boolean clampCenterX() {
Angus Kong8969a672013-08-19 23:25:48 -0700844 ViewItem curr = mViewItem[mCurrentItem];
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700845 if (curr == null) {
Angus Kong1f36cf12013-10-01 16:58:52 -0700846 return false;
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700847 }
Angus Kong49b9ba22013-05-13 13:42:21 -0700848
Angus Kong5fbe1342013-09-10 15:42:46 -0700849 boolean stopScroll = false;
Angus Kong166e36f2013-12-03 08:54:42 -0800850 if (curr.getId() == 1 && mCenterX < curr.getCenterX() && mDataIdOnUserScrolling > 1 &&
Angus Kong01054e92013-12-10 11:06:18 -0800851 mDataAdapter.getImageData(0).getViewType() == ImageData.VIEW_TYPE_STICKY &&
Angus Kong166e36f2013-12-03 08:54:42 -0800852 mController.isScrolling()) {
Angus Kong5fbe1342013-09-10 15:42:46 -0700853 stopScroll = true;
Angus Kong166e36f2013-12-03 08:54:42 -0800854 } else {
855 if (curr.getId() == 0 && mCenterX < curr.getCenterX()) {
856 // Stop at the first ViewItem.
857 stopScroll = true;
858 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800859 }
860 if (curr.getId() == mDataAdapter.getTotalNumber() - 1
Angus Kongaf24d282013-05-20 16:27:02 -0700861 && mCenterX > curr.getCenterX()) {
Angus Kong5fbe1342013-09-10 15:42:46 -0700862 // Stop at the end.
863 stopScroll = true;
864 }
865
Angus Kong7d2388d2013-09-18 23:56:01 -0700866 if (stopScroll) {
Angus Kongaf24d282013-05-20 16:27:02 -0700867 mCenterX = curr.getCenterX();
Angus Kong49b9ba22013-05-13 13:42:21 -0700868 }
Erin Dahlgren3044d8c2013-10-10 18:23:45 -0700869
Angus Kong1f36cf12013-10-01 16:58:52 -0700870 return stopScroll;
Angus Kong49b9ba22013-05-13 13:42:21 -0700871 }
872
Angus Kong53aedc02013-10-10 16:46:04 -0700873 /**
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800874 * Reorders the child views to be consistent with their data ID. This method
875 * should be called after adding/removing views.
Angus Kong53aedc02013-10-10 16:46:04 -0700876 */
Angus Konga6a6a732013-06-18 16:47:57 -0700877 private void adjustChildZOrder() {
878 for (int i = BUFFER_SIZE - 1; i >= 0; i--) {
Angus Kong8969a672013-08-19 23:25:48 -0700879 if (mViewItem[i] == null)
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700880 continue;
Angus Kong1f9db2d2014-01-09 00:56:35 -0800881 mViewItem[i].bringViewToFront();
Angus Konga6a6a732013-06-18 16:47:57 -0700882 }
Angus Kong563a2892013-09-09 17:15:01 -0700883 // ZoomView is a special case to always be in the front.
884 bringChildToFront(mZoomView);
Angus Konga6a6a732013-06-18 16:47:57 -0700885 }
886
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700887 /**
Angus Kong32226b92013-11-13 08:50:51 -0800888 * Returns the ID of the current item, or -1 if there is no data.
Angus Kongc02b13a2013-11-12 11:50:57 -0800889 */
Angus Kong62848152013-11-08 17:25:29 -0800890 private int getCurrentId() {
Angus Kong8969a672013-08-19 23:25:48 -0700891 ViewItem current = mViewItem[mCurrentItem];
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700892 if (current == null) {
893 return -1;
894 }
Angus Kong563a2892013-09-09 17:15:01 -0700895 return current.getId();
Sascha Haeberlingf1f51862013-07-31 11:28:21 -0700896 }
897
898 /**
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800899 * Keep the current item in the center. This functions does not check if the
900 * current item is null.
Angus Kong7d2388d2013-09-18 23:56:01 -0700901 */
Angus Kong47721fa2013-08-14 15:24:00 -0700902 private void snapInCenter() {
Angus Kongec2fb472013-12-10 01:06:43 -0800903 final ViewItem currItem = mViewItem[mCurrentItem];
904 if (currItem == null) {
905 return;
906 }
907 final int currentViewCenter = currItem.getCenterX();
Angus Kong7d2388d2013-09-18 23:56:01 -0700908 if (mController.isScrolling() || mIsUserScrolling
Angus Kongec2fb472013-12-10 01:06:43 -0800909 || isCurrentItemCentered()) {
Angus Kong47721fa2013-08-14 15:24:00 -0700910 return;
911 }
Angus Kong7d2388d2013-09-18 23:56:01 -0700912
913 int snapInTime = (int) (SNAP_IN_CENTER_TIME_MS
914 * ((float) Math.abs(mCenterX - currentViewCenter))
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800915 / mDrawArea.width());
Angus Kong7d2388d2013-09-18 23:56:01 -0700916 mController.scrollToPosition(currentViewCenter,
917 snapInTime, false);
Angus Kongec2fb472013-12-10 01:06:43 -0800918 if (isViewTypeSticky(currItem) && !mController.isScaling() && mScale != FULL_SCREEN_SCALE) {
Doris Liu742cd5b2013-09-12 16:17:43 -0700919 // Now going to full screen camera
Angus Kong734598c2013-08-21 15:25:05 -0700920 mController.goToFullScreen();
Angus Kong47721fa2013-08-14 15:24:00 -0700921 }
922 }
923
Angus Kong80af7dd2013-08-22 21:00:19 -0700924 /**
925 * Translates the {@link ViewItem} on the left of the current one to match
926 * the full-screen layout. In full-screen, we show only one {@link ViewItem}
927 * which occupies the whole screen. The other left ones are put on the left
Angus Kongd028dd42013-09-13 18:09:18 -0700928 * side in full scales. Does nothing if there's no next item.
Angus Kong80af7dd2013-08-22 21:00:19 -0700929 *
930 * @param currItem The item ID of the current one to be translated.
931 * @param drawAreaWidth The width of the current draw area.
932 * @param scaleFraction A {@code float} between 0 and 1. 0 if the current
Angus Kong1f9db2d2014-01-09 00:56:35 -0800933 * scale is {@code FILM_STRIP_SCALE}. 1 if the current scale is
934 * {@code FULL_SCREEN_SCALE}.
Angus Kong80af7dd2013-08-22 21:00:19 -0700935 */
936 private void translateLeftViewItem(
937 int currItem, int drawAreaWidth, float scaleFraction) {
938 if (currItem < 0 || currItem > BUFFER_SIZE - 1) {
939 Log.e(TAG, "currItem id out of bound.");
940 return;
941 }
942
943 final ViewItem curr = mViewItem[currItem];
944 final ViewItem next = mViewItem[currItem + 1];
945 if (curr == null || next == null) {
Angus Kong7d2388d2013-09-18 23:56:01 -0700946 Log.e(TAG, "Invalid view item (curr or next == null). curr = "
947 + currItem);
Angus Kong80af7dd2013-08-22 21:00:19 -0700948 return;
949 }
950
951 final int currCenterX = curr.getCenterX();
952 final int nextCenterX = next.getCenterX();
953 final int translate = (int) ((nextCenterX - drawAreaWidth
Sascha Haeberling280fd3e2013-11-21 13:52:15 -0800954 - currCenterX) * scaleFraction);
Angus Kong80af7dd2013-08-22 21:00:19 -0700955
Angus Kong1f9db2d2014-01-09 00:56:35 -0800956 curr.layoutWithTranslationX(mDrawArea, mCenterX, mScale);
957 curr.setAlpha(1f);
Sam Judd4021c892014-03-17 12:57:50 -0700958 curr.setVisibility(VISIBLE);
Angus Kong80af7dd2013-08-22 21:00:19 -0700959
960 if (inFullScreen()) {
Angus Kong1f9db2d2014-01-09 00:56:35 -0800961 curr.setTranslationX(translate * (mCenterX - currCenterX) / (nextCenterX - currCenterX));
Angus Kong80af7dd2013-08-22 21:00:19 -0700962 } else {
Angus Kong1f9db2d2014-01-09 00:56:35 -0800963 curr.setTranslationX(translate);
Angus Kong80af7dd2013-08-22 21:00:19 -0700964 }
965 }
966
967 /**
968 * Fade out the {@link ViewItem} on the right of the current one in
Angus Kongd028dd42013-09-13 18:09:18 -0700969 * full-screen layout. Does nothing if there's no previous item.
Angus Kong80af7dd2013-08-22 21:00:19 -0700970 *
Angus Kong1f9db2d2014-01-09 00:56:35 -0800971 * @param currItemId The ID of the item to fade.
Angus Kong80af7dd2013-08-22 21:00:19 -0700972 */
Angus Kong1f9db2d2014-01-09 00:56:35 -0800973 private void fadeAndScaleRightViewItem(int currItemId) {
974 if (currItemId < 1 || currItemId > BUFFER_SIZE) {
Angus Kong80af7dd2013-08-22 21:00:19 -0700975 Log.e(TAG, "currItem id out of bound.");
976 return;
977 }
978
Angus Kong1f9db2d2014-01-09 00:56:35 -0800979 final ViewItem currItem = mViewItem[currItemId];
980 final ViewItem prevItem = mViewItem[currItemId - 1];
981 if (currItem == null || prevItem == null) {
Angus Kong7d2388d2013-09-18 23:56:01 -0700982 Log.e(TAG, "Invalid view item (curr or prev == null). curr = "
Angus Kong1f9db2d2014-01-09 00:56:35 -0800983 + currItemId);
Angus Kong80af7dd2013-08-22 21:00:19 -0700984 return;
985 }
986
Angus Kong1f9db2d2014-01-09 00:56:35 -0800987 if (currItemId > mCurrentItem + 1) {
Angus Kong80af7dd2013-08-22 21:00:19 -0700988 // Every item not right next to the mCurrentItem is invisible.
Angus Kong1f9db2d2014-01-09 00:56:35 -0800989 currItem.setVisibility(INVISIBLE);
Angus Kong80af7dd2013-08-22 21:00:19 -0700990 return;
991 }
Angus Kong1f9db2d2014-01-09 00:56:35 -0800992 final int prevCenterX = prevItem.getCenterX();
Angus Kong80af7dd2013-08-22 21:00:19 -0700993 if (mCenterX <= prevCenterX) {
994 // Shortcut. If the position is at the center of the previous one,
995 // set to invisible too.
Angus Kong1f9db2d2014-01-09 00:56:35 -0800996 currItem.setVisibility(INVISIBLE);
Angus Kong80af7dd2013-08-22 21:00:19 -0700997 return;
998 }
Angus Kong1f9db2d2014-01-09 00:56:35 -0800999 final int currCenterX = currItem.getCenterX();
Angus Kong80af7dd2013-08-22 21:00:19 -07001000 final float fadeDownFraction =
1001 ((float) mCenterX - prevCenterX) / (currCenterX - prevCenterX);
Angus Kong1f9db2d2014-01-09 00:56:35 -08001002 currItem.layoutWithTranslationX(mDrawArea, currCenterX,
Angus Kong80af7dd2013-08-22 21:00:19 -07001003 FILM_STRIP_SCALE + (1f - FILM_STRIP_SCALE) * fadeDownFraction);
Angus Kong1f9db2d2014-01-09 00:56:35 -08001004 currItem.setAlpha(fadeDownFraction);
1005 currItem.setTranslationX(0);
1006 currItem.setVisibility(VISIBLE);
Angus Kong80af7dd2013-08-22 21:00:19 -07001007 }
1008
Angus Kongf1582c92013-10-12 18:52:33 -07001009 private void layoutViewItems(boolean layoutChanged) {
Angus Kong5fbe1342013-09-10 15:42:46 -07001010 if (mViewItem[mCurrentItem] == null ||
1011 mDrawArea.width() == 0 ||
1012 mDrawArea.height() == 0) {
Angus Kong3d0b6912013-08-09 13:05:55 -07001013 return;
1014 }
Angus Kong7d2388d2013-09-18 23:56:01 -07001015
Angus Kongf1582c92013-10-12 18:52:33 -07001016 // If the layout changed, we need to adjust the current position so
1017 // that if an item is centered before the change, it's still centered.
1018 if (layoutChanged) {
Angus Kong7d2388d2013-09-18 23:56:01 -07001019 mViewItem[mCurrentItem].setLeftPosition(
Angus Kong1f9db2d2014-01-09 00:56:35 -08001020 mCenterX - mViewItem[mCurrentItem].getMeasuredWidth() / 2);
Angus Kong49b9ba22013-05-13 13:42:21 -07001021 }
1022
Angus Kong45671602014-01-13 15:06:17 -08001023 if (inZoomView()) {
Angus Kong7d2388d2013-09-18 23:56:01 -07001024 return;
Angus Kong49b9ba22013-05-13 13:42:21 -07001025 }
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001026 /**
1027 * Transformed scale fraction between 0 and 1. 0 if the scale is
Sascha Haeberling88ef7662013-08-15 17:19:22 -07001028 * {@link FILM_STRIP_SCALE}. 1 if the scale is {@link FULL_SCREEN_SCALE}
1029 * .
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001030 */
Angus Kong80af7dd2013-08-22 21:00:19 -07001031 final float scaleFraction = mViewAnimInterpolator.getInterpolation(
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001032 (mScale - FILM_STRIP_SCALE) / (FULL_SCREEN_SCALE - FILM_STRIP_SCALE));
Angus Kongfaaee012013-12-07 00:38:46 -08001033 final int fullScreenWidth = mDrawArea.width() + mViewGapInPixel;
Angus Konga6a6a732013-06-18 16:47:57 -07001034
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001035 // Decide the position for all view items on the left and the right
1036 // first.
Angus Kong025136b2013-08-21 00:38:05 -07001037
Angus Kongd028dd42013-09-13 18:09:18 -07001038 // Left items.
Angus Kong8969a672013-08-19 23:25:48 -07001039 for (int itemID = mCurrentItem - 1; itemID >= 0; itemID--) {
Angus Kong80af7dd2013-08-22 21:00:19 -07001040 final ViewItem curr = mViewItem[itemID];
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001041 if (curr == null) {
Angus Kong80af7dd2013-08-22 21:00:19 -07001042 break;
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001043 }
Angus Konga6a6a732013-06-18 16:47:57 -07001044
Angus Kong80af7dd2013-08-22 21:00:19 -07001045 // First, layout relatively to the next one.
1046 final int currLeft = mViewItem[itemID + 1].getLeftPosition()
Angus Kong1f9db2d2014-01-09 00:56:35 -08001047 - curr.getMeasuredWidth() - mViewGapInPixel;
Angus Kong80af7dd2013-08-22 21:00:19 -07001048 curr.setLeftPosition(currLeft);
Angus Kong49b9ba22013-05-13 13:42:21 -07001049 }
Angus Kongd028dd42013-09-13 18:09:18 -07001050 // Right items.
Angus Kong8969a672013-08-19 23:25:48 -07001051 for (int itemID = mCurrentItem + 1; itemID < BUFFER_SIZE; itemID++) {
Angus Kongda568b22013-10-08 20:58:33 -07001052 final ViewItem curr = mViewItem[itemID];
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001053 if (curr == null) {
Angus Kongd028dd42013-09-13 18:09:18 -07001054 break;
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001055 }
Angus Konga6a6a732013-06-18 16:47:57 -07001056
Angus Kong80af7dd2013-08-22 21:00:19 -07001057 // First, layout relatively to the previous one.
Angus Kongda568b22013-10-08 20:58:33 -07001058 final ViewItem prev = mViewItem[itemID - 1];
1059 final int currLeft =
Angus Kong1f9db2d2014-01-09 00:56:35 -08001060 prev.getLeftPosition() + prev.getMeasuredWidth()
Angus Kongfaaee012013-12-07 00:38:46 -08001061 + mViewGapInPixel;
Angus Kong80af7dd2013-08-22 21:00:19 -07001062 curr.setLeftPosition(currLeft);
Angus Kongd028dd42013-09-13 18:09:18 -07001063 }
1064
Angus Kongda568b22013-10-08 20:58:33 -07001065 // Special case for the one immediately on the right of the camera
1066 // preview.
1067 boolean immediateRight =
1068 (mViewItem[mCurrentItem].getId() == 1 &&
Angus Kong01054e92013-12-10 11:06:18 -08001069 mDataAdapter.getImageData(0).getViewType() == ImageData.VIEW_TYPE_STICKY);
Angus Kongda568b22013-10-08 20:58:33 -07001070
Angus Kongd028dd42013-09-13 18:09:18 -07001071 // Layout the current ViewItem first.
Angus Kongda568b22013-10-08 20:58:33 -07001072 if (immediateRight) {
1073 // Just do a simple layout without any special translation or
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001074 // fading. The implementation in Gallery does not push the first
Angus Kongda568b22013-10-08 20:58:33 -07001075 // photo to the bottom of the camera preview. Simply place the
1076 // photo on the right of the preview.
1077 final ViewItem currItem = mViewItem[mCurrentItem];
Angus Kong1f9db2d2014-01-09 00:56:35 -08001078 currItem.layoutWithTranslationX(mDrawArea, mCenterX, mScale);
1079 currItem.setTranslationX(0f);
1080 currItem.setAlpha(1f);
Angus Kongda568b22013-10-08 20:58:33 -07001081 } else if (scaleFraction == 1f) {
Angus Kongd028dd42013-09-13 18:09:18 -07001082 final ViewItem currItem = mViewItem[mCurrentItem];
1083 final int currCenterX = currItem.getCenterX();
1084 if (mCenterX < currCenterX) {
1085 // In full-screen and mCenterX is on the left of the center,
1086 // we draw the current one to "fade down".
1087 fadeAndScaleRightViewItem(mCurrentItem);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001088 } else if (mCenterX > currCenterX) {
Angus Kongd028dd42013-09-13 18:09:18 -07001089 // In full-screen and mCenterX is on the right of the center,
1090 // we draw the current one translated.
1091 translateLeftViewItem(mCurrentItem, fullScreenWidth, scaleFraction);
1092 } else {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001093 currItem.layoutWithTranslationX(mDrawArea, mCenterX, mScale);
1094 currItem.setTranslationX(0f);
1095 currItem.setAlpha(1f);
Angus Kongd028dd42013-09-13 18:09:18 -07001096 }
1097 } else {
1098 final ViewItem currItem = mViewItem[mCurrentItem];
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001099 // The normal filmstrip has no translation for the current item. If
1100 // it has translation before, gradually set it to zero.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001101 currItem.setTranslationX(currItem.getTranslationX() * scaleFraction);
1102 currItem.layoutWithTranslationX(mDrawArea, mCenterX, mScale);
Angus Kongd028dd42013-09-13 18:09:18 -07001103 if (mViewItem[mCurrentItem - 1] == null) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001104 currItem.setAlpha(1f);
Angus Kongd028dd42013-09-13 18:09:18 -07001105 } else {
1106 final int currCenterX = currItem.getCenterX();
1107 final int prevCenterX = mViewItem[mCurrentItem - 1].getCenterX();
1108 final float fadeDownFraction =
1109 ((float) mCenterX - prevCenterX) / (currCenterX - prevCenterX);
Angus Kong1f9db2d2014-01-09 00:56:35 -08001110 currItem.setAlpha(
Angus Kongd028dd42013-09-13 18:09:18 -07001111 (1 - fadeDownFraction) * (1 - scaleFraction) + fadeDownFraction);
1112 }
1113 }
1114
1115 // Layout the rest dependent on the current scale.
1116
1117 // Items on the left
1118 for (int itemID = mCurrentItem - 1; itemID >= 0; itemID--) {
1119 final ViewItem curr = mViewItem[itemID];
1120 if (curr == null) {
1121 break;
1122 }
1123 translateLeftViewItem(itemID, fullScreenWidth, scaleFraction);
1124 }
1125
1126 // Items on the right
1127 for (int itemID = mCurrentItem + 1; itemID < BUFFER_SIZE; itemID++) {
Angus Kongda568b22013-10-08 20:58:33 -07001128 final ViewItem curr = mViewItem[itemID];
Angus Kongd028dd42013-09-13 18:09:18 -07001129 if (curr == null) {
1130 break;
1131 }
1132
Angus Kong1f9db2d2014-01-09 00:56:35 -08001133 curr.layoutWithTranslationX(mDrawArea, mCenterX, mScale);
Angus Kongec2fb472013-12-10 01:06:43 -08001134 if (curr.getId() == 1 && isViewTypeSticky(curr)) {
Angus Kongda568b22013-10-08 20:58:33 -07001135 // Special case for the one next to the camera preview.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001136 curr.setAlpha(1f);
Angus Kongda568b22013-10-08 20:58:33 -07001137 continue;
1138 }
1139
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001140 if (scaleFraction == 1) {
Angus Kong025136b2013-08-21 00:38:05 -07001141 // It's in full-screen mode.
Angus Kong80af7dd2013-08-22 21:00:19 -07001142 fadeAndScaleRightViewItem(itemID);
Angus Konga6a6a732013-06-18 16:47:57 -07001143 } else {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001144 if (curr.getVisibility() == INVISIBLE) {
1145 curr.setVisibility(VISIBLE);
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001146 }
Angus Kong8969a672013-08-19 23:25:48 -07001147 if (itemID == mCurrentItem + 1) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001148 curr.setAlpha(1f - scaleFraction);
Angus Konga6a6a732013-06-18 16:47:57 -07001149 } else {
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001150 if (scaleFraction == 0f) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001151 curr.setAlpha(1f);
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001152 } else {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001153 curr.setVisibility(INVISIBLE);
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001154 }
Angus Konga6a6a732013-06-18 16:47:57 -07001155 }
Angus Kong80af7dd2013-08-22 21:00:19 -07001156 curr.setTranslationX(
Angus Kong1f9db2d2014-01-09 00:56:35 -08001157 (mViewItem[mCurrentItem].getLeftPosition() - curr.getLeftPosition()) *
1158 scaleFraction);
Angus Kong49b9ba22013-05-13 13:42:21 -07001159 }
1160 }
1161
1162 stepIfNeeded();
Angus Kong7d2388d2013-09-18 23:56:01 -07001163 }
Angus Kongaa2a2432013-08-21 11:03:38 -07001164
Angus Kongec2fb472013-12-10 01:06:43 -08001165 private boolean isViewTypeSticky(ViewItem item) {
1166 if (item == null) {
1167 return false;
1168 }
1169 return mDataAdapter.getImageData(item.getId()).getViewType() ==
Angus Kong01054e92013-12-10 11:06:18 -08001170 ImageData.VIEW_TYPE_STICKY;
Angus Kongec2fb472013-12-10 01:06:43 -08001171 }
1172
Angus Kong7d2388d2013-09-18 23:56:01 -07001173 @Override
1174 public void onDraw(Canvas c) {
Angus Kongf1582c92013-10-12 18:52:33 -07001175 // TODO: remove layoutViewItems() here.
1176 layoutViewItems(false);
Angus Kong7d2388d2013-09-18 23:56:01 -07001177 super.onDraw(c);
Angus Kong49b9ba22013-05-13 13:42:21 -07001178 }
1179
1180 @Override
1181 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Angus Kong49b9ba22013-05-13 13:42:21 -07001182 mDrawArea.left = l;
1183 mDrawArea.top = t;
1184 mDrawArea.right = r;
1185 mDrawArea.bottom = b;
Angus Kong563a2892013-09-09 17:15:01 -07001186 mZoomView.layout(mDrawArea.left, mDrawArea.top, mDrawArea.right, mDrawArea.bottom);
Angus Kong7d2388d2013-09-18 23:56:01 -07001187 // TODO: Need a more robust solution to decide when to re-layout
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001188 // If in the middle of zooming, only re-layout when the layout has
1189 // changed.
Angus Kong45671602014-01-13 15:06:17 -08001190 if (!inZoomView() || changed) {
Doris Liu121022c2013-09-03 16:03:16 -07001191 resetZoomView();
Angus Kongf1582c92013-10-12 18:52:33 -07001192 layoutViewItems(changed);
Doris Liu121022c2013-09-03 16:03:16 -07001193 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001194 }
1195
Doris Liuf9ddb602013-08-30 16:20:58 -07001196 /**
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001197 * Clears the translation and scale that has been set on the view, cancels
1198 * any loading request for image partial decoding, and hides zoom view. This
1199 * is needed for when there is a layout change (e.g. when users re-enter the
1200 * app, or rotate the device, etc).
Doris Liuf9ddb602013-08-30 16:20:58 -07001201 */
1202 private void resetZoomView() {
Angus Kong45671602014-01-13 15:06:17 -08001203 if (!inZoomView()) {
Doris Liuf9ddb602013-08-30 16:20:58 -07001204 return;
1205 }
1206 ViewItem current = mViewItem[mCurrentItem];
1207 if (current == null) {
1208 return;
1209 }
1210 mScale = FULL_SCREEN_SCALE;
Doris Liud70f0fb2013-10-04 13:22:09 -07001211 mController.cancelZoomAnimation();
1212 mController.cancelFlingAnimation();
Doris Liuf9ddb602013-08-30 16:20:58 -07001213 current.resetTransform();
1214 mController.cancelLoadingZoomedImage();
1215 mZoomView.setVisibility(GONE);
Doris Liu03b75412013-09-09 17:26:14 -07001216 mController.setSurroundingViewsVisible(true);
Doris Liuf9ddb602013-08-30 16:20:58 -07001217 }
1218
Doris Liub0288ec2013-11-04 14:03:28 -08001219 private void hideZoomView() {
Angus Kong45671602014-01-13 15:06:17 -08001220 if (inZoomView()) {
Doris Liub0288ec2013-11-04 14:03:28 -08001221 mController.cancelLoadingZoomedImage();
1222 mZoomView.setVisibility(GONE);
1223 }
1224 }
1225
Angus Kongedbba622013-09-12 14:49:48 -07001226 private void slideViewBack(ViewItem item) {
Angus Kongec2fb472013-12-10 01:06:43 -08001227 item.animateTranslationX(0, GEOMETRY_ADJUST_TIME_MS, mViewAnimInterpolator);
Angus Kong1f9db2d2014-01-09 00:56:35 -08001228 item.animateTranslationY(0, GEOMETRY_ADJUST_TIME_MS, mViewAnimInterpolator);
1229 item.animateAlpha(1f, GEOMETRY_ADJUST_TIME_MS, mViewAnimInterpolator);
Angus Kong87f9a622013-05-16 16:59:39 -07001230 }
1231
Angus Kong01054e92013-12-10 11:06:18 -08001232 private void animateItemRemoval(int dataID, final ImageData data) {
Angus Kongc6953a42013-12-16 14:48:01 -08001233 if (mScale > FULL_SCREEN_SCALE) {
1234 resetZoomView();
1235 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08001236 int removedItemId = findItemByDataID(dataID);
Angus Kongc31569c2013-05-30 15:18:50 -07001237
1238 // adjust the data id to be consistent
1239 for (int i = 0; i < BUFFER_SIZE; i++) {
Angus Kong563a2892013-09-09 17:15:01 -07001240 if (mViewItem[i] == null || mViewItem[i].getId() <= dataID) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001241 continue;
1242 }
Angus Kong563a2892013-09-09 17:15:01 -07001243 mViewItem[i].setId(mViewItem[i].getId() - 1);
Angus Kongc31569c2013-05-30 15:18:50 -07001244 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08001245 if (removedItemId == -1) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001246 return;
1247 }
Angus Kongc31569c2013-05-30 15:18:50 -07001248
Angus Kong1f9db2d2014-01-09 00:56:35 -08001249 final ViewItem removedItem = mViewItem[removedItemId];
1250 final int offsetX = removedItem.getMeasuredWidth() + mViewGapInPixel;
Angus Kong87f9a622013-05-16 16:59:39 -07001251
Angus Kong1f9db2d2014-01-09 00:56:35 -08001252 for (int i = removedItemId + 1; i < BUFFER_SIZE; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07001253 if (mViewItem[i] != null) {
1254 mViewItem[i].setLeftPosition(mViewItem[i].getLeftPosition() - offsetX);
Angus Kong87f9a622013-05-16 16:59:39 -07001255 }
1256 }
1257
Angus Kong1f9db2d2014-01-09 00:56:35 -08001258 if (removedItemId >= mCurrentItem
1259 && mViewItem[removedItemId].getId() < mDataAdapter.getTotalNumber()) {
Angus Kong8969a672013-08-19 23:25:48 -07001260 // Fill the removed item by left shift when the current one or
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001261 // anyone on the right is removed, and there's more data on the
1262 // right available.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001263 for (int i = removedItemId; i < BUFFER_SIZE - 1; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07001264 mViewItem[i] = mViewItem[i + 1];
Angus Kong87f9a622013-05-16 16:59:39 -07001265 }
1266
1267 // pull data out from the DataAdapter for the last one.
1268 int curr = BUFFER_SIZE - 1;
1269 int prev = curr - 1;
Angus Kong8969a672013-08-19 23:25:48 -07001270 if (mViewItem[prev] != null) {
Angus Kong563a2892013-09-09 17:15:01 -07001271 mViewItem[curr] = buildItemFromData(mViewItem[prev].getId() + 1);
Angus Kong87f9a622013-05-16 16:59:39 -07001272 }
Angus Kong9f02c872013-05-20 13:46:14 -07001273
Angus Kongaa2a2432013-08-21 11:03:38 -07001274 // The animation part.
1275 if (inFullScreen()) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001276 mViewItem[mCurrentItem].setVisibility(VISIBLE);
Angus Kongaa2a2432013-08-21 11:03:38 -07001277 ViewItem nextItem = mViewItem[mCurrentItem + 1];
1278 if (nextItem != null) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001279 nextItem.setVisibility(INVISIBLE);
Angus Kongaa2a2432013-08-21 11:03:38 -07001280 }
1281 }
1282
Angus Kong0980fe82013-05-28 17:48:04 -07001283 // Translate the views to their original places.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001284 for (int i = removedItemId; i < BUFFER_SIZE; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07001285 if (mViewItem[i] != null) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001286 mViewItem[i].setTranslationX(offsetX);
Angus Kong9f02c872013-05-20 13:46:14 -07001287 }
1288 }
1289
1290 // The end of the filmstrip might have been changed.
Angus Kongaf24d282013-05-20 16:27:02 -07001291 // The mCenterX might be out of the bound.
Angus Kong8969a672013-08-19 23:25:48 -07001292 ViewItem currItem = mViewItem[mCurrentItem];
Angus Kong563a2892013-09-09 17:15:01 -07001293 if (currItem.getId() == mDataAdapter.getTotalNumber() - 1
Angus Kong8969a672013-08-19 23:25:48 -07001294 && mCenterX > currItem.getCenterX()) {
1295 int adjustDiff = currItem.getCenterX() - mCenterX;
1296 mCenterX = currItem.getCenterX();
Angus Kong9f02c872013-05-20 13:46:14 -07001297 for (int i = 0; i < BUFFER_SIZE; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07001298 if (mViewItem[i] != null) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001299 mViewItem[i].translateXScaledBy(adjustDiff);
Angus Kong9f02c872013-05-20 13:46:14 -07001300 }
1301 }
1302 }
Angus Kong87f9a622013-05-16 16:59:39 -07001303 } else {
Angus Kong87f9a622013-05-16 16:59:39 -07001304 // fill the removed place by right shift
Angus Kongaf24d282013-05-20 16:27:02 -07001305 mCenterX -= offsetX;
Angus Kong9f02c872013-05-20 13:46:14 -07001306
Angus Kong1f9db2d2014-01-09 00:56:35 -08001307 for (int i = removedItemId; i > 0; i--) {
Angus Kong8969a672013-08-19 23:25:48 -07001308 mViewItem[i] = mViewItem[i - 1];
Angus Kong87f9a622013-05-16 16:59:39 -07001309 }
1310
1311 // pull data out from the DataAdapter for the first one.
1312 int curr = 0;
1313 int next = curr + 1;
Angus Kong8969a672013-08-19 23:25:48 -07001314 if (mViewItem[next] != null) {
Angus Kong563a2892013-09-09 17:15:01 -07001315 mViewItem[curr] = buildItemFromData(mViewItem[next].getId() - 1);
Angus Kong87f9a622013-05-16 16:59:39 -07001316 }
Angus Kong0980fe82013-05-28 17:48:04 -07001317
1318 // Translate the views to their original places.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001319 for (int i = removedItemId; i >= 0; i--) {
Angus Kong8969a672013-08-19 23:25:48 -07001320 if (mViewItem[i] != null) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001321 mViewItem[i].setTranslationX(-offsetX);
Angus Kong0980fe82013-05-28 17:48:04 -07001322 }
1323 }
Angus Kong87f9a622013-05-16 16:59:39 -07001324 }
1325
Angus Kong1f9db2d2014-01-09 00:56:35 -08001326 int transY = getHeight() / 8;
1327 if (removedItem.getTranslationY() < 0) {
1328 transY = -transY;
1329 }
1330 removedItem.animateTranslationY(removedItem.getTranslationY() + transY,
1331 GEOMETRY_ADJUST_TIME_MS, mViewAnimInterpolator);
1332 removedItem.animateAlpha(0f, GEOMETRY_ADJUST_TIME_MS, mViewAnimInterpolator);
1333 postDelayed(new Runnable() {
1334 @Override
1335 public void run() {
1336 removedItem.removeViewFromHierarchy(false);
1337 }
1338 }, GEOMETRY_ADJUST_TIME_MS);
1339
1340 adjustChildZOrder();
1341 invalidate();
1342
Angus Kong9f02c872013-05-20 13:46:14 -07001343 // Now, slide every one back.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001344 if (mViewItem[mCurrentItem] == null) {
1345 return;
1346 }
Angus Kong9f02c872013-05-20 13:46:14 -07001347 for (int i = 0; i < BUFFER_SIZE; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07001348 if (mViewItem[i] != null
Angus Kong1f9db2d2014-01-09 00:56:35 -08001349 && mViewItem[i].getTranslationX() != 0f) {
Angus Kongedbba622013-09-12 14:49:48 -07001350 slideViewBack(mViewItem[i]);
Angus Kong9f02c872013-05-20 13:46:14 -07001351 }
1352 }
Angus Kongec2fb472013-12-10 01:06:43 -08001353 if (isCurrentItemCentered() && isViewTypeSticky(mViewItem[mCurrentItem])) {
Angus Kong6ddd31b2013-10-08 22:34:02 -07001354 // Special case for scrolling onto the camera preview after removal.
1355 mController.goToFullScreen();
1356 }
Angus Kong87f9a622013-05-16 16:59:39 -07001357 }
1358
Angus Konge78541f2013-05-28 12:30:33 -07001359 // returns -1 on failure.
Angus Kong8969a672013-08-19 23:25:48 -07001360 private int findItemByDataID(int dataID) {
Angus Konge78541f2013-05-28 12:30:33 -07001361 for (int i = 0; i < BUFFER_SIZE; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07001362 if (mViewItem[i] != null
Angus Kong563a2892013-09-09 17:15:01 -07001363 && mViewItem[i].getId() == dataID) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001364 return i;
1365 }
Angus Konge78541f2013-05-28 12:30:33 -07001366 }
1367 return -1;
1368 }
1369
1370 private void updateInsertion(int dataID) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001371 int insertedItemId = findItemByDataID(dataID);
1372 if (insertedItemId == -1) {
Angus Kong8969a672013-08-19 23:25:48 -07001373 // Not in the current item buffers. Check if it's inserted
Angus Konge78541f2013-05-28 12:30:33 -07001374 // at the end.
1375 if (dataID == mDataAdapter.getTotalNumber() - 1) {
Angus Kong8969a672013-08-19 23:25:48 -07001376 int prev = findItemByDataID(dataID - 1);
Angus Konge78541f2013-05-28 12:30:33 -07001377 if (prev >= 0 && prev < BUFFER_SIZE - 1) {
1378 // The previous data is in the buffer and we still
1379 // have room for the inserted data.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001380 insertedItemId = prev + 1;
Angus Konge78541f2013-05-28 12:30:33 -07001381 }
1382 }
1383 }
1384
1385 // adjust the data id to be consistent
1386 for (int i = 0; i < BUFFER_SIZE; i++) {
Angus Kong563a2892013-09-09 17:15:01 -07001387 if (mViewItem[i] == null || mViewItem[i].getId() < dataID) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001388 continue;
1389 }
Angus Kong563a2892013-09-09 17:15:01 -07001390 mViewItem[i].setId(mViewItem[i].getId() + 1);
Angus Konge78541f2013-05-28 12:30:33 -07001391 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08001392 if (insertedItemId == -1) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001393 return;
1394 }
Angus Konge78541f2013-05-28 12:30:33 -07001395
Angus Kong01054e92013-12-10 11:06:18 -08001396 final ImageData data = mDataAdapter.getImageData(dataID);
Angus Kongc195e7a2014-02-20 16:56:37 -08001397 Point dim = CameraUtil
1398 .resizeToFill(data.getWidth(), data.getHeight(), data.getRotation(),
1399 getMeasuredWidth(), getMeasuredHeight());
1400 final int offsetX = dim.x + mViewGapInPixel;
Angus Kong8969a672013-08-19 23:25:48 -07001401 ViewItem viewItem = buildItemFromData(dataID);
Angus Konge78541f2013-05-28 12:30:33 -07001402
Angus Kong1f9db2d2014-01-09 00:56:35 -08001403 if (insertedItemId >= mCurrentItem) {
1404 if (insertedItemId == mCurrentItem) {
Angus Kong8969a672013-08-19 23:25:48 -07001405 viewItem.setLeftPosition(mViewItem[mCurrentItem].getLeftPosition());
Angus Konge78541f2013-05-28 12:30:33 -07001406 }
1407 // Shift right to make rooms for newly inserted item.
Angus Kong8969a672013-08-19 23:25:48 -07001408 removeItem(BUFFER_SIZE - 1);
Angus Kong1f9db2d2014-01-09 00:56:35 -08001409 for (int i = BUFFER_SIZE - 1; i > insertedItemId; i--) {
Angus Kong8969a672013-08-19 23:25:48 -07001410 mViewItem[i] = mViewItem[i - 1];
1411 if (mViewItem[i] != null) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001412 mViewItem[i].setTranslationX(-offsetX);
Angus Kongedbba622013-09-12 14:49:48 -07001413 slideViewBack(mViewItem[i]);
Angus Konge78541f2013-05-28 12:30:33 -07001414 }
1415 }
1416 } else {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001417 // Shift left. Put the inserted data on the left instead of the
1418 // found position.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001419 --insertedItemId;
1420 if (insertedItemId < 0) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001421 return;
1422 }
Angus Kong8969a672013-08-19 23:25:48 -07001423 removeItem(0);
Angus Kong1f9db2d2014-01-09 00:56:35 -08001424 for (int i = 1; i <= insertedItemId; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07001425 if (mViewItem[i] != null) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08001426 mViewItem[i].setTranslationX(offsetX);
Angus Kongedbba622013-09-12 14:49:48 -07001427 slideViewBack(mViewItem[i]);
Angus Kong8969a672013-08-19 23:25:48 -07001428 mViewItem[i - 1] = mViewItem[i];
Angus Konge78541f2013-05-28 12:30:33 -07001429 }
1430 }
1431 }
1432
Angus Kong1f9db2d2014-01-09 00:56:35 -08001433 mViewItem[insertedItemId] = viewItem;
1434 viewItem.setAlpha(0f);
1435 viewItem.setTranslationY(getHeight() / 8);
1436 slideViewBack(viewItem);
Angus Kong53aedc02013-10-10 16:46:04 -07001437 adjustChildZOrder();
Angus Konge78541f2013-05-28 12:30:33 -07001438 invalidate();
1439 }
1440
Angus Kong01054e92013-12-10 11:06:18 -08001441 private void setDataAdapter(DataAdapter adapter) {
Angus Kong49b9ba22013-05-13 13:42:21 -07001442 mDataAdapter = adapter;
Sam Judd4021c892014-03-17 12:57:50 -07001443 int maxEdge = (int) ((float) Math.max(this.getHeight(), this.getWidth())
1444 * FILM_STRIP_SCALE);
1445 mDataAdapter.suggestViewSizeBound(maxEdge, maxEdge);
Angus Kong01054e92013-12-10 11:06:18 -08001446 mDataAdapter.setListener(new DataAdapter.Listener() {
Angus Kong49b9ba22013-05-13 13:42:21 -07001447 @Override
1448 public void onDataLoaded() {
1449 reload();
1450 }
1451
1452 @Override
Angus Kong01054e92013-12-10 11:06:18 -08001453 public void onDataUpdated(DataAdapter.UpdateReporter reporter) {
Angus Kong49b9ba22013-05-13 13:42:21 -07001454 update(reporter);
1455 }
1456
1457 @Override
Angus Kong01054e92013-12-10 11:06:18 -08001458 public void onDataInserted(int dataId, ImageData data) {
Angus Kong8969a672013-08-19 23:25:48 -07001459 if (mViewItem[mCurrentItem] == null) {
Angus Konge78541f2013-05-28 12:30:33 -07001460 // empty now, simply do a reload.
1461 reload();
Angus Kongfaaee012013-12-07 00:38:46 -08001462 } else {
1463 updateInsertion(dataId);
Angus Konge78541f2013-05-28 12:30:33 -07001464 }
Angus Kongfaaee012013-12-07 00:38:46 -08001465 if (mListener != null) {
1466 mListener.onDataFocusChanged(dataId, getCurrentId());
1467 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001468 }
1469
1470 @Override
Angus Kong01054e92013-12-10 11:06:18 -08001471 public void onDataRemoved(int dataId, ImageData data) {
Angus Kongfaaee012013-12-07 00:38:46 -08001472 animateItemRemoval(dataId, data);
1473 if (mListener != null) {
1474 mListener.onDataFocusChanged(dataId, getCurrentId());
1475 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001476 }
1477 });
1478 }
1479
Angus Kong62848152013-11-08 17:25:29 -08001480 private boolean inFilmstrip() {
Angus Konga6a6a732013-06-18 16:47:57 -07001481 return (mScale == FILM_STRIP_SCALE);
1482 }
1483
Angus Kong62848152013-11-08 17:25:29 -08001484 private boolean inFullScreen() {
Angus Kong4ff5a1a2013-08-14 16:07:54 -07001485 return (mScale == FULL_SCREEN_SCALE);
Angus Konga6a6a732013-06-18 16:47:57 -07001486 }
1487
Angus Kong45671602014-01-13 15:06:17 -08001488 private boolean inZoomView() {
1489 return (mScale > FULL_SCREEN_SCALE);
1490 }
1491
Angus Kong62848152013-11-08 17:25:29 -08001492 private boolean isCameraPreview() {
Angus Kongec2fb472013-12-10 01:06:43 -08001493 return isViewTypeSticky(mViewItem[mCurrentItem]);
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001494 }
1495
Angus Kong62848152013-11-08 17:25:29 -08001496 private boolean inCameraFullscreen() {
Angus Kong17e669d2013-09-05 16:12:22 -07001497 return isDataAtCenter(0) && inFullScreen()
Angus Kongec2fb472013-12-10 01:06:43 -08001498 && (isViewTypeSticky(mViewItem[mCurrentItem]));
Angus Kong49b9ba22013-05-13 13:42:21 -07001499 }
1500
1501 @Override
1502 public boolean onInterceptTouchEvent(MotionEvent ev) {
Angus Kong7e9e4b72014-03-06 17:03:31 -08001503 if (mController.isScrolling()) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001504 return true;
1505 }
Angus Konga6a6a732013-06-18 16:47:57 -07001506
Doris Liufb57df12013-05-14 14:37:46 -07001507 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
1508 mCheckToIntercept = true;
1509 mDown = MotionEvent.obtain(ev);
Angus Kong8969a672013-08-19 23:25:48 -07001510 ViewItem viewItem = mViewItem[mCurrentItem];
Doris Liuf7758b62013-06-06 16:54:18 -07001511 // Do not intercept touch if swipe is not enabled
Angus Kong563a2892013-09-09 17:15:01 -07001512 if (viewItem != null && !mDataAdapter.canSwipeInFullScreen(viewItem.getId())) {
Doris Liuf7758b62013-06-06 16:54:18 -07001513 mCheckToIntercept = false;
1514 }
Doris Liufb57df12013-05-14 14:37:46 -07001515 return false;
1516 } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
1517 // Do not intercept touch once child is in zoom mode
1518 mCheckToIntercept = false;
1519 return false;
1520 } else {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001521 if (!mCheckToIntercept) {
1522 return false;
1523 }
1524 if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT) {
1525 return false;
1526 }
Doris Liufb57df12013-05-14 14:37:46 -07001527 int deltaX = (int) (ev.getX() - mDown.getX());
1528 int deltaY = (int) (ev.getY() - mDown.getY());
1529 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE
1530 && deltaX < mSlop * (-1)) {
1531 // intercept left swipe
1532 if (Math.abs(deltaX) >= Math.abs(deltaY) * 2) {
1533 return true;
1534 }
1535 }
1536 }
1537 return false;
Angus Kong49b9ba22013-05-13 13:42:21 -07001538 }
1539
1540 @Override
1541 public boolean onTouchEvent(MotionEvent ev) {
Angus Kong166e36f2013-12-03 08:54:42 -08001542 return mGestureRecognizer.onTouchEvent(ev);
1543 }
1544
1545 FilmstripGestureRecognizer.Listener getGestureListener() {
1546 return mGestureListener;
Angus Kong49b9ba22013-05-13 13:42:21 -07001547 }
1548
Angus Kong8969a672013-08-19 23:25:48 -07001549 private void updateViewItem(int itemID) {
1550 ViewItem item = mViewItem[itemID];
1551 if (item == null) {
1552 Log.e(TAG, "trying to update an null item");
1553 return;
1554 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08001555 item.removeViewFromHierarchy(true);
Angus Kong8969a672013-08-19 23:25:48 -07001556
Angus Kong563a2892013-09-09 17:15:01 -07001557 ViewItem newItem = buildItemFromData(item.getId());
Angus Kong8969a672013-08-19 23:25:48 -07001558 if (newItem == null) {
1559 Log.e(TAG, "new item is null");
1560 // keep using the old data.
Angus Kong1f9db2d2014-01-09 00:56:35 -08001561 item.addViewToHierarchy();
Angus Kong8969a672013-08-19 23:25:48 -07001562 return;
1563 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08001564 newItem.copyAttributes(item);
Angus Kong8969a672013-08-19 23:25:48 -07001565 mViewItem[itemID] = newItem;
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001566
1567 boolean stopScroll = clampCenterX();
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001568 if (stopScroll) {
Angus Kong1f36cf12013-10-01 16:58:52 -07001569 mController.stopScrolling(true);
1570 }
Angus Kong53aedc02013-10-10 16:46:04 -07001571 adjustChildZOrder();
1572 invalidate();
Angus Kong8a2350a2013-12-16 15:02:34 -08001573 if (mListener != null) {
1574 mListener.onDataUpdated(newItem.getId());
1575 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001576 }
1577
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001578 /** Some of the data is changed. */
Angus Kong01054e92013-12-10 11:06:18 -08001579 private void update(DataAdapter.UpdateReporter reporter) {
Angus Kong49b9ba22013-05-13 13:42:21 -07001580 // No data yet.
Angus Kong8969a672013-08-19 23:25:48 -07001581 if (mViewItem[mCurrentItem] == null) {
Angus Kong49b9ba22013-05-13 13:42:21 -07001582 reload();
1583 return;
1584 }
1585
1586 // Check the current one.
Angus Kong8969a672013-08-19 23:25:48 -07001587 ViewItem curr = mViewItem[mCurrentItem];
Angus Kong563a2892013-09-09 17:15:01 -07001588 int dataId = curr.getId();
1589 if (reporter.isDataRemoved(dataId)) {
Angus Kong49b9ba22013-05-13 13:42:21 -07001590 reload();
1591 return;
1592 }
Angus Kong563a2892013-09-09 17:15:01 -07001593 if (reporter.isDataUpdated(dataId)) {
Angus Kong8969a672013-08-19 23:25:48 -07001594 updateViewItem(mCurrentItem);
Angus Kong01054e92013-12-10 11:06:18 -08001595 final ImageData data = mDataAdapter.getImageData(dataId);
Angus Konga5d6b242013-09-30 15:54:46 -07001596 if (!mIsUserScrolling && !mController.isScrolling()) {
1597 // If there is no scrolling at all, adjust mCenterX to place
1598 // the current item at the center.
Angus Kongc195e7a2014-02-20 16:56:37 -08001599 Point dim = CameraUtil.resizeToFill(data.getWidth(), data.getHeight(),
1600 data.getRotation(), getMeasuredWidth(), getMeasuredHeight());
1601 mCenterX = curr.getLeftPosition() + dim.x / 2;
Angus Konga5d6b242013-09-30 15:54:46 -07001602 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001603 }
1604
1605 // Check left
Angus Kong8969a672013-08-19 23:25:48 -07001606 for (int i = mCurrentItem - 1; i >= 0; i--) {
1607 curr = mViewItem[i];
Angus Kong49b9ba22013-05-13 13:42:21 -07001608 if (curr != null) {
Angus Kong563a2892013-09-09 17:15:01 -07001609 dataId = curr.getId();
1610 if (reporter.isDataRemoved(dataId) || reporter.isDataUpdated(dataId)) {
Angus Kong8969a672013-08-19 23:25:48 -07001611 updateViewItem(i);
Angus Kong49b9ba22013-05-13 13:42:21 -07001612 }
1613 } else {
Angus Kong8969a672013-08-19 23:25:48 -07001614 ViewItem next = mViewItem[i + 1];
Angus Kong87f9a622013-05-16 16:59:39 -07001615 if (next != null) {
Angus Kong563a2892013-09-09 17:15:01 -07001616 mViewItem[i] = buildItemFromData(next.getId() - 1);
Angus Kong87f9a622013-05-16 16:59:39 -07001617 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001618 }
1619 }
1620
1621 // Check right
Angus Kong8969a672013-08-19 23:25:48 -07001622 for (int i = mCurrentItem + 1; i < BUFFER_SIZE; i++) {
1623 curr = mViewItem[i];
Angus Kong49b9ba22013-05-13 13:42:21 -07001624 if (curr != null) {
Angus Kong563a2892013-09-09 17:15:01 -07001625 dataId = curr.getId();
1626 if (reporter.isDataRemoved(dataId) || reporter.isDataUpdated(dataId)) {
Angus Kong8969a672013-08-19 23:25:48 -07001627 updateViewItem(i);
Angus Kong49b9ba22013-05-13 13:42:21 -07001628 }
1629 } else {
Angus Kong8969a672013-08-19 23:25:48 -07001630 ViewItem prev = mViewItem[i - 1];
Angus Kong87f9a622013-05-16 16:59:39 -07001631 if (prev != null) {
Angus Kong563a2892013-09-09 17:15:01 -07001632 mViewItem[i] = buildItemFromData(prev.getId() + 1);
Angus Kong87f9a622013-05-16 16:59:39 -07001633 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001634 }
1635 }
Angus Kong53aedc02013-10-10 16:46:04 -07001636 adjustChildZOrder();
Erin Dahlgrenb7a52a42013-10-01 11:00:12 -07001637 // Request a layout to find the measured width/height of the view first.
Angus Kong8969a672013-08-19 23:25:48 -07001638 requestLayout();
Erin Dahlgrenb7a52a42013-10-01 11:00:12 -07001639 // Update photo sphere visibility after metadata fully written.
Angus Kong49b9ba22013-05-13 13:42:21 -07001640 }
1641
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001642 /**
1643 * The whole data might be totally different. Flush all and load from the
Alan Newberger3f969c12013-08-23 10:10:30 -07001644 * start. Filmstrip will be centered on the first item, i.e. the camera
1645 * preview.
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001646 */
Angus Kong49b9ba22013-05-13 13:42:21 -07001647 private void reload() {
Angus Kong7d2388d2013-09-18 23:56:01 -07001648 mController.stopScrolling(true);
1649 mController.stopScale();
1650 mDataIdOnUserScrolling = 0;
ztenghui56e44af2013-10-15 15:31:00 -07001651
Angus Kongfaaee012013-12-07 00:38:46 -08001652 int prevId = -1;
1653 if (mViewItem[mCurrentItem] != null) {
1654 prevId = mViewItem[mCurrentItem].getId();
Angus Kong7d2388d2013-09-18 23:56:01 -07001655 }
ztenghui56e44af2013-10-15 15:31:00 -07001656
1657 // Remove all views from the mViewItem buffer, except the camera view.
Alan Newberger3f969c12013-08-23 10:10:30 -07001658 for (int i = 0; i < mViewItem.length; i++) {
1659 if (mViewItem[i] == null) {
1660 continue;
1661 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08001662 mViewItem[i].removeViewFromHierarchy(false);
Alan Newberger3f969c12013-08-23 10:10:30 -07001663 }
1664
1665 // Clear out the mViewItems and rebuild with camera in the center.
1666 Arrays.fill(mViewItem, null);
Angus Kong49b9ba22013-05-13 13:42:21 -07001667 int dataNumber = mDataAdapter.getTotalNumber();
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001668 if (dataNumber == 0) {
1669 return;
1670 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001671
Angus Kong8969a672013-08-19 23:25:48 -07001672 mViewItem[mCurrentItem] = buildItemFromData(0);
Angus Kong8969a672013-08-19 23:25:48 -07001673 if (mViewItem[mCurrentItem] == null) {
Angus Kong3d0b6912013-08-09 13:05:55 -07001674 return;
1675 }
Angus Kong7d2388d2013-09-18 23:56:01 -07001676 mViewItem[mCurrentItem].setLeftPosition(0);
Alan Newberger3f969c12013-08-23 10:10:30 -07001677 for (int i = mCurrentItem + 1; i < BUFFER_SIZE; i++) {
Angus Kong563a2892013-09-09 17:15:01 -07001678 mViewItem[i] = buildItemFromData(mViewItem[i - 1].getId() + 1);
Alan Newberger3f969c12013-08-23 10:10:30 -07001679 if (mViewItem[i] == null) {
1680 break;
Angus Kong49b9ba22013-05-13 13:42:21 -07001681 }
1682 }
Alan Newberger3f969c12013-08-23 10:10:30 -07001683
1684 // Ensure that the views in mViewItem will layout the first in the
1685 // center of the display upon a reload.
1686 mCenterX = -1;
Angus Kong166e36f2013-12-03 08:54:42 -08001687 mScale = FILM_STRIP_SCALE;
Alan Newberger3f969c12013-08-23 10:10:30 -07001688
Angus Kong53aedc02013-10-10 16:46:04 -07001689 adjustChildZOrder();
Angus Kong7d2388d2013-09-18 23:56:01 -07001690 invalidate();
Doris Liu742cd5b2013-09-12 16:17:43 -07001691
Angus Kong7d2388d2013-09-18 23:56:01 -07001692 if (mListener != null) {
Angus Kongc02b13a2013-11-12 11:50:57 -08001693 mListener.onDataReloaded();
Angus Kongfaaee012013-12-07 00:38:46 -08001694 mListener.onDataFocusChanged(prevId, mViewItem[mCurrentItem].getId());
Angus Kong7d2388d2013-09-18 23:56:01 -07001695 }
Angus Kong49b9ba22013-05-13 13:42:21 -07001696 }
1697
Angus Kong8969a672013-08-19 23:25:48 -07001698 private void promoteData(int itemID, int dataID) {
Angus Kong87f9a622013-05-16 16:59:39 -07001699 if (mListener != null) {
Angus Kong26795a92014-02-20 09:18:09 -08001700 mListener.onFocusedDataPromoted(dataID);
Angus Kong87f9a622013-05-16 16:59:39 -07001701 }
1702 }
1703
Angus Kong8969a672013-08-19 23:25:48 -07001704 private void demoteData(int itemID, int dataID) {
Angus Kong87f9a622013-05-16 16:59:39 -07001705 if (mListener != null) {
Angus Kong26795a92014-02-20 09:18:09 -08001706 mListener.onFocusedDataDemoted(dataID);
Angus Kong87f9a622013-05-16 16:59:39 -07001707 }
1708 }
1709
Angus Kongfaaee012013-12-07 00:38:46 -08001710 private void onEnterFilmstrip() {
1711 if (mListener != null) {
1712 mListener.onEnterFilmstrip(getCurrentId());
1713 }
1714 }
1715
1716 private void onLeaveFilmstrip() {
1717 if (mListener != null) {
1718 mListener.onLeaveFilmstrip(getCurrentId());
1719 }
1720 }
1721
1722 private void onEnterFullScreen() {
Angus Kong45671602014-01-13 15:06:17 -08001723 mFullScreenUIHidden = false;
Angus Kongfaaee012013-12-07 00:38:46 -08001724 if (mListener != null) {
Angus Kong45671602014-01-13 15:06:17 -08001725 mListener.onEnterFullScreenUiShown(getCurrentId());
Angus Kongfaaee012013-12-07 00:38:46 -08001726 }
1727 }
1728
1729 private void onLeaveFullScreen() {
1730 if (mListener != null) {
Angus Kong45671602014-01-13 15:06:17 -08001731 mListener.onLeaveFullScreenUiShown(getCurrentId());
1732 }
1733 }
1734
1735 private void onEnterFullScreenUiHidden() {
1736 mFullScreenUIHidden = true;
1737 if (mListener != null) {
1738 mListener.onEnterFullScreenUiHidden(getCurrentId());
1739 }
1740 }
1741
1742 private void onLeaveFullScreenUiHidden() {
1743 mFullScreenUIHidden = false;
1744 if (mListener != null) {
1745 mListener.onLeaveFullScreenUiHidden(getCurrentId());
Angus Kongfaaee012013-12-07 00:38:46 -08001746 }
1747 }
1748
1749 private void onEnterZoomView() {
1750 if (mListener != null) {
1751 mListener.onEnterZoomView(getCurrentId());
1752 }
1753 }
1754
Angus Kong45671602014-01-13 15:06:17 -08001755 private void onLeaveZoomView() {
1756 mController.setSurroundingViewsVisible(true);
1757 }
1758
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001759 /**
1760 * MyController controls all the geometry animations. It passively tells the
1761 * geometry information on demand.
1762 */
Angus Kong62848152013-11-08 17:25:29 -08001763 private class MyController implements FilmstripController {
Angus Kong49b9ba22013-05-13 13:42:21 -07001764
Angus Kong734598c2013-08-21 15:25:05 -07001765 private final ValueAnimator mScaleAnimator;
Doris Liu121022c2013-09-03 16:03:16 -07001766 private ValueAnimator mZoomAnimator;
Doris Liud70f0fb2013-10-04 13:22:09 -07001767 private AnimatorSet mFlingAnimator;
Angus Kong49b9ba22013-05-13 13:42:21 -07001768
Angus Kong1f36cf12013-10-01 16:58:52 -07001769 private final MyScroller mScroller;
Angus Kong7d2388d2013-09-18 23:56:01 -07001770 private boolean mCanStopScroll;
Angus Kongb21215a2013-09-20 18:41:46 -07001771
1772 private final MyScroller.Listener mScrollerListener =
1773 new MyScroller.Listener() {
1774 @Override
1775 public void onScrollUpdate(int currX, int currY) {
1776 mCenterX = currX;
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001777
1778 boolean stopScroll = clampCenterX();
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07001779 if (stopScroll) {
Angus Kong1f36cf12013-10-01 16:58:52 -07001780 mController.stopScrolling(true);
1781 }
Angus Kongb21215a2013-09-20 18:41:46 -07001782 invalidate();
1783 }
Angus Kong734598c2013-08-21 15:25:05 -07001784
1785 @Override
Angus Kongb21215a2013-09-20 18:41:46 -07001786 public void onScrollEnd() {
1787 mCanStopScroll = true;
1788 if (mViewItem[mCurrentItem] == null) {
1789 return;
1790 }
Angus Kong7d2388d2013-09-18 23:56:01 -07001791 snapInCenter();
Angus Kongec2fb472013-12-10 01:06:43 -08001792 if (isCurrentItemCentered()
1793 && isViewTypeSticky(mViewItem[mCurrentItem])) {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001794 // Special case for the scrolling end on the camera
1795 // preview.
Angus Kong7d2388d2013-09-18 23:56:01 -07001796 goToFullScreen();
1797 }
1798 }
Angus Kongb21215a2013-09-20 18:41:46 -07001799 };
1800
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001801 private final ValueAnimator.AnimatorUpdateListener mScaleAnimatorUpdateListener =
Angus Kongb21215a2013-09-20 18:41:46 -07001802 new ValueAnimator.AnimatorUpdateListener() {
1803 @Override
1804 public void onAnimationUpdate(ValueAnimator animation) {
1805 if (mViewItem[mCurrentItem] == null) {
1806 return;
1807 }
1808 mScale = (Float) animation.getAnimatedValue();
1809 invalidate();
1810 }
1811 };
Angus Kong49b9ba22013-05-13 13:42:21 -07001812
Angus Kong6d23f4c2013-05-22 16:26:07 -07001813 MyController(Context context) {
Angus Kong1f36cf12013-10-01 16:58:52 -07001814 TimeInterpolator decelerateInterpolator = new DecelerateInterpolator(1.5f);
Angus Kongb21215a2013-09-20 18:41:46 -07001815 mScroller = new MyScroller(mActivity,
Angus Kong1f36cf12013-10-01 16:58:52 -07001816 new Handler(mActivity.getMainLooper()),
1817 mScrollerListener, decelerateInterpolator);
Angus Kong734598c2013-08-21 15:25:05 -07001818 mCanStopScroll = true;
Angus Kong734598c2013-08-21 15:25:05 -07001819
Angus Kong49b9ba22013-05-13 13:42:21 -07001820 mScaleAnimator = new ValueAnimator();
Angus Kongb21215a2013-09-20 18:41:46 -07001821 mScaleAnimator.addUpdateListener(mScaleAnimatorUpdateListener);
Angus Kong1f36cf12013-10-01 16:58:52 -07001822 mScaleAnimator.setInterpolator(decelerateInterpolator);
Angus Kongfaaee012013-12-07 00:38:46 -08001823 mScaleAnimator.addListener(new Animator.AnimatorListener() {
1824 @Override
1825 public void onAnimationStart(Animator animator) {
1826 if (mScale == FULL_SCREEN_SCALE) {
1827 onLeaveFullScreen();
1828 } else {
1829 if (mScale == FILM_STRIP_SCALE) {
1830 onLeaveFilmstrip();
1831 }
1832 }
1833 }
1834
1835 @Override
1836 public void onAnimationEnd(Animator animator) {
1837 if (mScale == FULL_SCREEN_SCALE) {
1838 onEnterFullScreen();
1839 } else {
1840 if (mScale == FILM_STRIP_SCALE) {
1841 onEnterFilmstrip();
1842 }
1843 }
1844 }
1845
1846 @Override
1847 public void onAnimationCancel(Animator animator) {
1848
1849 }
1850
1851 @Override
1852 public void onAnimationRepeat(Animator animator) {
1853
1854 }
1855 });
Angus Kong49b9ba22013-05-13 13:42:21 -07001856 }
1857
Angus Kong6d23f4c2013-05-22 16:26:07 -07001858 @Override
Angus Kongfaaee012013-12-07 00:38:46 -08001859 public void setImageGap(int imageGap) {
1860 FilmstripView.this.setViewGap(imageGap);
Angus Kong62848152013-11-08 17:25:29 -08001861 }
1862
1863 @Override
Angus Kong62848152013-11-08 17:25:29 -08001864 public int getCurrentId() {
1865 return FilmstripView.this.getCurrentId();
1866 }
1867
1868 @Override
Angus Kong01054e92013-12-10 11:06:18 -08001869 public void setDataAdapter(DataAdapter adapter) {
Angus Kong62848152013-11-08 17:25:29 -08001870 FilmstripView.this.setDataAdapter(adapter);
1871 }
1872
1873 @Override
1874 public boolean inFilmstrip() {
1875 return FilmstripView.this.inFilmstrip();
1876 }
1877
1878 @Override
1879 public boolean inFullScreen() {
1880 return FilmstripView.this.inFullScreen();
1881 }
1882
1883 @Override
1884 public boolean isCameraPreview() {
1885 return FilmstripView.this.isCameraPreview();
1886 }
1887
1888 @Override
1889 public boolean inCameraFullscreen() {
1890 return FilmstripView.this.inCameraFullscreen();
1891 }
1892
1893 @Override
1894 public void setListener(FilmstripListener l) {
1895 FilmstripView.this.setListener(l);
1896 }
1897
1898 @Override
Angus Kong6d23f4c2013-05-22 16:26:07 -07001899 public boolean isScrolling() {
Angus Kongaf24d282013-05-20 16:27:02 -07001900 return !mScroller.isFinished();
1901 }
1902
Angus Kong6d23f4c2013-05-22 16:26:07 -07001903 @Override
Doris Liu8de13112013-08-23 13:35:24 -07001904 public boolean isScaling() {
1905 return mScaleAnimator.isRunning();
Angus Kongaf24d282013-05-20 16:27:02 -07001906 }
1907
Angus Kong6d23f4c2013-05-22 16:26:07 -07001908 private int estimateMinX(int dataID, int leftPos, int viewWidth) {
Angus Kongfaaee012013-12-07 00:38:46 -08001909 return leftPos - (dataID + 100) * (viewWidth + mViewGapInPixel);
Angus Kong49b9ba22013-05-13 13:42:21 -07001910 }
1911
Angus Kong6d23f4c2013-05-22 16:26:07 -07001912 private int estimateMaxX(int dataID, int leftPos, int viewWidth) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07001913 return leftPos
Angus Kong6d23f4c2013-05-22 16:26:07 -07001914 + (mDataAdapter.getTotalNumber() - dataID + 100)
Angus Kongfaaee012013-12-07 00:38:46 -08001915 * (viewWidth + mViewGapInPixel);
Angus Kong6d23f4c2013-05-22 16:26:07 -07001916 }
1917
Doris Liu121022c2013-09-03 16:03:16 -07001918 /** Zoom all the way in or out on the image at the given pivot point. */
1919 private void zoomAt(final ViewItem current, final float focusX, final float focusY) {
1920 // End previous zoom animation, if any
1921 if (mZoomAnimator != null) {
1922 mZoomAnimator.end();
1923 }
1924 // Calculate end scale
Doris Liu3179f6a2013-10-08 16:54:22 -07001925 final float maxScale = getCurrentDataMaxScale(false);
Doris Liu121022c2013-09-03 16:03:16 -07001926 final float endScale = mScale < maxScale - maxScale * TOLERANCE
1927 ? maxScale : FULL_SCREEN_SCALE;
1928
1929 mZoomAnimator = new ValueAnimator();
1930 mZoomAnimator.setFloatValues(mScale, endScale);
1931 mZoomAnimator.setDuration(ZOOM_ANIMATION_DURATION_MS);
1932 mZoomAnimator.addListener(new Animator.AnimatorListener() {
1933 @Override
1934 public void onAnimationStart(Animator animation) {
1935 if (mScale == FULL_SCREEN_SCALE) {
Angus Kong45671602014-01-13 15:06:17 -08001936 if (mFullScreenUIHidden) {
1937 onLeaveFullScreenUiHidden();
1938 } else {
1939 onLeaveFullScreen();
1940 }
Doris Liu121022c2013-09-03 16:03:16 -07001941 setSurroundingViewsVisible(false);
Angus Kong45671602014-01-13 15:06:17 -08001942 } else if (inZoomView()) {
1943 onLeaveZoomView();
Doris Liu121022c2013-09-03 16:03:16 -07001944 }
1945 cancelLoadingZoomedImage();
1946 }
1947
1948 @Override
1949 public void onAnimationEnd(Animator animation) {
1950 // Make sure animation ends up having the correct scale even
1951 // if it is cancelled before it finishes
1952 if (mScale != endScale) {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08001953 current.postScale(focusX, focusY, endScale / mScale, mDrawArea.width(),
Doris Liu121022c2013-09-03 16:03:16 -07001954 mDrawArea.height());
1955 mScale = endScale;
1956 }
1957
Angus Kong45671602014-01-13 15:06:17 -08001958 if (inFullScreen()) {
Doris Liu121022c2013-09-03 16:03:16 -07001959 setSurroundingViewsVisible(true);
1960 mZoomView.setVisibility(GONE);
1961 current.resetTransform();
Angus Kong45671602014-01-13 15:06:17 -08001962 onEnterFullScreenUiHidden();
Doris Liu121022c2013-09-03 16:03:16 -07001963 } else {
1964 mController.loadZoomedImage();
Angus Kongfaaee012013-12-07 00:38:46 -08001965 onEnterZoomView();
Doris Liu121022c2013-09-03 16:03:16 -07001966 }
1967 mZoomAnimator = null;
1968 }
1969
1970 @Override
1971 public void onAnimationCancel(Animator animation) {
1972 // Do nothing.
1973 }
1974
1975 @Override
1976 public void onAnimationRepeat(Animator animation) {
1977 // Do nothing.
1978 }
1979 });
1980
1981 mZoomAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
1982 @Override
1983 public void onAnimationUpdate(ValueAnimator animation) {
1984 float newScale = (Float) animation.getAnimatedValue();
1985 float postScale = newScale / mScale;
1986 mScale = newScale;
1987 current.postScale(focusX, focusY, postScale, mDrawArea.width(),
1988 mDrawArea.height());
1989 }
1990 });
1991 mZoomAnimator.start();
1992 }
1993
Angus Kong6d23f4c2013-05-22 16:26:07 -07001994 @Override
Angus Konga6a6a732013-06-18 16:47:57 -07001995 public void scroll(float deltaX) {
Angus Kong7d2388d2013-09-18 23:56:01 -07001996 if (!stopScrolling(false)) {
Angus Konga6a6a732013-06-18 16:47:57 -07001997 return;
1998 }
1999 mCenterX += deltaX;
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07002000
2001 boolean stopScroll = clampCenterX();
Erin Dahlgren3044d8c2013-10-10 18:23:45 -07002002 if (stopScroll) {
Angus Kong1f36cf12013-10-01 16:58:52 -07002003 mController.stopScrolling(true);
2004 }
Angus Kong17e669d2013-09-05 16:12:22 -07002005 invalidate();
Angus Konga6a6a732013-06-18 16:47:57 -07002006 }
2007
2008 @Override
Angus Kong6d23f4c2013-05-22 16:26:07 -07002009 public void fling(float velocityX) {
Angus Kong7d2388d2013-09-18 23:56:01 -07002010 if (!stopScrolling(false)) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002011 return;
2012 }
Angus Kongec2fb472013-12-10 01:06:43 -08002013 final ViewItem item = mViewItem[mCurrentItem];
Angus Kong8969a672013-08-19 23:25:48 -07002014 if (item == null) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002015 return;
2016 }
Angus Kong6d23f4c2013-05-22 16:26:07 -07002017
2018 float scaledVelocityX = velocityX / mScale;
Angus Kongec2fb472013-12-10 01:06:43 -08002019 if (inFullScreen() && isViewTypeSticky(item) && scaledVelocityX < 0) {
Angus Konga6a6a732013-06-18 16:47:57 -07002020 // Swipe left in camera preview.
Angus Kongfaaee012013-12-07 00:38:46 -08002021 goToFilmstrip();
Angus Kong6d23f4c2013-05-22 16:26:07 -07002022 }
2023
2024 int w = getWidth();
2025 // Estimation of possible length on the left. To ensure the
2026 // velocity doesn't become too slow eventually, we add a huge number
2027 // to the estimated maximum.
Angus Kong563a2892013-09-09 17:15:01 -07002028 int minX = estimateMinX(item.getId(), item.getLeftPosition(), w);
Angus Kong6d23f4c2013-05-22 16:26:07 -07002029 // Estimation of possible length on the right. Likewise, exaggerate
2030 // the possible maximum too.
Angus Kong563a2892013-09-09 17:15:01 -07002031 int maxX = estimateMaxX(item.getId(), item.getLeftPosition(), w);
Angus Kong6d23f4c2013-05-22 16:26:07 -07002032 mScroller.fling(mCenterX, 0, (int) -velocityX, 0, minX, maxX, 0, 0);
Angus Kong6d23f4c2013-05-22 16:26:07 -07002033 }
2034
Angus Kongc02b13a2013-11-12 11:50:57 -08002035 void flingInsideZoomView(float velocityX, float velocityY) {
Angus Kong45671602014-01-13 15:06:17 -08002036 if (!inZoomView()) {
Doris Liud70f0fb2013-10-04 13:22:09 -07002037 return;
2038 }
2039
2040 final ViewItem current = mViewItem[mCurrentItem];
2041 if (current == null) {
2042 return;
2043 }
2044
2045 final int factor = DECELERATION_FACTOR;
2046 // Deceleration curve for distance:
2047 // S(t) = s + (e - s) * (1 - (1 - t/T) ^ factor)
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002048 // Need to find the ending distance (e), so that the starting
2049 // velocity is the velocity of fling.
Doris Liud70f0fb2013-10-04 13:22:09 -07002050 // Velocity is the derivative of distance
2051 // V(t) = (e - s) * factor * (-1) * (1 - t/T) ^ (factor - 1) * (-1/T)
2052 // = (e - s) * factor * (1 - t/T) ^ (factor - 1) / T
2053 // Since V(0) = V0, we have e = T / factor * V0 + s
2054
2055 // Duration T should be long enough so that at the end of the fling,
2056 // image moves at 1 pixel/s for about P = 50ms = 0.05s
2057 // i.e. V(T - P) = 1
2058 // V(T - P) = V0 * (1 - (T -P) /T) ^ (factor - 1) = 1
2059 // T = P * V0 ^ (1 / (factor -1))
2060
2061 final float velocity = Math.max(Math.abs(velocityX), Math.abs(velocityY));
2062 // Dynamically calculate duration
2063 final float duration = (float) (FLING_COASTING_DURATION_S
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002064 * Math.pow(velocity, (1f / (factor - 1f))));
Doris Liud70f0fb2013-10-04 13:22:09 -07002065
Angus Kong1f9db2d2014-01-09 00:56:35 -08002066 final float translationX = current.getTranslationX() * mScale;
2067 final float translationY = current.getTranslationY() * mScale;
Doris Liud70f0fb2013-10-04 13:22:09 -07002068
2069 final ValueAnimator decelerationX = ValueAnimator.ofFloat(translationX,
2070 translationX + duration / factor * velocityX);
2071 final ValueAnimator decelerationY = ValueAnimator.ofFloat(translationY,
2072 translationY + duration / factor * velocityY);
2073
2074 decelerationY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
2075 @Override
2076 public void onAnimationUpdate(ValueAnimator animation) {
2077 float transX = (Float) decelerationX.getAnimatedValue();
2078 float transY = (Float) decelerationY.getAnimatedValue();
2079
2080 current.updateTransform(transX, transY, mScale,
2081 mScale, mDrawArea.width(), mDrawArea.height());
2082 }
2083 });
2084
2085 mFlingAnimator = new AnimatorSet();
2086 mFlingAnimator.play(decelerationX).with(decelerationY);
2087 mFlingAnimator.setDuration((int) (duration * 1000));
2088 mFlingAnimator.setInterpolator(new TimeInterpolator() {
2089 @Override
2090 public float getInterpolation(float input) {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002091 return (float) (1.0f - Math.pow((1.0f - input), factor));
Doris Liud70f0fb2013-10-04 13:22:09 -07002092 }
2093 });
2094 mFlingAnimator.addListener(new Animator.AnimatorListener() {
2095 private boolean mCancelled = false;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002096
Doris Liud70f0fb2013-10-04 13:22:09 -07002097 @Override
2098 public void onAnimationStart(Animator animation) {
2099
2100 }
2101
2102 @Override
2103 public void onAnimationEnd(Animator animation) {
2104 if (!mCancelled) {
2105 loadZoomedImage();
2106 }
2107 mFlingAnimator = null;
2108 }
2109
2110 @Override
2111 public void onAnimationCancel(Animator animation) {
2112 mCancelled = true;
2113 }
2114
2115 @Override
2116 public void onAnimationRepeat(Animator animation) {
2117
2118 }
2119 });
2120 mFlingAnimator.start();
2121 }
2122
2123 @Override
Angus Kong7d2388d2013-09-18 23:56:01 -07002124 public boolean stopScrolling(boolean forced) {
2125 if (!isScrolling()) {
2126 return true;
2127 } else if (!mCanStopScroll && !forced) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002128 return false;
Angus Kong3d0b6912013-08-09 13:05:55 -07002129 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002130 mScroller.forceFinished(true);
Angus Kong49b9ba22013-05-13 13:42:21 -07002131 return true;
2132 }
2133
Angus Kong6d23f4c2013-05-22 16:26:07 -07002134 private void stopScale() {
Angus Kong49b9ba22013-05-13 13:42:21 -07002135 mScaleAnimator.cancel();
Angus Kong49b9ba22013-05-13 13:42:21 -07002136 }
2137
Angus Kong6d23f4c2013-05-22 16:26:07 -07002138 @Override
Angus Kong734598c2013-08-21 15:25:05 -07002139 public void scrollToPosition(int position, int duration, boolean interruptible) {
Angus Kong1f36cf12013-10-01 16:58:52 -07002140 if (mViewItem[mCurrentItem] == null) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002141 return;
Angus Kong3d0b6912013-08-09 13:05:55 -07002142 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002143 mCanStopScroll = interruptible;
Angus Kongfaaee012013-12-07 00:38:46 -08002144 mScroller.startScroll(mCenterX, 0, position - mCenterX, 0, duration);
Angus Kong49b9ba22013-05-13 13:42:21 -07002145 }
2146
Angus Kong734598c2013-08-21 15:25:05 -07002147 @Override
2148 public boolean goToNextItem() {
Alan Newberger8099a372014-03-24 17:17:38 -07002149 return goToItem(mCurrentItem + 1);
2150 }
2151
2152 @Override
2153 public boolean goToPreviousItem() {
2154 return goToItem(mCurrentItem - 1);
2155 }
2156
2157 private boolean goToItem(int itemIndex) {
2158 final ViewItem nextItem = mViewItem[itemIndex];
Angus Kong734598c2013-08-21 15:25:05 -07002159 if (nextItem == null) {
2160 return false;
2161 }
Angus Kongb21215a2013-09-20 18:41:46 -07002162 stopScrolling(true);
Angus Kong734598c2013-08-21 15:25:05 -07002163 scrollToPosition(nextItem.getCenterX(), GEOMETRY_ADJUST_TIME_MS * 2, false);
Angus Kongb21215a2013-09-20 18:41:46 -07002164
Angus Kongec2fb472013-12-10 01:06:43 -08002165 if (isViewTypeSticky(mViewItem[mCurrentItem])) {
Angus Kongb21215a2013-09-20 18:41:46 -07002166 // Special case when moving from camera preview.
2167 scaleTo(FILM_STRIP_SCALE, GEOMETRY_ADJUST_TIME_MS);
2168 }
Angus Kong734598c2013-08-21 15:25:05 -07002169 return true;
2170 }
2171
Angus Kong6d23f4c2013-05-22 16:26:07 -07002172 private void scaleTo(float scale, int duration) {
Angus Kong8969a672013-08-19 23:25:48 -07002173 if (mViewItem[mCurrentItem] == null) {
Angus Kong3d0b6912013-08-09 13:05:55 -07002174 return;
2175 }
Angus Kong6d23f4c2013-05-22 16:26:07 -07002176 stopScale();
Angus Kong49b9ba22013-05-13 13:42:21 -07002177 mScaleAnimator.setDuration(duration);
2178 mScaleAnimator.setFloatValues(mScale, scale);
Angus Kong49b9ba22013-05-13 13:42:21 -07002179 mScaleAnimator.start();
Angus Kong49b9ba22013-05-13 13:42:21 -07002180 }
2181
Angus Kong6d23f4c2013-05-22 16:26:07 -07002182 @Override
Angus Kongfaaee012013-12-07 00:38:46 -08002183 public void goToFilmstrip() {
Angus Kongec2fb472013-12-10 01:06:43 -08002184 if (mViewItem[mCurrentItem] == null) {
2185 return;
2186 }
Angus Kongfaaee012013-12-07 00:38:46 -08002187 if (mScale == FILM_STRIP_SCALE) {
2188 return;
2189 }
Angus Kong734598c2013-08-21 15:25:05 -07002190 scaleTo(FILM_STRIP_SCALE, GEOMETRY_ADJUST_TIME_MS);
Angus Kong17e669d2013-09-05 16:12:22 -07002191
Angus Kongec2fb472013-12-10 01:06:43 -08002192 final ViewItem currItem = mViewItem[mCurrentItem];
Angus Kong17e669d2013-09-05 16:12:22 -07002193 final ViewItem nextItem = mViewItem[mCurrentItem + 1];
Angus Kongec2fb472013-12-10 01:06:43 -08002194 if (currItem.getId() == 0 && isViewTypeSticky(currItem) && nextItem != null) {
Angus Kong17e669d2013-09-05 16:12:22 -07002195 // Deal with the special case of swiping in camera preview.
2196 scrollToPosition(nextItem.getCenterX(), GEOMETRY_ADJUST_TIME_MS, false);
2197 }
2198
Angus Kongfaaee012013-12-07 00:38:46 -08002199 if (mScale == FILM_STRIP_SCALE) {
2200 onLeaveFilmstrip();
Doris Liua52f04c2013-06-06 11:39:58 -07002201 }
Angus Kong6d23f4c2013-05-22 16:26:07 -07002202 }
2203
2204 @Override
Angus Kong734598c2013-08-21 15:25:05 -07002205 public void goToFullScreen() {
Doris Liu8de13112013-08-23 13:35:24 -07002206 if (inFullScreen()) {
2207 return;
2208 }
Angus Kongfaaee012013-12-07 00:38:46 -08002209 scaleTo(FULL_SCREEN_SCALE, GEOMETRY_ADJUST_TIME_MS);
Angus Konga6a6a732013-06-18 16:47:57 -07002210 }
2211
Doris Liud70f0fb2013-10-04 13:22:09 -07002212 private void cancelFlingAnimation() {
2213 // Cancels flinging for zoomed images
2214 if (isFlingAnimationRunning()) {
2215 mFlingAnimator.cancel();
2216 }
2217 }
2218
2219 private void cancelZoomAnimation() {
2220 if (isZoomAnimationRunning()) {
2221 mZoomAnimator.cancel();
2222 }
2223 }
2224
Doris Liu8de13112013-08-23 13:35:24 -07002225 private void setSurroundingViewsVisible(boolean visible) {
2226 // Hide everything on the left
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002227 // TODO: Need to find a better way to toggle the visibility of views
2228 // around the current view.
Doris Liu8de13112013-08-23 13:35:24 -07002229 for (int i = 0; i < mCurrentItem; i++) {
2230 if (i == mCurrentItem || mViewItem[i] == null) {
2231 continue;
2232 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08002233 mViewItem[i].setVisibility(visible ? VISIBLE : INVISIBLE);
Doris Liu8de13112013-08-23 13:35:24 -07002234 }
2235 }
2236
Angus Kong571a8c32014-03-13 12:53:03 -07002237 private Uri getCurrentUri() {
Doris Liu8de13112013-08-23 13:35:24 -07002238 ViewItem curr = mViewItem[mCurrentItem];
2239 if (curr == null) {
2240 return Uri.EMPTY;
2241 }
Angus Kong571a8c32014-03-13 12:53:03 -07002242 return mDataAdapter.getImageData(curr.getId()).getUri();
Doris Liu8de13112013-08-23 13:35:24 -07002243 }
2244
2245 /**
2246 * Here we only support up to 1:1 image zoom (i.e. a 100% view of the
2247 * actual pixels). The max scale that we can apply on the view should
2248 * make the view same size as the image, in pixels.
2249 */
Doris Liu3179f6a2013-10-08 16:54:22 -07002250 private float getCurrentDataMaxScale(boolean allowOverScale) {
Doris Liu8de13112013-08-23 13:35:24 -07002251 ViewItem curr = mViewItem[mCurrentItem];
Angus Kong01054e92013-12-10 11:06:18 -08002252 ImageData imageData = mDataAdapter.getImageData(curr.getId());
2253 if (curr == null || !imageData
2254 .isUIActionSupported(ImageData.ACTION_ZOOM)) {
Doris Liu8de13112013-08-23 13:35:24 -07002255 return FULL_SCREEN_SCALE;
2256 }
Angus Kong01054e92013-12-10 11:06:18 -08002257 float imageWidth = imageData.getWidth();
Angus Kongc195e7a2014-02-20 16:56:37 -08002258 if (imageData.getRotation() == 90
2259 || imageData.getRotation() == 270) {
Angus Kong01054e92013-12-10 11:06:18 -08002260 imageWidth = imageData.getHeight();
ztenghui06578b52013-10-07 14:54:55 -07002261 }
Doris Liu3179f6a2013-10-08 16:54:22 -07002262 float scale = imageWidth / curr.getWidth();
2263 if (allowOverScale) {
2264 // In addition to the scale we apply to the view for 100% view
2265 // (i.e. each pixel on screen corresponds to a pixel in image)
2266 // we allow scaling beyond that for better detail viewing.
2267 scale *= mOverScaleFactor;
2268 }
2269 return scale;
Doris Liu8de13112013-08-23 13:35:24 -07002270 }
2271
2272 private void loadZoomedImage() {
Angus Kong45671602014-01-13 15:06:17 -08002273 if (!inZoomView()) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002274 return;
2275 }
Doris Liu8de13112013-08-23 13:35:24 -07002276 ViewItem curr = mViewItem[mCurrentItem];
Doris Liu87fc5e12013-09-16 13:38:24 -07002277 if (curr == null) {
2278 return;
2279 }
Angus Kong01054e92013-12-10 11:06:18 -08002280 ImageData imageData = mDataAdapter.getImageData(curr.getId());
2281 if (!imageData.isUIActionSupported(ImageData.ACTION_ZOOM)) {
Doris Liu8de13112013-08-23 13:35:24 -07002282 return;
2283 }
Angus Kong571a8c32014-03-13 12:53:03 -07002284 Uri uri = getCurrentUri();
Doris Liu8de13112013-08-23 13:35:24 -07002285 RectF viewRect = curr.getViewRect();
2286 if (uri == null || uri == Uri.EMPTY) {
2287 return;
2288 }
Angus Kongc195e7a2014-02-20 16:56:37 -08002289 int orientation = imageData.getRotation();
Doris Liu87fc5e12013-09-16 13:38:24 -07002290 mZoomView.loadBitmap(uri, orientation, viewRect);
Doris Liu8de13112013-08-23 13:35:24 -07002291 }
2292
2293 private void cancelLoadingZoomedImage() {
2294 mZoomView.cancelPartialDecodingTask();
Angus Kong6d23f4c2013-05-22 16:26:07 -07002295 }
2296
2297 @Override
Alan Newberger3f969c12013-08-23 10:10:30 -07002298 public void goToFirstItem() {
Angus Kongfaaee012013-12-07 00:38:46 -08002299 if (mViewItem[mCurrentItem] == null) {
2300 return;
2301 }
Doris Liu03b75412013-09-09 17:26:14 -07002302 resetZoomView();
Alan Newberger3f969c12013-08-23 10:10:30 -07002303 // TODO: animate to camera if it is still in the mViewItem buffer
2304 // versus a full reload which will perform an immediate transition
2305 reload();
Angus Kong49b9ba22013-05-13 13:42:21 -07002306 }
2307
Angus Kong45671602014-01-13 15:06:17 -08002308 public boolean inZoomView() {
2309 return FilmstripView.this.inZoomView();
Doris Liu8de13112013-08-23 13:35:24 -07002310 }
Doris Liu121022c2013-09-03 16:03:16 -07002311
Doris Liud70f0fb2013-10-04 13:22:09 -07002312 public boolean isFlingAnimationRunning() {
2313 return mFlingAnimator != null && mFlingAnimator.isRunning();
2314 }
2315
Doris Liu121022c2013-09-03 16:03:16 -07002316 public boolean isZoomAnimationRunning() {
2317 return mZoomAnimator != null && mZoomAnimator.isRunning();
2318 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002319 }
2320
Angus Kongec2fb472013-12-10 01:06:43 -08002321 private boolean isCurrentItemCentered() {
2322 return mViewItem[mCurrentItem].getCenterX() == mCenterX;
2323 }
2324
Angus Kong1f36cf12013-10-01 16:58:52 -07002325 private static class MyScroller {
Angus Kongb21215a2013-09-20 18:41:46 -07002326 public interface Listener {
2327 public void onScrollUpdate(int currX, int currY);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002328
Angus Kongb21215a2013-09-20 18:41:46 -07002329 public void onScrollEnd();
2330 }
2331
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002332 private final Handler mHandler;
2333 private final Listener mListener;
Angus Kong1f36cf12013-10-01 16:58:52 -07002334
2335 private final Scroller mScroller;
2336
2337 private final ValueAnimator mXScrollAnimator;
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002338 private final Runnable mScrollChecker = new Runnable() {
Angus Kongb21215a2013-09-20 18:41:46 -07002339 @Override
2340 public void run() {
Angus Kong1f36cf12013-10-01 16:58:52 -07002341 boolean newPosition = mScroller.computeScrollOffset();
Angus Kongb21215a2013-09-20 18:41:46 -07002342 if (!newPosition) {
2343 mListener.onScrollEnd();
2344 return;
2345 }
Angus Kong1f36cf12013-10-01 16:58:52 -07002346 mListener.onScrollUpdate(mScroller.getCurrX(), mScroller.getCurrY());
Angus Kongb21215a2013-09-20 18:41:46 -07002347 mHandler.removeCallbacks(this);
2348 mHandler.post(this);
2349 }
2350 };
2351
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002352 private final ValueAnimator.AnimatorUpdateListener mXScrollAnimatorUpdateListener =
Angus Kong1f36cf12013-10-01 16:58:52 -07002353 new ValueAnimator.AnimatorUpdateListener() {
2354 @Override
2355 public void onAnimationUpdate(ValueAnimator animation) {
2356 mListener.onScrollUpdate((Integer) animation.getAnimatedValue(), 0);
2357 }
2358 };
2359
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002360 private final Animator.AnimatorListener mXScrollAnimatorListener =
Angus Kong1f36cf12013-10-01 16:58:52 -07002361 new Animator.AnimatorListener() {
2362 @Override
2363 public void onAnimationCancel(Animator animation) {
2364 // Do nothing.
2365 }
2366
2367 @Override
2368 public void onAnimationEnd(Animator animation) {
2369 mListener.onScrollEnd();
2370 }
2371
2372 @Override
2373 public void onAnimationRepeat(Animator animation) {
2374 // Do nothing.
2375 }
2376
2377 @Override
2378 public void onAnimationStart(Animator animation) {
2379 // Do nothing.
2380 }
2381 };
2382
Angus Kong1f36cf12013-10-01 16:58:52 -07002383 public MyScroller(Context ctx, Handler handler, Listener listener,
2384 TimeInterpolator interpolator) {
Angus Kongb21215a2013-09-20 18:41:46 -07002385 mHandler = handler;
2386 mListener = listener;
Angus Kong1f36cf12013-10-01 16:58:52 -07002387 mScroller = new Scroller(ctx);
2388 mXScrollAnimator = new ValueAnimator();
2389 mXScrollAnimator.addUpdateListener(mXScrollAnimatorUpdateListener);
2390 mXScrollAnimator.addListener(mXScrollAnimatorListener);
2391 mXScrollAnimator.setInterpolator(interpolator);
Angus Kongb21215a2013-09-20 18:41:46 -07002392 }
2393
Angus Kongb21215a2013-09-20 18:41:46 -07002394 public void fling(
2395 int startX, int startY,
2396 int velocityX, int velocityY,
2397 int minX, int maxX,
2398 int minY, int maxY) {
Angus Kong1f36cf12013-10-01 16:58:52 -07002399 mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
Angus Kongb21215a2013-09-20 18:41:46 -07002400 runChecker();
2401 }
2402
Angus Kongb21215a2013-09-20 18:41:46 -07002403 public void startScroll(int startX, int startY, int dx, int dy) {
Angus Kong1f36cf12013-10-01 16:58:52 -07002404 mScroller.startScroll(startX, startY, dx, dy);
Angus Kongb21215a2013-09-20 18:41:46 -07002405 runChecker();
2406 }
2407
Angus Kong1f36cf12013-10-01 16:58:52 -07002408 /** Only starts and updates scroll in x-axis. */
Angus Kongb21215a2013-09-20 18:41:46 -07002409 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
Angus Kong1f36cf12013-10-01 16:58:52 -07002410 mXScrollAnimator.cancel();
2411 mXScrollAnimator.setDuration(duration);
2412 mXScrollAnimator.setIntValues(startX, startX + dx);
2413 mXScrollAnimator.start();
2414 }
2415
2416 public boolean isFinished() {
2417 return (mScroller.isFinished() && !mXScrollAnimator.isRunning());
2418 }
2419
2420 public void forceFinished(boolean finished) {
2421 mScroller.forceFinished(finished);
2422 if (finished) {
2423 mXScrollAnimator.cancel();
2424 }
Angus Kongb21215a2013-09-20 18:41:46 -07002425 }
2426
2427 private void runChecker() {
2428 if (mHandler == null || mListener == null) {
2429 return;
2430 }
2431 mHandler.removeCallbacks(mScrollChecker);
2432 mHandler.post(mScrollChecker);
2433 }
2434 }
2435
Angus Kong166e36f2013-12-03 08:54:42 -08002436 private class MyGestureReceiver implements FilmstripGestureRecognizer.Listener {
Angus Kong69f09642013-12-13 18:25:22 -08002437
2438 private static final int SCROLL_DIR_NONE = 0;
2439 private static final int SCROLL_DIR_VERTICAL = 1;
2440 private static final int SCROLL_DIR_HORIZONTAL = 2;
Angus Kong49b9ba22013-05-13 13:42:21 -07002441 // Indicating the current trend of scaling is up (>1) or down (<1).
2442 private float mScaleTrend;
Doris Liu8de13112013-08-23 13:35:24 -07002443 private float mMaxScale;
Angus Kong69f09642013-12-13 18:25:22 -08002444 private int mScrollingDirection = SCROLL_DIR_NONE;
Sam Juddbcd11522014-03-12 15:48:18 -07002445 private long mLastDownTime;
2446 private float mLastDownY;
Angus Kong49b9ba22013-05-13 13:42:21 -07002447
2448 @Override
2449 public boolean onSingleTapUp(float x, float y) {
ztenghui8566dd72013-09-12 14:56:56 -07002450 ViewItem centerItem = mViewItem[mCurrentItem];
Angus Kong62848152013-11-08 17:25:29 -08002451 if (inFilmstrip()) {
Angus Kong8969a672013-08-19 23:25:48 -07002452 if (centerItem != null && centerItem.areaContains(x, y)) {
Angus Kong734598c2013-08-21 15:25:05 -07002453 mController.goToFullScreen();
Angus Konga6a6a732013-06-18 16:47:57 -07002454 return true;
2455 }
2456 } else if (inFullScreen()) {
Angus Kong45671602014-01-13 15:06:17 -08002457 if (mFullScreenUIHidden) {
2458 onLeaveFullScreenUiHidden();
2459 onEnterFullScreen();
2460 } else {
2461 onLeaveFullScreen();
2462 onEnterFullScreenUiHidden();
2463 }
Angus Konga6a6a732013-06-18 16:47:57 -07002464 return true;
2465 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002466 return false;
2467 }
2468
2469 @Override
Angus Kong7e1c5fe2013-08-14 16:44:51 -07002470 public boolean onDoubleTap(float x, float y) {
Doris Liu5326f022013-09-27 14:12:00 -07002471 ViewItem current = mViewItem[mCurrentItem];
Angus Kong45671602014-01-13 15:06:17 -08002472 if (current == null) {
2473 return false;
2474 }
2475 if (inFilmstrip()) {
Doris Liu5326f022013-09-27 14:12:00 -07002476 mController.goToFullScreen();
2477 return true;
2478 } else if (mScale < FULL_SCREEN_SCALE || inCameraFullscreen()) {
Doris Liu121022c2013-09-03 16:03:16 -07002479 return false;
2480 }
Angus Kong7d2388d2013-09-18 23:56:01 -07002481 if (!mController.stopScrolling(false)) {
2482 return false;
Doris Liue1d5ba42013-09-10 12:44:20 -07002483 }
Angus Kong45671602014-01-13 15:06:17 -08002484 if (inFullScreen()) {
2485 mController.zoomAt(current, x, y);
2486 checkItemAtMaxSize();
2487 return true;
2488 } else if (mScale > FULL_SCREEN_SCALE) {
2489 // In zoom view.
2490 mController.zoomAt(current, x, y);
2491 }
2492 return false;
Angus Kong7e1c5fe2013-08-14 16:44:51 -07002493 }
2494
2495 @Override
Angus Kong49b9ba22013-05-13 13:42:21 -07002496 public boolean onDown(float x, float y) {
Sam Juddbcd11522014-03-12 15:48:18 -07002497 mLastDownTime = SystemClock.uptimeMillis();
2498 mLastDownY = y;
Doris Liud70f0fb2013-10-04 13:22:09 -07002499 mController.cancelFlingAnimation();
Angus Kong7d2388d2013-09-18 23:56:01 -07002500 if (!mController.stopScrolling(false)) {
2501 return false;
Angus Konga6a6a732013-06-18 16:47:57 -07002502 }
Doris Liub0288ec2013-11-04 14:03:28 -08002503
Angus Kong49b9ba22013-05-13 13:42:21 -07002504 return true;
2505 }
2506
2507 @Override
Angus Kong87f9a622013-05-16 16:59:39 -07002508 public boolean onUp(float x, float y) {
Angus Kong1f9db2d2014-01-09 00:56:35 -08002509 ViewItem currItem = mViewItem[mCurrentItem];
Angus Kong7d2388d2013-09-18 23:56:01 -07002510 if (currItem == null) {
2511 return false;
2512 }
Doris Liud70f0fb2013-10-04 13:22:09 -07002513 if (mController.isZoomAnimationRunning() || mController.isFlingAnimationRunning()) {
Doris Liu121022c2013-09-03 16:03:16 -07002514 return false;
2515 }
Angus Kong45671602014-01-13 15:06:17 -08002516 if (inZoomView()) {
Doris Liu8de13112013-08-23 13:35:24 -07002517 mController.loadZoomedImage();
2518 return true;
2519 }
Sam Juddbcd11522014-03-12 15:48:18 -07002520 float promoteHeight = getHeight() * PROMOTE_HEIGHT_RATIO;
2521 float velocityPromoteHeight = getHeight() * VELOCITY_PROMOTE_HEIGHT_RATIO;
Angus Kong47721fa2013-08-14 15:24:00 -07002522 mIsUserScrolling = false;
Angus Kong69f09642013-12-13 18:25:22 -08002523 mScrollingDirection = SCROLL_DIR_NONE;
Angus Kong7d2388d2013-09-18 23:56:01 -07002524 // Finds items promoted/demoted.
Sam Juddbcd11522014-03-12 15:48:18 -07002525 float speedY = Math.abs(y - mLastDownY)
2526 / (SystemClock.uptimeMillis() - mLastDownTime);
Angus Kong49b9ba22013-05-13 13:42:21 -07002527 for (int i = 0; i < BUFFER_SIZE; i++) {
Angus Kong8969a672013-08-19 23:25:48 -07002528 if (mViewItem[i] == null) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002529 continue;
2530 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08002531 float transY = mViewItem[i].getTranslationY();
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002532 if (transY == 0) {
2533 continue;
2534 }
Angus Kong563a2892013-09-09 17:15:01 -07002535 int id = mViewItem[i].getId();
Angus Kong87f9a622013-05-16 16:59:39 -07002536
2537 if (mDataAdapter.getImageData(id)
Angus Kong01054e92013-12-10 11:06:18 -08002538 .isUIActionSupported(ImageData.ACTION_DEMOTE)
Sam Juddbcd11522014-03-12 15:48:18 -07002539 && ((transY > promoteHeight)
2540 || (transY > velocityPromoteHeight && speedY > PROMOTE_VELOCITY))) {
Angus Kong87f9a622013-05-16 16:59:39 -07002541 demoteData(i, id);
2542 } else if (mDataAdapter.getImageData(id)
Angus Kong01054e92013-12-10 11:06:18 -08002543 .isUIActionSupported(ImageData.ACTION_PROMOTE)
Sam Juddbcd11522014-03-12 15:48:18 -07002544 && (transY < -promoteHeight
2545 || (transY < -velocityPromoteHeight && speedY > PROMOTE_VELOCITY))) {
Angus Kong87f9a622013-05-16 16:59:39 -07002546 promoteData(i, id);
2547 } else {
Angus Kong9f02c872013-05-20 13:46:14 -07002548 // put the view back.
Angus Kong1f9db2d2014-01-09 00:56:35 -08002549 slideViewBack(mViewItem[i]);
Angus Kong49b9ba22013-05-13 13:42:21 -07002550 }
2551 }
Angus Kong7d2388d2013-09-18 23:56:01 -07002552
Angus Kong1f9db2d2014-01-09 00:56:35 -08002553 // The data might be changed. Re-check.
2554 currItem = mViewItem[mCurrentItem];
2555 if (currItem == null) {
2556 return true;
2557 }
2558
Doris Liu5326f022013-09-27 14:12:00 -07002559 int currId = currItem.getId();
Angus Kongec2fb472013-12-10 01:06:43 -08002560 if (mCenterX > currItem.getCenterX() + CAMERA_PREVIEW_SWIPE_THRESHOLD && currId == 0 &&
2561 isViewTypeSticky(currItem) && mDataIdOnUserScrolling == 0) {
Angus Kongfaaee012013-12-07 00:38:46 -08002562 mController.goToFilmstrip();
Angus Kong7d2388d2013-09-18 23:56:01 -07002563 // Special case to go from camera preview to the next photo.
2564 if (mViewItem[mCurrentItem + 1] != null) {
2565 mController.scrollToPosition(
2566 mViewItem[mCurrentItem + 1].getCenterX(),
2567 GEOMETRY_ADJUST_TIME_MS, false);
2568 } else {
2569 // No next photo.
2570 snapInCenter();
2571 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002572 }
Angus Kongec2fb472013-12-10 01:06:43 -08002573 if (isCurrentItemCentered() && currId == 0 && isViewTypeSticky(currItem)) {
Angus Kong7d2388d2013-09-18 23:56:01 -07002574 mController.goToFullScreen();
2575 } else {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002576 if (mDataIdOnUserScrolling == 0 && currId != 0) {
Angus Kong7d2388d2013-09-18 23:56:01 -07002577 // Special case to go to filmstrip when the user scroll away
2578 // from the camera preview and the current one is not the
2579 // preview anymore.
Angus Kongfaaee012013-12-07 00:38:46 -08002580 mController.goToFilmstrip();
Doris Liu5326f022013-09-27 14:12:00 -07002581 mDataIdOnUserScrolling = currId;
Angus Kong7d2388d2013-09-18 23:56:01 -07002582 }
2583 snapInCenter();
2584 }
Angus Kong87f9a622013-05-16 16:59:39 -07002585 return false;
2586 }
2587
2588 @Override
Angus Kong26795a92014-02-20 09:18:09 -08002589 public void onLongPress(float x, float y) {
2590 final int dataId = getCurrentId();
2591 if (dataId == -1) {
2592 return;
2593 }
2594 mListener.onFocusedDataLongPressed(dataId);
2595 }
2596
2597 @Override
Angus Kong87f9a622013-05-16 16:59:39 -07002598 public boolean onScroll(float x, float y, float dx, float dy) {
Angus Kong166e36f2013-12-03 08:54:42 -08002599 final ViewItem currItem = mViewItem[mCurrentItem];
ztenghui7be0e2b2013-10-15 14:13:26 -07002600 if (currItem == null) {
2601 return false;
2602 }
Seth Raphael455ba5a2014-02-13 15:10:06 -08002603 if (inFullScreen() && !mDataAdapter.canSwipeInFullScreen(currItem.getId())) {
Angus Kong3d0b6912013-08-09 13:05:55 -07002604 return false;
2605 }
Doris Liub0288ec2013-11-04 14:03:28 -08002606 hideZoomView();
Doris Liu8de13112013-08-23 13:35:24 -07002607 // When image is zoomed in to be bigger than the screen
Angus Kong45671602014-01-13 15:06:17 -08002608 if (inZoomView()) {
Doris Liu8de13112013-08-23 13:35:24 -07002609 ViewItem curr = mViewItem[mCurrentItem];
Angus Kong1f9db2d2014-01-09 00:56:35 -08002610 float transX = curr.getTranslationX() * mScale - dx;
2611 float transY = curr.getTranslationY() * mScale - dy;
Doris Liu8de13112013-08-23 13:35:24 -07002612 curr.updateTransform(transX, transY, mScale, mScale, mDrawArea.width(),
2613 mDrawArea.height());
2614 return true;
2615 }
Angus Konga6a6a732013-06-18 16:47:57 -07002616 int deltaX = (int) (dx / mScale);
Angus Kong7d2388d2013-09-18 23:56:01 -07002617 // Forces the current scrolling to stop.
2618 mController.stopScrolling(true);
2619 if (!mIsUserScrolling) {
2620 mIsUserScrolling = true;
2621 mDataIdOnUserScrolling = mViewItem[mCurrentItem].getId();
2622 }
Angus Kong62848152013-11-08 17:25:29 -08002623 if (inFilmstrip()) {
Angus Kong69f09642013-12-13 18:25:22 -08002624 // Disambiguate horizontal/vertical first.
2625 if (mScrollingDirection == SCROLL_DIR_NONE) {
2626 mScrollingDirection = (Math.abs(dx) > Math.abs(dy)) ? SCROLL_DIR_HORIZONTAL :
2627 SCROLL_DIR_VERTICAL;
2628 }
2629 if (mScrollingDirection == SCROLL_DIR_HORIZONTAL) {
2630 if (mCenterX == currItem.getCenterX() && currItem.getId() == 0 && dx < 0) {
2631 // Already at the beginning, don't process the swipe.
2632 mIsUserScrolling = false;
2633 mScrollingDirection = SCROLL_DIR_NONE;
2634 return false;
2635 }
Angus Konga6a6a732013-06-18 16:47:57 -07002636 mController.scroll(deltaX);
2637 } else {
2638 // Vertical part. Promote or demote.
Angus Konga6a6a732013-06-18 16:47:57 -07002639 int hit = 0;
2640 Rect hitRect = new Rect();
2641 for (; hit < BUFFER_SIZE; hit++) {
Angus Kong8969a672013-08-19 23:25:48 -07002642 if (mViewItem[hit] == null) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002643 continue;
2644 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08002645 mViewItem[hit].getHitRect(hitRect);
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002646 if (hitRect.contains((int) x, (int) y)) {
2647 break;
2648 }
Angus Konga6a6a732013-06-18 16:47:57 -07002649 }
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002650 if (hit == BUFFER_SIZE) {
Angus Kong166e36f2013-12-03 08:54:42 -08002651 // Hit none.
2652 return true;
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002653 }
Angus Konga6a6a732013-06-18 16:47:57 -07002654
Angus Kong01054e92013-12-10 11:06:18 -08002655 ImageData data = mDataAdapter.getImageData(mViewItem[hit].getId());
Angus Kong1f9db2d2014-01-09 00:56:35 -08002656 float transY = mViewItem[hit].getTranslationY() - dy / mScale;
Angus Kong01054e92013-12-10 11:06:18 -08002657 if (!data.isUIActionSupported(ImageData.ACTION_DEMOTE) &&
Angus Kongc02b13a2013-11-12 11:50:57 -08002658 transY > 0f) {
Angus Konga6a6a732013-06-18 16:47:57 -07002659 transY = 0f;
2660 }
Angus Kong01054e92013-12-10 11:06:18 -08002661 if (!data.isUIActionSupported(ImageData.ACTION_PROMOTE) &&
Angus Kongc02b13a2013-11-12 11:50:57 -08002662 transY < 0f) {
Angus Konga6a6a732013-06-18 16:47:57 -07002663 transY = 0f;
2664 }
Angus Kong1f9db2d2014-01-09 00:56:35 -08002665 mViewItem[hit].setTranslationY(transY);
Angus Konga6a6a732013-06-18 16:47:57 -07002666 }
2667 } else if (inFullScreen()) {
Angus Kong166e36f2013-12-03 08:54:42 -08002668 if (mViewItem[mCurrentItem] == null || (deltaX < 0 && mCenterX <=
2669 currItem.getCenterX() && currItem.getId() == 0)) {
2670 return false;
2671 }
Angus Kong025136b2013-08-21 00:38:05 -07002672 // Multiplied by 1.2 to make it more easy to swipe.
2673 mController.scroll((int) (deltaX * 1.2));
Angus Kong87f9a622013-05-16 16:59:39 -07002674 }
Angus Kong7d2388d2013-09-18 23:56:01 -07002675 invalidate();
Angus Konga6a6a732013-06-18 16:47:57 -07002676
Angus Kong49b9ba22013-05-13 13:42:21 -07002677 return true;
2678 }
2679
2680 @Override
2681 public boolean onFling(float velocityX, float velocityY) {
Angus Kong17e669d2013-09-05 16:12:22 -07002682 final ViewItem currItem = mViewItem[mCurrentItem];
2683 if (currItem == null) {
2684 return false;
2685 }
ztenghui7be0e2b2013-10-15 14:13:26 -07002686 if (!mDataAdapter.canSwipeInFullScreen(currItem.getId())) {
2687 return false;
2688 }
Angus Kong45671602014-01-13 15:06:17 -08002689 if (inZoomView()) {
Doris Liud70f0fb2013-10-04 13:22:09 -07002690 // Fling within the zoomed image
2691 mController.flingInsideZoomView(velocityX, velocityY);
2692 return true;
2693 }
Angus Kong025136b2013-08-21 00:38:05 -07002694 if (Math.abs(velocityX) < Math.abs(velocityY)) {
Angus Konga6a6a732013-06-18 16:47:57 -07002695 // ignore vertical fling.
Angus Kong025136b2013-08-21 00:38:05 -07002696 return true;
Angus Kong49b9ba22013-05-13 13:42:21 -07002697 }
Angus Kong025136b2013-08-21 00:38:05 -07002698
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002699 // In full-screen, fling of a velocity above a threshold should go
2700 // to the next/prev photos
Angus Kong17e669d2013-09-05 16:12:22 -07002701 if (mScale == FULL_SCREEN_SCALE) {
2702 int currItemCenterX = currItem.getCenterX();
Angus Kong7d2388d2013-09-18 23:56:01 -07002703
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002704 if (velocityX > 0) { // left
Angus Kong17e669d2013-09-05 16:12:22 -07002705 if (mCenterX > currItemCenterX) {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002706 // The visually previous item is actually the current
2707 // item.
Angus Kong17e669d2013-09-05 16:12:22 -07002708 mController.scrollToPosition(
2709 currItemCenterX, GEOMETRY_ADJUST_TIME_MS, true);
2710 return true;
2711 }
2712 ViewItem prevItem = mViewItem[mCurrentItem - 1];
2713 if (prevItem == null) {
2714 return false;
2715 }
2716 mController.scrollToPosition(
2717 prevItem.getCenterX(), GEOMETRY_ADJUST_TIME_MS, true);
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002718 } else { // right
Angus Kong7d2388d2013-09-18 23:56:01 -07002719 if (mController.stopScrolling(false)) {
2720 if (mCenterX < currItemCenterX) {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002721 // The visually next item is actually the current
2722 // item.
Angus Kong7d2388d2013-09-18 23:56:01 -07002723 mController.scrollToPosition(
2724 currItemCenterX, GEOMETRY_ADJUST_TIME_MS, true);
2725 return true;
2726 }
2727 final ViewItem nextItem = mViewItem[mCurrentItem + 1];
2728 if (nextItem == null) {
2729 return false;
2730 }
Angus Kong17e669d2013-09-05 16:12:22 -07002731 mController.scrollToPosition(
Angus Kong7d2388d2013-09-18 23:56:01 -07002732 nextItem.getCenterX(), GEOMETRY_ADJUST_TIME_MS, true);
Angus Kongec2fb472013-12-10 01:06:43 -08002733 if (isViewTypeSticky(currItem)) {
Angus Kongfaaee012013-12-07 00:38:46 -08002734 mController.goToFilmstrip();
Angus Kong7d2388d2013-09-18 23:56:01 -07002735 }
Angus Kong17e669d2013-09-05 16:12:22 -07002736 }
Angus Kong17e669d2013-09-05 16:12:22 -07002737 }
Angus Kong025136b2013-08-21 00:38:05 -07002738 }
Angus Kong17e669d2013-09-05 16:12:22 -07002739
2740 if (mScale == FILM_STRIP_SCALE) {
2741 mController.fling(velocityX);
2742 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002743 return true;
2744 }
2745
2746 @Override
2747 public boolean onScaleBegin(float focusX, float focusY) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002748 if (inCameraFullscreen()) {
2749 return false;
2750 }
Doris Liub0288ec2013-11-04 14:03:28 -08002751
2752 hideZoomView();
Angus Kong49b9ba22013-05-13 13:42:21 -07002753 mScaleTrend = 1f;
Doris Liu79dcb122013-09-16 12:21:21 -07002754 // If the image is smaller than screen size, we should allow to zoom
2755 // in to full screen size
Doris Liu3179f6a2013-10-08 16:54:22 -07002756 mMaxScale = Math.max(mController.getCurrentDataMaxScale(true), FULL_SCREEN_SCALE);
Angus Kong49b9ba22013-05-13 13:42:21 -07002757 return true;
2758 }
2759
2760 @Override
2761 public boolean onScale(float focusX, float focusY, float scale) {
Sascha Haeberlingf1f51862013-07-31 11:28:21 -07002762 if (inCameraFullscreen()) {
2763 return false;
2764 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002765
Angus Kong87f9a622013-05-16 16:59:39 -07002766 mScaleTrend = mScaleTrend * 0.3f + scale * 0.7f;
Doris Liu121022c2013-09-03 16:03:16 -07002767 float newScale = mScale * scale;
2768 if (mScale < FULL_SCREEN_SCALE && newScale < FULL_SCREEN_SCALE) {
Angus Kongfaaee012013-12-07 00:38:46 -08002769 if (newScale <= FILM_STRIP_SCALE) {
2770 newScale = FILM_STRIP_SCALE;
2771 }
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002772 // Scaled view is smaller than or equal to screen size both
2773 // before and after scaling
Angus Kongfaaee012013-12-07 00:38:46 -08002774 if (mScale != newScale) {
2775 if (mScale == FILM_STRIP_SCALE) {
2776 onLeaveFilmstrip();
2777 }
2778 if (newScale == FILM_STRIP_SCALE) {
2779 onEnterFilmstrip();
2780 }
Doris Liu8de13112013-08-23 13:35:24 -07002781 }
Angus Kongfaaee012013-12-07 00:38:46 -08002782 mScale = newScale;
Angus Kong7d2388d2013-09-18 23:56:01 -07002783 invalidate();
Doris Liu121022c2013-09-03 16:03:16 -07002784 } else if (mScale < FULL_SCREEN_SCALE && newScale >= FULL_SCREEN_SCALE) {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002785 // Going from smaller than screen size to bigger than or equal
2786 // to screen size
Angus Kongfaaee012013-12-07 00:38:46 -08002787 if (mScale == FILM_STRIP_SCALE) {
2788 onLeaveFilmstrip();
2789 }
Angus Kong4ff5a1a2013-08-14 16:07:54 -07002790 mScale = FULL_SCREEN_SCALE;
Angus Kongfaaee012013-12-07 00:38:46 -08002791 onEnterFullScreen();
Doris Liu8de13112013-08-23 13:35:24 -07002792 mController.setSurroundingViewsVisible(false);
Angus Kong45671602014-01-13 15:06:17 -08002793 invalidate();
Doris Liu121022c2013-09-03 16:03:16 -07002794 } else if (mScale >= FULL_SCREEN_SCALE && newScale < FULL_SCREEN_SCALE) {
Sascha Haeberling280fd3e2013-11-21 13:52:15 -08002795 // Going from bigger than or equal to screen size to smaller
2796 // than screen size
Angus Kong45671602014-01-13 15:06:17 -08002797 if (inFullScreen()) {
2798 if (mFullScreenUIHidden) {
2799 onLeaveFullScreenUiHidden();
2800 } else {
2801 onLeaveFullScreen();
2802 }
2803 } else {
2804 onLeaveZoomView();
Angus Kongfaaee012013-12-07 00:38:46 -08002805 }
Doris Liu121022c2013-09-03 16:03:16 -07002806 mScale = newScale;
Angus Kong45671602014-01-13 15:06:17 -08002807 onEnterFilmstrip();
Angus Kong7d2388d2013-09-18 23:56:01 -07002808 invalidate();
Doris Liu8de13112013-08-23 13:35:24 -07002809 } else {
2810 // Scaled view bigger than or equal to screen size both before
2811 // and after scaling
Angus Kong45671602014-01-13 15:06:17 -08002812 if (!inZoomView()) {
Doris Liu8de13112013-08-23 13:35:24 -07002813 mController.setSurroundingViewsVisible(false);
2814 }
2815 ViewItem curr = mViewItem[mCurrentItem];
2816 // Make sure the image is not overly scaled
Doris Liu121022c2013-09-03 16:03:16 -07002817 newScale = Math.min(newScale, mMaxScale);
Doris Liu87fc5e12013-09-16 13:38:24 -07002818 if (newScale == mScale) {
2819 return true;
2820 }
Doris Liu121022c2013-09-03 16:03:16 -07002821 float postScale = newScale / mScale;
2822 curr.postScale(focusX, focusY, postScale, mDrawArea.width(), mDrawArea.height());
2823 mScale = newScale;
Angus Kongfaaee012013-12-07 00:38:46 -08002824 if (mScale == FULL_SCREEN_SCALE) {
2825 onEnterFullScreen();
Angus Kong45671602014-01-13 15:06:17 -08002826 } else {
2827 onEnterZoomView();
Angus Kongfaaee012013-12-07 00:38:46 -08002828 }
Andy Huiberscaca8c72013-12-13 15:53:43 -08002829 checkItemAtMaxSize();
Angus Kong87f9a622013-05-16 16:59:39 -07002830 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002831 return true;
2832 }
2833
2834 @Override
2835 public void onScaleEnd() {
Doris Liu121022c2013-09-03 16:03:16 -07002836 if (mScale > FULL_SCREEN_SCALE + TOLERANCE) {
Doris Liu8de13112013-08-23 13:35:24 -07002837 return;
2838 }
2839 mController.setSurroundingViewsVisible(true);
Doris Liu121022c2013-09-03 16:03:16 -07002840 if (mScale <= FILM_STRIP_SCALE + TOLERANCE) {
Angus Kongfaaee012013-12-07 00:38:46 -08002841 mController.goToFilmstrip();
Doris Liu121022c2013-09-03 16:03:16 -07002842 } else if (mScaleTrend > 1f || mScale > FULL_SCREEN_SCALE - TOLERANCE) {
Angus Kong45671602014-01-13 15:06:17 -08002843 if (inZoomView()) {
Doris Liu8de13112013-08-23 13:35:24 -07002844 mScale = FULL_SCREEN_SCALE;
Doris Liue1d5ba42013-09-10 12:44:20 -07002845 resetZoomView();
Doris Liu8de13112013-08-23 13:35:24 -07002846 }
Angus Kong734598c2013-08-21 15:25:05 -07002847 mController.goToFullScreen();
Angus Kong49b9ba22013-05-13 13:42:21 -07002848 } else {
Angus Kongfaaee012013-12-07 00:38:46 -08002849 mController.goToFilmstrip();
Angus Kong49b9ba22013-05-13 13:42:21 -07002850 }
Angus Konga6a6a732013-06-18 16:47:57 -07002851 mScaleTrend = 1f;
Angus Kong49b9ba22013-05-13 13:42:21 -07002852 }
Angus Kong49b9ba22013-05-13 13:42:21 -07002853 }
2854}