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
diff --git a/proguard.flags b/proguard.flags
index 79378e6..39784b1 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -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
diff --git a/src/com/android/contacts/quickcontact/FloatingChildLayout.java b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
index dca738c..20a3c1e 100644
--- a/src/com/android/contacts/quickcontact/FloatingChildLayout.java
+++ b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
@@ -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 @@
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 @@
}
// 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 @@
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 @@
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 @@
if (mForegroundPhase == ForegroundPhase.APPEARING ||
mForegroundPhase == ForegroundPhase.IDLE) {
mForegroundPhase = ForegroundPhase.DISAPPEARING;
+
+ createChildLayer();
+
animateScale(true, onAnimationEndRunnable);
return true;
} else {
@@ -219,6 +241,12 @@
}
}
+ 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 @@
: 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 @@
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ mChild.setLayerType(LAYER_TYPE_NONE, null);
if (isExitAnimation) {
if (mForegroundPhase == ForegroundPhase.DISAPPEARING) {
mForegroundPhase = ForegroundPhase.AFTER;
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 5fe34e0..c54619c 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -218,7 +218,12 @@
mContactLoader = (ContactLoader) getLoaderManager().initLoader(
LOADER_ID, null, mLoaderCallbacks);
- mFloatingLayout.fadeInBackground();
+ SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
+ @Override
+ public void run() {
+ mFloatingLayout.fadeInBackground();
+ }
+ });
}
private void handleOutsideTouch() {
diff --git a/src/com/android/contacts/test/NeededForReflection.java b/src/com/android/contacts/test/NeededForReflection.java
new file mode 100644
index 0000000..feacca5
--- /dev/null
+++ b/src/com/android/contacts/test/NeededForReflection.java
@@ -0,0 +1,30 @@
+/*
+ * 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{}