blob: 551cc094fa0854686291f744fe21cce395241c4b [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.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import com.android.camera.CaptureLayoutHelper;
import com.android.camera.ShutterButton;
import com.android.camera.debug.Log;
import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
/**
* BottomBar swaps its width and height on rotation. In addition, it also
* changes gravity and layout orientation based on the new orientation.
* Specifically, in landscape it aligns to the right side of its parent and lays
* out its children vertically, whereas in portrait, it stays at the bottom of
* the parent and has a horizontal layout orientation.
*/
public class BottomBar extends FrameLayout {
private static final Log.Tag TAG = new Log.Tag("BottomBar");
private static final int CIRCLE_ANIM_DURATION_MS = 300;
private static final int DRAWABLE_MAX_LEVEL = 10000;
private static final int MODE_CAPTURE = 0;
private static final int MODE_INTENT = 1;
private static final int MODE_INTENT_REVIEW = 2;
private static final int MODE_CANCEL = 3;
private int mMode;
private final int mBackgroundAlphaOverlay;
private final int mBackgroundAlphaDefault;
private boolean mOverLayBottomBar;
private FrameLayout mCaptureLayout;
private FrameLayout mCancelLayout;
private TopRightWeightedLayout mIntentReviewLayout;
private ShutterButton mShutterButton;
private ImageButton mCancelButton;
private int mBackgroundColor;
private int mBackgroundPressedColor;
private int mBackgroundAlpha = 0xff;
private boolean mDrawCircle;
private final float mCircleRadius;
private CaptureLayoutHelper mCaptureLayoutHelper = null;
// for Android L, these backgrounds are RippleDrawables (ISA LayerDrawable)
// pre-L, they're plain old LayerDrawables
private final LayerDrawable[] mShutterButtonBackgrounds;
// a reference to the shutter background's first contained drawable
// if it's an animated circle drawable (for video mode)
private AnimatedCircleDrawable mAnimatedCircleDrawable;
// a reference to the shutter background's first contained drawable
// if it's a color drawable (for all other modes)
private ColorDrawable mColorDrawable;
private RectF mRect = new RectF();
public BottomBar(Context context, AttributeSet attrs) {
super(context, attrs);
mCircleRadius = getResources()
.getDimensionPixelSize(R.dimen.video_capture_circle_diameter) / 2;
mBackgroundAlphaOverlay = getResources()
.getInteger(R.integer.bottom_bar_background_alpha_overlay);
mBackgroundAlphaDefault = getResources()
.getInteger(R.integer.bottom_bar_background_alpha);
// preload all the drawable BGs
TypedArray ar = context.getResources()
.obtainTypedArray(R.array.shutter_button_backgrounds);
int len = ar.length();
mShutterButtonBackgrounds = new LayerDrawable[len];
for (int i = 0; i < len; i++) {
int drawableId = ar.getResourceId(i, -1);
LayerDrawable shutterBackground = mShutterButtonBackgrounds[i] =
(LayerDrawable) context.getResources().getDrawable(drawableId).mutate();
// the background for video has a circle_item drawable placeholder
// that gets replaced by an AnimatedCircleDrawable for the cool
// shrink-down-to-a-circle effect
// all other modes need not do this replace
Drawable d = shutterBackground.findDrawableByLayerId(R.id.circle_item);
if (d != null) {
Drawable animatedCircleDrawable =
new AnimatedCircleDrawable((int) mCircleRadius);
animatedCircleDrawable.setLevel(DRAWABLE_MAX_LEVEL);
shutterBackground
.setDrawableByLayerId(R.id.circle_item, animatedCircleDrawable);
}
}
ar.recycle();
}
private void setPaintColor(int alpha, int color) {
if (mAnimatedCircleDrawable != null) {
mAnimatedCircleDrawable.setColor(color);
mAnimatedCircleDrawable.setAlpha(alpha);
} else if (mColorDrawable != null) {
mColorDrawable.setColor(color);
mColorDrawable.setAlpha(alpha);
}
if (mIntentReviewLayout != null) {
ColorDrawable intentBackground = (ColorDrawable) mIntentReviewLayout
.getBackground();
intentBackground.setColor(color);
intentBackground.setAlpha(alpha);
}
}
private void refreshPaintColor() {
setPaintColor(mBackgroundAlpha, mBackgroundColor);
}
private void setCancelBackgroundColor(int alpha, int color) {
LayerDrawable layerDrawable = (LayerDrawable) mCancelButton.getBackground();
ColorDrawable colorDrawable = (ColorDrawable) layerDrawable.getDrawable(0);
if (!ApiHelper.isLOrHigher()) {
colorDrawable.setColor(color);
}
colorDrawable.setAlpha(alpha);
}
private void setCaptureButtonUp() {
setPaintColor(mBackgroundAlpha, mBackgroundColor);
}
private void setCaptureButtonDown() {
if (!ApiHelper.isLOrHigher()) {
setPaintColor(mBackgroundAlpha, mBackgroundPressedColor);
}
}
private void setCancelButtonUp() {
setCancelBackgroundColor(mBackgroundAlpha, mBackgroundColor);
}
private void setCancelButtonDown() {
setCancelBackgroundColor(mBackgroundAlpha, mBackgroundPressedColor);
}
@Override
public void onFinishInflate() {
mCaptureLayout =
(FrameLayout) findViewById(R.id.bottombar_capture);
mCancelLayout =
(FrameLayout) findViewById(R.id.bottombar_cancel);
mCancelLayout.setVisibility(View.GONE);
mIntentReviewLayout =
(TopRightWeightedLayout) findViewById(R.id.bottombar_intent_review);
mShutterButton =
(ShutterButton) findViewById(R.id.shutter_button);
mShutterButton.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getActionMasked()) {
setCaptureButtonDown();
} else if (MotionEvent.ACTION_UP == event.getActionMasked() ||
MotionEvent.ACTION_CANCEL == event.getActionMasked()) {
setCaptureButtonUp();
} else if (MotionEvent.ACTION_MOVE == event.getActionMasked()) {
mRect.set(0, 0, getWidth(), getHeight());
if (!mRect.contains(event.getX(), event.getY())) {
setCaptureButtonUp();
}
}
return false;
}
});
mCancelButton =
(ImageButton) findViewById(R.id.shutter_cancel_button);
mCancelButton.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (MotionEvent.ACTION_DOWN == event.getActionMasked()) {
setCancelButtonDown();
} else if (MotionEvent.ACTION_UP == event.getActionMasked() ||
MotionEvent.ACTION_CANCEL == event.getActionMasked()) {
setCancelButtonUp();
} else if (MotionEvent.ACTION_MOVE == event.getActionMasked()) {
mRect.set(0, 0, getWidth(), getHeight());
if (!mRect.contains(event.getX(), event.getY())) {
setCancelButtonUp();
}
}
return false;
}
});
}
/**
* Perform a transition from the bottom bar options layout to the bottom bar
* capture layout.
*/
public void transitionToCapture() {
mCaptureLayout.setVisibility(View.VISIBLE);
mCancelLayout.setVisibility(View.GONE);
mIntentReviewLayout.setVisibility(View.GONE);
mMode = MODE_CAPTURE;
}
/**
* Perform a transition from the bottom bar options layout to the bottom bar
* capture layout.
*/
public void transitionToCancel() {
mCaptureLayout.setVisibility(View.GONE);
mIntentReviewLayout.setVisibility(View.GONE);
mCancelLayout.setVisibility(View.VISIBLE);
mMode = MODE_CANCEL;
}
/**
* Perform a transition to the global intent layout. The current layout
* state of the bottom bar is irrelevant.
*/
public void transitionToIntentCaptureLayout() {
mIntentReviewLayout.setVisibility(View.GONE);
mCaptureLayout.setVisibility(View.VISIBLE);
mCancelLayout.setVisibility(View.GONE);
mMode = MODE_INTENT;
}
/**
* Perform a transition to the global intent review layout. The current
* layout state of the bottom bar is irrelevant.
*/
public void transitionToIntentReviewLayout() {
mCaptureLayout.setVisibility(View.GONE);
mIntentReviewLayout.setVisibility(View.VISIBLE);
mCancelLayout.setVisibility(View.GONE);
mMode = MODE_INTENT_REVIEW;
}
/**
* @return whether UI is in intent review mode
*/
public boolean isInIntentReview() {
return mMode == MODE_INTENT_REVIEW;
}
private void setButtonImageLevels(int level) {
((ImageButton) findViewById(R.id.cancel_button)).setImageLevel(level);
((ImageButton) findViewById(R.id.done_button)).setImageLevel(level);
((ImageButton) findViewById(R.id.retake_button)).setImageLevel(level);
}
private void setOverlayBottomBar(boolean overlay) {
mOverLayBottomBar = overlay;
if (overlay) {
setBackgroundAlpha(mBackgroundAlphaOverlay);
setButtonImageLevels(1);
} else {
setBackgroundAlpha(mBackgroundAlphaDefault);
setButtonImageLevels(0);
}
}
/**
* Sets a capture layout helper to query layout rect from.
*/
public void setCaptureLayoutHelper(CaptureLayoutHelper helper) {
mCaptureLayoutHelper = helper;
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
final int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
if (measureWidth == 0 || measureHeight == 0) {
return;
}
if (mCaptureLayoutHelper == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.e(TAG, "Capture layout helper needs to be set first.");
} else {
RectF bottomBarRect = mCaptureLayoutHelper.getBottomBarRect();
super.onMeasure(MeasureSpec.makeMeasureSpec(
(int) bottomBarRect.width(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec((int) bottomBarRect.height(), MeasureSpec.EXACTLY)
);
boolean shouldOverlayBottomBar = mCaptureLayoutHelper.shouldOverlayBottomBar();
setOverlayBottomBar(shouldOverlayBottomBar);
}
}
// prevent touches on bottom bar (not its children)
// from triggering a touch event on preview area
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
@Override
public void setBackgroundColor(int color) {
mBackgroundColor = color;
setPaintColor(mBackgroundAlpha, mBackgroundColor);
setCancelBackgroundColor(mBackgroundAlpha, mBackgroundColor);
}
private void setBackgroundPressedColor(int color) {
if (ApiHelper.isLOrHigher()) {
// not supported (setting a color on a RippleDrawable is hard =[ )
} else {
mBackgroundPressedColor = color;
}
}
private void setupShutterBackgroundForModeIndex(int index) {
LayerDrawable shutterBackground = mShutterButtonBackgrounds[index];
mShutterButton.setBackground(shutterBackground);
Drawable d = shutterBackground.getDrawable(0);
mAnimatedCircleDrawable = null;
mColorDrawable = null;
if (d instanceof AnimatedCircleDrawable) {
mAnimatedCircleDrawable = (AnimatedCircleDrawable) d;
} else if (d instanceof ColorDrawable) {
mColorDrawable = (ColorDrawable) d;
}
int colorId = CameraUtil.getCameraThemeColorId(index, getContext());
int pressedColor = getContext().getResources().getColor(colorId);
setBackgroundPressedColor(pressedColor);
refreshPaintColor();
}
public void setColorsForModeIndex(int index) {
setupShutterBackgroundForModeIndex(index);
}
public void setBackgroundAlpha(int alpha) {
mBackgroundAlpha = alpha;
setPaintColor(mBackgroundAlpha, mBackgroundColor);
setCancelBackgroundColor(mBackgroundAlpha, mBackgroundColor);
}
/**
* Sets the shutter button enabled if true, disabled if false.
* <p>
* Disabled means that the shutter button is not clickable and is greyed
* out.
*/
public void setShutterButtonEnabled(final boolean enabled) {
mShutterButton.post(new Runnable() {
@Override
public void run() {
mShutterButton.setEnabled(enabled);
setShutterButtonImportantToA11y(enabled);
}
});
}
/**
* Sets whether shutter button should be included in a11y announcement and
* navigation
*/
public void setShutterButtonImportantToA11y(boolean important) {
if (important) {
mShutterButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
} else {
mShutterButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
}
/**
* Returns whether the capture button is enabled.
*/
public boolean isShutterButtonEnabled() {
return mShutterButton.isEnabled();
}
private TransitionDrawable crossfadeDrawable(Drawable from, Drawable to) {
Drawable[] arrayDrawable = new Drawable[2];
arrayDrawable[0] = from;
arrayDrawable[1] = to;
TransitionDrawable transitionDrawable = new TransitionDrawable(arrayDrawable);
transitionDrawable.setCrossFadeEnabled(true);
return transitionDrawable;
}
/**
* Sets the shutter button's icon resource. By default, all drawables
* instances loaded from the same resource share a common state; if you
* modify the state of one instance, all the other instances will receive
* the same modification. In order to modify properties of this icon
* drawable without affecting other drawables, here we use a mutable
* drawable which is guaranteed to not share states with other drawables.
*/
public void setShutterButtonIcon(int resId) {
Drawable iconDrawable = getResources().getDrawable(resId);
if (iconDrawable != null) {
iconDrawable = iconDrawable.mutate();
}
mShutterButton.setImageDrawable(iconDrawable);
}
/**
* Animates bar to a single stop button
*/
public void animateToVideoStop(int resId) {
if (mOverLayBottomBar && mAnimatedCircleDrawable != null) {
mAnimatedCircleDrawable.animateToSmallRadius();
mDrawCircle = true;
}
TransitionDrawable transitionDrawable = crossfadeDrawable(
mShutterButton.getDrawable(),
getResources().getDrawable(resId));
mShutterButton.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(CIRCLE_ANIM_DURATION_MS);
}
/**
* Animates bar to full width / length with video capture icon
*/
public void animateToFullSize(int resId) {
if (mDrawCircle && mAnimatedCircleDrawable != null) {
mAnimatedCircleDrawable.animateToFullSize();
mDrawCircle = false;
}
TransitionDrawable transitionDrawable = crossfadeDrawable(
mShutterButton.getDrawable(),
getResources().getDrawable(resId));
mShutterButton.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(CIRCLE_ANIM_DURATION_MS);
}
}