Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d3809996 authored by Selim Cinek's avatar Selim Cinek
Browse files

Implemented animations for messaging changes

The animations for new messages coming in are now happening
more smoothly. They have proper appear and disappear
animations.

Test: manual, add new messages, observe animations
Bug: 63708826
Change-Id: I714e976cfee5c0a2c5187a5c3146a7530739f312
parent 5ec560a1
Loading
Loading
Loading
Loading
+122 −8
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
@@ -41,6 +42,7 @@ import android.widget.RemoteViews;
import com.android.internal.R;
import com.android.internal.util.NotificationColorUtil;

import java.util.ArrayList;
import java.util.List;

/**
@@ -48,7 +50,6 @@ import java.util.List;
 */
@RemoteViews.RemoteView
public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {

    private static Pools.SimplePool<MessagingGroup> sInstancePool
            = new Pools.SynchronizedPool<>(10);
    private MessagingLinearLayout mMessageContainer;
@@ -60,6 +61,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
    private Icon mAvatarIcon;
    private ColorFilter mMessageBackgroundFilter;
    private int mTextColor;
    private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
    private boolean mFirstLayout;
    private boolean mIsHidingAnimated;

    public MessagingGroup(@NonNull Context context) {
        super(context);
@@ -84,6 +88,7 @@ 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);
    }

