/* * Copyright (C) 2014 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.Configuration; import android.view.animation.Interpolator; import android.graphics.PointF; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import com.android.camera.CaptureLayoutHelper; import com.android.camera.debug.Log; import com.android.camera.ui.motion.InterpolatorHelper; import com.android.camera.widget.ModeOptions; import com.android.camera.widget.ModeOptionsOverlay; import com.android.camera.widget.RoundedThumbnailView; import com.android.camera2.R; /** * The goal of this class is to ensure mode options and capture indicator is * always laid out to the left of or above bottom bar in landscape or portrait * respectively. All the other children in this view group can be expected to * be laid out the same way as they are in a normal FrameLayout. */ public class StickyBottomCaptureLayout extends FrameLayout { private final static Log.Tag TAG = new Log.Tag("StickyBotCapLayout"); private RoundedThumbnailView mRoundedThumbnailView; private ModeOptionsOverlay mModeOptionsOverlay; private View mBottomBar; private CaptureLayoutHelper mCaptureLayoutHelper = null; private ModeOptions.Listener mModeOptionsListener = new ModeOptions.Listener() { @Override public void onBeginToShowModeOptions() { final PointF thumbnailViewPosition = getRoundedThumbnailPosition( mCaptureLayoutHelper.getUncoveredPreviewRect(), false, mModeOptionsOverlay.getModeOptionsToggleWidth()); final int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_PORTRAIT) { animateCaptureIndicatorToY(thumbnailViewPosition.y); } else { animateCaptureIndicatorToX(thumbnailViewPosition.x); } } @Override public void onBeginToHideModeOptions() { final PointF thumbnailViewPosition = getRoundedThumbnailPosition( mCaptureLayoutHelper.getUncoveredPreviewRect(), true, mModeOptionsOverlay.getModeOptionsToggleWidth()); final int orientation = getResources().getConfiguration().orientation; if (orientation == Configuration.ORIENTATION_PORTRAIT) { animateCaptureIndicatorToY(thumbnailViewPosition.y); } else { animateCaptureIndicatorToX(thumbnailViewPosition.x); } } }; public StickyBottomCaptureLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void onFinishInflate() { mRoundedThumbnailView = (RoundedThumbnailView) findViewById(R.id.rounded_thumbnail_view); mModeOptionsOverlay = (ModeOptionsOverlay) findViewById(R.id.mode_options_overlay); mModeOptionsOverlay.setModeOptionsListener(mModeOptionsListener); mBottomBar = findViewById(R.id.bottom_bar); } /** * Sets a capture layout helper to query layout rect from. */ public void setCaptureLayoutHelper(CaptureLayoutHelper helper) { mCaptureLayoutHelper = helper; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (mCaptureLayoutHelper == null) { Log.e(TAG, "Capture layout helper needs to be set first."); return; } // Layout mode options overlay. RectF uncoveredPreviewRect = mCaptureLayoutHelper.getUncoveredPreviewRect(); mModeOptionsOverlay.layout((int) uncoveredPreviewRect.left, (int) uncoveredPreviewRect.top, (int) uncoveredPreviewRect.right, (int) uncoveredPreviewRect.bottom); // Layout capture indicator. PointF roundedThumbnailViewPosition = getRoundedThumbnailPosition( uncoveredPreviewRect, mModeOptionsOverlay.isModeOptionsHidden(), mModeOptionsOverlay.getModeOptionsToggleWidth()); mRoundedThumbnailView.layout( (int) roundedThumbnailViewPosition.x, (int) roundedThumbnailViewPosition.y, (int) roundedThumbnailViewPosition.x + mRoundedThumbnailView.getMeasuredWidth(), (int) roundedThumbnailViewPosition.y + mRoundedThumbnailView.getMeasuredHeight()); // Layout bottom bar. RectF bottomBarRect = mCaptureLayoutHelper.getBottomBarRect(); mBottomBar.layout((int) bottomBarRect.left, (int) bottomBarRect.top, (int) bottomBarRect.right, (int) bottomBarRect.bottom); } /** * Calculates the desired layout of capture indicator. * * @param uncoveredPreviewRect The uncovered preview bound which contains mode option * overlay and capture indicator. * @param isModeOptionsHidden Whether the mode options button are hidden. * @param modeOptionsToggleWidth The width of mode options toggle (three dots button). * @return the desired view bound for capture indicator. */ private PointF getRoundedThumbnailPosition( RectF uncoveredPreviewRect, boolean isModeOptionsHidden, float modeOptionsToggleWidth) { final float threeDotsButtonDiameter = getResources().getDimension(R.dimen.option_button_circle_size); final float threeDotsButtonPadding = getResources().getDimension(R.dimen.mode_options_toggle_padding); final float modeOptionsHeight = getResources().getDimension(R.dimen.mode_options_height); final float roundedThumbnailViewSize = mRoundedThumbnailView.getMeasuredWidth(); final float roundedThumbnailFinalSize = mRoundedThumbnailView.getThumbnailFinalDiameter(); final float roundedThumbnailViewPadding = mRoundedThumbnailView.getThumbnailPadding(); // The view bound is based on the maximal ripple ring diameter. This is the diff of maximal // ripple ring radius and the final thumbnail radius. final float radiusDiffBetweenViewAndThumbnail = (roundedThumbnailViewSize - roundedThumbnailFinalSize) / 2.0f; final float distanceFromModeOptions = roundedThumbnailViewPadding + roundedThumbnailFinalSize + radiusDiffBetweenViewAndThumbnail; final int orientation = getResources().getConfiguration().orientation; float x = 0; float y = 0; if (orientation == Configuration.ORIENTATION_PORTRAIT) { // The view finder of 16:9 aspect ratio might have a black padding. x = uncoveredPreviewRect.right - distanceFromModeOptions; y = uncoveredPreviewRect.bottom; if (isModeOptionsHidden) { y -= threeDotsButtonPadding + threeDotsButtonDiameter; } else { y -= modeOptionsHeight; } y -= distanceFromModeOptions; } if (orientation == Configuration.ORIENTATION_LANDSCAPE) { if (isModeOptionsHidden) { x = uncoveredPreviewRect.right - threeDotsButtonPadding - modeOptionsToggleWidth; } else { x = uncoveredPreviewRect.right - modeOptionsHeight; } x -= distanceFromModeOptions; y = uncoveredPreviewRect.top + roundedThumbnailViewPadding - radiusDiffBetweenViewAndThumbnail; } return new PointF(x, y); } private void animateCaptureIndicatorToX(float x) { final Interpolator interpolator = InterpolatorHelper.getLinearOutSlowInInterpolator(getContext()); mRoundedThumbnailView.animate() .setDuration(ModeOptions.PADDING_ANIMATION_TIME) .setInterpolator(interpolator) .x(x) .withEndAction(new Runnable() { @Override public void run() { mRoundedThumbnailView.setTranslationX(0.0f); requestLayout(); } }); } private void animateCaptureIndicatorToY(float y) { final Interpolator interpolator = InterpolatorHelper.getLinearOutSlowInInterpolator(getContext()); mRoundedThumbnailView.animate() .setDuration(ModeOptions.PADDING_ANIMATION_TIME) .setInterpolator(interpolator) .y(y) .withEndAction(new Runnable() { @Override public void run() { mRoundedThumbnailView.setTranslationY(0.0f); requestLayout(); } }); } }