• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.camera.widget;
18 
19 import android.animation.Animator;
20 import android.animation.ValueAnimator;
21 import android.content.Context;
22 import android.graphics.Canvas;
23 import android.graphics.ColorFilter;
24 import android.graphics.Paint;
25 import android.graphics.PixelFormat;
26 import android.graphics.drawable.Drawable;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.util.AttributeSet;
30 import android.view.MotionEvent;
31 import android.view.View;
32 import android.widget.FrameLayout;
33 
34 import com.android.camera.filmstrip.FilmstripContentPanel;
35 import com.android.camera.filmstrip.FilmstripController;
36 import com.android.camera.ui.FilmstripGestureRecognizer;
37 import com.android.camera2.R;
38 
39 /**
40  * A {@link android.widget.FrameLayout} used for the parent layout of a
41  * {@link com.android.camera.widget.FilmstripView} to support animating in/out the
42  * filmstrip.
43  */
44 public class FilmstripLayout extends FrameLayout implements FilmstripContentPanel {
45 
46     private static final long DEFAULT_DURATION_MS = 200;
47 
48     /**
49      * The layout containing the {@link com.android.camera.widget.FilmstripView}
50      * and other controls.
51      */
52     private FrameLayout mFilmstripContentLayout;
53     private FilmstripView mFilmstripView;
54     private FilmstripGestureRecognizer mGestureRecognizer;
55     private FilmstripGestureRecognizer.Listener mFilmstripGestureListener;
56     private final ValueAnimator mFilmstripAnimator = ValueAnimator.ofFloat(null);
57     private int mSwipeTrend;
58     private MyBackgroundDrawable mBackgroundDrawable;
59     private Handler mHandler;
60     // We use this to record the current translation position instead of using
61     // the real value because we might set the translation before onMeasure()
62     // thus getMeasuredWidth() can be 0.
63     private float mFilmstripContentTranslationProgress;
64 
65     private Animator.AnimatorListener mFilmstripAnimatorListener = new Animator.AnimatorListener() {
66         private boolean mCanceled;
67 
68         @Override
69         public void onAnimationStart(Animator animator) {
70             mCanceled = false;
71         }
72 
73         @Override
74         public void onAnimationEnd(Animator animator) {
75             if (!mCanceled) {
76                 if (mFilmstripContentTranslationProgress != 0f) {
77                     mFilmstripView.getController().goToFilmstrip();
78                     setVisibility(INVISIBLE);
79                 } else {
80                     notifyShown();
81                 }
82             }
83         }
84 
85         @Override
86         public void onAnimationCancel(Animator animator) {
87             mCanceled = true;
88         }
89 
90         @Override
91         public void onAnimationRepeat(Animator animator) {
92             // Nothing.
93         }
94     };
95 
96     private ValueAnimator.AnimatorUpdateListener mFilmstripAnimatorUpdateListener =
97             new ValueAnimator.AnimatorUpdateListener() {
98                 @Override
99                 public void onAnimationUpdate(ValueAnimator valueAnimator) {
100                     translateContentLayout((Float) valueAnimator.getAnimatedValue());
101                     mBackgroundDrawable.invalidateSelf();
102                 }
103             };
104     private Listener mListener;
105 
FilmstripLayout(Context context)106     public FilmstripLayout(Context context) {
107         super(context);
108         init(context);
109     }
110 
FilmstripLayout(Context context, AttributeSet attrs)111     public FilmstripLayout(Context context, AttributeSet attrs) {
112         super(context, attrs);
113         init(context);
114     }
115 
FilmstripLayout(Context context, AttributeSet attrs, int defStyle)116     public FilmstripLayout(Context context, AttributeSet attrs, int defStyle) {
117         super(context, attrs, defStyle);
118         init(context);
119     }
120 
init(Context context)121     private void init(Context context) {
122         mGestureRecognizer = new FilmstripGestureRecognizer(context, new MyGestureListener());
123         mFilmstripAnimator.setDuration(DEFAULT_DURATION_MS);
124         mFilmstripAnimator.addUpdateListener(mFilmstripAnimatorUpdateListener);
125         mFilmstripAnimator.addListener(mFilmstripAnimatorListener);
126         mHandler = new Handler(Looper.getMainLooper());
127         mBackgroundDrawable = new MyBackgroundDrawable();
128         mBackgroundDrawable.setCallback(new Drawable.Callback() {
129             @Override
130             public void invalidateDrawable(Drawable drawable) {
131                 FilmstripLayout.this.invalidate();
132             }
133 
134             @Override
135             public void scheduleDrawable(Drawable drawable, Runnable runnable, long l) {
136                 mHandler.postAtTime(runnable, drawable, l);
137             }
138 
139             @Override
140             public void unscheduleDrawable(Drawable drawable, Runnable runnable) {
141                 mHandler.removeCallbacks(runnable, drawable);
142             }
143         });
144         setBackground(mBackgroundDrawable);
145     }
146 
147     @Override
setFilmstripListener(Listener listener)148     public void setFilmstripListener(Listener listener) {
149         mListener = listener;
150         if (getVisibility() == VISIBLE && mFilmstripContentTranslationProgress == 0f) {
151             notifyShown();
152         } else {
153             if (getVisibility() != VISIBLE) {
154                 notifyHidden();
155             }
156         }
157         mFilmstripView.getController().setListener(listener);
158     }
159 
160     @Override
hide()161     public void hide() {
162         translateContentLayout(1f);
163         mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
164     }
165 
166     @Override
show()167     public void show() {
168         translateContentLayout(0f);
169         mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
170     }
171 
172     @Override
setVisibility(int visibility)173     public void setVisibility(int visibility) {
174         super.setVisibility(visibility);
175         if (visibility != VISIBLE) {
176             notifyHidden();
177         }
178     }
179 
notifyHidden()180     private void notifyHidden() {
181         if (mListener == null) {
182             return;
183         }
184         mListener.onFilmstripHidden();
185     }
186 
notifyShown()187     private void notifyShown() {
188         if (mListener == null) {
189             return;
190         }
191         mListener.onFilmstripShown();
192         mFilmstripView.zoomAtIndexChanged();
193         FilmstripController controller = mFilmstripView.getController();
194         int currentId = controller.getCurrentId();
195         if (controller.inFilmstrip()) {
196             mListener.onEnterFilmstrip(currentId);
197         } else if (controller.inFullScreen()) {
198             mListener.onEnterFullScreenUiShown(currentId);
199         }
200     }
201 
202     @Override
onLayout(boolean changed, int l, int t, int r, int b)203     public void onLayout(boolean changed, int l, int t, int r, int b) {
204         super.onLayout(changed, l, t, r, b);
205         if (changed && mFilmstripView != null && getVisibility() == INVISIBLE) {
206             hide();
207         } else {
208             translateContentLayout(mFilmstripContentTranslationProgress);
209         }
210     }
211 
212     @Override
onTouchEvent(MotionEvent ev)213     public boolean onTouchEvent(MotionEvent ev) {
214         return mGestureRecognizer.onTouchEvent(ev);
215     }
216 
217     @Override
onInterceptTouchEvent(MotionEvent ev)218     public boolean onInterceptTouchEvent(MotionEvent ev) {
219         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
220             // TODO: Remove this after the touch flow refactor is done in
221             // MainAtivityLayout.
222             getParent().requestDisallowInterceptTouchEvent(true);
223         }
224         return false;
225     }
226 
227     @Override
onFinishInflate()228     public void onFinishInflate() {
229         mFilmstripView = (FilmstripView) findViewById(R.id.filmstrip_view);
230         mFilmstripView.setOnTouchListener(new OnTouchListener() {
231 
232             @Override
233             public boolean onTouch(View view, MotionEvent motionEvent) {
234                 // Adjust the coordinates back since they are relative to the
235                 // child view.
236                 motionEvent.setLocation(motionEvent.getX() + mFilmstripContentLayout.getX(),
237                         motionEvent.getY() + mFilmstripContentLayout.getY());
238                 mGestureRecognizer.onTouchEvent(motionEvent);
239                 return true;
240             }
241         });
242         mFilmstripGestureListener = mFilmstripView.getGestureListener();
243         mFilmstripContentLayout = (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
244     }
245 
246     @Override
onBackPressed()247     public boolean onBackPressed() {
248         return animateHide();
249     }
250 
251     @Override
animateHide()252     public boolean animateHide() {
253         if (getVisibility() == VISIBLE) {
254             if (!mFilmstripAnimator.isRunning()) {
255                 hideFilmstrip();
256             }
257             return true;
258         }
259         return false;
260     }
261 
hideFilmstrip()262     public void hideFilmstrip() {
263         // run the same view show/hides and animations
264         // that happen with a swipe gesture.
265         onSwipeOutBegin();
266         runAnimation(mFilmstripContentTranslationProgress, 1f);
267     }
268 
showFilmstrip()269     public void showFilmstrip() {
270         setVisibility(VISIBLE);
271         runAnimation(mFilmstripContentTranslationProgress, 0f);
272     }
273 
runAnimation(float begin, float end)274     private void runAnimation(float begin, float end) {
275         if (mFilmstripAnimator.isRunning()) {
276             return;
277         }
278         if (begin == end) {
279             // No need to start animation.
280             mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
281             return;
282         }
283         mFilmstripAnimator.setFloatValues(begin, end);
284         mFilmstripAnimator.start();
285     }
286 
translateContentLayout(float fraction)287     private void translateContentLayout(float fraction) {
288         mFilmstripContentTranslationProgress = fraction;
289         mFilmstripContentLayout.setTranslationX(fraction * getMeasuredWidth());
290     }
291 
translateContentLayoutByPixel(float pixel)292     private void translateContentLayoutByPixel(float pixel) {
293         mFilmstripContentLayout.setTranslationX(pixel);
294         mFilmstripContentTranslationProgress = pixel / getMeasuredWidth();
295     }
296 
onSwipeOut()297     private void onSwipeOut() {
298         if (mListener != null) {
299             mListener.onSwipeOut();
300         }
301     }
302 
onSwipeOutBegin()303     private void onSwipeOutBegin() {
304         if (mListener != null) {
305             mListener.onSwipeOutBegin();
306         }
307     }
308 
309     /**
310      * A gesture listener which passes all the gestures to the
311      * {@code mFilmstripView} by default and only intercepts scroll gestures
312      * when the {@code mFilmstripView} is not in full-screen.
313      */
314     private class MyGestureListener implements FilmstripGestureRecognizer.Listener {
315         @Override
onScroll(float x, float y, float dx, float dy)316         public boolean onScroll(float x, float y, float dx, float dy) {
317             if (mFilmstripView.getController().getCurrentId() == -1) {
318                 return true;
319             }
320             if (mFilmstripAnimator.isRunning()) {
321                 return true;
322             }
323             if (mFilmstripContentLayout.getTranslationX() == 0f &&
324                     mFilmstripGestureListener.onScroll(x, y, dx, dy)) {
325                 return true;
326             }
327             mSwipeTrend = (((int) dx) >> 1) + (mSwipeTrend >> 1);
328             if (dx < 0 && mFilmstripContentLayout.getTranslationX() == 0) {
329                 mBackgroundDrawable.setOffset(0);
330                 FilmstripLayout.this.onSwipeOutBegin();
331             }
332 
333             // When we start translating the filmstrip in, we want the left edge of the
334             // first view to always be at the rightmost edge of the screen so that it
335             // appears instantly, regardless of the view's distance from the edge of the
336             // filmstrip view. To do so, on our first translation, jump the filmstrip view
337             // to the correct position, and then smoothly animate the translation from that
338             // initial point.
339             if (dx > 0 && mFilmstripContentLayout.getTranslationX() == getMeasuredWidth()) {
340                 final int currentItemLeft = mFilmstripView.getCurrentItemLeft();
341                 dx = currentItemLeft;
342                 mBackgroundDrawable.setOffset(currentItemLeft);
343             }
344 
345             float translate = mFilmstripContentLayout.getTranslationX() - dx;
346             if (translate < 0f) {
347                 translate = 0f;
348             } else {
349                 if (translate > getMeasuredWidth()) {
350                     translate = getMeasuredWidth();
351                 }
352             }
353             translateContentLayoutByPixel(translate);
354             if (translate == 0 && dx > 0) {
355                 // This will only happen once since when this condition holds
356                 // the onScroll() callback will be forwarded to the filmstrip
357                 // view.
358                 mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
359             }
360             mBackgroundDrawable.invalidateSelf();
361             return true;
362         }
363 
364         @Override
onSingleTapUp(float x, float y)365         public boolean onSingleTapUp(float x, float y) {
366             if (mFilmstripContentTranslationProgress == 0f) {
367                 return mFilmstripGestureListener.onSingleTapUp(x, y);
368             }
369             return false;
370         }
371 
372         @Override
onDoubleTap(float x, float y)373         public boolean onDoubleTap(float x, float y) {
374             if (mFilmstripContentTranslationProgress == 0f) {
375                 return mFilmstripGestureListener.onDoubleTap(x, y);
376             }
377             return false;
378         }
379 
380         @Override
onFling(float velocityX, float velocityY)381         public boolean onFling(float velocityX, float velocityY) {
382             if (mFilmstripContentTranslationProgress == 0f) {
383                 return mFilmstripGestureListener.onFling(velocityX, velocityY);
384             }
385             return false;
386         }
387 
388         @Override
onScaleBegin(float focusX, float focusY)389         public boolean onScaleBegin(float focusX, float focusY) {
390             if (mFilmstripContentTranslationProgress == 0f) {
391                 return mFilmstripGestureListener.onScaleBegin(focusX, focusY);
392             }
393             return false;
394         }
395 
396         @Override
onScale(float focusX, float focusY, float scale)397         public boolean onScale(float focusX, float focusY, float scale) {
398             if (mFilmstripContentTranslationProgress == 0f) {
399                 return mFilmstripGestureListener.onScale(focusX, focusY, scale);
400             }
401             return false;
402         }
403 
404         @Override
onDown(float x, float y)405         public boolean onDown(float x, float y) {
406             if (mFilmstripContentLayout.getTranslationX() == 0f) {
407                 return mFilmstripGestureListener.onDown(x, y);
408             }
409             return false;
410         }
411 
412         @Override
onUp(float x, float y)413         public boolean onUp(float x, float y) {
414             if (mFilmstripContentLayout.getTranslationX() == 0f) {
415                 return mFilmstripGestureListener.onUp(x, y);
416             }
417             if (mSwipeTrend < 0) {
418                 hideFilmstrip();
419                 onSwipeOut();
420             } else if (mSwipeTrend > 0) {
421                 showFilmstrip();
422             } else {
423                 if (mFilmstripContentLayout.getTranslationX() >= getMeasuredWidth() / 2) {
424                     hideFilmstrip();
425                     onSwipeOut();
426                 } else {
427                     showFilmstrip();
428                 }
429             }
430             mSwipeTrend = 0;
431             return false;
432         }
433 
434         @Override
onLongPress(float x, float y)435         public void onLongPress(float x, float y) {
436             mFilmstripGestureListener.onLongPress(x, y);
437         }
438 
439         @Override
onScaleEnd()440         public void onScaleEnd() {
441             if (mFilmstripContentLayout.getTranslationX() == 0f) {
442                 mFilmstripGestureListener.onScaleEnd();
443             }
444         }
445     }
446 
447     private class MyBackgroundDrawable extends Drawable {
448         private Paint mPaint;
449         private int mOffset;
450 
MyBackgroundDrawable()451         public MyBackgroundDrawable() {
452             mPaint = new Paint();
453             mPaint.setAntiAlias(true);
454             mPaint.setColor(getResources().getColor(R.color.filmstrip_background));
455             mPaint.setAlpha(255);
456         }
457 
458         /**
459          * Adjust the target width and translation calculation when we start translating
460          * from a point where width != translationX so that alpha scales smoothly.
461          */
setOffset(int offset)462         public void setOffset(int offset) {
463             mOffset = offset;
464         }
465 
466         @Override
setAlpha(int i)467         public void setAlpha(int i) {
468             mPaint.setAlpha(i);
469         }
470 
setAlpha(float a)471         private void setAlpha(float a) {
472             setAlpha((int) (a*255.0f));
473         }
474 
475         @Override
setColorFilter(ColorFilter colorFilter)476         public void setColorFilter(ColorFilter colorFilter) {
477             mPaint.setColorFilter(colorFilter);
478         }
479 
480         @Override
getOpacity()481         public int getOpacity() {
482             return PixelFormat.TRANSLUCENT;
483         }
484 
485         @Override
draw(Canvas canvas)486         public void draw(Canvas canvas) {
487             int width = getMeasuredWidth() - mOffset;
488             float translation = mFilmstripContentLayout.getTranslationX() - mOffset;
489             if (translation == width) {
490                 return;
491             }
492 
493             setAlpha(1.0f - mFilmstripContentTranslationProgress);
494             canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
495         }
496     }
497 }
498