blob: 7c3ab7f9547ff2e86454fae19a627dea13c259f3 [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 {
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_LAYOUT_TOP = R.id.tag_layout_top;
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") {
@Override
public void setValue(View object, int value) {
setLocalTranslationY(object, value);
}
@Override
public Integer get(View object) {
return getLocalTranslationY(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
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));
}
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);
if (tag == null) {
return 0;
}
return tag;
}
private static void setLocalTranslationY(View v, int value) {
v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
updateTopAndBottom(v);
}
private static void updateTopAndBottom(View v) {
int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
int localTranslation = getLocalTranslationY(v);
int height = v.getHeight();
v.setTop(layoutTop + localTranslation);
v.setBottom(layoutTop + height + localTranslation);
}
private static void startLocalTranslation(final View v, int start, int end,
Interpolator interpolator) {
ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_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);
}
setLocalTranslationY(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_LOCAL_TRANSLATION_ANIMATOR, null);
setClippingDeactivated(v, false);
if (!mCancelled) {
setLocalTranslationY(v, 0);
v.setTagInternal(TAG_LAYOUT_TOP, null);
}
}
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
});
setClippingDeactivated(v, true);
v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
animator.start();
}
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();
}
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_LOCAL_TRANSLATION_ANIMATOR) != null;
}
public static boolean isAnimatingAlpha(View v) {
return v.getTag(TAG_ALPHA_ANIMATOR) != null;
}
}