1/*
2 * Copyright (C) 2014 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
17package com.android.camera.ui;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.view.animation.Interpolator;
22import android.graphics.PointF;
23import android.graphics.RectF;
24import android.util.AttributeSet;
25import android.view.View;
26import android.widget.FrameLayout;
27
28import com.android.camera.CaptureLayoutHelper;
29import com.android.camera.debug.Log;
30import com.android.camera.ui.motion.InterpolatorHelper;
31import com.android.camera.widget.ModeOptions;
32import com.android.camera.widget.ModeOptionsOverlay;
33import com.android.camera.widget.RoundedThumbnailView;
34import com.android.camera2.R;
35
36/**
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.
41 */
42public class StickyBottomCaptureLayout extends FrameLayout {
43
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;
49
50    private ModeOptions.Listener mModeOptionsListener = new ModeOptions.Listener() {
51        @Override
52        public void onBeginToShowModeOptions() {
53            final PointF thumbnailViewPosition = getRoundedThumbnailPosition(
54                    mCaptureLayoutHelper.getUncoveredPreviewRect(),
55                    false,
56                    mModeOptionsOverlay.getModeOptionsToggleWidth());
57            final int orientation = getResources().getConfiguration().orientation;
58            if (orientation == Configuration.ORIENTATION_PORTRAIT) {
59                animateCaptureIndicatorToY(thumbnailViewPosition.y);
60            } else {
61                animateCaptureIndicatorToX(thumbnailViewPosition.x);
62            }
63        }
64
65        @Override
66        public void onBeginToHideModeOptions() {
67            final PointF thumbnailViewPosition = getRoundedThumbnailPosition(
68                    mCaptureLayoutHelper.getUncoveredPreviewRect(),
69                    true,
70                    mModeOptionsOverlay.getModeOptionsToggleWidth());
71            final int orientation = getResources().getConfiguration().orientation;
72            if (orientation == Configuration.ORIENTATION_PORTRAIT) {
73                animateCaptureIndicatorToY(thumbnailViewPosition.y);
74            } else {
75                animateCaptureIndicatorToX(thumbnailViewPosition.x);
76            }
77        }
78    };
79
80    public StickyBottomCaptureLayout(Context context, AttributeSet attrs) {
81        super(context, attrs);
82    }
83
84    @Override
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);
90    }
91
92    /**
93     * Sets a capture layout helper to query layout rect from.
94     */
95    public void setCaptureLayoutHelper(CaptureLayoutHelper helper) {
96        mCaptureLayoutHelper = helper;
97    }
98
99    @Override
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.");
103            return;
104        }
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);
109
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());
120
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);
125    }
126
127    /**
128     * Calculates the desired layout of capture indicator.
129     *
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.
135     */
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);
143
144        final float roundedThumbnailViewSize = mRoundedThumbnailView.getMeasuredWidth();
145        final float roundedThumbnailFinalSize = mRoundedThumbnailView.getThumbnailFinalDiameter();
146        final float roundedThumbnailViewPadding = mRoundedThumbnailView.getThumbnailPadding();
147
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;
154
155        final int orientation = getResources().getConfiguration().orientation;
156
157        float x = 0;
158        float y = 0;
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;
162
163            y = uncoveredPreviewRect.bottom;
164            if (isModeOptionsHidden) {
165                y -= threeDotsButtonPadding + threeDotsButtonDiameter;
166            } else {
167                y -= modeOptionsHeight;
168            }
169            y -= distanceFromModeOptions;
170        }
171        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
172            if (isModeOptionsHidden) {
173                x = uncoveredPreviewRect.right - threeDotsButtonPadding - modeOptionsToggleWidth;
174            } else {
175                x = uncoveredPreviewRect.right - modeOptionsHeight;
176            }
177            x -= distanceFromModeOptions;
178            y = uncoveredPreviewRect.top + roundedThumbnailViewPadding -
179                    radiusDiffBetweenViewAndThumbnail;
180        }
181        return new PointF(x, y);
182    }
183
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)
190                .x(x)
191                .withEndAction(new Runnable() {
192                    @Override
193                    public void run() {
194                        mRoundedThumbnailView.setTranslationX(0.0f);
195                        requestLayout();
196                    }
197                });
198    }
199
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)
206                .y(y)
207                .withEndAction(new Runnable() {
208                    @Override
209                    public void run() {
210                        mRoundedThumbnailView.setTranslationY(0.0f);
211                        requestLayout();
212                    }
213                });
214    }
215}
216