blob: 0dd1d37f10b486251b60bfdee3385328a0162c52 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.widget;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.android.camera.filmstrip.FilmstripContentPanel;
import com.android.camera.filmstrip.FilmstripController;
import com.android.camera.ui.FilmstripGestureRecognizer;
import com.android.camera2.R;
/**
* A {@link android.widget.FrameLayout} used for the parent layout of a
* {@link com.android.camera.widget.FilmstripView} to support animating in/out the
* filmstrip.
*/
public class FilmstripLayout extends FrameLayout implements FilmstripContentPanel {
private static final long DEFAULT_DURATION_MS = 200;
/**
* The layout containing the {@link com.android.camera.widget.FilmstripView}
* and other controls.
*/
private FrameLayout mFilmstripContentLayout;
private FilmstripView mFilmstripView;
private FilmstripGestureRecognizer mGestureRecognizer;
private FilmstripGestureRecognizer.Listener mFilmstripGestureListener;
private final ValueAnimator mFilmstripAnimator = ValueAnimator.ofFloat(null);
private int mSwipeTrend;
private MyBackgroundDrawable mBackgroundDrawable;
private Handler mHandler;
// We use this to record the current translation position instead of using
// the real value because we might set the translation before onMeasure()
// thus getMeasuredWidth() can be 0.
private float mFilmstripContentTranslationProgress;
private Animator.AnimatorListener mFilmstripAnimatorListener = new Animator.AnimatorListener() {
private boolean mCanceled;
@Override
public void onAnimationStart(Animator animator) {
mCanceled = false;
}
@Override
public void onAnimationEnd(Animator animator) {
if (!mCanceled) {
if (mFilmstripContentTranslationProgress != 0f) {
mFilmstripView.getController().goToFilmstrip();
setVisibility(INVISIBLE);
} else {
notifyShown();
}
}
}
@Override
public void onAnimationCancel(Animator animator) {
mCanceled = true;
}
@Override
public void onAnimationRepeat(Animator animator) {
// Nothing.
}
};
private ValueAnimator.AnimatorUpdateListener mFilmstripAnimatorUpdateListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
translateContentLayout((Float) valueAnimator.getAnimatedValue());
mBackgroundDrawable.invalidateSelf();
}
};
private Listener mListener;
public FilmstripLayout(Context context) {
super(context);
init(context);
}
public FilmstripLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public FilmstripLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mGestureRecognizer = new FilmstripGestureRecognizer(context, new MyGestureListener());
mFilmstripAnimator.setDuration(DEFAULT_DURATION_MS);
mFilmstripAnimator.addUpdateListener(mFilmstripAnimatorUpdateListener);
mFilmstripAnimator.addListener(mFilmstripAnimatorListener);
mHandler = new Handler(Looper.getMainLooper());
mBackgroundDrawable = new MyBackgroundDrawable();
mBackgroundDrawable.setCallback(new Drawable.Callback() {
@Override
public void invalidateDrawable(Drawable drawable) {
FilmstripLayout.this.invalidate();
}
@Override
public void scheduleDrawable(Drawable drawable, Runnable runnable, long l) {
mHandler.postAtTime(runnable, drawable, l);
}
@Override
public void unscheduleDrawable(Drawable drawable, Runnable runnable) {
mHandler.removeCallbacks(runnable, drawable);
}
});
setBackground(mBackgroundDrawable);
}
@Override
public void setFilmstripListener(Listener listener) {
mListener = listener;
if (getVisibility() == VISIBLE && mFilmstripContentTranslationProgress == 0f) {
notifyShown();
} else {
if (getVisibility() != VISIBLE) {
notifyHidden();
}
}
mFilmstripView.getController().setListener(listener);
}
@Override
public void hide() {
translateContentLayout(1f);
mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
}
@Override
public void show() {
translateContentLayout(0f);
mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (visibility != VISIBLE) {
notifyHidden();
}
}
private void notifyHidden() {
if (mListener == null) {
return;
}
mListener.onFilmstripHidden();
}
private void notifyShown() {
if (mListener == null) {
return;
}
mListener.onFilmstripShown();
mFilmstripView.zoomAtIndexChanged();
FilmstripController controller = mFilmstripView.getController();
int currentId = controller.getCurrentId();
if (controller.inFilmstrip()) {
mListener.onEnterFilmstrip(currentId);
} else if (controller.inFullScreen()) {
mListener.onEnterFullScreenUiShown(currentId);
}
}
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed && mFilmstripView != null && getVisibility() == INVISIBLE) {
hide();
} else {
translateContentLayout(mFilmstripContentTranslationProgress);
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return mGestureRecognizer.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
// TODO: Remove this after the touch flow refactor is done in
// MainAtivityLayout.
getParent().requestDisallowInterceptTouchEvent(true);
}
return false;
}
@Override
public void onFinishInflate() {
mFilmstripView = (FilmstripView) findViewById(R.id.filmstrip_view);
mFilmstripView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// Adjust the coordinates back since they are relative to the
// child view.
motionEvent.setLocation(motionEvent.getX() + mFilmstripContentLayout.getX(),
motionEvent.getY() + mFilmstripContentLayout.getY());
mGestureRecognizer.onTouchEvent(motionEvent);
return true;
}
});
mFilmstripGestureListener = mFilmstripView.getGestureListener();
mFilmstripContentLayout = (FrameLayout) findViewById(R.id.camera_filmstrip_content_layout);
}
@Override
public boolean onBackPressed() {
return animateHide();
}
@Override
public boolean animateHide() {
if (getVisibility() == VISIBLE) {
if (!mFilmstripAnimator.isRunning()) {
hideFilmstrip();
}
return true;
}
return false;
}
public void hideFilmstrip() {
// run the same view show/hides and animations
// that happen with a swipe gesture.
onSwipeOutBegin();
runAnimation(mFilmstripContentTranslationProgress, 1f);
}
public void showFilmstrip() {
setVisibility(VISIBLE);
runAnimation(mFilmstripContentTranslationProgress, 0f);
}
private void runAnimation(float begin, float end) {
if (mFilmstripAnimator.isRunning()) {
return;
}
if (begin == end) {
// No need to start animation.
mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
return;
}
mFilmstripAnimator.setFloatValues(begin, end);
mFilmstripAnimator.start();
}
private void translateContentLayout(float fraction) {
mFilmstripContentTranslationProgress = fraction;
mFilmstripContentLayout.setTranslationX(fraction * getMeasuredWidth());
}
private void translateContentLayoutByPixel(float pixel) {
mFilmstripContentLayout.setTranslationX(pixel);
mFilmstripContentTranslationProgress = pixel / getMeasuredWidth();
}
private void onSwipeOut() {
if (mListener != null) {
mListener.onSwipeOut();
}
}
private void onSwipeOutBegin() {
if (mListener != null) {
mListener.onSwipeOutBegin();
}
}
/**
* A gesture listener which passes all the gestures to the
* {@code mFilmstripView} by default and only intercepts scroll gestures
* when the {@code mFilmstripView} is not in full-screen.
*/
private class MyGestureListener implements FilmstripGestureRecognizer.Listener {
@Override
public boolean onScroll(float x, float y, float dx, float dy) {
if (mFilmstripView.getController().getCurrentId() == -1) {
return true;
}
if (mFilmstripAnimator.isRunning()) {
return true;
}
if (mFilmstripContentLayout.getTranslationX() == 0f &&
mFilmstripGestureListener.onScroll(x, y, dx, dy)) {
return true;
}
mSwipeTrend = (((int) dx) >> 1) + (mSwipeTrend >> 1);
if (dx < 0 && mFilmstripContentLayout.getTranslationX() == 0) {
mBackgroundDrawable.setOffset(0);
FilmstripLayout.this.onSwipeOutBegin();
}
// When we start translating the filmstrip in, we want the left edge of the
// first view to always be at the rightmost edge of the screen so that it
// appears instantly, regardless of the view's distance from the edge of the
// filmstrip view. To do so, on our first translation, jump the filmstrip view
// to the correct position, and then smoothly animate the translation from that
// initial point.
if (dx > 0 && mFilmstripContentLayout.getTranslationX() == getMeasuredWidth()) {
final int currentItemLeft = mFilmstripView.getCurrentItemLeft();
dx = currentItemLeft;
mBackgroundDrawable.setOffset(currentItemLeft);
}
float translate = mFilmstripContentLayout.getTranslationX() - dx;
if (translate < 0f) {
translate = 0f;
} else {
if (translate > getMeasuredWidth()) {
translate = getMeasuredWidth();
}
}
translateContentLayoutByPixel(translate);
if (translate == 0 && dx > 0) {
// This will only happen once since when this condition holds
// the onScroll() callback will be forwarded to the filmstrip
// view.
mFilmstripAnimatorListener.onAnimationEnd(mFilmstripAnimator);
}
mBackgroundDrawable.invalidateSelf();
return true;
}
@Override
public boolean onMouseScroll(float hscroll, float vscroll) {
if (mFilmstripContentTranslationProgress == 0f) {
return mFilmstripGestureListener.onMouseScroll(hscroll, vscroll);
}
return false;
}
@Override
public boolean onSingleTapUp(float x, float y) {
if (mFilmstripContentTranslationProgress == 0f) {
return mFilmstripGestureListener.onSingleTapUp(x, y);
}
return false;
}
@Override
public boolean onDoubleTap(float x, float y) {
if (mFilmstripContentTranslationProgress == 0f) {
return mFilmstripGestureListener.onDoubleTap(x, y);
}
return false;
}
@Override
public boolean onFling(float velocityX, float velocityY) {
if (mFilmstripContentTranslationProgress == 0f) {
return mFilmstripGestureListener.onFling(velocityX, velocityY);
}
return false;
}
@Override
public boolean onScaleBegin(float focusX, float focusY) {
if (mFilmstripContentTranslationProgress == 0f) {
return mFilmstripGestureListener.onScaleBegin(focusX, focusY);
}
return false;
}
@Override
public boolean onScale(float focusX, float focusY, float scale) {
if (mFilmstripContentTranslationProgress == 0f) {
return mFilmstripGestureListener.onScale(focusX, focusY, scale);
}
return false;
}
@Override
public boolean onDown(float x, float y) {
if (mFilmstripContentLayout.getTranslationX() == 0f) {
return mFilmstripGestureListener.onDown(x, y);
}
return false;
}
@Override
public boolean onUp(float x, float y) {
if (mFilmstripContentLayout.getTranslationX() == 0f) {
return mFilmstripGestureListener.onUp(x, y);
}
if (mSwipeTrend < 0) {
hideFilmstrip();
onSwipeOut();
} else if (mSwipeTrend > 0) {
showFilmstrip();
} else {
if (mFilmstripContentLayout.getTranslationX() >= getMeasuredWidth() / 2) {
hideFilmstrip();
onSwipeOut();
} else {
showFilmstrip();
}
}
mSwipeTrend = 0;
return false;
}
@Override
public void onLongPress(float x, float y) {
mFilmstripGestureListener.onLongPress(x, y);
}
@Override
public void onScaleEnd() {
if (mFilmstripContentLayout.getTranslationX() == 0f) {
mFilmstripGestureListener.onScaleEnd();
}
}
}
private class MyBackgroundDrawable extends Drawable {
private Paint mPaint;
private int mOffset;
public MyBackgroundDrawable() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(getResources().getColor(R.color.filmstrip_background));
mPaint.setAlpha(255);
}
/**
* Adjust the target width and translation calculation when we start translating
* from a point where width != translationX so that alpha scales smoothly.
*/
public void setOffset(int offset) {
mOffset = offset;
}
@Override
public void setAlpha(int i) {
mPaint.setAlpha(i);
}
private void setAlpha(float a) {
setAlpha((int) (a*255.0f));
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void draw(Canvas canvas) {
int width = getMeasuredWidth() - mOffset;
float translation = mFilmstripContentLayout.getTranslationX() - mOffset;
if (translation == width) {
return;
}
setAlpha(1.0f - mFilmstripContentTranslationProgress);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);
}
}
}