Home | History | Annotate | Download | only in detail
      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 {
     78         public void onTouchDown();
     79         public void onTouchUp();
     80         public void onScrollChanged(int l, int t, int oldl, int oldt);
     81         public void onTabSelected(int position);
     82     }
     83 
     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
     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
    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
    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 
    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
    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      * Reset the carousel to the start position (i.e. because new data will be loaded in for a
    184      * different contact).
    185      */
    186     public void reset() {
    187         scrollTo(0, 0);
    188         setCurrentTab(0);
    189         moveToYCoordinate(0, 0);
    190     }
    191 
    192     /**
    193      * Set the current tab that should be restored when the view is first laid out.
    194      */
    195     public void restoreCurrentTab(int position) {
    196         setCurrentTab(position);
    197         // It is only possible to scroll the view after onMeasure() has been called (where the
    198         // allowed horizontal scroll length is determined). Hence, set a flag that will be read
    199         // in onLayout() after the children and this view have finished being laid out.
    200         mScrollToCurrentTab = true;
    201     }
    202 
    203     /**
    204      * Restore the Y position of this view to the last manually requested value. This can be done
    205      * after the parent has been re-laid out again, where this view's position could have been
    206      * lost if the view laid outside its parent's bounds.
    207      */
    208     public void restoreYCoordinate() {
    209         setY(getStoredYCoordinateForTab(mCurrentTab));
    210     }
    211 
    212     /**
    213      * Request that the view move to the given Y coordinate. Also store the Y coordinate as the
    214      * last requested Y coordinate for the given tabIndex.
    215      */
    216     public void moveToYCoordinate(int tabIndex, float y) {
    217         setY(y);
    218         storeYCoordinate(tabIndex, y);
    219     }
    220 
    221     /**
    222      * Store this information as the last requested Y coordinate for the given tabIndex.
    223      */
    224     public void storeYCoordinate(int tabIndex, float y) {
    225         mYCoordinateArray[tabIndex] = y;
    226     }
    227 
    228     /**
    229      * Returns the stored Y coordinate of this view the last time the user was on the selected
    230      * tab given by tabIndex.
    231      */
    232     public float getStoredYCoordinateForTab(int tabIndex) {
    233         return mYCoordinateArray[tabIndex];
    234     }
    235 
    236     /**
    237      * Returns the number of pixels that this view can be scrolled horizontally.
    238      */
    239     public int getAllowedHorizontalScrollLength() {
    240         return mAllowedHorizontalScrollLength;
    241     }
    242 
    243     /**
    244      * Returns the number of pixels that this view can be scrolled vertically while still allowing
    245      * the tab labels to still show.
    246      */
    247     public int getAllowedVerticalScrollLength() {
    248         return mAllowedVerticalScrollLength;
    249     }
    250 
    251     /**
    252      * Updates the tab selection.
    253      */
    254     public void setCurrentTab(int position) {
    255         switch (position) {
    256             case TAB_INDEX_ABOUT:
    257                 mAboutTab.showSelectedState();
    258                 mUpdatesTab.showDeselectedState();
    259                 break;
    260             case TAB_INDEX_UPDATES:
    261                 mUpdatesTab.showSelectedState();
    262                 mAboutTab.showDeselectedState();
    263                 break;
    264             default:
    265                 throw new IllegalStateException("Invalid tab position " + position);
    266         }
    267         mCurrentTab = position;
    268     }
    269 
    270     /**
    271      * Loads the data from the Loader-Result. This is the only function that has to be called
    272      * from the outside to fully setup the View
    273      */
    274     public void loadData(ContactLoader.Result contactData) {
    275         if (contactData == null) {
    276             return;
    277         }
    278 
    279         // TODO: Move this into the {@link CarouselTab} class when the updates fragment code is more
    280         // finalized
    281         ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView);
    282         ContactDetailDisplayUtils.setSocialSnippet(mContext, contactData, mStatusView,
    283                 mStatusPhotoView);
    284     }
    285 
    286     /**
    287      * Set the given {@link Listener} to handle carousel events.
    288      */
    289     public void setListener(Listener listener) {
    290         mListener = listener;
    291     }
    292 
    293     @Override
    294     public boolean onTouch(View v, MotionEvent event) {
    295         switch (event.getAction()) {
    296             case MotionEvent.ACTION_DOWN:
    297                 mListener.onTouchDown();
    298                 return true;
    299             case MotionEvent.ACTION_UP:
    300                 mListener.onTouchUp();
    301                 return true;
    302         }
    303         return super.onTouchEvent(event);
    304     }
    305 
    306     @Override
    307     public boolean onInterceptTouchEvent(MotionEvent ev) {
    308         boolean interceptTouch = super.onInterceptTouchEvent(ev);
    309         if (interceptTouch) {
    310             mListener.onTouchDown();
    311         }
    312         return interceptTouch;
    313     }
    314 }
    315