ContactDetailLayoutController.java revision bed71bedbaae4d6b26b2b64db476bffa710753e5
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 17package com.android.contacts.detail; 18 19import com.android.contacts.ContactLoader; 20import com.android.contacts.activities.PeopleActivity.ContactDetailFragmentListener; 21 22import android.app.Fragment; 23import android.app.FragmentManager; 24import android.app.FragmentTransaction; 25import android.os.Bundle; 26import android.support.v13.app.FragmentPagerAdapter; 27import android.support.v4.view.ViewPager; 28import android.support.v4.view.ViewPager.OnPageChangeListener; 29import android.view.View; 30import android.widget.AbsListView; 31import android.widget.AbsListView.OnScrollListener; 32 33/** 34 * Determines the layout of the contact card. 35 */ 36public class ContactDetailLayoutController { 37 38 public static final int FRAGMENT_COUNT = 2; 39 40 private static final String KEY_DETAIL_FRAGMENT_TAG = "detailFragTag"; 41 private static final String KEY_UPDATES_FRAGMENT_TAG = "updatesFragTag"; 42 43 private String mDetailFragmentTag; 44 private String mUpdatesFragmentTag; 45 46 private enum LayoutMode { 47 TWO_COLUMN, VIEW_PAGER_AND_CAROUSEL, 48 } 49 50 private final FragmentManager mFragmentManager; 51 52 private ContactDetailFragment mContactDetailFragment; 53 private ContactDetailUpdatesFragment mContactDetailUpdatesFragment; 54 55 private final ViewPager mViewPager; 56 private final ContactDetailTabCarousel mTabCarousel; 57 private ContactDetailFragment mPagerContactDetailFragment; 58 private ContactDetailUpdatesFragment mPagerContactDetailUpdatesFragment; 59 60 private ContactDetailFragmentListener mContactDetailFragmentListener; 61 62 private ContactLoader.Result mContactData; 63 64 private boolean mIsInitialized; 65 66 private LayoutMode mLayoutMode; 67 68 public ContactDetailLayoutController(FragmentManager fragmentManager, ViewPager viewPager, 69 ContactDetailTabCarousel tabCarousel, ContactDetailFragmentListener 70 contactDetailFragmentListener) { 71 if (fragmentManager == null) { 72 throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController " 73 + "without a non-null FragmentManager"); 74 } 75 76 mFragmentManager = fragmentManager; 77 mViewPager = viewPager; 78 mTabCarousel = tabCarousel; 79 mContactDetailFragmentListener = contactDetailFragmentListener; 80 81 // Determine the layout based on whether the {@link ViewPager} is null or not. If the 82 // {@link ViewPager} is null, then this is a wide screen and the content can be displayed 83 // in 2 columns side by side. If the {@link ViewPager} is non-null, then this is a narrow 84 // screen and the user will need to swipe to see all the data. 85 mLayoutMode = (mViewPager == null) ? LayoutMode.TWO_COLUMN : 86 LayoutMode.VIEW_PAGER_AND_CAROUSEL; 87 88 } 89 90 public boolean isInitialized() { 91 return mIsInitialized; 92 } 93 94 public void initialize() { 95 mIsInitialized = true; 96 if (mDetailFragmentTag != null || mUpdatesFragmentTag != null) { 97 // Manually remove any {@link ViewPager} fragments if there was an orientation change 98 ContactDetailFragment oldDetailFragment = (ContactDetailFragment) mFragmentManager. 99 findFragmentByTag(mDetailFragmentTag); 100 ContactDetailUpdatesFragment oldUpdatesFragment = (ContactDetailUpdatesFragment) 101 mFragmentManager.findFragmentByTag(mUpdatesFragmentTag); 102 103 if (oldDetailFragment != null && oldUpdatesFragment != null) { 104 FragmentTransaction ft = mFragmentManager.beginTransaction(); 105 ft.remove(oldDetailFragment); 106 ft.remove(oldUpdatesFragment); 107 ft.commitAllowingStateLoss(); 108 } 109 } 110 if (mViewPager != null) { 111 mViewPager.setAdapter(new ViewPagerAdapter(mFragmentManager)); 112 mViewPager.setOnPageChangeListener(mOnPageChangeListener); 113 mTabCarousel.setListener(mTabCarouselListener); 114 } 115 } 116 117 public void setContactDetailFragment(ContactDetailFragment contactDetailFragment) { 118 mContactDetailFragment = contactDetailFragment; 119 } 120 121 public void setContactDetailUpdatesFragment(ContactDetailUpdatesFragment updatesFragment) { 122 mContactDetailUpdatesFragment = updatesFragment; 123 } 124 125 public void setContactData(ContactLoader.Result data) { 126 mContactData = data; 127 if (!data.getStreamItems().isEmpty()) { 128 showContactWithUpdates(); 129 } else { 130 showContactWithoutUpdates(); 131 } 132 } 133 134 private void showContactWithUpdates() { 135 FragmentTransaction ft = mFragmentManager.beginTransaction(); 136 137 switch (mLayoutMode) { 138 case TWO_COLUMN: { 139 // Set the contact data (hide the static photo because the photo will already be in 140 // the header that scrolls with contact details). 141 mContactDetailFragment.setShowStaticPhoto(false); 142 mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData); 143 mContactDetailUpdatesFragment.setData(mContactData.getLookupUri(), mContactData); 144 145 // Update fragment visibility 146 ft.show(mContactDetailUpdatesFragment); 147 break; 148 } 149 case VIEW_PAGER_AND_CAROUSEL: { 150 // Set the contact data 151 mTabCarousel.loadData(mContactData); 152 mPagerContactDetailFragment.setData(mContactData.getLookupUri(), mContactData); 153 mPagerContactDetailUpdatesFragment.setData(mContactData.getLookupUri(), 154 mContactData); 155 156 // Update fragment and view visibility 157 mViewPager.setVisibility(View.VISIBLE); 158 mTabCarousel.setVisibility(View.VISIBLE); 159 ft.hide(mContactDetailFragment); 160 break; 161 } 162 default: 163 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode); 164 } 165 166 // If the activity has already saved its state, then allow this fragment 167 // transaction to be dropped because there's nothing else we can do to update the UI. 168 // The fact that the contact URI has already been saved by the activity means we can 169 // restore this later. 170 ft.commitAllowingStateLoss(); 171 } 172 173 private void showContactWithoutUpdates() { 174 FragmentTransaction ft = mFragmentManager.beginTransaction(); 175 176 switch (mLayoutMode) { 177 case TWO_COLUMN: 178 mContactDetailFragment.setShowStaticPhoto(true); 179 mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData); 180 ft.hide(mContactDetailUpdatesFragment); 181 break; 182 case VIEW_PAGER_AND_CAROUSEL: 183 mContactDetailFragment.setData(mContactData.getLookupUri(), mContactData); 184 ft.show(mContactDetailFragment); 185 mViewPager.setVisibility(View.GONE); 186 mTabCarousel.setVisibility(View.GONE); 187 break; 188 default: 189 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode); 190 } 191 192 // If the activity has already saved its state, then allow this fragment 193 // transaction to be dropped because there's nothing else we can do to update the UI. 194 // The fact that the contact URI has already been saved by the activity means we can 195 // restore this later. 196 ft.commitAllowingStateLoss(); 197 } 198 199 public void onSaveInstanceState(Bundle outState) { 200 if (mPagerContactDetailFragment != null) { 201 outState.putString(KEY_DETAIL_FRAGMENT_TAG, 202 mPagerContactDetailFragment.getTag()); 203 outState.putString(KEY_UPDATES_FRAGMENT_TAG, 204 mPagerContactDetailUpdatesFragment.getTag()); 205 } 206 } 207 208 public void onRestoreInstanceState(Bundle savedState) { 209 mDetailFragmentTag = savedState.getString(KEY_DETAIL_FRAGMENT_TAG); 210 mUpdatesFragmentTag = savedState.getString(KEY_UPDATES_FRAGMENT_TAG); 211 } 212 213 public class ViewPagerAdapter extends FragmentPagerAdapter{ 214 215 public ViewPagerAdapter(FragmentManager fm) { 216 super(fm); 217 } 218 219 @Override 220 public Fragment getItem(int position) { 221 switch (position) { 222 case 0: 223 mPagerContactDetailFragment = new ContactDetailFragment(); 224 if (mContactData != null) { 225 mPagerContactDetailFragment.setData(mContactData.getLookupUri(), 226 mContactData); 227 } 228 mPagerContactDetailFragment.setListener(mContactDetailFragmentListener); 229 mPagerContactDetailFragment.setVerticalScrollListener(mVerticalScrollListener); 230 return mPagerContactDetailFragment; 231 case 1: 232 mPagerContactDetailUpdatesFragment = new ContactDetailUpdatesFragment(); 233 if (mContactData != null) { 234 mPagerContactDetailUpdatesFragment.setData(mContactData.getLookupUri(), 235 mContactData); 236 } 237 return mPagerContactDetailUpdatesFragment; 238 } 239 throw new IllegalStateException("No fragment at position " + position); 240 } 241 242 @Override 243 public int getCount() { 244 return FRAGMENT_COUNT; 245 } 246 } 247 248 private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() { 249 250 @Override 251 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 252 // The user is horizontally dragging the {@link ViewPager}, so send 253 // these scroll changes to the tab carousel. Ignore these events though if the carousel 254 // is actually controlling the {@link ViewPager} scrolls because it will already be 255 // in the correct position. 256 if (mViewPager.isFakeDragging()) { 257 return; 258 } 259 int x = (int) ((position + positionOffset) * 260 mTabCarousel.getAllowedHorizontalScrollLength()); 261 mTabCarousel.scrollTo(x, 0); 262 } 263 264 @Override 265 public void onPageSelected(int position) { 266 // Since a new page has been selected by the {@link ViewPager}, 267 // update the tab selection in the carousel. 268 mTabCarousel.setCurrentTab(position); 269 } 270 271 @Override 272 public void onPageScrollStateChanged(int state) {} 273 274 }; 275 276 private ContactDetailTabCarousel.Listener mTabCarouselListener = 277 new ContactDetailTabCarousel.Listener() { 278 279 @Override 280 public void onTouchDown() { 281 // The user just started scrolling the carousel, so begin "fake dragging" the 282 // {@link ViewPager} if it's not already doing so. 283 if (mViewPager.isFakeDragging()) { 284 return; 285 } 286 mViewPager.beginFakeDrag(); 287 } 288 289 @Override 290 public void onTouchUp() { 291 // The user just stopped scrolling the carousel, so stop "fake dragging" the 292 // {@link ViewPager} if was doing so before. 293 if (mViewPager.isFakeDragging()) { 294 mViewPager.endFakeDrag(); 295 } 296 } 297 298 @Override 299 public void onScrollChanged(int l, int t, int oldl, int oldt) { 300 // The user is scrolling the carousel, so send the scroll deltas to the 301 // {@link ViewPager} so it can move in sync. 302 if (mViewPager.isFakeDragging()) { 303 mViewPager.fakeDragBy(oldl-l); 304 } 305 } 306 307 @Override 308 public void onTabSelected(int position) { 309 // The user selected a tab, so update the {@link ViewPager} 310 mViewPager.setCurrentItem(position); 311 } 312 }; 313 314 private OnScrollListener mVerticalScrollListener = new OnScrollListener() { 315 316 @Override 317 public void onScroll( 318 AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 319 if (mTabCarousel == null) { 320 return; 321 } 322 // If the FIRST item is not visible on the screen, then the carousel must be pinned 323 // at the top of the screen. 324 if (firstVisibleItem != 0) { 325 mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength()); 326 return; 327 } 328 View topView = view.getChildAt(firstVisibleItem); 329 if (topView == null) { 330 return; 331 } 332 int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(), 333 -mTabCarousel.getAllowedVerticalScrollLength()); 334 mTabCarousel.setY(amtToScroll); 335 } 336 337 @Override 338 public void onScrollStateChanged(AbsListView view, int scrollState) {} 339 340 }; 341} 342