Home | History | Annotate | Download | only in widget
      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 
    106     public FilmstripLayout(Context context) {
    107         super(context);
    108         init(context);
    109     }
    110 
    111     public FilmstripLayout(Context context, AttributeSet attrs) {
    112         super(context, attrs);
    113         init(context);
    114     }
    115 
    116     public FilmstripLayout(Context context, AttributeSet attrs, int defStyle) {
    117         super(context, attrs, defStyle);
    118         init(context);
    119     }
    120 
    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
    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
    161     public void hide() {
    162         translateContentLayout(1f);
    163         mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
    164     }
    165 
    166     @Override
    167     public void show() {
    168         translateContentLayout(0f);
    169         mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
    170     }
    171 
    172     @Override
    173     public void setVisibility(int visibility) {
    174         super.setVisibility(visibility);
    175         if (visibility != VISIBLE) {
    176             notifyHidden();
    177         }
    178     }
    179 
    180     private void notifyHidden() {
    181         if (mListener == null) {
    182             return;
    183         }
    184         mListener.onFilmstripHidden();
    185     }
    186 
    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
    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
    213     public boolean onTouchEvent(MotionEvent ev) {
    214         return mGestureRecognizer.onTouchEvent(ev);
    215     }
    216 
    217     @Override
    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
    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
    247     public boolean onBackPressed() {
    248         return animateHide();
    249     }
    250 
    251     @Override
    252     public boolean animateHide() {
    253         if (getVisibility() == VISIBLE) {
    254             if (!mFilmstripAnimator.isRunning()) {
    255                 hideFilmstrip();
    256             }
    257             return true;
    258         }
    259         return false;
    260     }
    261 
    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 
    269     public void showFilmstrip() {
    270         setVisibility(VISIBLE);
    271         runAnimation(mFilmstripContentTranslationProgress, 0f);
    272     }
    273 
    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 
    287     private void translateContentLayout(float fraction) {
    288         mFilmstripContentTranslationProgress = fraction;
    289         mFilmstripContentLayout.setTranslationX(fraction * getMeasuredWidth());
    290     }
    291 
    292     private void translateContentLayoutByPixel(float pixel) {
    293         mFilmstripContentLayout.setTranslationX(pixel);
    294         mFilmstripContentTranslationProgress = pixel / getMeasuredWidth();
    295     }
    296 
    297     private void onSwipeOut() {
    298         if (mListener != null) {
    299             mListener.onSwipeOut();
    300         }
    301     }
    302 
    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
    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
    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
    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
    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
    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
    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
    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
    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
    435         public void onLongPress(float x, float y) {
    436             mFilmstripGestureListener.onLongPress(x, y);
    437         }
    438 
    439         @Override
    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 
    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          */
    462         public void setOffset(int offset) {
    463             mOffset = offset;
    464         }
    465 
    466         @Override
    467         public void setAlpha(int i) {
    468             mPaint.setAlpha(i);
    469         }
    470 
    471         private void setAlpha(float a) {
    472             setAlpha((int) (a*255.0f));
    473         }
    474 
    475         @Override
    476         public void setColorFilter(ColorFilter colorFilter) {
    477             mPaint.setColorFilter(colorFilter);
    478         }
    479 
    480         @Override
    481         public int getOpacity() {
    482             return PixelFormat.TRANSLUCENT;
    483         }
    484 
    485         @Override
    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