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 17 package com.android.camera.ui; 18 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; 27 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; 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 */ 42 public 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 StickyBottomCaptureLayout(Context context, AttributeSet attrs)80 public StickyBottomCaptureLayout(Context context, AttributeSet attrs) { 81 super(context, attrs); 82 } 83 84 @Override onFinishInflate()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 */ setCaptureLayoutHelper(CaptureLayoutHelper helper)95 public void setCaptureLayoutHelper(CaptureLayoutHelper helper) { 96 mCaptureLayoutHelper = helper; 97 } 98 99 @Override onLayout(boolean changed, int left, int top, int right, int bottom)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 */ getRoundedThumbnailPosition( RectF uncoveredPreviewRect, boolean isModeOptionsHidden, float modeOptionsToggleWidth)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 animateCaptureIndicatorToX(float x)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 animateCaptureIndicatorToY(float y)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