@@ -124,22 +129,76 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
            createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
                    R.layout.notification_template_messaging_group, layout,
                    false);
            createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
        }
        layout.addView(createdGroup);
        return createdGroup;
    }

    public void removeMessage(MessagingMessage messagingMessage) {
        // TODO: add removal animation
        mMessageContainer.removeView(messagingMessage);
        if (mMessageContainer.getChildCount() == 0) {
        Runnable recycleRunnable = () -> {
            mMessageContainer.removeTransientView(messagingMessage);
            messagingMessage.recycle();
            if (mMessageContainer.getChildCount() == 0
                    && mMessageContainer.getTransientViewCount() == 0) {
                ViewParent parent = getParent();
                if (parent instanceof ViewGroup) {
                ((ViewGroup) parent).removeView(this);
                    ((ViewGroup) parent).removeView(MessagingGroup.this);
                }
                setAvatar(null);
            sInstancePool.release(this);
                mAvatarView.setAlpha(1.0f);
                mAvatarView.setTranslationY(0.0f);
                mSenderName.setAlpha(1.0f);
                mSenderName.setTranslationY(0.0f);
                sInstancePool.release(MessagingGroup.this);
            }
        };
        if (isShown()) {
            mMessageContainer.addTransientView(messagingMessage, 0);
            performRemoveAnimation(messagingMessage, recycleRunnable);
            if (mMessageContainer.getChildCount() == 0) {
                removeGroupAnimated(null);
            }
        } else {
            recycleRunnable.run();
        }

    }

    private void removeGroupAnimated(Runnable endAction) {
        MessagingPropertyAnimator.fadeOut(mAvatarView, null);
        MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
        MessagingPropertyAnimator.fadeOut(mSenderName, null);
        MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
        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;
        }
        if (!endActionTriggered && endAction != null) {
            endAction.run();
        }
    }

    public void performRemoveAnimation(View message,
            Runnable recycleRunnable) {
        MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
        MessagingPropertyAnimator.startLocalTranslationTo(message,
                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
    }

    public CharSequence getSenderName() {
@@ -195,6 +254,26 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
        mMessageContainer.setMaxDisplayedLines(lines);
    }

    @Override
    public void hideAnimated() {
        setIsHidingAnimated(true);
        removeGroupAnimated(() -> setIsHidingAnimated(false));
    }

    @Override
    public boolean isHidingAnimated() {
        return mIsHidingAnimated;
    }

    private void setIsHidingAnimated(boolean isHiding) {
        ViewParent parent = getParent();
        mIsHidingAnimated = isHiding;
        invalidate();
        if (parent instanceof ViewGroup) {
            ((ViewGroup) parent).invalidate();
        }
    }

    public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
            int layoutColor) {
        if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
@@ -230,6 +309,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
                    ((ViewGroup) parent).removeView(message);
                }
                mMessageContainer.addView(message, messageIndex);
                mAddedMessages.add(message);
            }
            if (messageIndex != mMessageContainer.indexOfChild(message)) {
                mMessageContainer.removeView(message);
@@ -244,4 +324,38 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
            message.setTextColor(mTextColor);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (!mAddedMessages.isEmpty()) {
            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    for (MessagingMessage message : mAddedMessages) {
                        if (!message.isShown()) {
                            continue;
                        }
                        MessagingPropertyAnimator.fadeIn(message);
                        if (!mFirstLayout) {
                            MessagingPropertyAnimator.startLocalTranslationFrom(message,
                                    message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
                        }
                    }
                    mAddedMessages.clear();
                    getViewTreeObserver().removeOnPreDrawListener(this);
                    return true;
                }
            });
        }
        mFirstLayout = false;
    }

    public View getSender() {
        return mSenderName;
    }

    public View getAvatar() {
        return mAvatarView;
    }
}
+42 −0
Original line number Diff line number Diff line
@@ -26,14 +26,19 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -56,6 +61,11 @@ public class MessagingLayout extends FrameLayout {
    private static final float COLOR_SHIFT_AMOUNT = 60;
    private static final Consumer<MessagingMessage> REMOVE_MESSAGE
            = MessagingMessage::removeMessage;
    public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
    public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
    public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
    public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
            = new MessagingPropertyAnimator();
    private List<MessagingMessage> mMessages = new ArrayList<>();
    private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
    private MessagingLinearLayout mMessagingLinearLayout;
@@ -70,6 +80,7 @@ public class MessagingLayout extends FrameLayout {
    private CharSequence mConversationTitle;
    private Icon mLargeIcon;
    private boolean mIsOneToOne;
    private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();

    public MessagingLayout(@NonNull Context context) {
        super(context);
@@ -93,6 +104,11 @@ public class MessagingLayout extends FrameLayout {
    protected void onFinishInflate() {
        super.onFinishInflate();
        mMessagingLinearLayout = findViewById(R.id.notification_messaging);
        // We still want to clip, but only on the top, since views can temporarily out of bounds
        // during transitions.
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        Rect rect = new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
        mMessagingLinearLayout.setClipBounds(rect);
        mTitleView = findViewById(R.id.title);
        mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
@@ -281,6 +297,7 @@ public class MessagingLayout extends FrameLayout {
            }
            if (newGroup == null) {
                newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
                mAddedGroups.add(newGroup);
            }
            newGroup.setLayoutColor(mLayoutColor);
            newGroup.setSender(senders.get(groupIndex));
@@ -350,6 +367,7 @@ public class MessagingLayout extends FrameLayout {
            MessagingMessage message = findAndRemoveMatchingMessage(m);
            if (message == null) {
                message = MessagingMessage.createMessage(this, m);
                message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
            }
            message.setIsHistoric(historic);
            result.add(message);
@@ -387,6 +405,30 @@ public class MessagingLayout extends FrameLayout {
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (!mAddedGroups.isEmpty()) {
            getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    for (MessagingGroup group : mAddedGroups) {
                        if (!group.isShown()) {
                            continue;
                        }
                        MessagingPropertyAnimator.fadeIn(group.getAvatar());
                        MessagingPropertyAnimator.fadeIn(group.getSender());
                        MessagingPropertyAnimator.startLocalTranslationFrom(group,
                                group.getHeight(), LINEAR_OUT_SLOW_IN);
                    }
                    mAddedGroups.clear();
                    getViewTreeObserver().removeOnPreDrawListener(this);
                    return true;
                }
            });
        }
    }

    public View getContractedMessage() {
        return mContractedMessage;
    }
+20 −5
Original line number Diff line number Diff line
@@ -176,13 +176,22 @@ public class MessagingLinearLayout extends ViewGroup {
        childTop = mPaddingTop;

        boolean first = true;

        final boolean shown = isShown();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (child.getVisibility() == GONE || lp.hide) {
            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();
@@ -213,8 +222,11 @@ public class MessagingLinearLayout extends ViewGroup {
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp.hide) {
            MessagingChild messagingChild = (MessagingChild) child;
            if (!messagingChild.isHidingAnimated()) {
                return true;
            }
        }
        return super.drawChild(canvas, child, drawingTime);
    }

@@ -254,11 +266,14 @@ public class MessagingLinearLayout extends ViewGroup {
        int getMeasuredType();
        int getConsumedLines();
        void setMaxDisplayedLines(int lines);
        void hideAnimated();
        boolean isHidingAnimated();
    }

    public static class LayoutParams extends MarginLayoutParams {

        boolean hide = false;
        public boolean hide = false;
        public boolean visibleBefore = false;

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
+27 −3
Original line number Diff line number Diff line
@@ -23,13 +23,11 @@ import android.annotation.StyleRes;
import android.app.Notification;
import android.content.Context;
import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Pools;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.view.ViewParent;
import android.widget.RemoteViews;

import com.android.internal.R;
@@ -48,6 +46,7 @@ public class MessagingMessage extends ImageFloatingTextView implements
    private Notification.MessagingStyle.Message mMessage;
    private MessagingGroup mGroup;
    private boolean mIsHistoric;
    private boolean mIsHidingAnimated;

    public MessagingMessage(@NonNull Context context) {
        super(context);
@@ -104,8 +103,13 @@ public class MessagingMessage extends ImageFloatingTextView implements

    public void removeMessage() {
        mGroup.removeMessage(this);
    }

    public void recycle() {
        mGroup = null;
        mMessage = null;
        setAlpha(1.0f);
        setTranslationY(0);
        sInstancePool.release(this);
    }

@@ -144,6 +148,26 @@ public class MessagingMessage extends ImageFloatingTextView implements
        }
    }

    @Override
    public void hideAnimated() {
        setIsHidingAnimated(true);
        mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
    }

    private void setIsHidingAnimated(boolean isHiding) {
        ViewParent parent = getParent();
        mIsHidingAnimated = isHiding;
        invalidate();
        if (parent instanceof ViewGroup) {
            ((ViewGroup) parent).invalidate();
        }
    }

    @Override
    public boolean isHidingAnimated() {
        return mIsHidingAnimated;
    }

    @Override
    public void setMaxDisplayedLines(int lines) {
        setMaxLines(lines);
+233 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading