Loading src/com/android/contacts/quickcontact/FloatingChildLayout.java +67 −34 Original line number Diff line number Diff line Loading @@ -23,15 +23,13 @@ import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; 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; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import android.widget.PopupWindow; Loading @@ -53,11 +51,30 @@ public class FloatingChildLayout extends FrameLayout { 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; /** The phase of the background dim. This is one of the values of {@link BackgroundPhase} */ private int mBackgroundPhase = BackgroundPhase.BEFORE; private interface BackgroundPhase { public static final int BEFORE = 0; public static final int APPEARING_OR_VISIBLE = 1; public static final int DISAPPEARING_OR_GONE = 3; } /** The phase of the contents window. This is one of the values of {@link ForegroundPhase} */ private int mForegroundPhase = ForegroundPhase.BEFORE; private interface ForegroundPhase { public static final int BEFORE = 0; public static final int APPEARING = 1; public static final int IDLE = 2; public static final int DISAPPEARING = 3; public static final int AFTER = 4; } // Black, 50% alpha as per the system default. private static final int DIM_BACKGROUND_COLOR = 0x7F000000; Loading @@ -83,8 +100,6 @@ public class FloatingChildLayout extends FrameLayout { mChild.setScaleX(0.5f); mChild.setScaleY(0.5f); mChild.setAlpha(0.0f); mIsShowingChild = false; } public View getChild() { Loading Loading @@ -163,43 +178,51 @@ public class FloatingChildLayout extends FrameLayout { child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); } /** Begin animating {@link #getChild()} visible. */ 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); public void fadeInBackground() { if (mBackgroundPhase == BackgroundPhase.BEFORE) { mBackgroundPhase = BackgroundPhase.APPEARING_OR_VISIBLE; mBackground.startTransition(mAnimationDuration); } } }); animateScale(false, onAnimationEndRunnable); public void fadeOutBackground() { if (mBackgroundPhase == BackgroundPhase.APPEARING_OR_VISIBLE) { mBackgroundPhase = BackgroundPhase.DISAPPEARING_OR_GONE; mBackground.reverseTransition(mAnimationDuration); } } /** Begin animating {@link #getChild()} invisible. */ public void hideChild(final Runnable onAnimationEndRunnable) { if (!mIsShowingChild) return; mIsShowingChild = false; public boolean isContentFullyVisible() { return mForegroundPhase == ForegroundPhase.IDLE; } animateBackground(true); animateScale(true, onAnimationEndRunnable); /** Begin animating {@link #getChild()} visible. */ public void showContent(final Runnable onAnimationEndRunnable) { if (mForegroundPhase == ForegroundPhase.BEFORE) { mForegroundPhase = ForegroundPhase.APPEARING; animateScale(false, onAnimationEndRunnable); } } private void animateBackground(boolean isExitAnimation) { if (isExitAnimation) { mBackground.reverseTransition(mAnimationDuration); /** * Begin animating {@link #getChild()} invisible. Returns false if animation is not valid in * this state */ public boolean hideContent(final Runnable onAnimationEndRunnable) { if (mForegroundPhase == ForegroundPhase.APPEARING || mForegroundPhase == ForegroundPhase.IDLE) { mForegroundPhase = ForegroundPhase.DISAPPEARING; animateScale(true, onAnimationEndRunnable); return true; } else { mBackground.startTransition(mAnimationDuration); return false; } } /** Creates the open/close animation */ private void animateScale(boolean isExitAnimation, final Runnable onAnimationEndRunnable) { private void animateScale( final boolean isExitAnimation, final Runnable onAnimationEndRunnable) { mChild.setPivotX(mTargetScreen.centerX() - mChild.getLeft()); mChild.setPivotY(mTargetScreen.centerY() - mChild.getTop()); Loading @@ -208,7 +231,7 @@ public class FloatingChildLayout extends FrameLayout { : android.R.interpolator.decelerate_quint; final float scaleTarget = isExitAnimation ? 0.5f : 1.0f; ViewPropertyAnimator animator = mChild.animate().withLayer() mChild.animate().withLayer() .setDuration(mAnimationDuration) .setInterpolator(AnimationUtils.loadInterpolator(getContext(), scaleInterpolator)) .scaleX(scaleTarget) Loading @@ -217,8 +240,18 @@ public class FloatingChildLayout extends FrameLayout { .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (isExitAnimation) { if (mForegroundPhase == ForegroundPhase.DISAPPEARING) { mForegroundPhase = ForegroundPhase.AFTER; if (onAnimationEndRunnable != null) onAnimationEndRunnable.run(); } } else { if (mForegroundPhase == ForegroundPhase.APPEARING) { mForegroundPhase = ForegroundPhase.IDLE; if (onAnimationEndRunnable != null) onAnimationEndRunnable.run(); } } } }); } Loading src/com/android/contacts/quickcontact/QuickContactActivity.java +25 −33 Original line number Diff line number Diff line Loading @@ -98,9 +98,6 @@ public class QuickContactActivity extends Activity { private String[] mExcludeMimes; private List<String> mSortedActionMimeTypes = Lists.newArrayList(); private boolean mHasFinishedAnimatingIn = false; private boolean mHasStartedAnimatingOut = false; private FloatingChildLayout mFloatingLayout; private View mPhotoContainer; Loading Loading @@ -154,6 +151,8 @@ public class QuickContactActivity extends Activity { protected void onCreate(Bundle icicle) { super.onCreate(icicle); if (TRACE_LAUNCH) android.os.Debug.startMethodTracing(TRACE_TAG); // Show QuickContact in front of soft input getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); Loading @@ -172,7 +171,8 @@ public class QuickContactActivity extends Activity { mFloatingLayout.setOnOutsideTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return handleOutsideTouch(); handleOutsideTouch(); return true; } }); Loading @@ -183,7 +183,7 @@ public class QuickContactActivity extends Activity { mContactLoader.cacheResult(); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(intent); hide(false); close(false); } }; mOpenDetailsButton.setOnClickListener(openDetailsClickHandler); Loading @@ -191,15 +191,6 @@ public class QuickContactActivity extends Activity { mListPager.setAdapter(new ViewPagerAdapter(getFragmentManager())); mListPager.setOnPageChangeListener(new PageChangeListener()); show(); } private void show() { if (TRACE_LAUNCH) { android.os.Debug.startMethodTracing(TRACE_TAG); } final Intent intent = getIntent(); Uri lookupUri = intent.getData(); Loading @@ -226,23 +217,23 @@ public class QuickContactActivity extends Activity { mContactLoader = (ContactLoader) getLoaderManager().initLoader( LOADER_ID, null, mLoaderCallbacks); } private boolean handleOutsideTouch() { if (!mHasFinishedAnimatingIn) return false; if (mHasStartedAnimatingOut) return false; mFloatingLayout.fadeInBackground(); } mHasStartedAnimatingOut = true; hide(true); return true; private void handleOutsideTouch() { if (mFloatingLayout.isContentFullyVisible()) { close(true); } } private void hide(boolean withAnimation) { private void close(boolean withAnimation) { // cancel any pending queries getLoaderManager().destroyLoader(LOADER_ID); if (withAnimation) { mFloatingLayout.hideChild(new Runnable() { mFloatingLayout.fadeOutBackground(); final boolean animated = mFloatingLayout.hideContent(new Runnable() { @Override public void run() { // Wait until the final animation frame has been drawn, otherwise Loading @@ -266,15 +257,19 @@ public class QuickContactActivity extends Activity { }); } }); if (!animated) { // If we were in the wrong state, simply quit (this can happen for example // if the user pushes BACK before anything has loaded) finish(); } } else { mFloatingLayout.hideChild(null); finish(); } } @Override public void onBackPressed() { hide(true); close(true); } /** Assign this string to the view if it is not empty. */ Loading Loading @@ -480,7 +475,7 @@ public class QuickContactActivity extends Activity { @Override public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) { if (isFinishing()) { hide(false); close(false); return; } if (data.isError()) { Loading @@ -492,25 +487,22 @@ public class QuickContactActivity extends Activity { Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri()); Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage, Toast.LENGTH_LONG).show(); hide(false); close(false); return; } bindData(data); if (TRACE_LAUNCH) { android.os.Debug.stopMethodTracing(); } if (TRACE_LAUNCH) android.os.Debug.stopMethodTracing(); // Data bound and ready, pull curtain to show. Put this on the Handler to ensure // that the layout passes are completed SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() { @Override public void run() { mFloatingLayout.showChild(new Runnable() { mFloatingLayout.showContent(new Runnable() { @Override public void run() { mHasFinishedAnimatingIn = true; mContactLoader.upgradeToFullContact(); } }); Loading Loading @@ -599,7 +591,7 @@ public class QuickContactActivity extends Activity { Toast.LENGTH_SHORT).show(); } hide(false); close(false); } }; // Defer the action to make the window properly repaint Loading Loading
src/com/android/contacts/quickcontact/FloatingChildLayout.java +67 −34 Original line number Diff line number Diff line Loading @@ -23,15 +23,13 @@ import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; 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; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import android.widget.PopupWindow; Loading @@ -53,11 +51,30 @@ public class FloatingChildLayout extends FrameLayout { 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; /** The phase of the background dim. This is one of the values of {@link BackgroundPhase} */ private int mBackgroundPhase = BackgroundPhase.BEFORE; private interface BackgroundPhase { public static final int BEFORE = 0; public static final int APPEARING_OR_VISIBLE = 1; public static final int DISAPPEARING_OR_GONE = 3; } /** The phase of the contents window. This is one of the values of {@link ForegroundPhase} */ private int mForegroundPhase = ForegroundPhase.BEFORE; private interface ForegroundPhase { public static final int BEFORE = 0; public static final int APPEARING = 1; public static final int IDLE = 2; public static final int DISAPPEARING = 3; public static final int AFTER = 4; } // Black, 50% alpha as per the system default. private static final int DIM_BACKGROUND_COLOR = 0x7F000000; Loading @@ -83,8 +100,6 @@ public class FloatingChildLayout extends FrameLayout { mChild.setScaleX(0.5f); mChild.setScaleY(0.5f); mChild.setAlpha(0.0f); mIsShowingChild = false; } public View getChild() { Loading Loading @@ -163,43 +178,51 @@ public class FloatingChildLayout extends FrameLayout { child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); } /** Begin animating {@link #getChild()} visible. */ 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); public void fadeInBackground() { if (mBackgroundPhase == BackgroundPhase.BEFORE) { mBackgroundPhase = BackgroundPhase.APPEARING_OR_VISIBLE; mBackground.startTransition(mAnimationDuration); } } }); animateScale(false, onAnimationEndRunnable); public void fadeOutBackground() { if (mBackgroundPhase == BackgroundPhase.APPEARING_OR_VISIBLE) { mBackgroundPhase = BackgroundPhase.DISAPPEARING_OR_GONE; mBackground.reverseTransition(mAnimationDuration); } } /** Begin animating {@link #getChild()} invisible. */ public void hideChild(final Runnable onAnimationEndRunnable) { if (!mIsShowingChild) return; mIsShowingChild = false; public boolean isContentFullyVisible() { return mForegroundPhase == ForegroundPhase.IDLE; } animateBackground(true); animateScale(true, onAnimationEndRunnable); /** Begin animating {@link #getChild()} visible. */ public void showContent(final Runnable onAnimationEndRunnable) { if (mForegroundPhase == ForegroundPhase.BEFORE) { mForegroundPhase = ForegroundPhase.APPEARING; animateScale(false, onAnimationEndRunnable); } } private void animateBackground(boolean isExitAnimation) { if (isExitAnimation) { mBackground.reverseTransition(mAnimationDuration); /** * Begin animating {@link #getChild()} invisible. Returns false if animation is not valid in * this state */ public boolean hideContent(final Runnable onAnimationEndRunnable) { if (mForegroundPhase == ForegroundPhase.APPEARING || mForegroundPhase == ForegroundPhase.IDLE) { mForegroundPhase = ForegroundPhase.DISAPPEARING; animateScale(true, onAnimationEndRunnable); return true; } else { mBackground.startTransition(mAnimationDuration); return false; } } /** Creates the open/close animation */ private void animateScale(boolean isExitAnimation, final Runnable onAnimationEndRunnable) { private void animateScale( final boolean isExitAnimation, final Runnable onAnimationEndRunnable) { mChild.setPivotX(mTargetScreen.centerX() - mChild.getLeft()); mChild.setPivotY(mTargetScreen.centerY() - mChild.getTop()); Loading @@ -208,7 +231,7 @@ public class FloatingChildLayout extends FrameLayout { : android.R.interpolator.decelerate_quint; final float scaleTarget = isExitAnimation ? 0.5f : 1.0f; ViewPropertyAnimator animator = mChild.animate().withLayer() mChild.animate().withLayer() .setDuration(mAnimationDuration) .setInterpolator(AnimationUtils.loadInterpolator(getContext(), scaleInterpolator)) .scaleX(scaleTarget) Loading @@ -217,8 +240,18 @@ public class FloatingChildLayout extends FrameLayout { .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (isExitAnimation) { if (mForegroundPhase == ForegroundPhase.DISAPPEARING) { mForegroundPhase = ForegroundPhase.AFTER; if (onAnimationEndRunnable != null) onAnimationEndRunnable.run(); } } else { if (mForegroundPhase == ForegroundPhase.APPEARING) { mForegroundPhase = ForegroundPhase.IDLE; if (onAnimationEndRunnable != null) onAnimationEndRunnable.run(); } } } }); } Loading
src/com/android/contacts/quickcontact/QuickContactActivity.java +25 −33 Original line number Diff line number Diff line Loading @@ -98,9 +98,6 @@ public class QuickContactActivity extends Activity { private String[] mExcludeMimes; private List<String> mSortedActionMimeTypes = Lists.newArrayList(); private boolean mHasFinishedAnimatingIn = false; private boolean mHasStartedAnimatingOut = false; private FloatingChildLayout mFloatingLayout; private View mPhotoContainer; Loading Loading @@ -154,6 +151,8 @@ public class QuickContactActivity extends Activity { protected void onCreate(Bundle icicle) { super.onCreate(icicle); if (TRACE_LAUNCH) android.os.Debug.startMethodTracing(TRACE_TAG); // Show QuickContact in front of soft input getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); Loading @@ -172,7 +171,8 @@ public class QuickContactActivity extends Activity { mFloatingLayout.setOnOutsideTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return handleOutsideTouch(); handleOutsideTouch(); return true; } }); Loading @@ -183,7 +183,7 @@ public class QuickContactActivity extends Activity { mContactLoader.cacheResult(); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); startActivity(intent); hide(false); close(false); } }; mOpenDetailsButton.setOnClickListener(openDetailsClickHandler); Loading @@ -191,15 +191,6 @@ public class QuickContactActivity extends Activity { mListPager.setAdapter(new ViewPagerAdapter(getFragmentManager())); mListPager.setOnPageChangeListener(new PageChangeListener()); show(); } private void show() { if (TRACE_LAUNCH) { android.os.Debug.startMethodTracing(TRACE_TAG); } final Intent intent = getIntent(); Uri lookupUri = intent.getData(); Loading @@ -226,23 +217,23 @@ public class QuickContactActivity extends Activity { mContactLoader = (ContactLoader) getLoaderManager().initLoader( LOADER_ID, null, mLoaderCallbacks); } private boolean handleOutsideTouch() { if (!mHasFinishedAnimatingIn) return false; if (mHasStartedAnimatingOut) return false; mFloatingLayout.fadeInBackground(); } mHasStartedAnimatingOut = true; hide(true); return true; private void handleOutsideTouch() { if (mFloatingLayout.isContentFullyVisible()) { close(true); } } private void hide(boolean withAnimation) { private void close(boolean withAnimation) { // cancel any pending queries getLoaderManager().destroyLoader(LOADER_ID); if (withAnimation) { mFloatingLayout.hideChild(new Runnable() { mFloatingLayout.fadeOutBackground(); final boolean animated = mFloatingLayout.hideContent(new Runnable() { @Override public void run() { // Wait until the final animation frame has been drawn, otherwise Loading @@ -266,15 +257,19 @@ public class QuickContactActivity extends Activity { }); } }); if (!animated) { // If we were in the wrong state, simply quit (this can happen for example // if the user pushes BACK before anything has loaded) finish(); } } else { mFloatingLayout.hideChild(null); finish(); } } @Override public void onBackPressed() { hide(true); close(true); } /** Assign this string to the view if it is not empty. */ Loading Loading @@ -480,7 +475,7 @@ public class QuickContactActivity extends Activity { @Override public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) { if (isFinishing()) { hide(false); close(false); return; } if (data.isError()) { Loading @@ -492,25 +487,22 @@ public class QuickContactActivity extends Activity { Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri()); Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage, Toast.LENGTH_LONG).show(); hide(false); close(false); return; } bindData(data); if (TRACE_LAUNCH) { android.os.Debug.stopMethodTracing(); } if (TRACE_LAUNCH) android.os.Debug.stopMethodTracing(); // Data bound and ready, pull curtain to show. Put this on the Handler to ensure // that the layout passes are completed SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() { @Override public void run() { mFloatingLayout.showChild(new Runnable() { mFloatingLayout.showContent(new Runnable() { @Override public void run() { mHasFinishedAnimatingIn = true; mContactLoader.upgradeToFullContact(); } }); Loading Loading @@ -599,7 +591,7 @@ public class QuickContactActivity extends Activity { Toast.LENGTH_SHORT).show(); } hide(false); close(false); } }; // Defer the action to make the window properly repaint Loading