Animate show/hide updates
Also fixes the vertical text position which was wrong due to the shadow
Bug:5268733
Bug:5204655
Change-Id: I011a482500e13b1b189c7e27dbcd40e2e1f42318
diff --git a/res/layout-sw580dp/detail_header_contact_with_updates.xml b/res/layout-sw580dp/detail_header_contact_with_updates.xml
index 43cf4c0..f732b23 100644
--- a/res/layout-sw580dp/detail_header_contact_with_updates.xml
+++ b/res/layout-sw580dp/detail_header_contact_with_updates.xml
@@ -27,14 +27,14 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="30dip"
- android:paddingBottom="8dip">
+ android:paddingBottom="16dip">
<!-- Add a first item that gives us enough space to show the carousel -->
<view
class="com.android.contacts.widget.ProportionalLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- ex:ratio="0.66"
+ ex:ratio="0.6667"
ex:direction="widthToHeight">
<!-- Put a dummy view here because the ProportionalLayout requires one -->
diff --git a/res/layout-sw580dp/detail_header_contact_without_updates.xml b/res/layout-sw580dp/detail_header_contact_without_updates.xml
index 0f0e3c2..3c01b16 100644
--- a/res/layout-sw580dp/detail_header_contact_without_updates.xml
+++ b/res/layout-sw580dp/detail_header_contact_without_updates.xml
@@ -32,7 +32,7 @@
class="com.android.contacts.widget.ProportionalLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- ex:ratio="0.66"
+ ex:ratio="0.6667"
ex:direction="widthToHeight">
<LinearLayout
diff --git a/res/layout-sw580dp/updates_header_contact.xml b/res/layout-sw580dp/updates_header_contact.xml
index 00e3d1b..cb55a2d 100644
--- a/res/layout-sw580dp/updates_header_contact.xml
+++ b/res/layout-sw580dp/updates_header_contact.xml
@@ -30,7 +30,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dip"
- ex:ratio="0.66"
+ ex:ratio="0.6667"
ex:direction="widthToHeight">
<!-- Put a dummy view here because the ProportionalLayout requires one -->
diff --git a/res/layout/carousel_about_tab.xml b/res/layout/carousel_about_tab.xml
index c5d4114..0f93482 100644
--- a/res/layout/carousel_about_tab.xml
+++ b/res/layout/carousel_about_tab.xml
@@ -30,7 +30,7 @@
<!-- Transparent view to overlay on the contact's photo
(to allow white text to appear over a white photo). -->
- <View
+ <View android:id="@+id/label_background"
android:layout_width="match_parent"
android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
android:layout_alignParentLeft="true"
diff --git a/res/layout/carousel_updates_tab.xml b/res/layout/carousel_updates_tab.xml
index d23f650..e4b61b9 100644
--- a/res/layout/carousel_updates_tab.xml
+++ b/res/layout/carousel_updates_tab.xml
@@ -36,7 +36,7 @@
<!-- Transparent view to overlay on the update photo
(to allow white text to appear over a white photo). -->
- <View
+ <View android:id="@+id/label_background"
android:layout_width="match_parent"
android:layout_height="@dimen/detail_tab_carousel_tab_label_height"
android:layout_alignParentLeft="true"
diff --git a/res/layout/contact_detail_tab_carousel.xml b/res/layout/contact_detail_tab_carousel.xml
index efd0164..711a6c3 100644
--- a/res/layout/contact_detail_tab_carousel.xml
+++ b/res/layout/contact_detail_tab_carousel.xml
@@ -23,6 +23,7 @@
android:fadingEdge="none">
<LinearLayout
+ android:id="@+id/tab_and_shadow_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@@ -42,6 +43,7 @@
<!-- Vertical divider -->
<View
+ android:id="@+id/tab_divider"
android:layout_width="1dip"
android:layout_height="match_parent"
android:background="@android:color/white"/>
@@ -59,6 +61,7 @@
@dimen/detail_contact_photo_shadow_height.
-->
<View
+ android:id="@+id/shadow"
android:layout_width="match_parent"
android:layout_height="@dimen/detail_contact_photo_shadow_height"
android:background="?android:attr/windowContentOverlay"/>
diff --git a/res/layout/detail_header_contact_with_updates.xml b/res/layout/detail_header_contact_with_updates.xml
index 8d18963..39f0582 100644
--- a/res/layout/detail_header_contact_with_updates.xml
+++ b/res/layout/detail_header_contact_with_updates.xml
@@ -18,16 +18,22 @@
This is a header entry in the contact details list for when the contact has social updates. The
entry maintains vertical padding to ensure that the first contact detail is visible (and below
the tab carousel). No information has to be displayed in this header.
+ The FrameLayout is used to apply additional padding which is needed for the shadow
-->
-<view
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
- class="com.android.contacts.widget.ProportionalLayout"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- ex:ratio="0.5"
- ex:direction="widthToHeight">
- <FrameLayout
+ android:layout_height="wrap_content">
+ <view
+ class="com.android.contacts.widget.ProportionalLayout"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-</view>
\ No newline at end of file
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/detail_contact_photo_shadow_height"
+ ex:ratio="0.5"
+ ex:direction="widthToHeight">
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+ </view>
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/updates_header_contact.xml b/res/layout/updates_header_contact.xml
index 774fa7b..bfcd6e0 100644
--- a/res/layout/updates_header_contact.xml
+++ b/res/layout/updates_header_contact.xml
@@ -29,6 +29,7 @@
class="com.android.contacts.widget.ProportionalLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/detail_contact_photo_shadow_height"
ex:ratio="0.5"
ex:direction="widthToHeight">
diff --git a/res/values-sw580dp/donottranslate_config.xml b/res/values-sw580dp/donottranslate_config.xml
index 57db36b..4e17168 100644
--- a/res/values-sw580dp/donottranslate_config.xml
+++ b/res/values-sw580dp/donottranslate_config.xml
@@ -21,6 +21,6 @@
<bool name="config_use_two_panes">true</bool>
<bool name="show_home_icon">true</bool>
<bool name="config_show_group_action_in_action_bar">false</bool>
- <item name="tab_width_screen_width_percentage" type="fraction">66%</item>
- <item name="tab_height_screen_width_percentage" type="fraction">66%</item>
+ <item name="tab_width_screen_width_percentage" type="fraction">66.67%</item>
+ <item name="tab_height_screen_width_percentage" type="fraction">66.67%</item>
</resources>
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index 2e59f71..b7cc87d 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -1265,6 +1265,21 @@
mLoadInvitableAccountTypes = loadInvitableAccountTypes;
}
+ /**
+ * Sets whether to load stream items. Will trigger a reload if the value has changed.
+ * At the moment, this is only used for debugging purposes
+ */
+ public void setLoadStreamItems(boolean value) {
+ if (mLoadStreamItems != value) {
+ mLoadStreamItems = value;
+ onContentChanged();
+ }
+ }
+
+ public boolean getLoadStreamItems() {
+ return mLoadStreamItems;
+ }
+
public Uri getLookupUri() {
return mLookupUri;
}
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index b353a0b..601e9fb 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -43,6 +43,7 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -56,6 +57,9 @@
public class ContactDetailActivity extends ContactsActivity {
private static final String TAG = "ContactDetailActivity";
+ /** Shows a toogle button for hiding/showing updates. Don't submit with true */
+ private static final boolean DEBUG_TRANSITIONS = false;
+
/**
* Boolean intent key that specifies whether pressing the "up" affordance in this activity
* should cause it to finish itself or launch an intent to bring the user back to a specific
@@ -129,6 +133,19 @@
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.star, menu);
+ if (DEBUG_TRANSITIONS) {
+ final MenuItem toggleSocial =
+ menu.add(mLoaderFragment.getLoadStreamItems() ? "less" : "more");
+ toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ mLoaderFragment.toggleLoadStreamItems();
+ invalidateOptionsMenu();
+ return false;
+ }
+ });
+ }
return true;
}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index b87edbd..de08c4c 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -55,7 +55,6 @@
import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
import com.android.contacts.util.AccountFilterUtil;
import com.android.contacts.util.AccountPromptUtils;
-import com.android.contacts.util.AccountSelectionUtil;
import com.android.contacts.util.AccountsListAdapter;
import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import com.android.contacts.util.Constants;
@@ -88,6 +87,7 @@
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -111,6 +111,9 @@
private static final String TAG = "PeopleActivity";
+ /** Shows a toogle button for hiding/showing updates. Don't submit with true */
+ private static final boolean DEBUG_TRANSITIONS = false;
+
// These values needs to start at 2. See {@link ContactEntryListFragment}.
private static final int SUBACTIVITY_NEW_CONTACT = 2;
private static final int SUBACTIVITY_EDIT_CONTACT = 3;
@@ -1324,6 +1327,21 @@
});
addGroup.setActionView(mAddGroupImageView);
}
+
+ if (DEBUG_TRANSITIONS && mContactDetailLoaderFragment != null) {
+ final MenuItem toggleSocial =
+ menu.add(mContactDetailLoaderFragment.getLoadStreamItems() ? "less" : "more");
+ toggleSocial.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ toggleSocial.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ mContactDetailLoaderFragment.toggleLoadStreamItems();
+ invalidateOptionsMenu();
+ return false;
+ }
+ });
+ }
+
return true;
}
diff --git a/src/com/android/contacts/detail/CarouselTab.java b/src/com/android/contacts/detail/CarouselTab.java
index cdcf6b2..9331bed 100644
--- a/src/com/android/contacts/detail/CarouselTab.java
+++ b/src/com/android/contacts/detail/CarouselTab.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewPropertyAnimator;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -31,7 +32,10 @@
private static final String TAG = CarouselTab.class.getSimpleName();
+ private static final long FADE_TRANSITION_TIME = 150;
+
private TextView mLabelView;
+ private View mLabelBackgroundView;
/**
* This view adds an alpha layer over the entire tab.
@@ -55,6 +59,8 @@
mLabelView = (TextView) findViewById(R.id.label);
mLabelView.setClickable(true);
+ mLabelBackgroundView = findViewById(R.id.label_background);
+
mAlphaLayer = findViewById(R.id.alpha_overlay);
mTouchInterceptLayer = findViewById(R.id.touch_intercept_overlay);
}
@@ -90,4 +96,20 @@
public void setAlphaLayerValue(float alpha) {
ContactDetailDisplayUtils.setAlphaOnViewBackground(mAlphaLayer, alpha);
}
+
+ public void fadeInLabelViewAnimator(int startDelay, boolean fadeBackground) {
+ final ViewPropertyAnimator labelAnimator = mLabelView.animate();
+ mLabelView.setAlpha(0.0f);
+ labelAnimator.alpha(1.0f);
+ labelAnimator.setStartDelay(startDelay);
+ labelAnimator.setDuration(FADE_TRANSITION_TIME);
+
+ if (fadeBackground) {
+ final ViewPropertyAnimator backgroundAnimator = mLabelBackgroundView.animate();
+ mLabelBackgroundView.setAlpha(0.0f);
+ backgroundAnimator.alpha(1.0f);
+ backgroundAnimator.setStartDelay(startDelay);
+ backgroundAnimator.setDuration(FADE_TRANSITION_TIME);
+ }
+ }
}
diff --git a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
index 756b1c7..0c3e6ac 100644
--- a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
@@ -23,6 +23,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewPropertyAnimator;
import android.view.View.OnTouchListener;
import android.widget.HorizontalScrollView;
@@ -84,7 +85,7 @@
private ViewOverlay mAboutFragment;
private ViewOverlay mUpdatesFragment;
- private View mDetailFragmentView;
+ private View mAboutFragmentView;
private View mUpdatesFragmentView;
public ContactDetailFragmentCarousel(Context context) {
@@ -157,8 +158,8 @@
/**
* Set the view containers for the detail and updates fragment.
*/
- public void setFragmentViews(View detailFragmentView, View updatesFragmentView) {
- mDetailFragmentView = detailFragmentView;
+ public void setFragmentViews(View aboutFragmentView, View updatesFragmentView) {
+ mAboutFragmentView = aboutFragmentView;
mUpdatesFragmentView = updatesFragmentView;
}
@@ -180,7 +181,7 @@
if (mUpdatesFragmentView != null) {
mUpdatesFragmentView.setVisibility(enable ? View.VISIBLE : View.GONE);
if (mCurrentPage == ABOUT_PAGE) {
- mDetailFragmentView.requestFocus();
+ mAboutFragmentView.requestFocus();
} else {
mUpdatesFragmentView.requestFocus();
}
@@ -237,7 +238,7 @@
if (!mEnableSwipe) {
return;
}
- mLastScrollPosition= l;
+ mLastScrollPosition = l;
updateAlphaLayers();
}
@@ -283,4 +284,14 @@
}
return false;
}
+
+ /**
+ * Starts an "appear" animation by moving in the "Updates" from the right.
+ */
+ public void animateAppear() {
+ final int x = Math.round((1.0f - FRAGMENT_WIDTH_SCREEN_WIDTH_FRACTION) * getWidth());
+ mUpdatesFragmentView.setTranslationX(x);
+ final ViewPropertyAnimator animator = mUpdatesFragmentView.animate();
+ animator.translationX(0.0f);
+ }
}
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index 74811e4..cd479ca 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -65,6 +65,7 @@
private final LayoutInflater mLayoutInflater;
private final FragmentManager mFragmentManager;
+ private View mViewContainer;
private ContactDetailFragment mDetailFragment;
private ContactDetailUpdatesFragment mUpdatesFragment;
@@ -84,6 +85,7 @@
private Uri mContactUri;
private boolean mTabCarouselIsAnimating;
+
private boolean mContactHasUpdates;
private LayoutMode mLayoutMode;
@@ -104,6 +106,7 @@
mContactDetailFragmentListener = contactDetailFragmentListener;
// Retrieve views in case this is view pager and carousel mode
+ mViewContainer = viewContainer;
mViewPager = (ViewPager) viewContainer.findViewById(R.id.pager);
mTabCarousel = (ContactDetailTabCarousel) viewContainer.findViewById(R.id.tab_carousel);
@@ -228,7 +231,7 @@
// Setup the layout if we already have a saved state
if (savedState != null) {
if (mContactHasUpdates) {
- showContactWithUpdates();
+ showContactWithUpdates(false);
} else {
showContactWithoutUpdates();
}
@@ -236,10 +239,17 @@
}
public void setContactData(ContactLoader.Result data) {
+ final Boolean contactHadUpdates;
+ if (mContactData == null) {
+ contactHadUpdates = null;
+ } else {
+ contactHadUpdates = mContactHasUpdates;
+ }
mContactData = data;
mContactHasUpdates = !data.getStreamItems().isEmpty();
if (mContactHasUpdates) {
- showContactWithUpdates();
+ showContactWithUpdates(
+ contactHadUpdates != null && contactHadUpdates.booleanValue() == false);
} else {
showContactWithoutUpdates();
}
@@ -277,7 +287,7 @@
* Setup the layout for the contact with updates.
* TODO: Clean up this method so it's easier to understand.
*/
- private void showContactWithUpdates() {
+ private void showContactWithUpdates(boolean animateStateChange) {
if (mContactData == null) {
return;
}
@@ -288,6 +298,10 @@
switch (mLayoutMode) {
case TWO_COLUMN: {
+ // This is screen is very hard to animate properly, because there is such a hard
+ // cut from the regular version. A proper animation would have to reflow text and
+ // move things around. No animation for now
+
// Set the contact data (hide the static photo because the photo will already be in
// the header that scrolls with contact details).
mDetailFragment.setShowStaticPhoto(false);
@@ -307,11 +321,18 @@
resetViewPager();
resetTabCarousel();
}
+ if (!isDifferentContact && animateStateChange) {
+ mTabCarousel.animateAppear(mViewContainer.getWidth(),
+ mDetailFragment.getFirstListItemOffset());
+ }
break;
}
case FRAGMENT_CAROUSEL: {
// Allow swiping between all fragments
mFragmentCarousel.enableSwipe(true);
+ if (!isDifferentContact && animateStateChange) {
+ mFragmentCarousel.animateAppear();
+ }
break;
}
default:
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index 186cedd..6cd48e3 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -20,13 +20,14 @@
import com.android.contacts.R;
import com.android.contacts.util.PhoneCapabilityTester;
-import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
+import android.view.ViewPropertyAnimator;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.TextView;
@@ -39,6 +40,9 @@
private static final String TAG = ContactDetailTabCarousel.class.getSimpleName();
+ private static final int TRANSITION_TIME = 200;
+ private static final int TRANSITION_MOVE_IN_TIME = 150;
+
private static final int TAB_INDEX_ABOUT = 0;
private static final int TAB_INDEX_UPDATES = 1;
private static final int TAB_COUNT = 2;
@@ -55,14 +59,16 @@
private ImageView mPhotoView;
private TextView mStatusView;
private ImageView mStatusPhotoView;
- private boolean mHasPhoto;
private OnClickListener mPhotoClickListener;
private Listener mListener;
private int mCurrentTab = TAB_INDEX_ABOUT;
+ private View mTabAndShadowContainer;
+ private View mShadow;
private CarouselTab mAboutTab;
+ private View mTabDivider;
private CarouselTab mUpdatesTab;
/** Last Y coordinate of the carousel when the tab at the given index was selected */
@@ -107,19 +113,26 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mTabAndShadowContainer = findViewById(R.id.tab_and_shadow_container);
mAboutTab = (CarouselTab) findViewById(R.id.tab_about);
mAboutTab.setLabel(mContext.getString(R.string.contactDetailAbout));
+ mTabDivider = findViewById(R.id.tab_divider);
+
mUpdatesTab = (CarouselTab) findViewById(R.id.tab_update);
mUpdatesTab.setLabel(mContext.getString(R.string.contactDetailUpdates));
mAboutTab.enableTouchInterceptor(mAboutTabTouchInterceptListener);
mUpdatesTab.enableTouchInterceptor(mUpdatesTabTouchInterceptListener);
+ mShadow = findViewById(R.id.shadow);
+
// Retrieve the photo view for the "about" tab
+ // TODO: This should be moved down to mAboutTab, so that it hosts its own controls
mPhotoView = (ImageView) mAboutTab.findViewById(R.id.photo);
// Retrieve the social update views for the "updates" tab
+ // TODO: This should be moved down to mUpdatesTab, so that it hosts its own controls
mStatusView = (TextView) mUpdatesTab.findViewById(R.id.status);
mStatusPhotoView = (ImageView) mUpdatesTab.findViewById(R.id.status_photo);
}
@@ -128,22 +141,31 @@
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
// Compute the width of a tab as a fraction of the screen width
- int tabWidth = (int) (mTabWidthScreenWidthFraction * screenWidth);
+ int tabWidth = Math.round(mTabWidthScreenWidthFraction * screenWidth);
// Find the allowed scrolling length by subtracting the current visible screen width
// from the total length of the tabs.
mAllowedHorizontalScrollLength = tabWidth * TAB_COUNT - screenWidth;
- int tabHeight = (int) (screenWidth * mTabHeightScreenWidthFraction) + mTabShadowHeight;
+ int tabHeight = Math.round(screenWidth * mTabHeightScreenWidthFraction) + mTabShadowHeight;
// Set the child {@link LinearLayout} to be TAB_COUNT * the computed tab width so that the
// {@link LinearLayout}'s children (which are the tabs) will evenly split that width.
if (getChildCount() > 0) {
View child = getChildAt(0);
- child.measure(MeasureSpec.makeMeasureSpec(TAB_COUNT * tabWidth, MeasureSpec.EXACTLY),
+
+ // add 1 dip of seperation between the tabs
+ final int seperatorPixels =
+ (int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1,
+ getResources().getDisplayMetrics()) + 0.5f);
+
+ child.measure(
+ MeasureSpec.makeMeasureSpec(
+ TAB_COUNT * tabWidth +
+ (TAB_COUNT - 1) * seperatorPixels, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(tabHeight, MeasureSpec.EXACTLY));
}
- mAllowedVerticalScrollLength = tabHeight - mTabDisplayLabelHeight;
+ mAllowedVerticalScrollLength = tabHeight - mTabDisplayLabelHeight - mTabShadowHeight;
setMeasuredDimension(
resolveSize(screenWidth, widthMeasureSpec),
resolveSize(tabHeight, heightMeasureSpec));
@@ -177,6 +199,105 @@
}
};
+ /**
+ * Does in "appear" animation to allow a seamless transition from
+ * the "No updates" mode.
+ * @param width Width of the container. As we haven't been layed out yet, we can't know
+ * @param scrollOffset The offset by how far we scrolled, where 0=not scrolled, -x=scrolled by
+ * x pixels, Integer.MIN_VALUE=scrolled so far that the image is not visible in "no updates"
+ * mode of this screen
+ */
+ public void animateAppear(int width, int scrollOffset) {
+ final float photoHeight = mTabHeightScreenWidthFraction * width;
+ final boolean animateZoomAndFade;
+ int pixelsToScrollVertically = 0;
+
+ // Depending on how far we are scrolled down, there is one of three animations:
+ // - Zoom and fade the picture (if it is still visible)
+ // - Scroll, zoom and fade (if the picture is mostly invisible and we now have a
+ // bigger visible region due to the pinning)
+ // - Just scroll if the picture is completely invisible. This time, no zoom is needed
+ if (scrollOffset == Integer.MIN_VALUE) {
+ // animate in completely by scrolling. no need for zooming here
+ pixelsToScrollVertically = mTabDisplayLabelHeight;
+ animateZoomAndFade = false;
+ } else {
+ final int pixelsOfPhotoLeft = Math.round(photoHeight) + scrollOffset;
+ if (pixelsOfPhotoLeft > mTabDisplayLabelHeight) {
+ // nothing to scroll
+ pixelsToScrollVertically = 0;
+ } else {
+ pixelsToScrollVertically = mTabDisplayLabelHeight - pixelsOfPhotoLeft;
+ }
+ animateZoomAndFade = true;
+ }
+
+ if (pixelsToScrollVertically != 0) {
+ // We can't animate ourselves here, because our own translation is needed for the user's
+ // scrolling. Instead, we use our only child. As we are transparent, that is just as
+ // good
+ mTabAndShadowContainer.setTranslationY(-pixelsToScrollVertically);
+ final ViewPropertyAnimator animator = mTabAndShadowContainer.animate();
+ animator.translationY(0.0f);
+ animator.setDuration(TRANSITION_MOVE_IN_TIME);
+ }
+
+ if (animateZoomAndFade) {
+ // Hack: We have two types of possible layouts:
+ // If the picture is square, it is square in both "with updates" and "without updates"
+ // --> no need for scale animation here
+ // example: 10inch tablet portrait
+ // If the picture is non-square, it is full-width in "without updates" and something
+ // arbitrary in "with updates"
+ // --> do animation with container
+ // example: 4.6inch phone portrait
+ final boolean squarePicture =
+ mTabWidthScreenWidthFraction == mTabHeightScreenWidthFraction;
+ final int firstTransitionTime;
+ if (squarePicture) {
+ firstTransitionTime = 0;
+ } else {
+ // For x, we need to scale our container so we'll animate the whole tab
+ // (unfortunately, we need to have the text invisible during this transition as it
+ // would also be stretched)
+ float revScale = 1.0f/mTabWidthScreenWidthFraction;
+ mAboutTab.setScaleX(revScale);
+ mAboutTab.setPivotX(0.0f);
+ final ViewPropertyAnimator aboutAnimator = mAboutTab.animate();
+ aboutAnimator.setDuration(TRANSITION_TIME);
+ aboutAnimator.scaleX(1.0f);
+
+ // For y, we need to scale only the picture itself because we want it to be cropped
+ mPhotoView.setScaleY(revScale);
+ mPhotoView.setPivotY(photoHeight * 0.5f);
+ final ViewPropertyAnimator photoAnimator = mPhotoView.animate();
+ photoAnimator.setDuration(TRANSITION_TIME);
+ photoAnimator.scaleY(1.0f);
+ firstTransitionTime = TRANSITION_TIME;
+ }
+
+ // Animate in the labels after the above transition is finished
+ mAboutTab.fadeInLabelViewAnimator(firstTransitionTime, true);
+ mUpdatesTab.fadeInLabelViewAnimator(firstTransitionTime, false);
+
+ final float pixelsToTranslate = (1.0f - mTabWidthScreenWidthFraction) * width;
+ // Views to translate
+ for (View view : new View[] { mUpdatesTab, mTabDivider }) {
+ view.setTranslationX(pixelsToTranslate);
+ final ViewPropertyAnimator translateAnimator = view.animate();
+ translateAnimator.translationX(0.0f);
+ translateAnimator.setDuration(TRANSITION_TIME);
+ }
+
+ // Another hack: If the picture is square, there is no shadow in "Without updates"
+ // --> fade it in after the translations are done
+ if (squarePicture) {
+ mShadow.setAlpha(0.0f);
+ mShadow.animate().setStartDelay(TRANSITION_TIME).alpha(1.0f);
+ }
+ }
+ }
+
private void updateAlphaLayers() {
mAboutTab.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
mAllowedHorizontalScrollLength);
@@ -290,7 +411,6 @@
if (contactData == null) {
return;
}
- mHasPhoto = contactData.getPhotoUri() != null;
// TODO: Move this into the {@link CarouselTab} class when the updates fragment code is more
// finalized
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index 008aff8..ddccfe6 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -407,4 +407,18 @@
mContext, mLookupUri, mCustomRingtone);
mContext.startService(intent);
}
+
+ /** Toggles whether to load stream items. Just for debugging */
+ public void toggleLoadStreamItems() {
+ Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
+ ContactLoader loader = (ContactLoader) loaderObj;
+ loader.setLoadStreamItems(!loader.getLoadStreamItems());
+ }
+
+ /** Returns whether to load stream items. Just for debugging */
+ public boolean getLoadStreamItems() {
+ Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
+ ContactLoader loader = (ContactLoader) loaderObj;
+ return loader != null && loader.getLoadStreamItems();
+ }
}