2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera.ui;
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.view.animation.Interpolator;
22 import android.graphics.PointF;
23 import android.graphics.RectF;
24 import android.util.AttributeSet;
25 import android.view.View;
26 import android.widget.FrameLayout;
28 import com.android.camera.CaptureLayoutHelper;
29 import com.android.camera.debug.Log;
30 import com.android.camera.ui.motion.InterpolatorHelper;
31 import com.android.camera.widget.ModeOptions;
32 import com.android.camera.widget.ModeOptionsOverlay;
33 import com.android.camera.widget.RoundedThumbnailView;
34 import com.android.camera2.R;
37 * The goal of this class is to ensure mode options and capture indicator is
38 * always laid out to the left of or above bottom bar in landscape or portrait
39 * respectively. All the other children in this view group can be expected to
40 * be laid out the same way as they are in a normal FrameLayout.
42 public class StickyBottomCaptureLayout extends FrameLayout {
44 private final static Log.Tag TAG = new Log.Tag("StickyBotCapLayout");
45 private RoundedThumbnailView mRoundedThumbnailView;
46 private ModeOptionsOverlay mModeOptionsOverlay;
47 private View mBottomBar;
48 private CaptureLayoutHelper mCaptureLayoutHelper = null;
50 private ModeOptions.Listener mModeOptionsListener = new ModeOptions.Listener() {
52 public void onBeginToShowModeOptions() {
53 final PointF thumbnailViewPosition = getRoundedThumbnailPosition(
54 mCaptureLayoutHelper.getUncoveredPreviewRect(),
56 mModeOptionsOverlay.getModeOptionsToggleWidth());
57 final int orientation = getResources().getConfiguration().orientation;
58 if (orientation == Configuration.ORIENTATION_PORTRAIT) {
59 animateCaptureIndicatorToY(thumbnailViewPosition.y);
61 animateCaptureIndicatorToX(thumbnailViewPosition.x);
66 public void onBeginToHideModeOptions() {
67 final PointF thumbnailViewPosition = getRoundedThumbnailPosition(
68 mCaptureLayoutHelper.getUncoveredPreviewRect(),
70 mModeOptionsOverlay.getModeOptionsToggleWidth());
71 final int orientation = getResources().getConfiguration().orientation;
72 if (orientation == Configuration.ORIENTATION_PORTRAIT) {
73 animateCaptureIndicatorToY(thumbnailViewPosition.y);
75 animateCaptureIndicatorToX(thumbnailViewPosition.x);
80 public StickyBottomCaptureLayout(Context context, AttributeSet attrs) {
81 super(context, attrs);
85 public void onFinishInflate() {
86 mRoundedThumbnailView = (RoundedThumbnailView) findViewById(R.id.rounded_thumbnail_view);
87 mModeOptionsOverlay = (ModeOptionsOverlay) findViewById(R.id.mode_options_overlay);
88 mModeOptionsOverlay.setModeOptionsListener(mModeOptionsListener);
89 mBottomBar = findViewById(R.id.bottom_bar);
93 * Sets a capture layout helper to query layout rect from.
95 public void setCaptureLayoutHelper(CaptureLayoutHelper helper) {
96 mCaptureLayoutHelper = helper;
100 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
101 if (mCaptureLayoutHelper == null) {
102 Log.e(TAG, "Capture layout helper needs to be set first.");
105 // Layout mode options overlay.
106 RectF uncoveredPreviewRect = mCaptureLayoutHelper.getUncoveredPreviewRect();
107 mModeOptionsOverlay.layout((int) uncoveredPreviewRect.left, (int) uncoveredPreviewRect.top,
108 (int) uncoveredPreviewRect.right, (int) uncoveredPreviewRect.bottom);
110 // Layout capture indicator.
111 PointF roundedThumbnailViewPosition = getRoundedThumbnailPosition(
112 uncoveredPreviewRect,
113 mModeOptionsOverlay.isModeOptionsHidden(),
114 mModeOptionsOverlay.getModeOptionsToggleWidth());
115 mRoundedThumbnailView.layout(
116 (int) roundedThumbnailViewPosition.x,
117 (int) roundedThumbnailViewPosition.y,
118 (int) roundedThumbnailViewPosition.x + mRoundedThumbnailView.getMeasuredWidth(),
119 (int) roundedThumbnailViewPosition.y + mRoundedThumbnailView.getMeasuredHeight());
121 // Layout bottom bar.
122 RectF bottomBarRect = mCaptureLayoutHelper.getBottomBarRect();
123 mBottomBar.layout((int) bottomBarRect.left, (int) bottomBarRect.top,
124 (int) bottomBarRect.right, (int) bottomBarRect.bottom);
128 * Calculates the desired layout of capture indicator.
130 * @param uncoveredPreviewRect The uncovered preview bound which contains mode option
131 * overlay and capture indicator.
132 * @param isModeOptionsHidden Whether the mode options button are hidden.
133 * @param modeOptionsToggleWidth The width of mode options toggle (three dots button).
134 * @return the desired view bound for capture indicator.
136 private PointF getRoundedThumbnailPosition(
137 RectF uncoveredPreviewRect, boolean isModeOptionsHidden, float modeOptionsToggleWidth) {
138 final float threeDotsButtonDiameter =
139 getResources().getDimension(R.dimen.option_button_circle_size);
140 final float threeDotsButtonPadding =
141 getResources().getDimension(R.dimen.mode_options_toggle_padding);
142 final float modeOptionsHeight = getResources().getDimension(R.dimen.mode_options_height);
144 final float roundedThumbnailViewSize = mRoundedThumbnailView.getMeasuredWidth();
145 final float roundedThumbnailFinalSize = mRoundedThumbnailView.getThumbnailFinalDiameter();
146 final float roundedThumbnailViewPadding = mRoundedThumbnailView.getThumbnailPadding();
148 // The view bound is based on the maximal ripple ring diameter. This is the diff of maximal
149 // ripple ring radius and the final thumbnail radius.
150 final float radiusDiffBetweenViewAndThumbnail =
151 (roundedThumbnailViewSize - roundedThumbnailFinalSize) / 2.0f;
152 final float distanceFromModeOptions = roundedThumbnailViewPadding +
153 roundedThumbnailFinalSize + radiusDiffBetweenViewAndThumbnail;
155 final int orientation = getResources().getConfiguration().orientation;
159 if (orientation == Configuration.ORIENTATION_PORTRAIT) {
160 // The view finder of 16:9 aspect ratio might have a black padding.
161 x = uncoveredPreviewRect.right - distanceFromModeOptions;
163 y = uncoveredPreviewRect.bottom;
164 if (isModeOptionsHidden) {
165 y -= threeDotsButtonPadding + threeDotsButtonDiameter;
167 y -= modeOptionsHeight;
169 y -= distanceFromModeOptions;
171 if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
172 if (isModeOptionsHidden) {
173 x = uncoveredPreviewRect.right - threeDotsButtonPadding - modeOptionsToggleWidth;
175 x = uncoveredPreviewRect.right - modeOptionsHeight;
177 x -= distanceFromModeOptions;
178 y = uncoveredPreviewRect.top + roundedThumbnailViewPadding -
179 radiusDiffBetweenViewAndThumbnail;
181 return new PointF(x, y);
184 private void animateCaptureIndicatorToX(float x) {
185 final Interpolator interpolator =
186 InterpolatorHelper.getLinearOutSlowInInterpolator(getContext());
187 mRoundedThumbnailView.animate()
188 .setDuration(ModeOptions.PADDING_ANIMATION_TIME)
189 .setInterpolator(interpolator)
191 .withEndAction(new Runnable() {
194 mRoundedThumbnailView.setTranslationX(0.0f);
200 private void animateCaptureIndicatorToY(float y) {
201 final Interpolator interpolator =
202 InterpolatorHelper.getLinearOutSlowInInterpolator(getContext());
203 mRoundedThumbnailView.animate()
204 .setDuration(ModeOptions.PADDING_ANIMATION_TIME)
205 .setInterpolator(interpolator)
207 .withEndAction(new Runnable() {
210 mRoundedThumbnailView.setTranslationY(0.0f);