OSDN Git Service

Adjust capture indicator position while open/close mode options.
authorSenpo Hu <senpo@google.com>
Mon, 2 Feb 2015 20:41:57 +0000 (12:41 -0800)
committerSenpo Hu <senpo@google.com>
Wed, 4 Feb 2015 23:59:06 +0000 (15:59 -0800)
This CL also fixes the layout issue in landscape mode when
mode option indicators are visible.

Bug: 18866551
Bug: 18317565
Bug: 18905659
Change-Id: I34ca5aa77aa9d37a7f0ad3d2d7efbed92fe94f2b

src/com/android/camera/ui/StickyBottomCaptureLayout.java
src/com/android/camera/ui/motion/InterpolatorHelper.java [new file with mode: 0644]
src/com/android/camera/widget/ModeOptions.java
src/com/android/camera/widget/ModeOptionsOverlay.java
src/com/android/camera/widget/RoundedThumbnailView.java

index 2c0dec0..14c7e73 100644 (file)
@@ -17,6 +17,9 @@
 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;
@@ -24,6 +27,9 @@ 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;
 
@@ -37,10 +43,40 @@ public class StickyBottomCaptureLayout extends FrameLayout {
 
     private final static Log.Tag TAG = new Log.Tag("StickyBotCapLayout");
     private RoundedThumbnailView mRoundedThumbnailView;
-    private View mModeOptionsOverlay;
+    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);
     }
@@ -48,7 +84,8 @@ public class StickyBottomCaptureLayout extends FrameLayout {
     @Override
     public void onFinishInflate() {
         mRoundedThumbnailView = (RoundedThumbnailView) findViewById(R.id.rounded_thumbnail_view);
-        mModeOptionsOverlay = findViewById(R.id.mode_options_overlay);
+        mModeOptionsOverlay = (ModeOptionsOverlay) findViewById(R.id.mode_options_overlay);
+        mModeOptionsOverlay.setModeOptionsListener(mModeOptionsListener);
         mBottomBar = findViewById(R.id.bottom_bar);
     }
 
@@ -65,19 +102,114 @@ public class StickyBottomCaptureLayout extends FrameLayout {
             Log.e(TAG, "Capture layout helper needs to be set first.");
             return;
         }
-        RectF drawRect = new RectF(left, top, right, bottom);
+        // Layout mode options overlay.
         RectF uncoveredPreviewRect = mCaptureLayoutHelper.getUncoveredPreviewRect();
-        RectF bottomBarRect = mCaptureLayoutHelper.getBottomBarRect();
-        RectF roundedThumbnailViewRect =
-                mRoundedThumbnailView.getDesiredLayout(drawRect, uncoveredPreviewRect);
-        mRoundedThumbnailView.layout(
-                (int) roundedThumbnailViewRect.left,
-                (int) roundedThumbnailViewRect.top,
-                (int) roundedThumbnailViewRect.right,
-                (int) roundedThumbnailViewRect.bottom);
         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);
     }
-}
\ No newline at end of file
+
+    /**
+     * 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();
+                    }
+                });
+    }
+}
diff --git a/src/com/android/camera/ui/motion/InterpolatorHelper.java b/src/com/android/camera/ui/motion/InterpolatorHelper.java
new file mode 100644 (file)
index 0000000..b78bb8b
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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.motion;
+
+import android.content.Context;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.camera.util.ApiHelper;
+
+import javax.annotation.Nonnull;
+
+public class InterpolatorHelper {
+    private static Interpolator LINEAR_OUT_SLOW_IN = null;
+
+    @Nonnull
+    public static Interpolator getLinearOutSlowInInterpolator(final @Nonnull Context context) {
+        if (LINEAR_OUT_SLOW_IN != null) {
+            return LINEAR_OUT_SLOW_IN;
+        }
+
+        if (ApiHelper.isLOrHigher()) {
+            LINEAR_OUT_SLOW_IN = AnimationUtils.loadInterpolator(
+                    context, android.R.interpolator.linear_out_slow_in);
+        } else {
+            LINEAR_OUT_SLOW_IN = new DecelerateInterpolator();
+        }
+        return LINEAR_OUT_SLOW_IN;
+    }
+}
index 1183594..647de6b 100644 (file)
@@ -15,6 +15,8 @@
  */
 package com.android.camera.widget;
 
