ContactDetailLayoutController.java revision 22cb663a251af60bc6beeb1954568c8e6a4c34e9
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.R; 21import com.android.contacts.activities.PeopleActivity.ContactDetailFragmentListener; 22 23import android.app.FragmentManager; 24import android.app.FragmentTransaction; 25import android.content.Context; 26import android.os.Bundle; 27import android.support.v4.view.ViewPager; 28import android.support.v4.view.ViewPager.OnPageChangeListener; 29import android.view.LayoutInflater; 30import android.view.View; 31import android.view.ViewGroup; 32import android.widget.AbsListView; 33import android.widget.AbsListView.OnScrollListener; 34 35/** 36 * Determines the layout of the contact card. 37 */ 38public class ContactDetailLayoutController { 39 40 public static final int FRAGMENT_COUNT = 2; 41 42 private static final String KEY_CONTACT_HAS_UPDATES = "contactHasUpdates"; 43 44 private enum LayoutMode { 45 TWO_COLUMN, VIEW_PAGER_AND_CAROUSEL, 46 } 47 48 private final LayoutInflater mLayoutInflater; 49 private final FragmentManager mFragmentManager; 50 51 private ContactDetailFragment mDetailFragment; 52 private ContactDetailUpdatesFragment mUpdatesFragment; 53 54 private View mDetailFragmentView; 55 private View mUpdatesFragmentView; 56 57 private final ViewPager mViewPager; 58 private final ContactDetailTabCarousel mTabCarousel; 59 private ContactDetailViewPagerAdapter mViewPagerAdapter; 60 61 private ContactDetailFragmentListener mContactDetailFragmentListener; 62 63 private ContactLoader.Result mContactData; 64 65 private boolean mContactHasUpdates; 66 67 private LayoutMode mLayoutMode; 68 69 public ContactDetailLayoutController(Context context, Bundle savedState, 70 FragmentManager fragmentManager, View viewContainer, ContactDetailFragmentListener 71 contactDetailFragmentListener) { 72 73 if (fragmentManager == null) { 74 throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController " 75 + "without a non-null FragmentManager"); 76 } 77 78 mLayoutInflater = (LayoutInflater) context.getSystemService( 79 Context.LAYOUT_INFLATER_SERVICE); 80 mFragmentManager = fragmentManager; 81 mContactDetailFragmentListener = contactDetailFragmentListener; 82 83 // Retrieve views in case this is view pager and carousel mode 84 mViewPager = (ViewPager) viewContainer.findViewById(R.id.pager); 85 mTabCarousel = (ContactDetailTabCarousel) viewContainer.findViewById(R.id.tab_carousel); 86 87 // Retrieve views in case this is 2-column layout mode 88 mDetailFragmentView = viewContainer.findViewById(R.id.about_fragment_container); 89 mUpdatesFragmentView = viewContainer.findViewById(R.id.updates_fragment_container); 90 91 // Determine the layout mode based on whether the {@link ViewPager} is null or not. If the 92 // {@link ViewPager} is null, then this is a wide screen and the content can be displayed 93 // in 2 columns side by side. If the {@link ViewPager} is non-null, then this is a narrow 94 // screen and the user will need to swipe to see all the data. 95 mLayoutMode = (mViewPager == null) ? LayoutMode.TWO_COLUMN : 96 LayoutMode.VIEW_PAGER_AND_CAROUSEL; 97 98 initialize(savedState); 99 } 100 101 private void initialize(Bundle savedState) { 102 boolean fragmentsAddedToFragmentManager = true; 103 mDetailFragment = (ContactDetailFragment) mFragmentManager.findFragmentByTag( 104 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG); 105 mUpdatesFragment = (ContactDetailUpdatesFragment) mFragmentManager.findFragmentByTag( 106 ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG); 107 108 // If the detail fragment was found in the {@link FragmentManager} then we don't need to add 109 // it again. Otherwise, create the fragments dynamically and remember to add them to the 110 // {@link FragmentManager}. 111 if (mDetailFragment == null) { 112 mDetailFragment = new ContactDetailFragment(); 113 mUpdatesFragment = new ContactDetailUpdatesFragment(); 114 fragmentsAddedToFragmentManager = false; 115 } 116 117 mDetailFragment.setListener(mContactDetailFragmentListener); 118 mDetailFragment.setVerticalScrollListener(mVerticalScrollListener); 119 mUpdatesFragment.setVerticalScrollListener(mVerticalScrollListener); 120 121 switch (mLayoutMode) { 122 case VIEW_PAGER_AND_CAROUSEL: { 123 mTabCarousel.setListener(mTabCarouselListener); 124 // Inflate 2 view containers to pass in as children to the {@link ViewPager}, 125 // which will in turn be the parents to the mDetailFragment and mUpdatesFragment 126 // since the fragments must have the same parent view IDs in both landscape and 127 // portrait layouts. 128 ViewGroup detailContainer = (ViewGroup) mLayoutInflater.inflate( 129 R.layout.contact_detail_about_fragment_container, mViewPager, false); 130 ViewGroup updatesContainer = (ViewGroup) mLayoutInflater.inflate( 131 R.layout.contact_detail_updates_fragment_container, mViewPager, false); 132 133 mViewPagerAdapter = new ContactDetailViewPagerAdapter(); 134 mViewPagerAdapter.setAboutFragmentView(detailContainer); 135 mViewPagerAdapter.setUpdatesFragmentView(updatesContainer); 136 137 mViewPager.addView(detailContainer); 138 mViewPager.addView(updatesContainer); 139 mViewPager.setAdapter(mViewPagerAdapter); 140 mViewPager.setOnPageChangeListener(mOnPageChangeListener); 141 142 FragmentTransaction transaction = mFragmentManager.beginTransaction(); 143 if (!fragmentsAddedToFragmentManager) { 144 transaction.add(R.id.about_fragment_container, mDetailFragment, 145 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG); 146 transaction.add(R.id.updates_fragment_container, mUpdatesFragment, 147 ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG); 148 } else { 149 transaction.show(mDetailFragment); 150 transaction.show(mUpdatesFragment); 151 } 152 transaction.commit(); 153 mFragmentManager.executePendingTransactions(); 154 break; 155 } 156 case TWO_COLUMN: { 157 if (!fragmentsAddedToFragmentManager) { 158 FragmentTransaction transaction = mFragmentManager.beginTransaction(); 159 transaction.add(R.id.about_fragment_container, mDetailFragment, 160 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG); 161 transaction.add(R.id.updates_fragment_container, mUpdatesFragment, 162 ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG); 163 transaction.commit(); 164 mFragmentManager.executePendingTransactions(); 165 } 166 } 167 } 168 169 if (savedState != null) { 170 if (savedState.getBoolean(KEY_CONTACT_HAS_UPDATES)) { 171 showContactWithUpdates(); 172 } else { 173 showContactWithoutUpdates(); 174 } 175 } 176 } 177 178 public void setContactData(ContactLoader.Result data) { 179 mContactData = data; 180 mContactHasUpdates = !data.getStreamItems().isEmpty(); 181 if (mContactHasUpdates) { 182 showContactWithUpdates(); 183 } else { 184 showContactWithoutUpdates(); 185 } 186 } 187 188 private void showContactWithUpdates() { 189 switch (mLayoutMode) { 190 case TWO_COLUMN: { 191 // Set the contact data (hide the static photo because the photo will already be in 192 // the header that scrolls with contact details). 193 mDetailFragment.setShowStaticPhoto(false); 194 // Show the updates fragment 195 mUpdatesFragmentView.setVisibility(View.VISIBLE); 196 break; 197 } 198 case VIEW_PAGER_AND_CAROUSEL: { 199 // Update and show the tab carousel 200 mTabCarousel.loadData(mContactData); 201 mTabCarousel.setVisibility(View.VISIBLE); 202 // Update ViewPager so that it has the max # of tabs (to show updates) 203 mViewPagerAdapter.setFragmentViewCount(FRAGMENT_COUNT); 204 break; 205 } 206 default: 207 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode); 208 } 209 210 if (mContactData != null) { 211 mDetailFragment.setData(mContactData.getLookupUri(), mContactData); 212 mUpdatesFragment.setData(mContactData.getLookupUri(), mContactData); 213 } 214 } 215 216 private void showContactWithoutUpdates() { 217 switch (mLayoutMode) { 218 case TWO_COLUMN: 219 // Show the static photo which is next to the list of scrolling contact details 220 mDetailFragment.setShowStaticPhoto(true); 221 // Hide the updates fragment 222 mUpdatesFragmentView.setVisibility(View.GONE); 223 break; 224 case VIEW_PAGER_AND_CAROUSEL: 225 // Hide the tab carousel 226 mTabCarousel.setVisibility(View.GONE); 227 // Update ViewPager so that it only has 1 tab and switch to the first indexed tab 228 mViewPagerAdapter.setFragmentViewCount(1); 229 mViewPager.setCurrentItem(0); 230 break; 231 default: 232 throw new IllegalStateException("Invalid LayoutMode " + mLayoutMode); 233 } 234 235 if (mContactData != null) { 236 mDetailFragment.setData(mContactData.getLookupUri(), mContactData); 237 } 238 } 239 240 public void onSaveInstanceState(Bundle outState) { 241 outState.putBoolean(KEY_CONTACT_HAS_UPDATES, mContactHasUpdates); 242 } 243 244 private OnPageChangeListener mOnPageChangeListener = new OnPageChangeListener() { 245 246 @Override 247 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 248 // The user is horizontally dragging the {@link ViewPager}, so send 249 // these scroll changes to the tab carousel. Ignore these events though if the carousel 250 // is actually controlling the {@link ViewPager} scrolls because it will already be 251 // in the correct position. 252 if (mViewPager.isFakeDragging()) { 253 return; 254 } 255 int x = (int) ((position + positionOffset) * 256 mTabCarousel.getAllowedHorizontalScrollLength()); 257 mTabCarousel.scrollTo(x, 0); 258 } 259 260 @Override 261 public void onPageSelected(int position) { 262 // Since a new page has been selected by the {@link ViewPager}, 263 // update the tab selection in the carousel. 264 mTabCarousel.setCurrentTab(position); 265 } 266 267 @Override 268 public void onPageScrollStateChanged(int state) {} 269 270 }; 271 272 private ContactDetailTabCarousel.Listener mTabCarouselListener = 273 new ContactDetailTabCarousel.Listener() { 274 275 @Override 276 public void onTouchDown() { 277 // The user just started scrolling the carousel, so begin "fake dragging" the 278 // {@link ViewPager} if it's not already doing so. 279 if (mViewPager.isFakeDragging()) { 280 return; 281 } 282 mViewPager.beginFakeDrag(); 283 } 284 285 @Override 286 public void onTouchUp() { 287 // The user just stopped scrolling the carousel, so stop "fake dragging" the 288 // {@link ViewPager} if was doing so before. 289 if (mViewPager.isFakeDragging()) { 290 mViewPager.endFakeDrag(); 291 } 292 } 293 294 @Override 295 public void onScrollChanged(int l, int t, int oldl, int oldt) { 296 // The user is scrolling the carousel, so send the scroll deltas to the 297 // {@link ViewPager} so it can move in sync. 298 if (mViewPager.isFakeDragging()) { 299 mViewPager.fakeDragBy(oldl-l); 300 } 301 } 302 303 @Override 304 public void onTabSelected(int position) { 305 // The user selected a tab, so update the {@link ViewPager} 306 mViewPager.setCurrentItem(position); 307 } 308 }; 309 310 private OnScrollListener mVerticalScrollListener = new OnScrollListener() { 311 312 @Override 313 public void onScroll( 314 AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 315 if (mTabCarousel == null) { 316 return; 317 } 318 // If the FIRST item is not visible on the screen, then the carousel must be pinned 319 // at the top of the screen. 320 if (firstVisibleItem != 0) { 321 mTabCarousel.setY(-mTabCarousel.getAllowedVerticalScrollLength()); 322 return; 323 } 324 View topView = view.getChildAt(firstVisibleItem); 325 if (topView == null) { 326 return; 327 } 328 int amtToScroll = Math.max((int) view.getChildAt(firstVisibleItem).getY(), 329 -mTabCarousel.getAllowedVerticalScrollLength()); 330 mTabCarousel.setY(amtToScroll); 331 } 332 333 @Override 334 public void onScrollStateChanged(AbsListView view, int scrollState) {} 335 336 }; 337} 338