blob: 2c18d4f3f6580c9d6b700e9b4bbaced3c0e57498 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts.detail;
import com.android.contacts.ContactLoader;
import com.android.contacts.NfcHandler;
import com.android.contacts.R;
import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
import com.android.contacts.util.PhoneCapabilityTester;
import com.android.contacts.util.UriUtils;
import com.android.contacts.widget.FrameLayoutWithOverlay;
import com.android.contacts.widget.TransitionAnimationView;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
/**
* Determines the layout of the contact card.
*/
public class ContactDetailLayoutController {
private static final String KEY_CONTACT_URI = "contactUri";
private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates";
private static final String KEY_CURRENT_PAGE_INDEX = "currentPageIndex";
private static final int TAB_INDEX_DETAIL = 0;
private static final int TAB_INDEX_UPDATES = 1;
private final int SINGLE_PANE_FADE_IN_DURATION = 275;
/**
* There are 4 possible layouts for the contact detail screen: TWO_COLUMN,
* VIEW_PAGER_AND_TAB_CAROUSEL, FRAGMENT_CAROUSEL, and TWO_COLUMN_FRAGMENT_CAROUSEL.
*/
private interface LayoutMode {
/**
* Tall and wide screen with details and updates shown side-by-side.
*/
static final int TWO_COLUMN = 0;
/**
* Tall and narrow screen to allow swipe between the details and updates.
*/
static final int VIEW_PAGER_AND_TAB_CAROUSEL = 1;
/**
* Short and wide screen to allow part of the other page to show.
*/
static final int FRAGMENT_CAROUSEL = 2;
/**
* Same as FRAGMENT_CAROUSEL (allowing part of the other page to show) except the details
* layout is similar to the details layout in TWO_COLUMN mode.
*/
static final int TWO_COLUMN_FRAGMENT_CAROUSEL = 3;
}
private final Activity mActivity;
private final LayoutInflater mLayoutInflater;
private final FragmentManager mFragmentManager;
private final View mViewContainer;
private final TransitionAnimationView mTransitionAnimationView;
private ContactDetailFragment mDetailFragment;
private ContactDetailUpdatesFragment mUpdatesFragment;
private View mDetailFragmentView;
private View mUpdatesFragmentView;
private final ViewPager mViewPager;
private ContactDetailViewPagerAdapter mViewPagerAdapter;
private int mViewPagerState;
private final ContactDetailTabCarousel mTabCarousel;
private final ContactDetailFragmentCarousel mFragmentCarousel;
private final ContactDetailFragment.Listener mContactDetailFragmentListener;
private ContactLoader.Result mContactData;
private Uri mContactUri;
private boolean mTabCarouselIsAnimating;
private boolean mContactHasUpdates;
private int mLayoutMode;
public ContactDetailLayoutController(Activity activity, Bundle savedState,
FragmentManager fragmentManager, TransitionAnimationView animationView,
View viewContainer, ContactDetailFragment.Listener contactDetailFragmentListener) {
if (fragmentManager == null) {
throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController "
+ "without a non-null FragmentManager");
}
mActivity = activity;
mLayoutInflater = (LayoutInflater) activity.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mFragmentManager = fragmentManager;
mContactDetailFragmentListener = contactDetailFragmentListener;
mTransitionAnimationView = animationView;
// 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);
// Retrieve view in case this is in fragment carousel mode
mFragmentCarousel = (ContactDetailFragmentCarousel) viewContainer.findViewById(
R.id.fragment_carousel);
// Retrieve container views in case they are already in the XML layout
mDetailFragmentView = viewContainer.findViewById(R.id.about_fragment_container);
mUpdatesFragmentView = viewContainer.findViewById(R.id.updates_fragment_container);
// Determine the layout mode based on the presence of certain views in the layout XML.
if (mViewPager != null) {
mLayoutMode = LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL;
} else if (mFragmentCarousel != null) {
if (PhoneCapabilityTester.isUsingTwoPanes(mActivity)) {
mLayoutMode = LayoutMode.TWO_COLUMN_FRAGMENT_CAROUSEL;
} else {
mLayoutMode = LayoutMode.FRAGMENT_CAROUSEL;
}
} else {
mLayoutMode = LayoutMode.TWO_COLUMN;
}
initialize(savedState);
}
private void initialize(Bundle savedState) {
boolean fragmentsAddedToFragmentManager = true;
mDetailFragment = (ContactDetailFragment) mFragmentManager.findFragmentByTag(
ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
mUpdatesFragment = (ContactDetailUpdatesFragment) mFragmentManager.findFragmentByTag(
ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
// If the detail fragment was found in the {@link FragmentManager} then we don't need to add
// it again. Otherwise, create the fragments dynamically and remember to add them to the
// {@link FragmentManager}.
if (mDetailFragment == null) {
mDetailFragment = new ContactDetailFragment();
mUpdatesFragment = new ContactDetailUpdatesFragment();
fragmentsAddedToFragmentManager = false;
}
mDetailFragment.setListener(mContactDetailFragmentListener);
NfcHandler.register(mActivity, mDetailFragment);
// Read from savedState if possible
int currentPageIndex = 0;
if (savedState != null) {
mContactUri = savedState.getParcelable(KEY_CONTACT_URI);
mContactHasUpdates = savedState.getBoolean(KEY_CONTACT_HAS_UPDATES);
currentPageIndex = savedState.getInt(KEY_CURRENT_PAGE_INDEX, 0);
}
switch (mLayoutMode) {
case LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL: {
// Inflate 2 view containers to pass in as children to the {@link ViewPager},
// which will in turn be the parents to the mDetailFragment and mUpdatesFragment
// since the fragments must have the same parent view IDs in both landscape and
// portrait layouts.
mDetailFragmentView = mLayoutInflater.inflate(
R.layout.contact_detail_about_fragment_container, mViewPager, false);
mUpdatesFragmentView = mLayoutInflater.inflate(
R.layout.contact_detail_updates_fragment_container, mViewPager, false);
mViewPagerAdapter = new ContactDetailViewPagerAdapter();
mViewPagerAdapter.setAboutFragmentView(mDetailFragmentView);
mViewPagerAdapter.setUpdatesFragmentView(mUpdatesFragmentView);
mViewPager.addView(mDetailFragmentView);
mViewPager.addView(mUpdatesFragmentView);
mViewPager.setAdapter(mViewPagerAdapter);
mViewPager.setOnPageChangeListener(mOnPageChangeListener);
if (!fragmentsAddedToFragmentManager) {
FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.add(R.id.about_fragment_container, mDetailFragment,
ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
transaction.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions();
}
mTabCarousel.setListener(mTabCarouselListener);
mTabCarousel.restoreCurrentTab(currentPageIndex);
mDetailFragment.setVerticalScrollListener(
new VerticalScrollListener(TAB_INDEX_DETAIL));
mUpdatesFragment.setVerticalScrollListener(
new VerticalScrollListener(TAB_INDEX_UPDATES));
mViewPager.setCurrentItem(currentPageIndex);
break;
}
case LayoutMode.TWO_COLUMN: {
if (!fragmentsAddedToFragmentManager) {
FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.add(R.id.about_fragment_container, mDetailFragment,
ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
transaction.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions();
}
break;
}
case LayoutMode.FRAGMENT_CAROUSEL:
case LayoutMode.TWO_COLUMN_FRAGMENT_CAROUSEL: {
// Add the fragments to the fragment containers in the carousel using a
// {@link FragmentTransaction} if they haven't already been added to the
// {@link FragmentManager}.
if (!fragmentsAddedToFragmentManager) {
FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.add(R.id.about_fragment_container, mDetailFragment,
ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
transaction.commitAllowingStateLoss();
mFragmentManager.executePendingTransactions();
}
mFragmentCarousel.setFragmentViews(
(FrameLayoutWithOverlay) mDetailFragmentView,
(FrameLayoutWithOverlay) mUpdatesFragmentView);
mFragmentCarousel.setCurrentPage(currentPageIndex);
break;
}
}
// Setup the layout if we already have a saved state
if (savedState != null) {
if (mContactHasUpdates) {
showContactWithUpdates(false);
} else {
showContactWithoutUpdates();
}
}
}
public void setContactData(ContactLoader.Result data) {
final boolean contactWasLoaded;
final boolean contactHadUpdates;
final boolean isDifferentContact;
if (mContactData == null) {
contactHadUpdates = false;
contactWasLoaded = false;
isDifferentContact = true;
} else {
contactHadUpdates = mContactHasUpdates;
contactWasLoaded = true;
isDifferentContact =
!UriUtils.areEqual(mContactData.getLookupUri(), data.getLookupUri());
}
mContactData = data;
mContactHasUpdates = !data.getStreamItems().isEmpty();
if (PhoneCapabilityTester.isUsingTwoPanes(mActivity)) {
// Tablet: If we already showed data before, we want to cross-fade from screen to screen
if (contactWasLoaded && mTransitionAnimationView != null && isDifferentContact) {
mTransitionAnimationView.startMaskTransition(mContactData == null);
}
} else {
// Small screen: We are on our own screen. Fade the data in, but only the first time
if (!contactWasLoaded) {
mViewContainer.setAlpha(0.0f);
final ViewPropertyAnimator animator = mViewContainer.animate();
animator.alpha(1.0f);
animator.setDuration(SINGLE_PANE_FADE_IN_DURATION);
}
}
if (mContactHasUpdates) {
showContactWithUpdates(
contactWasLoaded && contactHadUpdates == false);
} else {
showContactWithoutUpdates();
}
}
public void showEmptyState() {
switch (mLayoutMode) {
case LayoutMode.FRAGMENT_CAROUSEL: {
mFragmentCarousel.setCurrentPage(0);
mFragmentCarousel.enableSwipe(false);
mDetailFragment.showEmptyState();
break;
}
case LayoutMode.TWO_COLUMN: {
mDetailFragment.setShowStaticPhoto(false);
mUpdatesFragmentView.setVisibility(View.GONE);
mDetailFragment.showEmptyState();
break;
}
case LayoutMode.TWO_COLUMN_FRAGMENT_CAROUSEL: {
mFragmentCarousel.setCurrentPage(0);
mFragmentCarousel.enableSwipe(false);
mDetailFragment.setShowStaticPhoto(false);
mUpdatesFragmentView.setVisibility(View.GONE);
mDetailFragment.showEmptyState();
break;
}
case LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL: {
mDetailFragment.setShowStaticPhoto(false);
mDetailFragment.showEmptyState();
mTabCarousel.loadData(null);
mTabCarousel.setVisibility(View.GONE);
mViewPagerAdapter.enableSwipe(false);
mViewPager.setCurrentItem(0);
break;
}
default:
throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
}
}
/**
* Setup the layout for the contact with updates.
* TODO: Clean up this method so it's easier to understand.
*/
private void showContactWithUpdates(boolean animateStateChange) {
if (mContactData == null) {
return;
}
Uri previousContactUri = mContactUri;
mContactUri = mContactData.getLookupUri();
boolean isDifferentContact = !UriUtils.areEqual(previousContactUri, mContactUri);
switch (mLayoutMode) {
case LayoutMode.TWO_COLUMN: {
if (!isDifferentContact && animateStateChange) {
// 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. Doing a simple cross-fade instead.
mTransitionAnimationView.startMaskTransition(false);
}
// 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);
// Show the updates fragment
mUpdatesFragmentView.setVisibility(View.VISIBLE);
break;
}
case LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL: {
// Update and show the tab carousel (also restore its last saved position)
mTabCarousel.loadData(mContactData);
mTabCarousel.restoreYCoordinate();
mTabCarousel.setVisibility(View.VISIBLE);
// Update ViewPager to allow swipe between all the fragments (to see updates)
mViewPagerAdapter.enableSwipe(true);
// If this is a different contact than before, then reset some views.
if (isDifferentContact) {
resetViewPager();
resetTabCarousel();
}
if (!isDifferentContact && animateStateChange) {
mTabCarousel.animateAppear(mViewContainer.getWidth(),
mDetailFragment.getFirstListItemOffset());
}
break;
}
case LayoutMode.FRAGMENT_CAROUSEL: {
// Allow swiping between all fragments
mFragmentCarousel.enableSwipe(true);
if (!isDifferentContact && animateStateChange) {
mFragmentCarousel.animateAppear();
}
break;
}
case LayoutMode.TWO_COLUMN_FRAGMENT_CAROUSEL: {
// Allow swiping between all fragments
mFragmentCarousel.enableSwipe(true);
if (isDifferentContact) {
mFragmentCarousel.reset();
}
if (!isDifferentContact && animateStateChange) {
mFragmentCarousel.animateAppear();
}
mDetailFragment.setShowStaticPhoto(false);
break;
}
default:
throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
}
if (isDifferentContact) {
resetFragments();
}
mDetailFragment.setData(mContactUri, mContactData);
mUpdatesFragment.setData(mContactUri, mContactData);
}
/**
* Setup the layout for the contact without updates.
* TODO: Clean up this method so it's easier to understand.
*/
private void showContactWithoutUpdates() {
if (mContactData == null) {
return;
}
Uri previousContactUri = mContactUri;
mContactUri = mContactData.getLookupUri();
boolean isDifferentContact = !UriUtils.areEqual(previousContactUri, mContactUri);
switch (mLayoutMode) {
case LayoutMode.TWO_COLUMN:
// Show the static photo which is next to the list of scrolling contact details
mDetailFragment.setShowStaticPhoto(true);
// Hide the updates fragment
mUpdatesFragmentView.setVisibility(View.GONE);
break;
case LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL:
// Hide the tab carousel
mTabCarousel.setVisibility(View.GONE);
// Update ViewPager to disable swipe so that it only shows the detail fragment
// and switch to the detail fragment
mViewPagerAdapter.enableSwipe(false);
mViewPager.setCurrentItem(0, false /* smooth transition */);
break;
case LayoutMode.FRAGMENT_CAROUSEL:
// Disable swipe so only the detail fragment shows
mFragmentCarousel.setCurrentPage(0);
mFragmentCarousel.enableSwipe(false);
break;
case LayoutMode.TWO_COLUMN_FRAGMENT_CAROUSEL:
mFragmentCarousel.setCurrentPage(0);
mFragmentCarousel.enableSwipe(false);
mDetailFragment.setShowStaticPhoto(true);
break;
default:
throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode);
}
if (isDifferentContact) {
resetFragments();
}
mDetailFragment.setData(mContactUri, mContactData);
}
private void resetTabCarousel() {
mTabCarousel.reset();
}
private void resetViewPager() {
mViewPager.setCurrentItem(0, false /* smooth transition */);
}
private void resetFragments() {
mDetailFragment.resetAdapter();
mUpdatesFragment.resetAdapter();
}
public FragmentKeyListener getCurrentPage() {
switch (getCurrentPageIndex()) {
case 0:
return mDetailFragment;
case 1:
return mUpdatesFragment;
default:
throw new IllegalStateException("Invalid current item for ViewPager");
}
}
private int getCurrentPageIndex() {
// If the contact has social updates, then retrieve the current page based on the
// {@link ViewPager} or fragment carousel.
if (mContactHasUpdates) {
if (mViewPager != null) {
return mViewPager.getCurrentItem();
} else if (mFragmentCarousel != null) {
return mFragmentCarousel.getCurrentPage();
}
}
// Otherwise return the default page (detail fragment).
return 0;
}
public void onSaveInstanceState(Bundle outState) {
outState.putParcelable(KEY_CONTACT_URI, mContactUri);
outState.putBoolean(KEY_CONTACT_HAS_UPDATES, mContactHasUpdates);
outState.putInt(KEY_CURRENT_PAGE_INDEX, getCurrentPageIndex());
}
private final OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() {
private ObjectAnimator mTabCarouselAnimator;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// The user is horizontally dragging the {@link ViewPager}, so send
// these scroll changes to the tab carousel. Ignore these events though if the carousel
// is actually controlling the {@link ViewPager} scrolls because it will already be
// in the correct position.
if (mViewPager.isFakeDragging()) return;
int x = (int) ((position + positionOffset) *
mTabCarousel.getAllowedHorizontalScrollLength());
mTabCarousel.scrollTo(x, 0);
}
@Override
public void onPageSelected(int position) {
// Since the {@link ViewPager} has committed to a new page now (but may not have
// finished scrolling yet), update the tab selection in the carousel.
mTabCarousel.setCurrentTab(position);
}
@Override
public void onPageScrollStateChanged(int state) {
if (mViewPagerState == ViewPager.SCROLL_STATE_IDLE) {
// If we are leaving the IDLE state, we are starting a swipe.
// First cancel any pending animations on the tab carousel.
cancelTabCarouselAnimator();
// Sync the two lists because the list on the other page will start to show as
// we swipe over more.
syncScrollStateBetweenLists(mViewPager.getCurrentItem());
} else if (state == ViewPager.SCROLL_STATE_IDLE) {
// Otherwise if the {@link ViewPager} is idle now, a page has been selected and
// scrolled into place. Perform an animation of the tab carousel is needed.
int currentPageIndex = mViewPager.getCurrentItem();
int tabCarouselOffset = (int) mTabCarousel.getY();
boolean shouldAnimateTabCarousel;
// Find the offset position of the first item in the list of the current page.
int listOffset = getOffsetOfFirstItemInList(currentPageIndex);
// If the list was able to successfully offset by the tab carousel amount, then
// log this as the new Y coordinate for that page, and no animation is needed.
if (listOffset == tabCarouselOffset) {
mTabCarousel.storeYCoordinate(currentPageIndex, tabCarouselOffset);
shouldAnimateTabCarousel = false;
} else if (listOffset == Integer.MIN_VALUE) {
// If the offset of the first item in the list is unknown (i.e. the item
// is no longer visible on screen) then just animate the tab carousel to the
// previously logged position.
shouldAnimateTabCarousel = true;
} else if (Math.abs(listOffset) < Math.abs(tabCarouselOffset)) {
// If the list could not offset the full amount of the tab carousel offset (i.e.
// the list can only be scrolled a tiny amount), then animate the carousel down
// to compensate.
mTabCarousel.storeYCoordinate(currentPageIndex, listOffset);
shouldAnimateTabCarousel = true;
} else {
// By default, animate back to the Y coordinate of the tab carousel the last
// time the other page was selected.
shouldAnimateTabCarousel = true;
}
if (shouldAnimateTabCarousel) {
float desiredOffset = mTabCarousel.getStoredYCoordinateForTab(currentPageIndex);
if (desiredOffset != tabCarouselOffset) {
createTabCarouselAnimator(desiredOffset);
mTabCarouselAnimator.start();
}
}
}
mViewPagerState = state;
}
private void createTabCarouselAnimator(float desiredValue) {
mTabCarouselAnimator = ObjectAnimator.ofFloat(
mTabCarousel, "y", desiredValue).setDuration(75);
mTabCarouselAnimator.setInterpolator(AnimationUtils.loadInterpolator(
mActivity, android.R.anim.accelerate_decelerate_interpolator));
mTabCarouselAnimator.addListener(mTabCarouselAnimatorListener);
}
private void cancelTabCarouselAnimator() {
if (mTabCarouselAnimator != null) {
mTabCarouselAnimator.cancel();
mTabCarouselAnimator = null;
mTabCarouselIsAnimating = false;
}
}
};
private void syncScrollStateBetweenLists(int currentPageIndex) {
// Since the user interacted with the currently visible page, we need to sync the
// list on the other page (i.e. if the updates page is the current page, modify the
// list in the details page).
if (currentPageIndex == TAB_INDEX_UPDATES) {
mDetailFragment.requestToMoveToOffset((int) mTabCarousel.getY());
} else {
mUpdatesFragment.requestToMoveToOffset((int) mTabCarousel.getY());
}
}
private int getOffsetOfFirstItemInList(int currentPageIndex) {
if (currentPageIndex == TAB_INDEX_DETAIL) {
return mDetailFragment.getFirstListItemOffset();
} else {
return mUpdatesFragment.getFirstListItemOffset();
}
}
/**
* This listener keeps track of whether the tab carousel animation is currently going on or not,
* in order to prevent other simultaneous changes to the Y position of the tab carousel which
* can cause flicker.
*/
private final AnimatorListener mTabCarouselAnimatorListener = new AnimatorListener() {
@Override
public void onAnimationCancel(Animator animation) {
mTabCarouselIsAnimating = false;
}
@Override
public void onAnimationEnd(Animator animation) {
mTabCarouselIsAnimating = false;
}
@Override
public void onAnimationRepeat(Animator animation) {
mTabCarouselIsAnimating = true;
}
@Override
public void onAnimationStart(Animator animation) {
mTabCarouselIsAnimating = true;
}
};
private final ContactDetailTabCarousel.Listener mTabCarouselListener
= new ContactDetailTabCarousel.Listener() {
@Override
public void onTouchDown() {
// The user just started scrolling the carousel, so begin
// "fake dragging" the {@link ViewPager} if it's not already
// doing so.
if (!mViewPager.isFakeDragging()) mViewPager.beginFakeDrag();
}
@Override
public void onTouchUp() {
// The user just stopped scrolling the carousel, so stop
// "fake dragging" the {@link ViewPager} if it was doing so
// before.
if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
}
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
// The user is scrolling the carousel, so send the scroll
// deltas to the {@link ViewPager} so it can move in sync.
if (mViewPager.isFakeDragging()) {
mViewPager.fakeDragBy(oldl - l);
}
}
@Override
public void onTabSelected(int position) {
// The user selected a tab, so update the {@link ViewPager}
mViewPager.setCurrentItem(position);
}
};
private final class VerticalScrollListener implements OnScrollListener {
private final int mPageIndex;
public VerticalScrollListener(int pageIndex) {
mPageIndex = pageIndex;
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
int currentPageIndex = mViewPager.getCurrentItem();
// Don't move the carousel if: 1) the contact does not have social updates because then
// tab carousel must not be visible, 2) if the view pager is still being scrolled,
// 3) if the current page being viewed is not this one, or 4) if the tab carousel
// is already being animated vertically.
if (!mContactHasUpdates || mViewPagerState != ViewPager.SCROLL_STATE_IDLE ||
mPageIndex != currentPageIndex || mTabCarouselIsAnimating) {
return;
}
// If the FIRST item is not visible on the screen, then the carousel must be pinned
// at the top of the screen.
if (firstVisibleItem != 0) {
mTabCarousel.moveToYCoordinate(mPageIndex,
-mTabCarousel.getAllowedVerticalScrollLength());
return;
}
View topView = view.getChildAt(firstVisibleItem);
if (topView == null) {
return;
}
int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(),
-mTabCarousel.getAllowedVerticalScrollLength());
mTabCarousel.moveToYCoordinate(mPageIndex, amtToScroll);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// Once the list has become IDLE, check if we need to sync the scroll position of
// the other list now. This will make swiping faster by doing the re-layout now
// (instead of at the start of a swipe). However, there will still be another check
// when we start swiping if the scroll positions are correct (to catch the edge case
// where the user flings and immediately starts a swipe so we never get the idle state).
if (scrollState == SCROLL_STATE_IDLE) {
syncScrollStateBetweenLists(mPageIndex);
}
}
}
}