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

Commit 3a53c73f authored by Daniel Lehmann's avatar Daniel Lehmann
Browse files

De-jank quick contact animation

Bug:6508325
Bug:6501917

This change ensures the layer allocation does not happen during the animation.
This change also modifies the way the background fade is implemented to
make it faster (halves the required fillrate, which is necessary given we
are falling back to GPU composition in this particular case.)

Change-Id: I27023ad1a5af06d2d2036baed24c2f47deb85184
parent 23f6049c
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -9,10 +9,12 @@
  public void *(android.view.MenuItem);
}

# Any class or method annotated with NeededForTesting.
# Any class or method annotated with NeededForTesting or NeededForReflection.
-keep @com.android.contacts.test.NeededForTesting class *
-keep @com.android.contacts.test.NeededForReflection class *
-keepclassmembers class * {
@com.android.contacts.test.NeededForTesting *;
@com.android.contacts.test.NeededForReflection *;
}

-verbose
+39 −10
Original line number Diff line number Diff line
@@ -17,15 +17,17 @@
package com.android.contacts.quickcontact;

import com.android.contacts.R;
import com.android.contacts.test.NeededForReflection;
import com.android.contacts.util.SchedulingUtils;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -53,11 +55,13 @@ public class FloatingChildLayout extends FrameLayout {
    private View mChild;
    private Rect mTargetScreen = new Rect();
    private final int mAnimationDuration;
    private final TransitionDrawable mBackground;

    /** The phase of the background dim. This is one of the values of {@link BackgroundPhase}  */
    private int mBackgroundPhase = BackgroundPhase.BEFORE;

    private ObjectAnimator mBackgroundAnimator = ObjectAnimator.ofInt(this,
            "backgroundColorAlpha", 0, DIM_BACKGROUND_ALPHA);

    private interface BackgroundPhase {
        public static final int BEFORE = 0;
        public static final int APPEARING_OR_VISIBLE = 1;
@@ -76,7 +80,7 @@ public class FloatingChildLayout extends FrameLayout {
    }

    // Black, 50% alpha as per the system default.
    private static final int DIM_BACKGROUND_COLOR = 0x7F000000;
    private static final int DIM_BACKGROUND_ALPHA = 0x7F;

    public FloatingChildLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
@@ -85,10 +89,7 @@ public class FloatingChildLayout extends FrameLayout {
                resources.getDimensionPixelOffset(R.dimen.quick_contact_top_position);
        mAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime);

        final ColorDrawable[] drawables =
            { new ColorDrawable(0), new ColorDrawable(DIM_BACKGROUND_COLOR) };
        mBackground = new TransitionDrawable(drawables);
        super.setBackground(mBackground);
        super.setBackground(new ColorDrawable(0));
    }

    @Override
@@ -178,17 +179,35 @@ public class FloatingChildLayout extends FrameLayout {
        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
    }

    @NeededForReflection
    public void setBackgroundColorAlpha(int alpha) {
        setBackgroundColor(alpha << 24);
    }

    public void fadeInBackground() {
        if (mBackgroundPhase == BackgroundPhase.BEFORE) {
            mBackgroundPhase = BackgroundPhase.APPEARING_OR_VISIBLE;
            mBackground.startTransition(mAnimationDuration);

            createChildLayer();

            SchedulingUtils.doAfterDraw(this, new Runnable() {
                @Override
                public void run() {
                    mBackgroundAnimator.setDuration(mAnimationDuration).start();
                }
            });
        }
    }

    public void fadeOutBackground() {
        if (mBackgroundPhase == BackgroundPhase.APPEARING_OR_VISIBLE) {
            mBackgroundPhase = BackgroundPhase.DISAPPEARING_OR_GONE;
            mBackground.reverseTransition(mAnimationDuration);
            if (mBackgroundAnimator.isRunning()) {
                mBackgroundAnimator.reverse();
            } else {
                ObjectAnimator.ofInt(this, "backgroundColorAlpha", DIM_BACKGROUND_ALPHA, 0).
                        setDuration(mAnimationDuration).start();
            }
        }
    }

@@ -212,6 +231,9 @@ public class FloatingChildLayout extends FrameLayout {
        if (mForegroundPhase == ForegroundPhase.APPEARING ||
                mForegroundPhase == ForegroundPhase.IDLE) {
            mForegroundPhase = ForegroundPhase.DISAPPEARING;

            createChildLayer();

            animateScale(true, onAnimationEndRunnable);
            return true;
        } else {
@@ -219,6 +241,12 @@ public class FloatingChildLayout extends FrameLayout {
        }
    }

    private void createChildLayer() {
        mChild.invalidate();
        mChild.setLayerType(LAYER_TYPE_HARDWARE, null);
        mChild.buildLayer();
    }

    /** Creates the open/close animation */
    private void animateScale(
            final boolean isExitAnimation,
@@ -231,7 +259,7 @@ public class FloatingChildLayout extends FrameLayout {
                : android.R.interpolator.decelerate_quint;
        final float scaleTarget = isExitAnimation ? 0.5f : 1.0f;

        mChild.animate().withLayer()
        mChild.animate()
                .setDuration(mAnimationDuration)
                .setInterpolator(AnimationUtils.loadInterpolator(getContext(), scaleInterpolator))
                .scaleX(scaleTarget)
@@ -240,6 +268,7 @@ public class FloatingChildLayout extends FrameLayout {
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mChild.setLayerType(LAYER_TYPE_NONE, null);
                        if (isExitAnimation) {
                            if (mForegroundPhase == ForegroundPhase.DISAPPEARING) {
                                mForegroundPhase = ForegroundPhase.AFTER;
+6 −1
Original line number Diff line number Diff line
@@ -218,8 +218,13 @@ public class QuickContactActivity extends Activity {
        mContactLoader = (ContactLoader) getLoaderManager().initLoader(
                LOADER_ID, null, mLoaderCallbacks);

        SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
            @Override
            public void run() {
                mFloatingLayout.fadeInBackground();
            }
        });
    }

    private void handleOutsideTouch() {
        if (mFloatingLayout.isContentFullyVisible()) {
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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.contacts.test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Denotes that the class, constructor, method or field is used by tests and therefore cannot be
 * removed by tools like ProGuard.
 */
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD})
public @interface NeededForReflection{}