1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.contacts.detail; 18 19 import com.android.contacts.ContactLoader; 20 import com.android.contacts.NfcHandler; 21 import com.android.contacts.R; 22 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener; 23 24 import android.animation.Animator; 25 import android.animation.Animator.AnimatorListener; 26 import android.animation.ObjectAnimator; 27 import android.app.Activity; 28 import android.app.FragmentManager; 29 import android.app.FragmentTransaction; 30 import android.content.Context; 31 import android.os.Bundle; 32 import android.support.v4.view.ViewPager; 33 import android.support.v4.view.ViewPager.OnPageChangeListener; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.animation.AnimationUtils; 37 import android.widget.AbsListView; 38 import android.widget.AbsListView.OnScrollListener; 39 40 /** 41 * Determines the layout of the contact card. 42 */ 43 public class ContactDetailLayoutController { 44 45 private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates"; 46 private static final String KEY_CURRENT_PAGE_INDEX = "currentPageIndex"; 47 48 private static final int TAB_INDEX_DETAIL = 0; 49 private static final int TAB_INDEX_UPDATES = 1; 50 51 /** 52 * There are 3 possible layouts for the contact detail screen: 53 * 1. TWO_COLUMN - Tall and wide screen so the 2 pages can be shown side-by-side 54 * 2. VIEW_PAGER_AND_TAB_CAROUSEL - Tall and narrow screen to allow swipe between the 2 pages 55 * 3. FRAGMENT_CAROUSEL- Short and wide screen to allow half of the other page to show at a time 56 */ 57 private enum LayoutMode { 58 TWO_COLUMN, VIEW_PAGER_AND_TAB_CAROUSEL, FRAGMENT_CAROUSEL, 59 } 60 61 private final Activity mActivity; 62 private final LayoutInflater mLayoutInflater; 63 private final FragmentManager mFragmentManager; 64 65 private ContactDetailFragment mDetailFragment; 66 private ContactDetailUpdatesFragment mUpdatesFragment; 67 68 private View mDetailFragmentView; 69 private View mUpdatesFragmentView; 70 71 private final ViewPager mViewPager; 72 private ContactDetailViewPagerAdapter mViewPagerAdapter; 73 private int mViewPagerState; 74 75 private final ContactDetailTabCarousel mTabCarousel; 76 private final ContactDetailFragmentCarousel mFragmentCarousel; 77 78 private ContactDetailFragment.Listener mContactDetailFragmentListener; 79 80 private ContactLoader.Result mContactData; 81 82 private boolean mTabCarouselIsAnimating; 83 private boolean mContactHasUpdates; 84 85 private LayoutMode mLayoutMode; 86 87 public ContactDetailLayoutController(Activity activity, Bundle savedState, 88 FragmentManager fragmentManager, View viewContainer, ContactDetailFragment.Listener 89 contactDetailFragmentListener) { 90 91 if (fragmentManager == null) { 92 throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController " 93 + "without a non-null FragmentManager"); 94 } 95 96 mActivity = activity; 97 mLayoutInflater = (LayoutInflater) activity.getSystemService( 98 Context.LAYOUT_INFLATER_SERVICE); 99 mFragmentManager = fragmentManager; 100 mContactDetailFragmentListener = contactDetailFragmentListener; 101 102 // Retrieve views in case this is view pager and carousel mode 103 mViewPager = (ViewPager) viewContainer.findViewById(R.id.pager); 104 mTabCarousel = (ContactDetailTabCarousel) viewContainer.findViewById(R.id.tab_carousel); 105 106 // Retrieve view in case this is in fragment carousel mode 107 mFragmentCarousel = (ContactDetailFragmentCarousel) viewContainer.findViewById( 108 R.id.fragment_carousel); 109 110 // Retrieve container views in case they are already in the XML layout 111 mDetailFragmentView = viewContainer.findViewById(R.id.about_fragment_container); 112 mUpdatesFragmentView = viewContainer.findViewById(R.id.updates_fragment_container); 113 114 // Determine the layout mode based on the presence of certain views in the layout XML. 115 if (mViewPager != null) { 116 mLayoutMode = LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL; 117 } else { 118 mLayoutMode = (mFragmentCarousel != null) ? LayoutMode.FRAGMENT_CAROUSEL : 119 LayoutMode.TWO_COLUMN; 120 } 121 122 initialize(savedState); 123 } 124 125 private void initialize(Bundle savedState) { 126 boolean fragmentsAddedToFragmentManager = true; 127 mDetailFragment = (ContactDetailFragment) mFragmentManager.findFragmentByTag( 128 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG); 129 mUpdatesFragment = (ContactDetailUpdatesFragment) mFragmentManager.findFragmentByTag( 130 ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG); 131 132 // If the detail fragment was found in the {@link FragmentManager} then we don't need to add 133 // it again. Otherwise, create the fragments dynamically and remember to add them to the 134 // {@link FragmentManager}. 135 if (mDetailFragment == null) { 136 mDetailFragment = new ContactDetailFragment(); 137 mUpdatesFragment = new ContactDetailUpdatesFragment(); 138 fragmentsAddedToFragmentManager = false; 139 } 140 141 mDetailFragment.setListener(mContactDetailFragmentListener); 142 NfcHandler.register(mActivity, mDetailFragment); 143 144 // Read from savedState if possible 145 int currentPageIndex = 0; 146 if (savedState != null) { 147 mContactHasUpdates = savedState.getBoolean(KEY_CONTACT_HAS_UPDATES); 148 currentPageIndex = savedState.getInt(KEY_CURRENT_PAGE_INDEX, 0); 149 } 150 151 switch (mLayoutMode) { 152 case VIEW_PAGER_AND_TAB_CAROUSEL: { 153 // Inflate 2 view containers to pass in as children to the {@link ViewPager}, 154 // which will in turn be the parents to the mDetailFragment and mUpdatesFragment 155 // since the fragments must have the same parent view IDs in both landscape and 156 // portrait layouts. 157 mDetailFragmentView = mLayoutInflater.inflate( 158 R.layout.contact_detail_about_fragment_container, mViewPager, false); 159 mUpdatesFragmentView = mLayoutInflater.inflate( 160 R.layout.contact_detail_updates_fragment_container, mViewPager, false); 161 162 mViewPagerAdapter = new ContactDetailViewPagerAdapter(); 163 mViewPagerAdapter.setAboutFragmentView(mDetailFragmentView); 164 mViewPagerAdapter.setUpdatesFragmentView(mUpdatesFragmentView); 165 166 mViewPager.addView(mDetailFragmentView); 167 mViewPager.addView(mUpdatesFragmentView); 168 mViewPager.setAdapter(mViewPagerAdapter); 169 mViewPager.setOnPageChangeListener(mOnPageChangeListener); 170 171 if (!fragmentsAddedToFragmentManager) { 172 FragmentTransaction transaction = mFragmentManager.beginTransaction(); 173 transaction.add(R.id.about_fragment_container, mDetailFragment, 174 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG); 175 transaction.add(R.id.updates_fragment_container, mUpdatesFragment, 176 ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG); 177 transaction.commitAllowingStateLoss(); 178 mFragmentManager.executePendingTransactions(); 179 } 180 181 mTabCarousel.setListener(mTabCarouselListener); 182 mTabCarousel.restoreCurrentTab(currentPageIndex); 183 mDetailFragment.setVerticalScrollListener( 184 new VerticalScrollListener(TAB_INDEX_DETAIL)); 185 mUpdatesFragment.setVerticalScrollListener( 186 new VerticalScrollListener(TAB_INDEX_UPDATES)); 187 mViewPager.setCurrentItem(currentPageIndex); 188 break; 189 } 190 case TWO_COLUMN: { 191 if (!fragmentsAddedToFragmentManager) { 192 FragmentTransaction transaction = mFragmentManager.beginTransaction(); 193 transaction.add(R.id.about_fragment_container, mDetailFragment, 194 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG); 195 transaction.add(R.id.updates_fragment_container, mUpdatesFragment, 196 ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG); 197 transaction.commitAllowingStateLoss(); 198 mFragmentManager.executePendingTransactions(); 199 } 200 break; 201 } 202 case FRAGMENT_CAROUSEL: { 203 // Add the fragments to the fragment containers in the carousel using a 204 // {@link FragmentTransaction} if they haven't already been added to the 205 // {@link FragmentManager}. 206 if (!fragmentsAddedToFragmentManager) { 207 FragmentTransaction transaction = mFragmentManager.beginTransaction(); 208 transaction.add(R.id.about_fragment_container, mDetailFragment, 209 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG); 210 transaction.add(R.id.updates_fragment_container, mUpdatesFragment, 211 ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG); 212 transaction.commitAllowingStateLoss(); 213 mFragmentManager.executePendingTransactions(); 214 } 215 216 mFragmentCarousel.setFragmentViews(mDetailFragmentView, mUpdatesFragmentView); 217 mFragmentCarousel.setFragments(mDetailFragment, mUpdatesFragment); 218 mFragmentCarousel.setCurrentPage(currentPageIndex); 219 break; 220 } 221 } 222 223 // Setup the layout if we already have a saved state 224 if (savedState != null) { 225 if (mContactHasUpdates) { 226 showContactWithUpdates(); 227 } else { 228 showContactWithoutUpdates(); 229 } 230 } 231 } 232 233 public void setContactData(ContactLoader.Result data) { 234 mContactData = data; 235 mContactHasUpdates = !data.getStreamItems().isEmpty(); 236 if (mContactHasUpdates) { 237 showContactWithUpdates(); 238 } else { 239 showContactWithoutUpdates(); 240 } 241 } 242 243 public void showEmptyState() { 244 switch (mLayoutMode) { 245 case FRAGMENT_CAROUSEL: { 246 mFragmentCarousel.setCurrentPage(0); 247 mFragmentCarousel.enableSwipe(false); 248 mDetailFragment.showEmptyState(); 249 break; 250 } 251 case TWO_COLUMN: { 252 mDetailFragment.setShowStaticPhoto(false); 253 mUpdatesFragmentView.setVisibility(View.GONE); 254 mDetailFragment.showEmptyState(); 255 break; 256 } 257 case VIEW_PAGER_AND_TAB_CAROUSEL: { 258 mDetailFragment.setShowStaticPhoto(false); 259 mDetailFragment.showEmptyState(); 260 mTabCarousel.loadData(null); 261 mTabCarousel.setVisibility(View.GONE); 262 mViewPagerAdapter.enableSwipe(false); 263 mViewPager.setCurrentItem(0); 264 break; 265 } 266 default: 267 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode); 268 } 269 } 270 271 /** 272 * Setup the layout for the contact with updates. Pass in the index of the current page to 273 * select or null if the current selection should be left as is. 274 */ 275 private void showContactWithUpdates() { 276 if (mContactData == null) { 277 return; 278 } 279 switch (mLayoutMode) { 280 case TWO_COLUMN: { 281 // Set the contact data (hide the static photo because the photo will already be in 282 // the header that scrolls with contact details). 283 mDetailFragment.setShowStaticPhoto(false); 284 // Show the updates fragment 285 mUpdatesFragmentView.setVisibility(View.VISIBLE); 286 break; 287 } 288 case VIEW_PAGER_AND_TAB_CAROUSEL: { 289 // Update and show the tab carousel (also restore its last saved position) 290 mTabCarousel.loadData(mContactData); 291 mTabCarousel.restoreYCoordinate(); 292 mTabCarousel.setVisibility(View.VISIBLE); 293 // Update ViewPager to allow swipe between all the fragments (to see updates) 294 mViewPagerAdapter.enableSwipe(true); 295 break; 296 } 297 case FRAGMENT_CAROUSEL: { 298 // Allow swiping between all fragments 299 mFragmentCarousel.enableSwipe(true); 300 break; 301 } 302 default: 303 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode); 304 } 305 306 mDetailFragment.setData(mContactData.getLookupUri(), mContactData); 307 mUpdatesFragment.setData(mContactData.getLookupUri(), mContactData); 308 } 309 310 private void showContactWithoutUpdates() { 311 if (mContactData == null) { 312 return; 313 } 314 switch (mLayoutMode) { 315 case TWO_COLUMN: 316 // Show the static photo which is next to the list of scrolling contact details 317 mDetailFragment.setShowStaticPhoto(true); 318 // Hide the updates fragment 319 mUpdatesFragmentView.setVisibility(View.GONE); 320 break; 321 case VIEW_PAGER_AND_TAB_CAROUSEL: 322 // Hide the tab carousel 323 mTabCarousel.setVisibility(View.GONE); 324 // Update ViewPager to disable swipe so that it only shows the detail fragment 325 // and switch to the detail fragment 326 mViewPagerAdapter.enableSwipe(false); 327 mViewPager.setCurrentItem(0); 328 break; 329 case FRAGMENT_CAROUSEL: { 330 // Disable swipe so only the detail fragment shows 331 mFragmentCarousel.setCurrentPage(0); 332 mFragmentCarousel.enableSwipe(false); 333 break; 334 } 335 default: 336 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode); 337 } 338 339 mDetailFragment.setData(mContactData.getLookupUri(), mContactData); 340 } 341 342 public FragmentKeyListener getCurrentPage() { 343 switch (getCurrentPageIndex()) { 344 case 0: 345 return mDetailFragment; 346 case 1: 347 return mUpdatesFragment; 348 default: 349 throw new IllegalStateException("Invalid current item for ViewPager"); 350 } 351 } 352 353 private int getCurrentPageIndex() { 354 // If the contact has social updates, then retrieve the current page based on the 355 // {@link ViewPager} or fragment carousel. 356 if (mContactHasUpdates) { 357 if (mViewPager != null) { 358 return mViewPager.getCurrentItem(); 359 } else if (mFragmentCarousel != null) { 360 return mFragmentCarousel.getCurrentPage(); 361 } 362 } 363 // Otherwise return the default page (detail fragment). 364 return 0; 365 } 366 367 public void onSaveInstanceState(Bundle outState) { 368 outState.putBoolean(KEY_CONTACT_HAS_UPDATES, mContactHasUpdates); 369 outState.putInt(KEY_CURRENT_PAGE_INDEX, getCurrentPageIndex()); 370 } 371 372 private final OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() { 373 374 private ObjectAnimator mTabCarouselAnimator; 375 376 @Override 377 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 378 // The user is horizontally dragging the {@link ViewPager}, so send 379 // these scroll changes to the tab carousel. Ignore these events though if the carousel 380 // is actually controlling the {@link ViewPager} scrolls because it will already be 381 // in the correct position. 382 if (mViewPager.isFakeDragging()) { 383 return; 384 } 385 int x = (int) ((position + positionOffset) * 386 mTabCarousel.getAllowedHorizontalScrollLength()); 387 mTabCarousel.scrollTo(x, 0); 388 } 389 390 @Override 391 public void onPageSelected(int position) { 392 // Since the {@link ViewPager} has committed to a new page now (but may not have 393 // finished scrolling yet), update the tab selection in the carousel. 394 mTabCarousel.setCurrentTab(position); 395 } 396 397 @Override 398 public void onPageScrollStateChanged(int state) { 399 if (mViewPagerState == ViewPager.SCROLL_STATE_IDLE) { 400 401 // If we are leaving the IDLE state, we are starting a swipe. 402 // First cancel any pending animations on the tab carousel. 403 cancelTabCarouselAnimator(); 404 405 // Sync the two lists because the list on the other page will start to show as 406 // we swipe over more. 407 syncScrollStateBetweenLists(mViewPager.getCurrentItem()); 408 409 } else if (state == ViewPager.SCROLL_STATE_IDLE) { 410 411 // Otherwise if the {@link ViewPager} is idle now, a page has been selected and 412 // scrolled into place. Perform an animation of the tab carousel is needed. 413 int currentPageIndex = mViewPager.getCurrentItem(); 414 int tabCarouselOffset = (int) mTabCarousel.getY(); 415 boolean shouldAnimateTabCarousel; 416 417 // Find the offset position of the first item in the list of the current page. 418 int listOffset = getOffsetOfFirstItemInList(currentPageIndex); 419 420 // If the list was able to successfully offset by the tab carousel amount, then 421 // log this as the new Y coordinate for that page, and no animation is needed. 422 if (listOffset == tabCarouselOffset) { 423 mTabCarousel.storeYCoordinate(currentPageIndex, tabCarouselOffset); 424 shouldAnimateTabCarousel = false; 425 } else if (listOffset == Integer.MIN_VALUE) { 426 // If the offset of the first item in the list is unknown (i.e. the item 427 // is no longer visible on screen) then just animate the tab carousel to the 428 // previously logged position. 429 shouldAnimateTabCarousel = true; 430 } else if (Math.abs(listOffset) < Math.abs(tabCarouselOffset)) { 431 // If the list could not offset the full amount of the tab carousel offset (i.e. 432 // the list can only be scrolled a tiny amount), then animate the carousel down 433 // to compensate. 434 mTabCarousel.storeYCoordinate(currentPageIndex, listOffset); 435 shouldAnimateTabCarousel = true; 436 } else { 437 // By default, animate back to the Y coordinate of the tab carousel the last 438 // time the other page was selected. 439 shouldAnimateTabCarousel = true; 440 } 441 442 if (shouldAnimateTabCarousel) { 443 float desiredOffset = mTabCarousel.getStoredYCoordinateForTab(currentPageIndex); 444 if (desiredOffset != tabCarouselOffset) { 445 createTabCarouselAnimator(desiredOffset); 446 mTabCarouselAnimator.start(); 447 } 448 } 449 } 450 mViewPagerState = state; 451 } 452 453 private void createTabCarouselAnimator(float desiredValue) { 454 mTabCarouselAnimator = ObjectAnimator.ofFloat( 455 mTabCarousel, "y", desiredValue).setDuration(75); 456 mTabCarouselAnimator.setInterpolator(AnimationUtils.loadInterpolator( 457 mActivity, android.R.anim.accelerate_decelerate_interpolator)); 458 mTabCarouselAnimator.addListener(mTabCarouselAnimatorListener); 459 } 460 461 private void cancelTabCarouselAnimator() { 462 if (mTabCarouselAnimator != null) { 463 mTabCarouselAnimator.cancel(); 464 mTabCarouselAnimator = null; 465 mTabCarouselIsAnimating = false; 466 } 467 } 468 }; 469 470 private void syncScrollStateBetweenLists(int currentPageIndex) { 471 // Since the user interacted with the currently visible page, we need to sync the 472 // list on the other page (i.e. if the updates page is the current page, modify the 473 // list in the details page). 474 if (currentPageIndex == TAB_INDEX_UPDATES) { 475 mDetailFragment.requestToMoveToOffset((int) mTabCarousel.getY()); 476 } else { 477 mUpdatesFragment.requestToMoveToOffset((int) mTabCarousel.getY()); 478 } 479 } 480 481 private int getOffsetOfFirstItemInList(int currentPageIndex) { 482 if (currentPageIndex == TAB_INDEX_DETAIL) { 483 return mDetailFragment.getFirstListItemOffset(); 484 } else { 485 return mUpdatesFragment.getFirstListItemOffset(); 486 } 487 } 488 489 /** 490 * This listener keeps track of whether the tab carousel animation is currently going on or not, 491 * in order to prevent other simultaneous changes to the Y position of the tab carousel which 492 * can cause flicker. 493 */ 494 private final AnimatorListener mTabCarouselAnimatorListener = new AnimatorListener() { 495 496 @Override 497 public void onAnimationCancel(Animator animation) { 498 mTabCarouselIsAnimating = false; 499 } 500 501 @Override 502 public void onAnimationEnd(Animator animation) { 503 mTabCarouselIsAnimating = false; 504 } 505 506 @Override 507 public void onAnimationRepeat(Animator animation) { 508 mTabCarouselIsAnimating = true; 509 } 510 511 @Override 512 public void onAnimationStart(Animator animation) { 513 mTabCarouselIsAnimating = true; 514 } 515 }; 516 517 private final ContactDetailTabCarousel.Listener mTabCarouselListener = 518 new ContactDetailTabCarousel.Listener() { 519 520 @Override 521 public void onTouchDown() { 522 // The user just started scrolling the carousel, so begin "fake dragging" the 523 // {@link ViewPager} if it's not already doing so. 524 if (mViewPager.isFakeDragging()) { 525 return; 526 } 527 mViewPager.beginFakeDrag(); 528 } 529 530 @Override 531 public void onTouchUp() { 532 // The user just stopped scrolling the carousel, so stop "fake dragging" the 533 // {@link ViewPager} if was doing so before. 534 if (mViewPager.isFakeDragging()) { 535 mViewPager.endFakeDrag(); 536 } 537 } 538 539 @Override 540 public void onScrollChanged(int l, int t, int oldl, int oldt) { 541 // The user is scrolling the carousel, so send the scroll deltas to the 542 // {@link ViewPager} so it can move in sync. 543 if (mViewPager.isFakeDragging()) { 544 mViewPager.fakeDragBy(oldl-l); 545 } 546 } 547 548 @Override 549 public void onTabSelected(int position) { 550 // The user selected a tab, so update the {@link ViewPager} 551 mViewPager.setCurrentItem(position); 552 } 553 }; 554 555 private final class VerticalScrollListener implements OnScrollListener { 556 557 private final int mPageIndex; 558 559 public VerticalScrollListener(int pageIndex) { 560 mPageIndex = pageIndex; 561 } 562 563 @Override 564 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 565 int totalItemCount) { 566 int currentPageIndex = mViewPager.getCurrentItem(); 567 // Don't move the carousel if: 1) the contact does not have social updates because then 568 // tab carousel must not be visible, 2) if the view pager is still being scrolled, 569 // 3) if the current page being viewed is not this one, or 4) if the tab carousel 570 // is already being animated vertically. 571 if (!mContactHasUpdates || mViewPagerState != ViewPager.SCROLL_STATE_IDLE || 572 mPageIndex != currentPageIndex || mTabCarouselIsAnimating) { 573 return; 574 } 575 // If the FIRST item is not visible on the screen, then the carousel must be pinned 576 // at the top of the screen. 577 if (firstVisibleItem != 0) { 578 mTabCarousel.moveToYCoordinate(mPageIndex, 579 -mTabCarousel.getAllowedVerticalScrollLength()); 580 return; 581 } 582 View topView = view.getChildAt(firstVisibleItem); 583 if (topView == null) { 584 return; 585 } 586 int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(), 587 -mTabCarousel.getAllowedVerticalScrollLength()); 588 mTabCarousel.moveToYCoordinate(mPageIndex, amtToScroll); 589 } 590 591 @Override 592 public void onScrollStateChanged(AbsListView view, int scrollState) { 593 // Once the list has become IDLE, check if we need to sync the scroll position of 594 // the other list now. This will make swiping faster by doing the re-layout now 595 // (instead of at the start of a swipe). However, there will still be another check 596 // when we start swiping if the scroll positions are correct (to catch the edge case 597 // where the user flings and immediately starts a swipe so we never get the idle state). 598 if (scrollState == SCROLL_STATE_IDLE) { 599 syncScrollStateBetweenLists(mPageIndex); 600 } 601 } 602 } 603 } 604