+import com.google.common.base.Optional;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -55,7 +57,7 @@ public class ModeOptions extends FrameLayout {
     private static final int RADIUS_ANIMATION_TIME = 250;
     private static final int SHOW_ALPHA_ANIMATION_TIME = 350;
     private static final int HIDE_ALPHA_ANIMATION_TIME = 200;
-    private static final int PADDING_ANIMATION_TIME = 350;
+    public static final int PADDING_ANIMATION_TIME = 350;
 
     private ViewGroup mMainBar;
     private ViewGroup mActiveBar;
@@ -66,8 +68,47 @@ public class ModeOptions extends FrameLayout {
     private boolean mIsPortrait;
     private float mRadius = 0f;
 
+    /**
+     * A class implementing this interface will receive callback events from
+     * mode options.
+     */
+    public interface Listener {
+        /**
+         * Called when about to start animating the mode options from hidden
+         * to visible.
+         */
+        public void onBeginToShowModeOptions();
+
+        /**
+         * Called when about to start animating the mode options from visible
+         * to hidden.
+         */
+        public void onBeginToHideModeOptions();
+    }
+
+    /** The listener. */
+    private Optional<Listener> mListener;
+
     public ModeOptions(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mListener = Optional.absent();
+    }
+
+    /**
+     * Whether the mode options is hidden or in the middle of fading
+     * out.
+     */
+    public boolean isHiddenOrHiding() {
+        return mIsHiddenOrHiding;
+    }
+
+    /**
+     * Sets the listener.
+     *
+     * @param listener The listener to be set.
+     */
+    public void setListener(Listener listener) {
+        mListener = Optional.of(listener);
     }
 
     public void setViewToShowHide(View v) {
@@ -346,6 +387,9 @@ public class ModeOptions extends FrameLayout {
             mVisibleAnimator.end();
             setVisibility(View.VISIBLE);
             mVisibleAnimator.start();
+            if (mListener.isPresent()) {
+                mListener.get().onBeginToShowModeOptions();
+            }
         }
         mIsHiddenOrHiding = false;
     }
@@ -355,6 +399,9 @@ public class ModeOptions extends FrameLayout {
             mVisibleAnimator.cancel();
             mHiddenAnimator.end();
             mHiddenAnimator.start();
+            if (mListener.isPresent()) {
+                mListener.get().onBeginToHideModeOptions();
+            }
         }
         mIsHiddenOrHiding = true;
     }
index a4a4434..c38360d 100644 (file)
@@ -46,8 +46,8 @@ public class ModeOptionsOverlay extends FrameLayout
     private final static Log.Tag TAG = new Log.Tag("ModeOptionsOverlay");
 
     private static final int BOTTOMBAR_OPTIONS_TIMEOUT_MS = 2000;
-    private final static int BOTTOM_RIGHT = Gravity.BOTTOM | Gravity.RIGHT;
-    private final static int TOP_RIGHT = Gravity.TOP | Gravity.RIGHT;
+    private static final int BOTTOM_RIGHT = Gravity.BOTTOM | Gravity.RIGHT;
+    private static final int TOP_RIGHT = Gravity.TOP | Gravity.RIGHT;
 
     private ModeOptions mModeOptions;
     // need a reference to set the onClickListener and fix the layout gravity on orientation change
@@ -61,6 +61,21 @@ public class ModeOptionsOverlay extends FrameLayout
     }
 
     /**
+     * Whether the mode options are hidden.
+     */
+    public boolean isModeOptionsHidden() {
+        return mModeOptions.isHiddenOrHiding();
+    }
+
+    /**
+     * Gets the current width of the mode options toggle including the three dots and various mode
+     * option indicators.
+     */
+    public float getModeOptionsToggleWidth() {
+        return mModeOptionsToggle.getWidth();
+    }
+
+    /**
      * Sets a capture layout helper to query layout rect from.
      */
     public void setCaptureLayoutHelper(CaptureLayoutHelper helper) {
@@ -71,6 +86,15 @@ public class ModeOptionsOverlay extends FrameLayout
         mModeOptionsToggle.setClickable(clickable);
     }
 
