summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPaul Soulos <psoulos@google.com>2014-07-23 11:27:28 -0700
committerPaul Soulos <psoulos@google.com>2014-07-23 11:28:16 -0700
commit0cda9aeb01f1922fce2a9e87ae4c0146c177b4f0 (patch)
treeaaece4a5b0175b44b286e6cb9bd13fc39d8a41a3 /src
parent81cc3b3d09d9296e521ac3454ad01c6b6c2ba71b (diff)
downloadpackages_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')
-rw-r--r--src/com/android/contacts/quickcontact/ExpandingEntryCardView.java216
-rw-r--r--src/com/android/contacts/quickcontact/QuickContactActivity.java19
-rw-r--r--src/com/android/contacts/widget/MultiShrinkScroller.java24
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);
+ }
}