summaryrefslogtreecommitdiff
path: root/java/com/android/newbubble
diff options
context:
space:
mode:
authoryueg <yueg@google.com>2017-12-05 10:29:03 -0800
committerCopybara-Service <copybara-piper@google.com>2017-12-07 11:02:15 -0800
commit81a77ffc4d36c6054a75acfe7b048e7c0d7a8744 (patch)
tree650eabb797c3a2176bf4260dfadddfd9e4d9908e /java/com/android/newbubble
parent5b77c7dfd420cb77cdb195fd441a8d57676a837f (diff)
Bubble v2 animation changes.
Including: - expanded view expands/collapses from top of itself - small icon on avatar shows on left side when bubble is on right side - when expand on bottom, bubble move up a bit so that expanded view doesn't go off screen. It also go back to previous position when collapse. - remove animation for collapse when move expanded bubble This change should not enable bubble v2 for anyone. Bug: 67605985 Test: manual PiperOrigin-RevId: 177974562 Change-Id: Id83f3f744b717d51fbe58e58769ac2cd2810d2b5
Diffstat (limited to 'java/com/android/newbubble')
-rw-r--r--java/com/android/newbubble/NewBubble.java468
-rw-r--r--java/com/android/newbubble/NewMoveHandler.java40
-rw-r--r--java/com/android/newbubble/res/layout/new_bubble_base.xml129
-rw-r--r--java/com/android/newbubble/res/values/values.xml7
4 files changed, 415 insertions, 229 deletions
diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java
index e690f4be4..ef3a971dd 100644
--- a/java/com/android/newbubble/NewBubble.java
+++ b/java/com/android/newbubble/NewBubble.java
@@ -17,12 +17,15 @@
package com.android.newbubble;
import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
@@ -51,10 +54,16 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.OvershootInterpolator;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewAnimator;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.logging.DialerImpression;
+import com.android.dialer.logging.Logger;
import com.android.dialer.util.DrawableConverter;
+import com.android.incallui.call.CallList;
+import com.android.incallui.call.DialerCall;
import com.android.newbubble.NewBubbleInfo.Action;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -96,11 +105,14 @@ public class NewBubble {
private CharSequence textAfterShow;
private int collapseEndAction;
- @VisibleForTesting ViewHolder viewHolder;
+ ViewHolder viewHolder;
private ViewPropertyAnimator collapseAnimation;
private Integer overrideGravity;
private ViewPropertyAnimator exitAnimator;
+ private int leftBoundary;
+ private int savedYPosition = -1;
+
private final Runnable collapseRunnable =
new Runnable() {
@Override
@@ -110,17 +122,11 @@ public class NewBubble {
// Always reset here since text shouldn't keep showing.
hideAndReset();
} else {
- doResize(
- () ->
- viewHolder
- .getPrimaryButton()
- .setDisplayedChild(ViewHolder.CHILD_INDEX_AVATAR_AND_ICON));
+ viewHolder.getPrimaryButton().setDisplayedChild(ViewHolder.CHILD_INDEX_AVATAR_AND_ICON);
}
}
};
- private BubbleExpansionStateListener bubbleExpansionStateListener;
-
/** Type of action after bubble collapse */
@Retention(RetentionPolicy.SOURCE)
@IntDef({CollapseEnd.NOTHING, CollapseEnd.HIDE})
@@ -206,15 +212,20 @@ public class NewBubble {
windowManager = context.getSystemService(WindowManager.class);
viewHolder = new ViewHolder(context);
+
+ leftBoundary =
+ context.getResources().getDimensionPixelOffset(R.dimen.bubble_off_screen_size_horizontal)
+ - context
+ .getResources()
+ .getDimensionPixelSize(R.dimen.bubble_shadow_padding_size_horizontal);
}
/** Expands the main bubble menu. */
public void expand(boolean isUserAction) {
- if (bubbleExpansionStateListener != null) {
- bubbleExpansionStateListener.onBubbleExpansionStateChanged(
- ExpansionState.START_EXPANDING, isUserAction);
+ if (isUserAction) {
+ logBasicOrCallImpression(DialerImpression.Type.BUBBLE_PRIMARY_BUTTON_EXPAND);
}
- doResize(() -> viewHolder.setDrawerVisibility(View.VISIBLE));
+ viewHolder.setDrawerVisibility(View.INVISIBLE);
View expandedView = viewHolder.getExpandedView();
expandedView
.getViewTreeObserver()
@@ -222,13 +233,62 @@ public class NewBubble {
new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
- // Animate expanded view to move from above primary button to its final position
+ // Move the whole bubble up so that expanded view is still in screen
+ int moveUpDistance = viewHolder.getMoveUpDistance();
+ if (moveUpDistance != 0) {
+ savedYPosition = windowParams.y;
+ }
+
+ // Calculate the move-to-middle distance
+ int deltaX =
+ (int) viewHolder.getRoot().findViewById(R.id.bubble_primary_container).getX();
+ float k = (float) moveUpDistance / deltaX;
+ if (isDrawingFromRight()) {
+ deltaX = -deltaX;
+ }
+
+ // Do X-move and Y-move together
+
+ final int startX = windowParams.x - deltaX;
+ final int startY = windowParams.y;
+ ValueAnimator animator = ValueAnimator.ofFloat(startX, windowParams.x);
+ animator.setInterpolator(new LinearOutSlowInInterpolator());
+ animator.addUpdateListener(
+ (valueAnimator) -> {
+ // Update windowParams and the root layout.
+ // We can't do ViewPropertyAnimation since it clips children.
+ float newX = (float) valueAnimator.getAnimatedValue();
+ if (moveUpDistance != 0) {
+ windowParams.y = startY - (int) (Math.abs(newX - (float) startX) * k);
+ }
+ windowParams.x = (int) newX;
+ windowManager.updateViewLayout(viewHolder.getRoot(), windowParams);
+ });
+ animator.addListener(
+ new AnimatorListener() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Show expanded view
+ expandedView.setVisibility(View.VISIBLE);
+ expandedView.setTranslationY(-expandedView.getHeight());
+ expandedView
+ .animate()
+ .setInterpolator(new LinearOutSlowInInterpolator())
+ .translationY(0);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+ animator.start();
+
expandedView.getViewTreeObserver().removeOnPreDrawListener(this);
- expandedView.setTranslationY(-viewHolder.getRoot().getHeight());
- expandedView
- .animate()
- .setInterpolator(new LinearOutSlowInInterpolator())
- .translationY(0);
return false;
}
});
@@ -236,6 +296,115 @@ public class NewBubble {
expanded = true;
}
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public void startCollapse(
+ @CollapseEnd int endAction, boolean isUserAction, boolean shouldRecoverYPosition) {
+ View expandedView = viewHolder.getExpandedView();
+ if (expandedView.getVisibility() != View.VISIBLE || collapseAnimation != null) {
+ // Drawer is already collapsed or animation is running.
+ return;
+ }
+
+ overrideGravity = isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT;
+ setFocused(false);
+
+ if (collapseEndAction == CollapseEnd.NOTHING) {
+ collapseEndAction = endAction;
+ }
+ if (isUserAction && collapseEndAction == CollapseEnd.NOTHING) {
+ logBasicOrCallImpression(DialerImpression.Type.BUBBLE_COLLAPSE_BY_USER);
+ }
+ // Animate expanded view to move from its position to above primary button and hide
+ collapseAnimation =
+ expandedView
+ .animate()
+ .translationY(-expandedView.getHeight())
+ .setInterpolator(new FastOutLinearInInterpolator())
+ .withEndAction(
+ () -> {
+ collapseAnimation = null;
+ expanded = false;
+
+ if (textShowing) {
+ // Will do resize once the text is done.
+ return;
+ }
+
+ // Set drawer visibility to INVISIBLE instead of GONE to keep primary button fixed
+ viewHolder.setDrawerVisibility(View.INVISIBLE);
+
+ // Do X-move and Y-move together
+ int deltaX =
+ (int) viewHolder.getRoot().findViewById(R.id.bubble_primary_container).getX();
+ int startX = windowParams.x;
+ int startY = windowParams.y;
+ float k =
+ (savedYPosition != -1 && shouldRecoverYPosition)
+ ? (savedYPosition - startY) / (float) deltaX
+ : 0;
+ Path path = new Path();
+ path.moveTo(windowParams.x, windowParams.y);
+ path.lineTo(
+ windowParams.x - deltaX,
+ (savedYPosition != -1 && shouldRecoverYPosition)
+ ? savedYPosition
+ : windowParams.y);
+ // The position is not useful after collapse
+ savedYPosition = -1;
+
+ ValueAnimator animator = ValueAnimator.ofFloat(startX, startX - deltaX);
+ animator.setInterpolator(new LinearOutSlowInInterpolator());
+ animator.addUpdateListener(
+ (valueAnimator) -> {
+ // Update windowParams and the root layout.
+ // We can't do ViewPropertyAnimation since it clips children.
+ float newX = (float) valueAnimator.getAnimatedValue();
+ if (k != 0) {
+ windowParams.y = startY + (int) (Math.abs(newX - (float) startX) * k);
+ }
+ windowParams.x = (int) newX;
+ windowManager.updateViewLayout(viewHolder.getRoot(), windowParams);
+ });
+ animator.addListener(
+ new AnimatorListener() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // If collapse on the right side, the primary button move left a bit after
+ // drawer
+ // visibility becoming GONE. To avoid it, we create a new ViewHolder.
+ replaceViewHolder();
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ });
+ animator.start();
+
+ // If this collapse was to come before a hide, do it now.
+ if (collapseEndAction == CollapseEnd.HIDE) {
+ hide();
+ }
+ collapseEndAction = CollapseEnd.NOTHING;
+
+ // Resume normal gravity after any resizing is done.
+ handler.postDelayed(
+ () -> {
+ overrideGravity = null;
+ if (!viewHolder.isMoving()) {
+ viewHolder.undoGravityOverride();
+ }
+ },
+ // Need to wait twice as long for resize and layout
+ WINDOW_REDRAW_DELAY_MILLIS * 2);
+ });
+ }
+
/**
* Make the bubble visible. Will show a short entrance animation as it enters. If the bubble is
* already showing this method does nothing.
@@ -269,8 +438,7 @@ public class NewBubble {
| LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSLUCENT);
windowParams.gravity = Gravity.TOP | Gravity.LEFT;
- windowParams.x =
- context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_horizontal);
+ windowParams.x = leftBoundary;
windowParams.y = currentInfo.getStartingYPosition();
windowParams.height = LayoutParams.WRAP_CONTENT;
windowParams.width = LayoutParams.WRAP_CONTENT;
@@ -392,7 +560,8 @@ public class NewBubble {
public void showText(@NonNull CharSequence text) {
textShowing = true;
if (expanded) {
- startCollapse(CollapseEnd.NOTHING, false);
+ startCollapse(
+ CollapseEnd.NOTHING, false /* isUserAction */, false /* shouldRecoverYPosition */);
doShowText(text);
} else {
// Need to transition from old bounds to new bounds manually
@@ -409,68 +578,65 @@ public class NewBubble {
return;
}
- doResize(
- () -> {
- doShowText(text);
- // Hide the text so we can animate it in
- viewHolder.getPrimaryText().setAlpha(0);
-
- ViewAnimator primaryButton = viewHolder.getPrimaryButton();
- // Cancel the automatic transition scheduled in doShowText
- TransitionManager.endTransitions((ViewGroup) primaryButton.getParent());
- primaryButton
- .getViewTreeObserver()
- .addOnPreDrawListener(
- new OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- primaryButton.getViewTreeObserver().removeOnPreDrawListener(this);
-
- // Prepare and capture end values, always use the size of primaryText since
- // its invisibility makes primaryButton smaller than expected
- TransitionValues endValues = new TransitionValues();
- endValues.values.put(
- NewChangeOnScreenBounds.PROPNAME_WIDTH,
- viewHolder.getPrimaryText().getWidth());
- endValues.values.put(
- NewChangeOnScreenBounds.PROPNAME_HEIGHT,
- viewHolder.getPrimaryText().getHeight());
- endValues.view = primaryButton;
- transition.addTarget(endValues.view);
- transition.captureEndValues(endValues);
-
- // animate the primary button bounds change
- Animator bounds =
- transition.createAnimator(primaryButton, startValues, endValues);
-
- // Animate the text in
- Animator alpha =
- ObjectAnimator.ofFloat(viewHolder.getPrimaryText(), View.ALPHA, 1f);
-
- AnimatorSet set = new AnimatorSet();
- set.play(bounds).before(alpha);
- set.start();
- return false;
- }
- });
- });
+ doShowText(text);
+ // Hide the text so we can animate it in
+ viewHolder.getPrimaryText().setAlpha(0);
+
+ ViewAnimator primaryButton = viewHolder.getPrimaryButton();
+ // Cancel the automatic transition scheduled in doShowText
+ TransitionManager.endTransitions((ViewGroup) primaryButton.getParent());
+ primaryButton
+ .getViewTreeObserver()
+ .addOnPreDrawListener(
+ new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ primaryButton.getViewTreeObserver().removeOnPreDrawListener(this);
+
+ // Prepare and capture end values, always use the size of primaryText since
+ // its invisibility makes primaryButton smaller than expected
+ TransitionValues endValues = new TransitionValues();
+ endValues.values.put(
+ NewChangeOnScreenBounds.PROPNAME_WIDTH,
+ viewHolder.getPrimaryText().getWidth());
+ endValues.values.put(
+ NewChangeOnScreenBounds.PROPNAME_HEIGHT,
+ viewHolder.getPrimaryText().getHeight());
+ endValues.view = primaryButton;
+ transition.addTarget(endValues.view);
+ transition.captureEndValues(endValues);
+
+ // animate the primary button bounds change
+ Animator bounds =
+ transition.createAnimator(primaryButton, startValues, endValues);
+
+ // Animate the text in
+ Animator alpha =
+ ObjectAnimator.ofFloat(viewHolder.getPrimaryText(), View.ALPHA, 1f);
+
+ AnimatorSet set = new AnimatorSet();
+ set.play(bounds).before(alpha);
+ set.start();
+ return false;
+ }
+ });
}
handler.removeCallbacks(collapseRunnable);
handler.postDelayed(collapseRunnable, SHOW_TEXT_DURATION_MILLIS);
}
- public void setBubbleExpansionStateListener(
- BubbleExpansionStateListener bubbleExpansionStateListener) {
- this.bubbleExpansionStateListener = bubbleExpansionStateListener;
- }
-
@Nullable
Integer getGravityOverride() {
return overrideGravity;
}
void onMoveStart() {
- startCollapse(CollapseEnd.NOTHING, true);
+ if (viewHolder.getExpandedView().getVisibility() == View.VISIBLE) {
+ viewHolder.setDrawerVisibility(View.INVISIBLE);
+ }
+ expanded = false;
+ savedYPosition = -1;
+
viewHolder
.getPrimaryButton()
.animate()
@@ -482,11 +648,6 @@ public class NewBubble {
void onMoveFinish() {
viewHolder.getPrimaryButton().animate().translationZ(0);
- // If it's GONE, no resize is necessary. If it's VISIBLE, it will get cleaned up when the
- // collapse animation finishes
- if (viewHolder.getExpandedView().getVisibility() == View.INVISIBLE) {
- doResize(null);
- }
}
void primaryButtonClick() {
@@ -494,12 +655,22 @@ public class NewBubble {
return;
}
if (expanded) {
- startCollapse(CollapseEnd.NOTHING, true);
+ startCollapse(
+ CollapseEnd.NOTHING, true /* isUserAction */, true /* shouldRecoverYPosition */);
} else {
expand(true);
}
}
+ void onLeftRightSwitch(boolean onRight) {
+ // Set layout direction so the small icon is not partially hidden.
+ View primaryIcon = viewHolder.getPrimaryIcon();
+ int newGravity = (onRight ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM;
+ FrameLayout.LayoutParams layoutParams =
+ new FrameLayout.LayoutParams(primaryIcon.getWidth(), primaryIcon.getHeight(), newGravity);
+ primaryIcon.setLayoutParams(layoutParams);
+ }
+
LayoutParams getWindowParams() {
return windowParams;
}
@@ -532,7 +703,7 @@ public class NewBubble {
}
if (expanded) {
- startCollapse(CollapseEnd.HIDE, false);
+ startCollapse(CollapseEnd.HIDE, false /* isUserAction */, false /* shouldRecoverYPosition */);
return;
}
@@ -618,34 +789,39 @@ public class NewBubble {
}
}
- private void doResize(@Nullable Runnable operation) {
- // If we're resizing on the right side of the screen, there is an implicit move operation
- // necessary. The WindowManager does not sync the move and resize operations, so serious jank
- // would occur. To fix this, instead of resizing the window, we create a new one and destroy
- // the old one. There is a short delay before destroying the old view to ensure the new one has
- // had time to draw.
+ /**
+ * Create a new ViewHolder object to replace the old one.It only happens when not moving and
+ * collapsed.
+ */
+ void replaceViewHolder() {
+ LogUtil.enterBlock("NewBubble.replaceViewHolder");
ViewHolder oldViewHolder = viewHolder;
- if (isDrawingFromRight()) {
- viewHolder = new ViewHolder(oldViewHolder.getRoot().getContext());
- update();
- viewHolder
- .getPrimaryButton()
- .setDisplayedChild(oldViewHolder.getPrimaryButton().getDisplayedChild());
- viewHolder.getPrimaryText().setText(oldViewHolder.getPrimaryText().getText());
- }
- if (operation != null) {
- operation.run();
- }
+ // Create a new ViewHolder and copy needed info.
+ viewHolder = new ViewHolder(oldViewHolder.getRoot().getContext());
+ viewHolder
+ .getPrimaryButton()
+ .setDisplayedChild(oldViewHolder.getPrimaryButton().getDisplayedChild());
+ viewHolder.getPrimaryText().setText(oldViewHolder.getPrimaryText().getText());
- if (isDrawingFromRight()) {
- swapViewHolders(oldViewHolder);
- }
- }
+ int size = context.getResources().getDimensionPixelSize(R.dimen.bubble_small_icon_size);
+ viewHolder
+ .getPrimaryIcon()
+ .setLayoutParams(
+ new FrameLayout.LayoutParams(
+ size,
+ size,
+ Gravity.BOTTOM | (isDrawingFromRight() ? Gravity.LEFT : Gravity.RIGHT)));
- private void swapViewHolders(ViewHolder oldViewHolder) {
+ update();
+
+ // Add new view at its horizontal boundary
ViewGroup root = viewHolder.getRoot();
+ windowParams.x = leftBoundary;
+ windowParams.gravity = Gravity.TOP | (isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT);
windowManager.addView(root, windowParams);
+
+ // Remove the old view after delay
root.getViewTreeObserver()
.addOnPreDrawListener(
new OnPreDrawListener() {
@@ -661,63 +837,8 @@ public class NewBubble {
});
}
- @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
- public void startCollapse(@CollapseEnd int endAction, boolean isUserAction) {
- View expandedView = viewHolder.getExpandedView();
- if (expandedView.getVisibility() != View.VISIBLE || collapseAnimation != null) {
- // Drawer is already collapsed or animation is running.
- return;
- }
-
- overrideGravity = isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT;
- setFocused(false);
-
- if (collapseEndAction == CollapseEnd.NOTHING) {
- collapseEndAction = endAction;
- }
- if (bubbleExpansionStateListener != null && collapseEndAction == CollapseEnd.NOTHING) {
- bubbleExpansionStateListener.onBubbleExpansionStateChanged(
- ExpansionState.START_COLLAPSING, isUserAction);
- }
- // Animate expanded view to move from its position to above primary button and hide
- collapseAnimation =
- expandedView
- .animate()
- .translationY(-viewHolder.getRoot().getHeight())
- .setInterpolator(new FastOutLinearInInterpolator())
- .withEndAction(
- () -> {
- collapseAnimation = null;
- expanded = false;
-
- if (textShowing) {
- // Will do resize once the text is done.
- return;
- }
-
- // Hide the drawer and resize if possible.
- viewHolder.setDrawerVisibility(View.INVISIBLE);
- if (!viewHolder.isMoving() || !isDrawingFromRight()) {
- doResize(() -> viewHolder.setDrawerVisibility(View.GONE));
- }
-
- // If this collapse was to come before a hide, do it now.
- if (collapseEndAction == CollapseEnd.HIDE) {
- hide();
- }
- collapseEndAction = CollapseEnd.NOTHING;
-
- // Resume normal gravity after any resizing is done.
- handler.postDelayed(
- () -> {
- overrideGravity = null;
- if (!viewHolder.isMoving()) {
- viewHolder.undoGravityOverride();
- }
- },
- // Need to wait twice as long for resize and layout
- WINDOW_REDRAW_DELAY_MILLIS * 2);
- });
+ int getDrawerVisibility() {
+ return viewHolder.getExpandedView().getVisibility();
}
private boolean isDrawingFromRight() {
@@ -741,6 +862,16 @@ public class NewBubble {
updatePrimaryIconAnimation();
}
+ private void logBasicOrCallImpression(DialerImpression.Type impressionType) {
+ DialerCall call = CallList.getInstance().getActiveOrBackgroundCall();
+ if (call != null) {
+ Logger.get(context)
+ .logCallImpression(impressionType, call.getUniqueCallId(), call.getTimeAddedMs());
+ } else {
+ Logger.get(context).logImpression(impressionType);
+ }
+ }
+
@VisibleForTesting
class ViewHolder {
@@ -779,7 +910,8 @@ public class NewBubble {
root.setOnBackPressedListener(
() -> {
if (visibility == Visibility.SHOWING && expanded) {
- startCollapse(CollapseEnd.NOTHING, true);
+ startCollapse(
+ CollapseEnd.NOTHING, true /* isUserAction */, true /* shouldRecoverYPosition */);
return true;
}
return false;
@@ -794,7 +926,8 @@ public class NewBubble {
root.setOnTouchListener(
(v, event) -> {
if (expanded && event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
- startCollapse(CollapseEnd.NOTHING, true);
+ startCollapse(
+ CollapseEnd.NOTHING, true /* isUserAction */, true /* shouldRecoverYPosition */);
return true;
}
return false;
@@ -812,6 +945,16 @@ public class NewBubble {
moveHandler.setClickable(clickable);
}
+ public int getMoveUpDistance() {
+ int deltaAllowed =
+ expandedView.getHeight()
+ - context
+ .getResources()
+ .getDimensionPixelOffset(R.dimen.bubble_button_padding_vertical)
+ * 2;
+ return moveHandler.getMoveUpDistance(deltaAllowed);
+ }
+
public ViewGroup getRoot() {
return root;
}
@@ -864,9 +1007,4 @@ public class NewBubble {
moveHandler.undoGravityOverride();
}
}
-
- /** Listener for bubble expansion state change. */
- public interface BubbleExpansionStateListener {
- void onBubbleExpansionStateChanged(@ExpansionState int expansionState, boolean isUserAction);
- }
}
diff --git a/java/com/android/newbubble/NewMoveHandler.java b/java/com/android/newbubble/NewMoveHandler.java
index 189ad8472..9cb1f1eca 100644
--- a/java/com/android/newbubble/NewMoveHandler.java
+++ b/java/com/android/newbubble/NewMoveHandler.java
@@ -48,6 +48,8 @@ class NewMoveHandler implements OnTouchListener {
private final int maxX;
private final int maxY;
private final int bubbleSize;
+ private final int bubbleShadowPaddingHorizontal;
+ private final int bubbleExpandedViewWidth;
private final float touchSlopSquared;
private boolean clickable = true;
@@ -70,8 +72,14 @@ class NewMoveHandler implements OnTouchListener {
@Override
public float getValue(LayoutParams windowParams) {
int realX = windowParams.x;
- realX = realX + bubbleSize / 2;
+ // Get bubble center position from real position
+ if (bubble.getDrawerVisibility() == View.INVISIBLE) {
+ realX += bubbleExpandedViewWidth / 2 + bubbleShadowPaddingHorizontal * 2;
+ } else {
+ realX += bubbleSize / 2 + bubbleShadowPaddingHorizontal;
+ }
if (relativeToRight(windowParams)) {
+ // If gravity is right, get distant from bubble center position to screen right edge
int displayWidth = context.getResources().getDisplayMetrics().widthPixels;
realX = displayWidth - realX;
}
@@ -88,12 +96,19 @@ class NewMoveHandler implements OnTouchListener {
} else {
onRight = (gravityOverride & Gravity.RIGHT) == Gravity.RIGHT;
}
- int centeringOffset = bubbleSize / 2;
+ // Get real position from bubble center position
+ int centeringOffset;
+ if (bubble.getDrawerVisibility() == View.INVISIBLE) {
+ centeringOffset = bubbleExpandedViewWidth / 2 + bubbleShadowPaddingHorizontal * 2;
+ } else {
+ centeringOffset = bubbleSize / 2 + bubbleShadowPaddingHorizontal;
+ }
windowParams.x =
(int) (onRight ? (displayWidth - value - centeringOffset) : value - centeringOffset);
windowParams.gravity = Gravity.TOP | (onRight ? Gravity.RIGHT : Gravity.LEFT);
if (bubble.isVisible()) {
windowManager.updateViewLayout(bubble.getRootView(), windowParams);
+ bubble.onLeftRightSwitch(onRight);
}
}
};
@@ -120,8 +135,13 @@ class NewMoveHandler implements OnTouchListener {
windowManager = context.getSystemService(WindowManager.class);
bubbleSize = context.getResources().getDimensionPixelSize(R.dimen.bubble_size);
+ bubbleShadowPaddingHorizontal =
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_shadow_padding_size_horizontal);
+ bubbleExpandedViewWidth =
+ context.getResources().getDimensionPixelSize(R.dimen.bubble_expanded_width);
+ // The following value is based on bubble center
minX =
- context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_horizontal)
+ context.getResources().getDimensionPixelOffset(R.dimen.bubble_off_screen_size_horizontal)
+ bubbleSize / 2;
minY =
context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_vertical)
@@ -156,6 +176,12 @@ class NewMoveHandler implements OnTouchListener {
moveYAnimation.animateToFinalPosition(yProperty.getValue(bubble.getWindowParams()));
}
+ public int getMoveUpDistance(int deltaAllowed) {
+ int currentY = (int) yProperty.getValue(bubble.getWindowParams());
+ int currentDelta = maxY - currentY;
+ return currentDelta >= deltaAllowed ? 0 : deltaAllowed - currentDelta;
+ }
+
@Override
public boolean onTouch(View v, MotionEvent event) {
float eventX = event.getRawX();
@@ -222,6 +248,14 @@ class NewMoveHandler implements OnTouchListener {
moveXAnimation = new SpringAnimation(bubble.getWindowParams(), xProperty);
moveXAnimation.setSpring(new SpringForce());
moveXAnimation.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
+ // Moving when expanded makes expanded view INVISIBLE, and the whole view is not at the
+ // boundary. It's time to create a viewHolder.
+ moveXAnimation.addEndListener(
+ (animation, canceled, value, velocity) -> {
+ if (!isMoving && bubble.getDrawerVisibility() == View.INVISIBLE) {
+ bubble.replaceViewHolder();
+ }
+ });
}
if (moveYAnimation == null) {
diff --git a/java/com/android/newbubble/res/layout/new_bubble_base.xml b/java/com/android/newbubble/res/layout/new_bubble_base.xml
index 8cac982f4..8d4771631 100644
--- a/java/com/android/newbubble/res/layout/new_bubble_base.xml
+++ b/java/com/android/newbubble/res/layout/new_bubble_base.xml
@@ -19,7 +19,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:clipChildren="false"
+ android:clipChildren="true"
+ android:clipToPadding="false"
tools:theme="@style/Theme.AppCompat">
<RelativeLayout
android:id="@+id/bubble_primary_container"
@@ -41,7 +42,8 @@
android:measureAllChildren="false"
android:elevation="@dimen/bubble_elevation"
tools:backgroundTint="#FF0000AA">
- <RelativeLayout
+ <FrameLayout
+ android:id="@+id/bubble_icon_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
@@ -53,8 +55,7 @@
android:id="@+id/bubble_icon_primary"
android:layout_width="@dimen/bubble_small_icon_size"
android:layout_height="@dimen/bubble_small_icon_size"
- android:layout_alignBottom="@id/bubble_icon_avatar"
- android:layout_alignEnd="@id/bubble_icon_avatar"
+ android:layout_gravity="bottom|right"
android:padding="@dimen/bubble_small_icon_padding"
android:tint="@android:color/white"
android:tintMode="src_in"
@@ -62,7 +63,7 @@
android:measureAllChildren="false"
tools:backgroundTint="#FF0000AA"
tools:src="@android:drawable/ic_btn_speak_now"/>
- </RelativeLayout>
+ </FrameLayout>
<TextView
android:id="@+id/bubble_text"
android:layout_width="wrap_content"
@@ -75,67 +76,77 @@
tools:text="Call ended"/>
</ViewAnimator>
</RelativeLayout>
+ <!-- The RelativeLayout below serves as boundary for @id/bubble_expanded_layout during animation -->
<RelativeLayout
- android:id="@+id/bubble_expanded_layout"
- android:layout_width="@dimen/bubble_expanded_width"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/bubble_primary_container"
- android:layout_marginStart="@dimen/bubble_shadow_padding_size_horizontal_double"
- android:layout_marginEnd="@dimen/bubble_shadow_padding_size_horizontal_double"
android:layout_marginTop="@dimen/bubble_shadow_padding_size_vertical_minus"
- android:layout_marginBottom="@dimen/bubble_shadow_padding_size_vertical"
- android:visibility="gone"
- tools:visibility="visible">
+ android:clipChildren="true"
+ android:clipToPadding="false"
+ android:layout_below="@id/bubble_primary_container">
<RelativeLayout
- android:id="@+id/bubble_triangle"
- android:layout_width="12dp"
- android:layout_height="12dp"
- android:layout_marginTop="7dp"
- android:layout_marginBottom="-6dp"
- android:layout_centerHorizontal="true"
- android:background="@color/background_dialer_white"
- android:elevation="@dimen/bubble_expanded_elevation"
- android:rotation="45">
- </RelativeLayout>
- <RelativeLayout
- android:layout_width="match_parent"
+ android:id="@+id/bubble_expanded_layout"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/bubble_triangle"
+ android:paddingStart="@dimen/bubble_shadow_padding_size_horizontal_double"
+ android:paddingEnd="@dimen/bubble_shadow_padding_size_horizontal_double"
+ android:paddingBottom="@dimen/bubble_shadow_padding_size_vertical"
+ android:clipChildren="false"
android:clipToPadding="false"
- android:background="@drawable/bubble_background_with_radius"
- android:elevation="@dimen/bubble_expanded_elevation"
- android:layoutDirection="inherit">
- <com.android.newbubble.NewCheckableButton
- android:id="@+id/bubble_button_full_screen"
- android:layout_marginTop="8dp"
- android:textColor="@color/bubble_button_color_grey"
- android:background="@drawable/bubble_ripple_pill_up"
- android:drawableTint="@color/bubble_button_color_grey"
- style="@style/CheckableButton"/>
- <com.android.newbubble.NewCheckableButton
- android:id="@+id/bubble_button_mute"
- android:layout_below="@id/bubble_button_full_screen"
- android:textColor="@color/bubble_button_color_grey"
- android:background="@color/background_dialer_white"
- android:drawableTint="@color/bubble_button_color_grey"
- style="@style/CheckableButtonWithSelectableItemBackground"/>
- <com.android.newbubble.NewCheckableButton
- android:id="@+id/bubble_button_audio_route"
- android:layout_below="@id/bubble_button_mute"
- android:textColor="@color/bubble_button_color_grey"
+ android:visibility="gone"
+ tools:visibility="visible">
+ <RelativeLayout
+ android:id="@+id/bubble_triangle"
+ android:layout_width="12dp"
+ android:layout_height="12dp"
+ android:layout_marginTop="7dp"
+ android:layout_marginBottom="-6dp"
+ android:layout_centerHorizontal="true"
android:background="@color/background_dialer_white"
- android:drawableTint="@color/bubble_button_color_grey"
- style="@style/CheckableButtonWithSelectableItemBackground"/>
- <com.android.newbubble.NewCheckableButton
- android:id="@+id/bubble_button_end_call"
- android:layout_below="@id/bubble_button_audio_route"
- android:layout_marginTop="@dimen/bubble_expanded_separator_height"
- android:textColor="@color/bubble_button_color_white"
- android:background="@drawable/bubble_pill_down"
- android:backgroundTint="@color/dialer_end_call_button_color"
- android:foreground="?attr/selectableItemBackground"
- android:drawableTint="@color/bubble_button_color_white"
- style="@style/CheckableButton"/>
+ android:elevation="@dimen/bubble_expanded_elevation"
+ android:rotation="45">
+ </RelativeLayout>
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/bubble_triangle"
+ android:background="@drawable/bubble_background_with_radius"
+ android:elevation="@dimen/bubble_expanded_elevation"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="inherit">
+ <com.android.newbubble.NewCheckableButton
+ android:id="@+id/bubble_button_full_screen"
+ android:layout_marginTop="8dp"
+ android:textColor="@color/bubble_button_color_grey"
+ android:background="@drawable/bubble_ripple_pill_up"
+ android:drawableTint="@color/bubble_button_color_grey"
+ style="@style/CheckableButton"/>
+ <com.android.newbubble.NewCheckableButton
+ android:id="@+id/bubble_button_mute"
+ android:layout_below="@id/bubble_button_full_screen"
+ android:textColor="@color/bubble_button_color_grey"
+ android:background="@color/background_dialer_white"
+ android:drawableTint="@color/bubble_button_color_grey"
+ style="@style/CheckableButtonWithSelectableItemBackground"/>
+ <com.android.newbubble.NewCheckableButton
+ android:id="@+id/bubble_button_audio_route"
+ android:layout_below="@id/bubble_button_mute"
+ android:textColor="@color/bubble_button_color_grey"
+ android:background="@color/background_dialer_white"
+ android:drawableTint="@color/bubble_button_color_grey"
+ style="@style/CheckableButtonWithSelectableItemBackground"/>
+ <com.android.newbubble.NewCheckableButton
+ android:id="@+id/bubble_button_end_call"
+ android:layout_below="@id/bubble_button_audio_route"
+ android:layout_marginTop="@dimen/bubble_expanded_separator_height"
+ android:textColor="@color/bubble_button_color_white"
+ android:background="@drawable/bubble_pill_down"
+ android:backgroundTint="@color/dialer_end_call_button_color"
+ android:foreground="?attr/selectableItemBackground"
+ android:drawableTint="@color/bubble_button_color_white"
+ style="@style/CheckableButton"/>
+ </RelativeLayout>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
diff --git a/java/com/android/newbubble/res/values/values.xml b/java/com/android/newbubble/res/values/values.xml
index 6dda61d6c..71f813ac6 100644
--- a/java/com/android/newbubble/res/values/values.xml
+++ b/java/com/android/newbubble/res/values/values.xml
@@ -24,8 +24,11 @@
<dimen name="bubble_button_icon_padding">16dp</dimen>
<dimen name="bubble_button_padding_vertical">12dp</dimen>
<dimen name="bubble_button_padding_horizontal">16dp</dimen>
- <dimen name="bubble_safe_margin_horizontal">-16dp</dimen>
- <dimen name="bubble_safe_margin_vertical">64dp</dimen>
+
+ <dimen name="bubble_off_screen_size_horizontal">-4dp</dimen>
+ <!-- 64dp - 16dp(bubble_shadow_padding_size_vertical) -->
+ <dimen name="bubble_safe_margin_vertical">48dp</dimen>
+
<dimen name="bubble_shadow_padding_size_vertical">16dp</dimen>
<dimen name="bubble_shadow_padding_size_vertical_minus">-16dp</dimen>
<dimen name="bubble_shadow_padding_size_horizontal">12dp</dimen>