• 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