blob: a3a75c098a00b59e6c4eaf7f1901f5ae5e2ec57f [file] [log] [blame]
/*
* Copyright (C) 2017 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.internal.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.util.IntProperty;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.internal.R;
/**
* A listener that automatically starts animations when the layout bounds change.
*/
public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
private static final long APPEAR_ANIMATION_LENGTH = 210;
public 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_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> TOP =
new IntProperty<View>("top") {
@Override
public void setValue(View object, int value) {
setTop(object, value);
}
@Override
public Integer get(View 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) {
setLayoutTop(v, top);
if (isFirstLayout(v)) {
setFirstLayout(v, false /* first */);
setTop(v, top);
return;
}
startTopAnimation(v, getTop(v), top, MessagingLayout.FAST_OUT_SLOW_IN);
}
private static boolean isFirstLayout(View view) {
Boolean tag = (Boolean) view.getTag(TAG_FIRST_LAYOUT);
if (tag == null) {
return true;
}
return tag;
}
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 top = getTop(v);
int height = v.getHeight();
v.setTop(top);
v.setBottom(height + top);
}
private static void startTopAnimation(final View v, int start, int end,
Interpolator interpolator) {
ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_TOP_ANIMATOR);
if (existing != null) {
existing.cancel();
}
if (!v.isShown() || start == end
|| (MessagingLinearLayout.isGone(v) && !isHidingAnimated(v))) {
setTop(v, end);
return;
}
ObjectAnimator animator = ObjectAnimator.ofInt(v, TOP, start, end);
setTop(v, start);
animator.setInterpolator(interpolator);
animator.setDuration(APPEAR_ANIMATION_LENGTH);
animator.addListener(new AnimatorListenerAdapter() {
public boolean mCancelled;
@Override
public void onAnimationEnd(Animator animation) {
v.setTagInternal(TAG_TOP_ANIMATOR, null);
setClippingDeactivated(v, false);
}
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
});
setClippingDeactivated(v, true);
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) {
existing.cancel();
}
if (v.getVisibility() == View.INVISIBLE) {
v.setVisibility(View.VISIBLE);
}
ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
0.0f, 1.0f);
v.setAlpha(0.0f);
animator.setInterpolator(ALPHA_IN);
animator.setDuration(APPEAR_ANIMATION_LENGTH);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
updateLayerType(v, false /* animating */);
}
});
updateLayerType(v, true /* animating */);
v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
animator.start();
}
private static void updateLayerType(View view, boolean animating) {
if (view.hasOverlappingRendering() && animating) {
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
} else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
public static void fadeOut(final View view, Runnable endAction) {
ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
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);
animator.setDuration(APPEAR_ANIMATION_LENGTH);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
updateLayerType(view, false /* animating */);
if (endAction != null) {
endAction.run();
}
}
});
updateLayerType(view, true /* animating */);
view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
animator.start();
}
public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
CLIPPING_PARAMETERS);
}
public static boolean isAnimatingTranslation(View v) {
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));
}
}