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.R; 21 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.util.AttributeSet; 25 import android.view.MotionEvent; 26 import android.view.View; 27 import android.view.View.OnTouchListener; 28 import android.widget.HorizontalScrollView; 29 import android.widget.ImageView; 30 import android.widget.TextView; 31 32 /** 33 * This is a horizontally scrolling carousel with 2 tabs: one to see info about the contact and 34 * one to see updates from the contact. 35 */ 36 public class ContactDetailTabCarousel extends HorizontalScrollView implements OnTouchListener { 37 38 private static final String TAG = ContactDetailTabCarousel.class.getSimpleName(); 39 40 private static final int TAB_INDEX_ABOUT = 0; 41 private static final int TAB_INDEX_UPDATES = 1; 42 private static final int TAB_COUNT = 2; 43 44 /** Tab width as defined as a fraction of the screen width */ 45 private float mTabWidthScreenWidthFraction; 46 47 /** Tab height as defined as a fraction of the screen width */ 48 private float mTabHeightScreenWidthFraction; 49 50 private ImageView mPhotoView; 51 private TextView mStatusView; 52 private ImageView mStatusPhotoView; 53 54 private Listener mListener; 55 56 private int mCurrentTab = TAB_INDEX_ABOUT; 57 58 private CarouselTab mAboutTab; 59 private CarouselTab mUpdatesTab; 60 61 /** Last Y coordinate of the carousel when the tab at the given index was selected */ 62 private final float[] mYCoordinateArray = new float[TAB_COUNT]; 63 64 private int mTabDisplayLabelHeight; 65 66 private boolean mScrollToCurrentTab = false; 67 private int mLastScrollPosition; 68 69 private int mAllowedHorizontalScrollLength = Integer.MIN_VALUE; 70 private int mAllowedVerticalScrollLength = Integer.MIN_VALUE; 71 72 private static final float MAX_ALPHA = 0.5f; 73 74 /** 75 * Interface for callbacks invoked when the user interacts with the carousel. 76 */ 77 public interface Listener { onTouchDown()78 public void onTouchDown(); onTouchUp()79 public void onTouchUp(); onScrollChanged(int l, int t, int oldl, int oldt)80 public void onScrollChanged(int l, int t, int oldl, int oldt); onTabSelected(int position)81 public void onTabSelected(int position); 82 } 83 ContactDetailTabCarousel(Context context, AttributeSet attrs)84 public ContactDetailTabCarousel(Context context, AttributeSet attrs) { 85 super(context, attrs); 86 87 setOnTouchListener(this); 88 89 Resources resources = mContext.getResources(); 90 mTabDisplayLabelHeight = resources.getDimensionPixelSize( 91 R.dimen.detail_tab_carousel_tab_label_height); 92 mTabWidthScreenWidthFraction = resources.getFraction( 93 R.fraction.tab_width_screen_width_percentage, 1, 1); 94 mTabHeightScreenWidthFraction = resources.getFraction( 95 R.fraction.tab_height_screen_width_percentage, 1, 1); 96 } 97 98 @Override onFinishInflate()99 protected void onFinishInflate() { 100 super.onFinishInflate(); 101 mAboutTab = (CarouselTab) findViewById(R.id.tab_about); 102 mAboutTab.setLabel(mContext.getString(R.string.contactDetailAbout)); 103 104 mUpdatesTab = (CarouselTab) findViewById(R.id.tab_update); 105 mUpdatesTab.setLabel(mContext.getString(R.string.contactDetailUpdates)); 106 107 mAboutTab.enableTouchInterceptor(mAboutTabTouchInterceptListener); 108 mUpdatesTab.enableTouchInterceptor(mUpdatesTabTouchInterceptListener); 109 110 // Retrieve the photo view for the "about" tab 111 mPhotoView = (ImageView) mAboutTab.findViewById(R.id.photo); 112 113 // Retrieve the social update views for the "updates" tab 114 mStatusView = (TextView) mUpdatesTab.findViewById(R.id.status); 115 mStatusPhotoView = (ImageView) mUpdatesTab.findViewById(R.id.status_photo); 116 } 117 118 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)119 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 120 int screenWidth = MeasureSpec.getSize(widthMeasureSpec); 121 // Compute the width of a tab as a fraction of the screen width 122 int tabWidth = (int) (mTabWidthScreenWidthFraction * screenWidth); 123 124 // Find the allowed scrolling length by subtracting the current visible screen width 125 // from the total length of the tabs. 126 mAllowedHorizontalScrollLength = tabWidth * TAB_COUNT - screenWidth; 127 128 int tabHeight = (int) (screenWidth * mTabHeightScreenWidthFraction); 129 // Set the child {@link LinearLayout} to be TAB_COUNT * the computed tab width so that the 130 // {@link LinearLayout}'s children (which are the tabs) will evenly split that width. 131 if (getChildCount() > 0) { 132 View child = getChildAt(0); 133 child.measure(MeasureSpec.makeMeasureSpec(TAB_COUNT * tabWidth, MeasureSpec.EXACTLY), 134 MeasureSpec.makeMeasureSpec(tabHeight, MeasureSpec.EXACTLY)); 135 } 136 137 mAllowedVerticalScrollLength = tabHeight - mTabDisplayLabelHeight; 138 setMeasuredDimension( 139 resolveSize(screenWidth, widthMeasureSpec), 140 resolveSize(tabHeight, heightMeasureSpec)); 141 } 142 143 @Override onLayout(boolean changed, int l, int t, int r, int b)144 protected void onLayout(boolean changed, int l, int t, int r, int b) { 145 super.onLayout(changed, l, t, r, b); 146 if (mScrollToCurrentTab) { 147 mScrollToCurrentTab = false; 148 scrollTo(mCurrentTab == TAB_INDEX_ABOUT ? 0 : mAllowedHorizontalScrollLength, 0); 149 updateAlphaLayers(); 150 } 151 } 152 153 private final OnClickListener mAboutTabTouchInterceptListener = new OnClickListener() { 154 @Override 155 public void onClick(View v) { 156 mListener.onTabSelected(TAB_INDEX_ABOUT); 157 } 158 }; 159 160 private final OnClickListener mUpdatesTabTouchInterceptListener = new OnClickListener() { 161 @Override 162 public void onClick(View v) { 163 mListener.onTabSelected(TAB_INDEX_UPDATES); 164 } 165 }; 166 updateAlphaLayers()167 private void updateAlphaLayers() { 168 mAboutTab.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA / 169 mAllowedHorizontalScrollLength); 170 mUpdatesTab.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA / 171 mAllowedHorizontalScrollLength); 172 } 173 174 @Override onScrollChanged(int l, int t, int oldl, int oldt)175 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 176 super.onScrollChanged(l, t, oldl, oldt); 177 mListener.onScrollChanged(l, t, oldl, oldt); 178 mLastScrollPosition = l; 179 updateAlphaLayers(); 180 } 181 182 /** 183 * Set the current tab that should be restored when the view is first laid out. 184 */ restoreCurrentTab(int position)185 public void restoreCurrentTab(int position) { 186 setCurrentTab(position); 187 // It is only possible to scroll the view after onMeasure() has been called (where the 188 // allowed horizontal scroll length is determined). Hence, set a flag that will be read 189 // in onLayout() after the children and this view have finished being laid out. 190 mScrollToCurrentTab = true; 191 } 192 193 /** 194 * Restore the Y position of this view to the last manually requested value. This can be done 195 * after the parent has been re-laid out again, where this view's position could have been 196 * lost if the view laid outside its parent's bounds. 197 */ restoreYCoordinate()198 public void restoreYCoordinate() { 199 setY(getStoredYCoordinateForTab(mCurrentTab)); 200 } 201 202 /** 203 * Request that the view move to the given Y coordinate. Also store the Y coordinate as the 204 * last requested Y coordinate for the given tabIndex. 205 */ moveToYCoordinate(int tabIndex, float y)206 public void moveToYCoordinate(int tabIndex, float y) { 207 setY(y); 208 storeYCoordinate(tabIndex, y); 209 } 210 211 /** 212 * Store this information as the last requested Y coordinate for the given tabIndex. 213 */ storeYCoordinate(int tabIndex, float y)214 public void storeYCoordinate(int tabIndex, float y) { 215 mYCoordinateArray[tabIndex] = y; 216 } 217 218 /** 219 * Returns the stored Y coordinate of this view the last time the user was on the selected 220 * tab given by tabIndex. 221 */ getStoredYCoordinateForTab(int tabIndex)222 public float getStoredYCoordinateForTab(int tabIndex) { 223 return mYCoordinateArray[tabIndex]; 224 } 225 226 /** 227 * Returns the number of pixels that this view can be scrolled horizontally. 228 */ getAllowedHorizontalScrollLength()229 public int getAllowedHorizontalScrollLength() { 230 return mAllowedHorizontalScrollLength; 231 } 232 233 /** 234 * Returns the number of pixels that this view can be scrolled vertically while still allowing 235 * the tab labels to still show. 236 */ getAllowedVerticalScrollLength()237 public int getAllowedVerticalScrollLength() { 238 return mAllowedVerticalScrollLength; 239 } 240 241 /** 242 * Updates the tab selection. 243 */ setCurrentTab(int position)244 public void setCurrentTab(int position) { 245 switch (position) { 246 case TAB_INDEX_ABOUT: 247 mAboutTab.showSelectedState(); 248 mUpdatesTab.showDeselectedState(); 249 break; 250 case TAB_INDEX_UPDATES: 251 mUpdatesTab.showSelectedState(); 252 mAboutTab.showDeselectedState(); 253 break; 254 default: 255 throw new IllegalStateException("Invalid tab position " + position); 256 } 257 mCurrentTab = position; 258 } 259 260 /** 261 * Loads the data from the Loader-Result. This is the only function that has to be called 262 * from the outside to fully setup the View 263 */ loadData(ContactLoader.Result contactData)264 public void loadData(ContactLoader.Result contactData) { 265 if (contactData == null) { 266 return; 267 } 268 269 // TODO: Move this into the {@link CarouselTab} class when the updates fragment code is more 270 // finalized 271 ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView); 272 ContactDetailDisplayUtils.setSocialSnippet(mContext, contactData, mStatusView, 273 mStatusPhotoView); 274 } 275 276 /** 277 * Set the given {@link Listener} to handle carousel events. 278 */ setListener(Listener listener)279 public void setListener(Listener listener) { 280 mListener = listener; 281 } 282 283 @Override onTouch(View v, MotionEvent event)284 public boolean onTouch(View v, MotionEvent event) { 285 switch (event.getAction()) { 286 case MotionEvent.ACTION_DOWN: 287 mListener.onTouchDown(); 288 return true; 289 case MotionEvent.ACTION_UP: 290 mListener.onTouchUp(); 291 return true; 292 } 293 return super.onTouchEvent(event); 294 } 295 296 @Override onInterceptTouchEvent(MotionEvent ev)297 public boolean onInterceptTouchEvent(MotionEvent ev) { 298 boolean interceptTouch = super.onInterceptTouchEvent(ev); 299 if (interceptTouch) { 300 mListener.onTouchDown(); 301 } 302 return interceptTouch; 303 } 304 } 305