Merge "Fixed the animations of Messaging Layout, leading to overlaps" into pi-dev

This commit is contained in:
TreeHugger Robot
2018-05-30 00:52:32 +00:00
committed by Android (Google) Code Review
11 changed files with 228 additions and 155 deletions

View File

@@ -100,7 +100,6 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
super.onFinishInflate();
mMessageContainer = findViewById(R.id.group_message_container);
mSenderName = findViewById(R.id.message_name);
mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
mAvatarView = findViewById(R.id.message_icon);
mImageContainer = findViewById(R.id.messaging_group_icon_container);
mSendingSpinner = findViewById(R.id.messaging_group_sending_progress);
@@ -190,73 +189,66 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
public void removeMessage(MessagingMessage messagingMessage) {
ViewGroup messageParent = (ViewGroup) messagingMessage.getView().getParent();
messageParent.removeView(messagingMessage.getView());
View view = messagingMessage.getView();
boolean wasShown = view.isShown();
ViewGroup messageParent = (ViewGroup) view.getParent();
if (messageParent == null) {
return;
}
messageParent.removeView(view);
Runnable recycleRunnable = () -> {
messageParent.removeTransientView(messagingMessage.getView());
messageParent.removeTransientView(view);
messagingMessage.recycle();
if (mMessageContainer.getChildCount() == 0
&& mMessageContainer.getTransientViewCount() == 0
&& mImageContainer.getChildCount() == 0) {
ViewParent parent = getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(MessagingGroup.this);
}
setAvatar(null);
mAvatarView.setAlpha(1.0f);
mAvatarView.setTranslationY(0.0f);
mSenderName.setAlpha(1.0f);
mSenderName.setTranslationY(0.0f);
mIsolatedMessage = null;
mMessages = null;
sInstancePool.release(MessagingGroup.this);
}
};
if (isShown()) {
messageParent.addTransientView(messagingMessage.getView(), 0);
performRemoveAnimation(messagingMessage.getView(), recycleRunnable);
if (mMessageContainer.getChildCount() == 0
&& mImageContainer.getChildCount() == 0) {
removeGroupAnimated(null);
}
if (wasShown && !MessagingLinearLayout.isGone(view)) {
messageParent.addTransientView(view, 0);
performRemoveAnimation(view, recycleRunnable);
} else {
recycleRunnable.run();
}
}
private void removeGroupAnimated(Runnable endAction) {
performRemoveAnimation(mAvatarView, null);
performRemoveAnimation(mSenderName, null);
boolean endActionTriggered = false;
for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
View child = mMessageContainer.getChildAt(i);
if (child.getVisibility() == View.GONE) {
continue;
}
final ViewGroup.LayoutParams lp = child.getLayoutParams();
if (lp instanceof MessagingLinearLayout.LayoutParams
&& ((MessagingLinearLayout.LayoutParams) lp).hide
&& !((MessagingLinearLayout.LayoutParams) lp).visibleBefore) {
continue;
}
Runnable childEndAction = endActionTriggered ? null : endAction;
performRemoveAnimation(child, childEndAction);
endActionTriggered = true;
}
public void recycle() {
if (mIsolatedMessage != null) {
performRemoveAnimation(mIsolatedMessage, !endActionTriggered ? endAction : null);
endActionTriggered = true;
mImageContainer.removeView(mIsolatedMessage);
}
if (!endActionTriggered && endAction != null) {
endAction.run();
for (int i = 0; i < mMessages.size(); i++) {
MessagingMessage message = mMessages.get(i);
mMessageContainer.removeView(message.getView());
message.recycle();
}
setAvatar(null);
mAvatarView.setAlpha(1.0f);
mAvatarView.setTranslationY(0.0f);
mSenderName.setAlpha(1.0f);
mSenderName.setTranslationY(0.0f);
setAlpha(1.0f);
mIsolatedMessage = null;
mMessages = null;
mAddedMessages.clear();
mFirstLayout = true;
MessagingPropertyAnimator.recycle(this);
sInstancePool.release(MessagingGroup.this);
}
public void removeGroupAnimated(Runnable endAction) {
performRemoveAnimation(this, () -> {
setAlpha(1.0f);
MessagingPropertyAnimator.setToLaidOutPosition(this);
if (endAction != null) {
endAction.run();
}
});
}
public void performRemoveAnimation(View message, Runnable endAction) {
MessagingPropertyAnimator.fadeOut(message, endAction);
MessagingPropertyAnimator.startLocalTranslationTo(message,
(int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
performRemoveAnimation(message, -message.getHeight(), endAction);
}
private void performRemoveAnimation(View view, int disappearTranslation, Runnable endAction) {
MessagingPropertyAnimator.startLocalTranslationTo(view, disappearTranslation,
MessagingLayout.FAST_OUT_LINEAR_IN);
MessagingPropertyAnimator.fadeOut(view, endAction);
}
public CharSequence getSenderName() {
@@ -341,6 +333,11 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
int layoutColor) {
if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
@@ -458,6 +455,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!mAddedMessages.isEmpty()) {
final boolean firstLayout = mFirstLayout;
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
@@ -466,7 +464,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
continue;
}
MessagingPropertyAnimator.fadeIn(message.getView());
if (!mFirstLayout) {
if (!firstLayout) {
MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(),
message.getView().getHeight(),
MessagingLayout.LINEAR_OUT_SLOW_IN);

View File

@@ -170,8 +170,6 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage
public void recycle() {
MessagingMessage.super.recycle();
setAlpha(1.0f);
setTranslationY(0);
setImageBitmap(null);
mDrawable = null;
sInstancePool.release(this);

View File

@@ -180,8 +180,13 @@ public class MessagingLayout extends FrameLayout {
List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
true /* isHistoric */);
List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
addMessagesToGroups(historicMessages, messages, showSpinner);
// Let's first check which groups were removed altogether and remove them in one animation
removeGroups(oldGroups);
// Let's remove the remaining messages
mMessages.forEach(REMOVE_MESSAGE);
mHistoricMessages.forEach(REMOVE_MESSAGE);
@@ -193,6 +198,31 @@ public class MessagingLayout extends FrameLayout {
updateTitleAndNamesDisplay();
}
private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
int size = oldGroups.size();
for (int i = 0; i < size; i++) {
MessagingGroup group = oldGroups.get(i);
if (!mGroups.contains(group)) {
List<MessagingMessage> messages = group.getMessages();
Runnable endRunnable = () -> {
mMessagingLinearLayout.removeTransientView(group);
group.recycle();
};
boolean wasShown = group.isShown();
mMessagingLinearLayout.removeView(group);
if (wasShown && !MessagingLinearLayout.isGone(group)) {
mMessagingLinearLayout.addTransientView(group, 0);
group.removeGroupAnimated(endRunnable);
} else {
endRunnable.run();
}
mMessages.removeAll(messages);
mHistoricMessages.removeAll(messages);
}
}
}
private void updateTitleAndNamesDisplay() {
ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();

View File

@@ -163,15 +163,6 @@ public class MessagingLinearLayout extends ViewGroup {
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
MessagingChild messagingChild = (MessagingChild) child;
if (lp.hide) {
if (shown && lp.visibleBefore) {
messagingChild.hideAnimated();
}
lp.visibleBefore = false;
continue;
} else {
lp.visibleBefore = true;
}
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
@@ -182,6 +173,19 @@ public class MessagingLinearLayout extends ViewGroup {
} else {
childLeft = paddingLeft + lp.leftMargin;
}
if (lp.hide) {
if (shown && lp.visibleBefore) {
// We still want to lay out the child to have great animations
child.layout(childLeft, childTop, childLeft + childWidth,
childTop + lp.lastVisibleHeight);
messagingChild.hideAnimated();
}
lp.visibleBefore = false;
continue;
} else {
lp.visibleBefore = true;
lp.lastVisibleHeight = childHeight;
}
if (!first) {
childTop += mSpacing;
@@ -228,6 +232,18 @@ public class MessagingLinearLayout extends ViewGroup {
return copy;
}
public static boolean isGone(View view) {
if (view.getVisibility() == View.GONE) {
return true;
}
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof MessagingLinearLayout.LayoutParams
&& ((MessagingLinearLayout.LayoutParams) lp).hide) {
return true;
}
return false;
}
/**
* Sets how many lines should be displayed at most
*/
@@ -263,6 +279,7 @@ public class MessagingLinearLayout extends ViewGroup {
public boolean hide = false;
public boolean visibleBefore = false;
public int lastVisibleHeight;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);

View File

@@ -124,8 +124,7 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
@Override
default void hideAnimated() {
setIsHidingAnimated(true);
getGroup().performRemoveAnimation(getState().getHostView(),
() -> setIsHidingAnimated(false));
getGroup().performRemoveAnimation(getView(), () -> setIsHidingAnimated(false));
}
default boolean hasOverlappingRendering() {
@@ -133,7 +132,7 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
}
default void recycle() {
getState().reset();
getState().recycle();
}
default View getView() {

View File

@@ -72,7 +72,10 @@ public class MessagingMessageState {
return mHostView;
}
public void reset() {
public void recycle() {
mHostView.setAlpha(1.0f);
mHostView.setTranslationY(0);
MessagingPropertyAnimator.recycle(mHostView);
mIsHidingAnimated = false;
mIsHistoric = false;
mGroup = null;

View File

@@ -31,111 +31,125 @@ import com.android.internal.R;
* A listener that automatically starts animations when the layout bounds change.
*/
public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
static final long APPEAR_ANIMATION_LENGTH = 210;
private static final long APPEAR_ANIMATION_LENGTH = 210;
private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
private static final int TAG_LOCAL_TRANSLATION_ANIMATOR = R.id.tag_local_translation_y_animator;
private static final int TAG_LOCAL_TRANSLATION_Y = R.id.tag_local_translation_y;
private static final int TAG_TOP_ANIMATOR = R.id.tag_top_animator;
private static final int TAG_TOP = R.id.tag_top_override;
private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
private static final int TAG_FIRST_LAYOUT = R.id.tag_is_first_layout;
private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
view -> view.getId() == com.android.internal.R.id.notification_messaging;
private static final IntProperty<View> LOCAL_TRANSLATION_Y =
new IntProperty<View>("localTranslationY") {
private static final IntProperty<View> TOP =
new IntProperty<View>("top") {
@Override
public void setValue(View object, int value) {
setLocalTranslationY(object, value);
setTop(object, value);
}
@Override
public Integer get(View object) {
return getLocalTranslationY(object);
return getTop(object);
}
};
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
int oldTop, int oldRight, int oldBottom) {
int oldHeight = oldBottom - oldTop;
Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
if (layoutTop != null) {
oldTop = layoutTop;
}
int topChange = oldTop - top;
if (oldHeight == 0 || topChange == 0 || !v.isShown() || isGone(v)) {
// First layout
setLayoutTop(v, top);
if (isFirstLayout(v)) {
setFirstLayout(v, false /* first */);
setTop(v, top);
return;
}
if (layoutTop != null) {
v.setTagInternal(TAG_LAYOUT_TOP, top);
}
int newHeight = bottom - top;
int heightDifference = oldHeight - newHeight;
// Only add the difference if the height changes and it's getting smaller
heightDifference = Math.max(heightDifference, 0);
startLocalTranslationFrom(v, topChange + heightDifference + getLocalTranslationY(v));
startTopAnimation(v, getTop(v), top, MessagingLayout.FAST_OUT_SLOW_IN);
}
private boolean isGone(View view) {
if (view.getVisibility() == View.GONE) {
return true;
}
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof MessagingLinearLayout.LayoutParams
&& ((MessagingLinearLayout.LayoutParams) lp).hide) {
return true;
}
return false;
}
public static void startLocalTranslationFrom(View v, int startTranslation) {
startLocalTranslationFrom(v, startTranslation, MessagingLayout.FAST_OUT_SLOW_IN);
}
public static void startLocalTranslationFrom(View v, int startTranslation,
Interpolator interpolator) {
startLocalTranslation(v, startTranslation, 0, interpolator);
}
public static void startLocalTranslationTo(View v, int endTranslation,
Interpolator interpolator) {
startLocalTranslation(v, getLocalTranslationY(v), endTranslation, interpolator);
}
public static int getLocalTranslationY(View v) {
Integer tag = (Integer) v.getTag(TAG_LOCAL_TRANSLATION_Y);
private static boolean isFirstLayout(View view) {
Boolean tag = (Boolean) view.getTag(TAG_FIRST_LAYOUT);
if (tag == null) {
return 0;
return true;
}
return tag;
}
private static void setLocalTranslationY(View v, int value) {
v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
public static void recycle(View view) {
setFirstLayout(view, true /* first */);
}
private static void setFirstLayout(View view, boolean first) {
view.setTagInternal(TAG_FIRST_LAYOUT, first);
}
private static void setLayoutTop(View view, int top) {
view.setTagInternal(TAG_LAYOUT_TOP, top);
}
public static int getLayoutTop(View view) {
Integer tag = (Integer) view.getTag(TAG_LAYOUT_TOP);
if (tag == null) {
return getTop(view);
}
return tag;
}
/**
* Start a translation animation from a start offset to the laid out location
* @param view The view to animate
* @param startTranslation The starting translation to start from.
* @param interpolator The interpolator to use.
*/
public static void startLocalTranslationFrom(View view, int startTranslation,
Interpolator interpolator) {
startTopAnimation(view, getTop(view) + startTranslation, getLayoutTop(view), interpolator);
}
/**
* Start a translation animation from a start offset to the laid out location
* @param view The view to animate
* @param endTranslation The end translation to go to.
* @param interpolator The interpolator to use.
*/
public static void startLocalTranslationTo(View view, int endTranslation,
Interpolator interpolator) {
int top = getTop(view);
startTopAnimation(view, top, top + endTranslation, interpolator);
}
public static int getTop(View v) {
Integer tag = (Integer) v.getTag(TAG_TOP);
if (tag == null) {
return v.getTop();
}
return tag;
}
private static void setTop(View v, int value) {
v.setTagInternal(TAG_TOP, value);
updateTopAndBottom(v);
}
private static void updateTopAndBottom(View v) {
int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
int localTranslation = getLocalTranslationY(v);
int top = getTop(v);
int height = v.getHeight();
v.setTop(layoutTop + localTranslation);
v.setBottom(layoutTop + height + localTranslation);
v.setTop(top);
v.setBottom(height + top);
}
private static void startLocalTranslation(final View v, int start, int end,
private static void startTopAnimation(final View v, int start, int end,
Interpolator interpolator) {
ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR);
ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_TOP_ANIMATOR);
if (existing != null) {
existing.cancel();
}
ObjectAnimator animator = ObjectAnimator.ofInt(v, LOCAL_TRANSLATION_Y, start, end);
Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
if (layoutTop == null) {
layoutTop = v.getTop();
v.setTagInternal(TAG_LAYOUT_TOP, layoutTop);
if (!v.isShown() || start == end
|| (MessagingLinearLayout.isGone(v) && !isHidingAnimated(v))) {
setTop(v, end);
return;
}
setLocalTranslationY(v, start);
ObjectAnimator animator = ObjectAnimator.ofInt(v, TOP, start, end);
setTop(v, start);
animator.setInterpolator(interpolator);
animator.setDuration(APPEAR_ANIMATION_LENGTH);
animator.addListener(new AnimatorListenerAdapter() {
@@ -143,12 +157,8 @@ public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
@Override
public void onAnimationEnd(Animator animation) {
v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, null);
v.setTagInternal(TAG_TOP_ANIMATOR, null);
setClippingDeactivated(v, false);
if (!mCancelled) {
setLocalTranslationY(v, 0);
v.setTagInternal(TAG_LAYOUT_TOP, null);
}
}
@Override
@@ -157,10 +167,17 @@ public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
}
});
setClippingDeactivated(v, true);
v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
v.setTagInternal(TAG_TOP_ANIMATOR, animator);
animator.start();
}
private static boolean isHidingAnimated(View v) {
if (v instanceof MessagingLinearLayout.MessagingChild) {
return ((MessagingLinearLayout.MessagingChild) v).isHidingAnimated();
}
return false;
}
public static void fadeIn(final View v) {
ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
if (existing != null) {
@@ -199,6 +216,13 @@ public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
if (existing != null) {
existing.cancel();
}
if (!view.isShown() || (MessagingLinearLayout.isGone(view) && !isHidingAnimated(view))) {
view.setAlpha(0.0f);
if (endAction != null) {
endAction.run();
}
return;
}
ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
view.getAlpha(), 0.0f);
animator.setInterpolator(ALPHA_OUT);
@@ -224,10 +248,14 @@ public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
}
public static boolean isAnimatingTranslation(View v) {
return v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR) != null;
return v.getTag(TAG_TOP_ANIMATOR) != null;
}
public static boolean isAnimatingAlpha(View v) {
return v.getTag(TAG_ALPHA_ANIMATOR) != null;
}
public static void setToLaidOutPosition(View view) {
setTop(view, getLayoutTop(view));
}
}

View File

@@ -92,8 +92,6 @@ public class MessagingTextMessage extends ImageFloatingTextView implements Messa
public void recycle() {
MessagingMessage.super.recycle();
setAlpha(1.0f);
setTranslationY(0);
sInstancePool.release(this);
}

View File

@@ -140,15 +140,18 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. -->
<item type="id" name="accessibilityActionMoveWindow" />
<!-- A tag used to save an animator in local y translation -->
<item type="id" name="tag_local_translation_y_animator" />
<!-- A tag used to save an animator in top -->
<item type="id" name="tag_top_animator" />
<!-- A tag used to save the local translation y -->
<item type="id" name="tag_local_translation_y" />
<!-- A tag used to save the current top override -->
<item type="id" name="tag_top_override" />
<!-- A tag used to save the original top of a view -->
<item type="id" name="tag_layout_top" />
<!-- A tag used to save whether a view was laid out before -->
<item type="id" name="tag_is_first_layout" />
<!-- A tag used to save an animator in alpha -->
<item type="id" name="tag_alpha_animator" />

View File

@@ -3260,9 +3260,10 @@
<java-symbol type="id" name="message_name" />
<java-symbol type="id" name="message_icon" />
<java-symbol type="id" name="group_message_container" />
<java-symbol type="id" name="tag_local_translation_y_animator" />
<java-symbol type="id" name="tag_local_translation_y" />
<java-symbol type="id" name="tag_top_animator" />
<java-symbol type="id" name="tag_top_override" />
<java-symbol type="id" name="tag_layout_top" />
<java-symbol type="id" name="tag_is_first_layout" />
<java-symbol type="id" name="tag_alpha_animator" />
<java-symbol type="id" name="clip_children_set_tag" />
<java-symbol type="id" name="clip_to_padding_tag" />

View File

@@ -16,11 +16,8 @@
package com.android.systemui.statusbar.notification;
import android.util.ArraySet;
import android.util.Pools;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.ProgressBar;
@@ -411,7 +408,8 @@ public class TransformState {
mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
// Remove local translations
mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
mOwnPosition[1] -= MessagingPropertyAnimator.getTop(mTransformedView)
- MessagingPropertyAnimator.getLayoutTop(mTransformedView);
return mOwnPosition;
}