Merge "Reduce jank in QuickContactActivity dismissal."
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2132dc3..f11449c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -121,7 +121,6 @@
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowAnimationStyle">@null</item>
<item name="android:windowIsFloating">false</item>
- <item name="android:backgroundDimEnabled">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:listViewStyle">@style/ListViewStyle</item>
diff --git a/src/com/android/contacts/quickcontact/FloatingChildLayout.java b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
index 75141ae..6302c5d 100644
--- a/src/com/android/contacts/quickcontact/FloatingChildLayout.java
+++ b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
@@ -24,7 +24,11 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.os.Handler;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewPropertyAnimator;
@@ -46,11 +50,16 @@
* {@link PopupWindow} might work better.
*/
public class FloatingChildLayout extends FrameLayout {
- private static final String TAG = "FloatingChild";
+ private static final String TAG = "FloatingChildLayout";
private int mFixedTopPosition;
private View mChild;
+ private boolean mIsShowingChild;
private Rect mTargetScreen = new Rect();
private final int mAnimationDuration;
+ private final TransitionDrawable mBackground;
+
+ // Black, 50% alpha as per the system default.
+ private static final int DIM_BACKGROUND_COLOR = 0x7F000000;
public FloatingChildLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -58,6 +67,11 @@
mFixedTopPosition =
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);
}
@Override
@@ -69,6 +83,8 @@
mChild.setScaleX(0.5f);
mChild.setScaleY(0.5f);
mChild.setAlpha(0.0f);
+
+ mIsShowingChild = false;
}
public View getChild() {
@@ -76,6 +92,14 @@
}
/**
+ * FloatingChildLayout manages its own background, don't set it.
+ */
+ @Override
+ public void setBackground(Drawable background) {
+ Log.wtf(TAG, "don't setBackground(), it is managed internally");
+ }
+
+ /**
* Set {@link Rect} in screen coordinates that {@link #getChild()} should be
* centered around.
*/
@@ -140,15 +164,40 @@
}
/** Begin animating {@link #getChild()} visible. */
- public void showChild(Runnable onAnimationEndRunnable) {
+ public void showChild(final Runnable onAnimationEndRunnable) {
+ if (mIsShowingChild) return;
+ mIsShowingChild = true;
+
+ // TODO: understand this.
+ // For some reason this needs wait a tick in order to avoid jank.
+ // Maybe because we set up a hardware layer in animateScale()?
+ // Probably not, since it should also be required in hideChild().
+ new Handler().post(new Runnable() {
+ @Override public void run() {
+ animateBackground(false);
+ }
+ });
+
animateScale(false, onAnimationEndRunnable);
}
/** Begin animating {@link #getChild()} invisible. */
- public void hideChild(Runnable onAnimationEndRunnable) {
+ public void hideChild(final Runnable onAnimationEndRunnable) {
+ if (!mIsShowingChild) return;
+ mIsShowingChild = false;
+
+ animateBackground(true);
animateScale(true, onAnimationEndRunnable);
}
+ private void animateBackground(boolean isExitAnimation) {
+ if (isExitAnimation) {
+ mBackground.reverseTransition(mAnimationDuration);
+ } else {
+ mBackground.startTransition(mAnimationDuration);
+ }
+ }
+
/** Creates the open/close animation */
private void animateScale(boolean isExitAnimation, final Runnable onAnimationEndRunnable) {
mChild.setPivotX(mTargetScreen.centerX() - mChild.getLeft());
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 92e68d4..e00f051 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -18,7 +18,6 @@
import com.android.contacts.Collapser;
import com.android.contacts.ContactLoader;
-import com.android.contacts.ContactPhotoManager;
import com.android.contacts.R;
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.DataKind;
@@ -41,7 +40,6 @@
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
-import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
@@ -91,6 +89,7 @@
private static final boolean TRACE_LAUNCH = false;
private static final String TRACE_TAG = "quickcontact";
+ private static final int POST_DRAW_WAIT_DURATION = 60;
@SuppressWarnings("deprecation")
private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
@@ -242,7 +241,25 @@
mFloatingLayout.hideChild(new Runnable() {
@Override
public void run() {
- finish();
+ // Wait until the final animation frame has been drawn, otherwise
+ // there is jank as the framework transitions to the next Activity.
+ SchedulingUtils.doAfterDraw(mFloatingLayout, new Runnable() {
+ @Override
+ public void run() {
+ // Unfortunately, we need to also use postDelayed() to wait a moment
+ // for the frame to be drawn, else the framework's activity-transition
+ // animation will kick in before the final frame is available to it.
+ // This seems unavoidable. The problem isn't merely that there is no
+ // post-draw listener API; if that were so, it would be sufficient to
+ // call post() instead of postDelayed().
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ }, POST_DRAW_WAIT_DURATION);
+ }
+ });
}
});
} else {
diff --git a/src/com/android/contacts/util/SchedulingUtils.java b/src/com/android/contacts/util/SchedulingUtils.java
index 71fad05..c55bc16 100644
--- a/src/com/android/contacts/util/SchedulingUtils.java
+++ b/src/com/android/contacts/util/SchedulingUtils.java
@@ -18,10 +18,12 @@
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.ViewTreeObserver.OnDrawListener;
/** Static methods that are useful for scheduling actions to occur at a later time. */
public class SchedulingUtils {
+
/** Runs a piece of code after the next layout run */
public static void doAfterLayout(final View view, final Runnable runnable) {
final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
@@ -34,4 +36,16 @@
};
view.getViewTreeObserver().addOnGlobalLayoutListener(listener);
}
+
+ /** Runs a piece of code just before the next draw. */
+ public static void doAfterDraw(final View view, final Runnable runnable) {
+ final OnDrawListener listener = new OnDrawListener() {
+ @Override
+ public void onDraw() {
+ view.getViewTreeObserver().removeOnDrawListener(this);
+ runnable.run();
+ }
+ };
+ view.getViewTreeObserver().addOnDrawListener(listener);
+ }
}