• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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