+    /**
+     * Sets the mode options listener.
+     *
+     * @param listener The listener to be set.
+     */
+    public void setModeOptionsListener(ModeOptions.Listener listener) {
+        mModeOptions.setListener(listener);
+    }
+
     @Override
     public void onFinishInflate() {
         mModeOptions = (ModeOptions) findViewById(R.id.mode_options);
index d6a02f8..ca5ffe6 100644 (file)
@@ -23,7 +23,6 @@ import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
@@ -36,13 +35,12 @@ import android.util.AttributeSet;
 import android.view.View;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AnimationUtils;
-import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
 import com.android.camera.async.MainThread;
 import com.android.camera.debug.Log;
+import com.android.camera.ui.motion.InterpolatorHelper;
 import com.android.camera.util.ApiHelper;
-import com.android.camera.util.CameraUtil;
 import com.android.camera2.R;
 
 import com.google.common.base.Optional;
@@ -226,7 +224,6 @@ public class RoundedThumbnailView extends View {
         setClickable(true);
         setOnClickListener(mOnClickListener);
 
-        // TODO: Adjust layout when mode option overlay is visible.
         mThumbnailPadding = getResources().getDimension(R.dimen.rounded_thumbnail_padding);
 
         // Load thumbnail pop-out effect constants.
@@ -331,7 +328,6 @@ public class RoundedThumbnailView extends View {
                         centerX,
                         centerY,
                         thumbnailPaint);
-
             }
 
             // Draw the reveal while circle.
@@ -370,44 +366,21 @@ public class RoundedThumbnailView extends View {
     }
 
     /**
-     * Calculates the desired layout of capture indicator.
+     * Gets the padding size with mode options and preview edges.
      *
-     * @param parentRect The bound of the view which contains capture indicator.
-     * @param uncoveredPreviewRect The uncovered preview bound which contains mode option
-     *                             overlay and capture indicator.
-     * @return the desired view bound for capture indicator.
+     * @return The padding size with mode options and preview edges.
      */
-    public RectF getDesiredLayout(RectF parentRect, RectF uncoveredPreviewRect) {
-        float parentViewWidth = parentRect.right - parentRect.left;
-        float x = 0;
-        float y = 0;
-
-        // 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.
-        float radius_diff_max_normal = (mRippleRingDiameterEnd - mThumbnailShrinkDiameterEnd) / 2;
-        float modeSwitchThreeDotsDiameter = mThumbnailShrinkDiameterEnd;
-        float modeSwitchThreeDotsBottomPadding = mThumbnailPadding;
-
-        int orientation = getResources().getConfiguration().orientation;
-        int rotation = CameraUtil.getDisplayRotation();
-        if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-            // The view finder of 16:9 aspect ratio might have a black padding.
-            float previewRightEdgeGap =
-                    parentRect.right - uncoveredPreviewRect.right;
-            x = parentViewWidth - previewRightEdgeGap - mThumbnailPadding -
-                    mThumbnailShrinkDiameterEnd - radius_diff_max_normal;
-            y = uncoveredPreviewRect.bottom;
-            y -= modeSwitchThreeDotsBottomPadding + modeSwitchThreeDotsDiameter +
-                    mThumbnailPadding + mThumbnailShrinkDiameterEnd + radius_diff_max_normal;
-        }
-        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            float previewTopEdgeGap = uncoveredPreviewRect.top;
-            x = uncoveredPreviewRect.right;
-            x -= modeSwitchThreeDotsBottomPadding + modeSwitchThreeDotsDiameter +
-                    mThumbnailPadding + mThumbnailShrinkDiameterEnd + radius_diff_max_normal;
-            y = previewTopEdgeGap + mThumbnailPadding - radius_diff_max_normal;
-        }
-        return new RectF(x, y, x + mRippleRingDiameterEnd, y + mRippleRingDiameterEnd);
+    public float getThumbnailPadding() {
+        return mThumbnailPadding;
+    }
+
+    /**
+     * Gets the diameter of the thumbnail image after the revealing animation.
+     *
+     * @return The diameter of the thumbnail image after the revealing animation.
+     */
+    public float getThumbnailFinalDiameter() {
+        return mThumbnailShrinkDiameterEnd;
     }
 
     /**
@@ -559,14 +532,8 @@ public class RoundedThumbnailView extends View {
         if (mRippleAnimator == null) {
 
             // Ripple effect uses linear_out_slow_in interpolator.
-            Interpolator rippleInterpolator;
-            if (ApiHelper.isLOrHigher()) {
-                // Both phases use fast_out_flow_in interpolator.
-                rippleInterpolator = AnimationUtils.loadInterpolator(
-                        getContext(), android.R.interpolator.linear_out_slow_in);
-            } else {
-                rippleInterpolator = new DecelerateInterpolator();
-            }
+            Interpolator rippleInterpolator =
+                    InterpolatorHelper.getLinearOutSlowInInterpolator(getContext());
 
             // When start shrinking the thumbnail, a ripple effect is triggered at the same time.
             mRippleAnimator =