diff options
author | Paul Soulos <psoulos@google.com> | 2014-07-23 11:27:28 -0700 |
---|---|---|
committer | Paul Soulos <psoulos@google.com> | 2014-07-23 11:28:16 -0700 |
commit | 0cda9aeb01f1922fce2a9e87ae4c0146c177b4f0 (patch) | |
tree | aaece4a5b0175b44b286e6cb9bd13fc39d8a41a3 /src | |
parent | 81cc3b3d09d9296e521ac3454ad01c6b6c2ba71b (diff) | |
download | packages_apps_Contacts-0cda9aeb01f1922fce2a9e87ae4c0146c177b4f0.tar.gz packages_apps_Contacts-0cda9aeb01f1922fce2a9e87ae4c0146c177b4f0.tar.bz2 packages_apps_Contacts-0cda9aeb01f1922fce2a9e87ae4c0146c177b4f0.zip |
Adds fancier animation to ExpandingEntryCardView
Bug: 16218702
Change-Id: I2b3d440b3cedf48becb9f82c8fe67f903f8611c8
Diffstat (limited to 'src')
3 files changed, 161 insertions, 98 deletions
diff --git a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java index 2063c51f9..0ec1bacb6 100644 --- a/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java +++ b/src/com/android/contacts/quickcontact/ExpandingEntryCardView.java @@ -15,11 +15,6 @@ */ package com.android.contacts.quickcontact; -import com.android.contacts.R; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -27,18 +22,25 @@ import android.graphics.ColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.TextUtils; +import android.transition.ChangeBounds; +import android.transition.ChangeScroll; +import android.transition.Fade; +import android.transition.Transition; +import android.transition.Transition.TransitionListener; +import android.transition.TransitionManager; +import android.transition.TransitionSet; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.TouchDelegate; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.ViewTreeObserver.OnPreDrawListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.contacts.R; + import java.util.ArrayList; import java.util.List; @@ -48,6 +50,11 @@ import java.util.List; public class ExpandingEntryCardView extends LinearLayout { private static final String TAG = "ExpandingEntryCardView"; + private static final int DURATION_EXPAND_ANIMATION_FADE_IN = 200; + private static final int DELAY_EXPAND_ANIMATION_FADE_IN = 100; + + public static final int DURATION_EXPAND_ANIMATION_CHANGE_BOUNDS = 300; + public static final int DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS = 300; /** * Entry data. @@ -150,6 +157,7 @@ public class ExpandingEntryCardView extends LinearLayout { public interface ExpandingEntryCardViewListener { void onCollapse(int heightDelta); + void onExpand(int heightDelta); } private View mExpandCollapseButton; @@ -171,6 +179,8 @@ public class ExpandingEntryCardView extends LinearLayout { private int mThemeColor; private ColorFilter mThemeColorFilter; private boolean mIsAlwaysExpanded; + /** The ViewGroup to run the expand/collapse animation on */ + private ViewGroup mAnimationViewGroup; private final OnClickListener mExpandCollapseButtonListener = new OnClickListener() { @Override @@ -214,7 +224,7 @@ public class ExpandingEntryCardView extends LinearLayout { */ public void initialize(List<List<Entry>> entries, int numInitialVisibleEntries, boolean isExpanded, boolean isAlwaysExpanded, - ExpandingEntryCardViewListener listener) { + ExpandingEntryCardViewListener listener, ViewGroup animationViewGroup) { LayoutInflater layoutInflater = LayoutInflater.from(getContext()); mIsExpanded = isExpanded; mIsAlwaysExpanded = isAlwaysExpanded; @@ -235,6 +245,7 @@ public class ExpandingEntryCardView extends LinearLayout { mCollapsedEntriesCount = mEntries.size(); } mListener = listener; + mAnimationViewGroup = animationViewGroup; if (mIsExpanded) { updateExpandCollapseButton(getCollapseButtonText()); @@ -300,37 +311,41 @@ public class ExpandingEntryCardView extends LinearLayout { private void addEntry(View entry) { if (mEntriesViewGroup.getChildCount() > 0) { - View separator = new View(getContext()); - separator.setBackgroundColor(getResources().getColor( - R.color.expanding_entry_card_item_separator_color)); - LayoutParams layoutParams = generateDefaultLayoutParams(); - Resources resources = getResources(); - layoutParams.height = resources.getDimensionPixelSize( - R.dimen.expanding_entry_card_item_separator_height); - // The separator is aligned with the text in the entry. This is offset by a default - // margin. If there is an icon present, the icon's width and margin are added - int marginStart = resources.getDimensionPixelSize( - R.dimen.expanding_entry_card_item_padding_start); - ImageView entryIcon = (ImageView) entry.findViewById(R.id.icon); - if (entryIcon.getDrawable() != null) { - int imageWidthAndMargin = - resources.getDimensionPixelSize( - R.dimen.expanding_entry_card_item_icon_width) + - resources.getDimensionPixelSize( - R.dimen.expanding_entry_card_item_image_spacing); - marginStart += imageWidthAndMargin; - } - if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - layoutParams.rightMargin = marginStart; - } else { - layoutParams.leftMargin = marginStart; - } - separator.setLayoutParams(layoutParams); - mEntriesViewGroup.addView(separator); + mEntriesViewGroup.addView(createSeparator(entry)); } mEntriesViewGroup.addView(entry); } + private View createSeparator(View entry) { + View separator = new View(getContext()); + separator.setBackgroundColor(getResources().getColor( + R.color.expanding_entry_card_item_separator_color)); + LayoutParams layoutParams = generateDefaultLayoutParams(); + Resources resources = getResources(); + layoutParams.height = resources.getDimensionPixelSize( + R.dimen.expanding_entry_card_item_separator_height); + // The separator is aligned with the text in the entry. This is offset by a default + // margin. If there is an icon present, the icon's width and margin are added + int marginStart = resources.getDimensionPixelSize( + R.dimen.expanding_entry_card_item_padding_start); + ImageView entryIcon = (ImageView) entry.findViewById(R.id.icon); + if (entryIcon.getDrawable() != null) { + int imageWidthAndMargin = + resources.getDimensionPixelSize( + R.dimen.expanding_entry_card_item_icon_width) + + resources.getDimensionPixelSize( + R.dimen.expanding_entry_card_item_image_spacing); + marginStart += imageWidthAndMargin; + } + if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { + layoutParams.rightMargin = marginStart; + } else { + layoutParams.leftMargin = marginStart; + } + separator.setLayoutParams(layoutParams); + return separator; + } + private CharSequence getExpandButtonText() { if (!TextUtils.isEmpty(mExpandButtonText)) { return mExpandButtonText; @@ -543,77 +558,102 @@ public class ExpandingEntryCardView extends LinearLayout { } private void expand() { - final int startingHeight = mEntriesViewGroup.getHeight(); + ChangeBounds boundsTransition = new ChangeBounds(); + boundsTransition.setDuration(DURATION_EXPAND_ANIMATION_CHANGE_BOUNDS); + + Fade fadeIn = new Fade(Fade.IN); + fadeIn.setDuration(DURATION_EXPAND_ANIMATION_FADE_IN); + fadeIn.setStartDelay(DELAY_EXPAND_ANIMATION_FADE_IN); + + TransitionSet transitionSet = new TransitionSet(); + transitionSet.addTransition(boundsTransition); + transitionSet.addTransition(fadeIn); + + final ViewGroup transitionViewContainer = mAnimationViewGroup == null ? + this : mAnimationViewGroup; + + transitionSet.addListener(new TransitionListener() { + @Override + public void onTransitionStart(Transition transition) { + // The listener is used to turn off suppressing, the proper delta is not necessary + mListener.onExpand(0); + } + + @Override + public void onTransitionEnd(Transition transition) { + } + + @Override + public void onTransitionCancel(Transition transition) { + } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { + } + }); + + TransitionManager.beginDelayedTransition(transitionViewContainer, transitionSet); mIsExpanded = true; // In order to insert new entries, we may need to inflate them for the first time inflateAllEntries(LayoutInflater.from(getContext())); insertEntriesIntoViewGroup(); updateExpandCollapseButton(getCollapseButtonText()); - - // When expanding, all the TextViews haven't been laid out yet. Therefore, - // calling measure() would return an incorrect result. Therefore, we need a pre draw - // listener. - final ViewTreeObserver observer = mEntriesViewGroup.getViewTreeObserver(); - observer.addOnPreDrawListener(new OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (observer.isAlive()) { - mEntriesViewGroup.getViewTreeObserver().removeOnPreDrawListener(this); - } - createExpandAnimator(startingHeight, mEntriesViewGroup.getHeight()).start(); - // Do not draw the final frame of the animation immediately. - return false; - } - }); } private void collapse() { - int startingHeight = mEntriesViewGroup.getHeight(); - int finishHeight = measureCollapsedViewGroupHeight(); - mListener.onCollapse(startingHeight - finishHeight); - + final int startingHeight = mEntriesViewGroup.getMeasuredHeight(); mIsExpanded = false; updateExpandCollapseButton(getExpandButtonText()); - createExpandAnimator(startingHeight, finishHeight).start(); - } - private int measureCollapsedViewGroupHeight() { - if (mCollapsedEntriesCount == 0) { - return 0; - } - final View bottomCollapsedView = mEntryViews.get(mCollapsedEntriesCount - 1).get(0); - return bottomCollapsedView.getTop() + bottomCollapsedView.getHeight(); - } + final ChangeBounds boundsTransition = new ChangeBounds(); + boundsTransition.setDuration(DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS); - /** - * Create ValueAnimator that performs an expand animation on the content LinearLayout. - * - * The animation needs to be performed manually using a ValueAnimator, since LinearLayout - * doesn't have a single set-able height property (ie, no setHeight()). - */ - private ValueAnimator createExpandAnimator(int start, int end) { - ValueAnimator animator = ValueAnimator.ofInt(start, end); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + final ChangeScroll scrollTransition = new ChangeScroll(); + scrollTransition.setDuration(DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS); + + TransitionSet transitionSet = new TransitionSet(); + transitionSet.addTransition(boundsTransition); + transitionSet.addTransition(scrollTransition); + + final ViewGroup transitionViewContainer = mAnimationViewGroup == null ? + this : mAnimationViewGroup; + + boundsTransition.addListener(new TransitionListener() { @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - int value = (Integer) valueAnimator.getAnimatedValue(); - ViewGroup.LayoutParams layoutParams = mEntriesViewGroup.getLayoutParams(); - layoutParams.height = value; - mEntriesViewGroup.setLayoutParams(layoutParams); + public void onTransitionStart(Transition transition) { + /* + * onTransitionStart is called after the view hierarchy has been changed but before + * the animation begins. + */ + int finishingHeight = mEntriesViewGroup.getMeasuredHeight(); + mListener.onCollapse(startingHeight - finishingHeight); } - }); - animator.addListener(new AnimatorListenerAdapter() { + + @Override + public void onTransitionEnd(Transition transition) { + } + @Override - public void onAnimationEnd(Animator animation) { - insertEntriesIntoViewGroup(); - // Now that the animation is done, stop using a fixed height. - ViewGroup.LayoutParams layoutParams = mEntriesViewGroup.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; - mEntriesViewGroup.setLayoutParams(layoutParams); + public void onTransitionCancel(Transition transition) { + } + + @Override + public void onTransitionPause(Transition transition) { + } + + @Override + public void onTransitionResume(Transition transition) { } }); - return animator; + + TransitionManager.beginDelayedTransition(transitionViewContainer, transitionSet); + + insertEntriesIntoViewGroup(); } /** diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java index 06622b6b1..669b46045 100644 --- a/src/com/android/contacts/quickcontact/QuickContactActivity.java +++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java @@ -77,6 +77,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Toast; import android.widget.Toolbar; @@ -320,6 +321,11 @@ public class QuickContactActivity extends ContactsActivity { public void onCollapse(int heightDelta) { mScroller.prepareForShrinkingScrollChild(heightDelta); } + + @Override + public void onExpand(int heightDelta) { + mScroller.prepareForExpandingScrollChild(); + } }; /** @@ -493,11 +499,12 @@ public class QuickContactActivity extends ContactsActivity { mMaterialColorMapUtils = new MaterialColorMapUtils(getResources()); + mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller); + mContactCard = (ExpandingEntryCardView) findViewById(R.id.communication_card); mNoContactDetailsCard = (ExpandingEntryCardView) findViewById(R.id.no_contact_data_card); mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card); mAboutCard = (ExpandingEntryCardView) findViewById(R.id.about_card); - mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller); mNoContactDetailsCard.setOnClickListener(mEntryClickHandler); mContactCard.setOnClickListener(mEntryClickHandler); @@ -808,7 +815,8 @@ public class QuickContactActivity extends ContactsActivity { /* numInitialVisibleEntries = */ MIN_NUM_CONTACT_ENTRIES_SHOWN, /* isExpanded = */ mContactCard.isExpanded(), /* isAlwaysExpanded = */ false, - mExpandingEntryCardViewListener); + mExpandingEntryCardViewListener, + mScroller); mContactCard.setVisibility(View.VISIBLE); } else { mContactCard.setVisibility(View.GONE); @@ -840,7 +848,8 @@ public class QuickContactActivity extends ContactsActivity { /* numInitialVisibleEntries = */ 1, /* isExpanded = */ true, /* isAlwaysExpanded = */ true, - mExpandingEntryCardViewListener); + mExpandingEntryCardViewListener, + mScroller); if (contactCardEntries.size() == 0 && aboutCardEntries.size() == 0) { initializeNoContactDetailCard(); @@ -888,7 +897,7 @@ public class QuickContactActivity extends ContactsActivity { final PorterDuffColorFilter greyColorFilter = new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP); mNoContactDetailsCard.initialize(promptEntries, 2, /* isExpanded = */ true, - /* isAlwaysExpanded = */ true, mExpandingEntryCardViewListener); + /* isAlwaysExpanded = */ true, mExpandingEntryCardViewListener, mScroller); mNoContactDetailsCard.setVisibility(View.VISIBLE); mNoContactDetailsCard.setEntryHeaderColor(subHeaderTextColor); mNoContactDetailsCard.setColorAndFilter(subHeaderTextColor, greyColorFilter); @@ -1525,7 +1534,7 @@ public class QuickContactActivity extends ContactsActivity { mRecentCard.initialize(interactionsWrapper, /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN, /* isExpanded = */ mRecentCard.isExpanded(), /* isAlwaysExpanded = */ false, - mExpandingEntryCardViewListener); + mExpandingEntryCardViewListener, mScroller); mRecentCard.setVisibility(View.VISIBLE); } diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java index 23481a7f7..22f1a9d62 100644 --- a/src/com/android/contacts/widget/MultiShrinkScroller.java +++ b/src/com/android/contacts/widget/MultiShrinkScroller.java @@ -1,6 +1,7 @@ package com.android.contacts.widget; import com.android.contacts.R; +import com.android.contacts.quickcontact.ExpandingEntryCardView; import com.android.contacts.test.NeededForReflection; import com.android.contacts.util.SchedulingUtils; @@ -86,7 +87,6 @@ public class MultiShrinkScroller extends LinearLayout { private MultiShrinkScrollerListener mListener; private TextView mLargeTextView; private View mPhotoTouchInterceptOverlay; - private View mLeftOverSpaceView; /** Contains desired location/size of the title, once the header is fully compressed */ private TextView mInvisiblePlaceholderTextView; private View mTitleGradientView; @@ -243,7 +243,6 @@ public class MultiShrinkScroller extends LinearLayout { mTransparentView = findViewById(R.id.transparent_view); mLargeTextView = (TextView) findViewById(R.id.large_title); mInvisiblePlaceholderTextView = (TextView) findViewById(R.id.placeholder_textview); - mLeftOverSpaceView = findViewById(R.id.card_empty_space); mListener = listener; mIsOpenContactSquare = isOpenContactSquare; @@ -436,6 +435,7 @@ public class MultiShrinkScroller extends LinearLayout { final ObjectAnimator animator = ObjectAnimator.ofInt(this, "headerHeight", mMaximumHeaderHeight); animator.addListener(mHeaderExpandAnimationListener); + animator.setDuration(ExpandingEntryCardView.DURATION_EXPAND_ANIMATION_CHANGE_BOUNDS); animator.start(); // Scroll nested scroll view to its top if (mScrollView.getScrollY() != 0) { @@ -787,8 +787,7 @@ public class MultiShrinkScroller extends LinearLayout { * Returns the amount of mScrollViewChild that doesn't fit inside its parent. */ private int getOverflowingChildViewSize() { - final int usedScrollViewSpace = mScrollViewChild.getHeight() - - mLeftOverSpaceView.getHeight(); + final int usedScrollViewSpace = mScrollViewChild.getHeight(); return -getHeight() + usedScrollViewSpace + mToolbar.getLayoutParams().height; } @@ -1098,11 +1097,26 @@ public class MultiShrinkScroller extends LinearLayout { * space at the bottom of this ViewGroup. */ public void prepareForShrinkingScrollChild(int heightDelta) { + // The Transition framework may suppress layout on the scene root and its children. If + // mScrollView has its layout suppressed, user scrolling interactions will not display + // correctly. By turning suppress off for mScrollView, mScrollView properly adjusts its + // graphics as the user scrolls during the transition. + mScrollView.suppressLayout(false); + final int newEmptyScrollViewSpace = -getOverflowingChildViewSize() + heightDelta; if (newEmptyScrollViewSpace > 0 && !mIsTwoPanel) { final int newDesiredToolbarHeight = Math.min(mToolbar.getLayoutParams().height + newEmptyScrollViewSpace, getMaximumScrollableHeaderHeight()); - ObjectAnimator.ofInt(this, "toolbarHeight", newDesiredToolbarHeight).start(); + ObjectAnimator.ofInt(this, "toolbarHeight", newDesiredToolbarHeight).setDuration( + ExpandingEntryCardView.DURATION_COLLAPSE_ANIMATION_CHANGE_BOUNDS).start(); } } + + public void prepareForExpandingScrollChild() { + // The Transition framework may suppress layout on the scene root and its children. If + // mScrollView has its layout suppressed, user scrolling interactions will not display + // correctly. By turning suppress off for mScrollView, mScrollView properly adjusts its + // graphics as the user scrolls during the transition. + mScrollView.suppressLayout(false); + } } |