/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts.detail;
import com.android.contacts.ContactLoader;
import com.android.contacts.R;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.TextView;
/**
* This is a horizontally scrolling carousel with 2 tabs: one to see info about the contact and
* one to see updates from the contact.
*/
public class ContactDetailTabCarousel extends HorizontalScrollView implements OnTouchListener {
private static final String TAG = ContactDetailTabCarousel.class.getSimpleName();
private static final int TAB_INDEX_ABOUT = 0;
private static final int TAB_INDEX_UPDATES = 1;
private static final int TAB_COUNT = 2;
/** Tab width as defined as a fraction of the screen width */
private float mTabWidthScreenWidthFraction;
/** Tab height as defined as a fraction of the screen width */
private float mTabHeightScreenWidthFraction;
private ImageView mPhotoView;
private TextView mStatusView;
private ImageView mStatusPhotoView;
private Listener mListener;
private int mCurrentTab = TAB_INDEX_ABOUT;
private CarouselTab mAboutTab;
private CarouselTab mUpdatesTab;
/** Last Y coordinate of the carousel when the tab at the given index was selected */
private final float[] mYCoordinateArray = new float[TAB_COUNT];
private int mTabDisplayLabelHeight;
private boolean mScrollToCurrentTab = false;
private int mLastScrollPosition;
private int mAllowedHorizontalScrollLength = Integer.MIN_VALUE;
private int mAllowedVerticalScrollLength = Integer.MIN_VALUE;
private static final float MAX_ALPHA = 0.5f;
/**
* Interface for callbacks invoked when the user interacts with the carousel.
*/
public interface Listener {
public void onTouchDown();
public void onTouchUp();
public void onScrollChanged(int l, int t, int oldl, int oldt);
public void onTabSelected(int position);
}
public ContactDetailTabCarousel(Context context, AttributeSet attrs) {
super(context, attrs);
setOnTouchListener(this);
Resources resources = mContext.getResources();
mTabDisplayLabelHeight = resources.getDimensionPixelSize(
R.dimen.detail_tab_carousel_tab_label_height);
mTabWidthScreenWidthFraction = resources.getFraction(
R.fraction.tab_width_screen_width_percentage, 1, 1);
mTabHeightScreenWidthFraction = resources.getFraction(
R.fraction.tab_height_screen_width_percentage, 1, 1);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mAboutTab = (CarouselTab) findViewById(R.id.tab_about);
mAboutTab.setLabel(mContext.getString(R.string.contactDetailAbout));
mUpdatesTab = (CarouselTab) findViewById(R.id.tab_update);
mUpdatesTab.setLabel(mContext.getString(R.string.contactDetailUpdates));
mAboutTab.enableTouchInterceptor(mAboutTabTouchInterceptListener);
mUpdatesTab.enableTouchInterceptor(mUpdatesTabTouchInterceptListener);
// Retrieve the photo view for the "about" tab
mPhotoView = (ImageView) mAboutTab.findViewById(R.id.photo);
// Retrieve the social update views for the "updates" tab
mStatusView = (TextView) mUpdatesTab.findViewById(R.id.status);
mStatusPhotoView = (ImageView) mUpdatesTab.findViewById(R.id.status_photo);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int screenWidth = MeasureSpec.getSize(widthMeasureSpec);
// Compute the width of a tab as a fraction of the screen width
int tabWidth = (int) (mTabWidthScreenWidthFraction * screenWidth);
// Find the allowed scrolling length by subtracting the current visible screen width
// from the total length of the tabs.
mAllowedHorizontalScrollLength = tabWidth * TAB_COUNT - screenWidth;
int tabHeight = (int) (screenWidth * mTabHeightScreenWidthFraction);
// Set the child {@link LinearLayout} to be TAB_COUNT * the computed tab width so that the
// {@link LinearLayout}'s children (which are the tabs) will evenly split that width.
if (getChildCount() > 0) {
View child = getChildAt(0);
child.measure(MeasureSpec.makeMeasureSpec(TAB_COUNT * tabWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(tabHeight, MeasureSpec.EXACTLY));
}
mAllowedVerticalScrollLength = tabHeight - mTabDisplayLabelHeight;
setMeasuredDimension(
resolveSize(screenWidth, widthMeasureSpec),
resolveSize(tabHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mScrollToCurrentTab) {
mScrollToCurrentTab = false;
scrollTo(mCurrentTab == TAB_INDEX_ABOUT ? 0 : mAllowedHorizontalScrollLength, 0);
updateAlphaLayers();
}
}
private final OnClickListener mAboutTabTouchInterceptListener = new OnClickListener() {
@Override
public void onClick(View v) {
mListener.onTabSelected(TAB_INDEX_ABOUT);
}
};
private final OnClickListener mUpdatesTabTouchInterceptListener = new OnClickListener() {
@Override
public void onClick(View v) {
mListener.onTabSelected(TAB_INDEX_UPDATES);
}
};
private void updateAlphaLayers() {
mAboutTab.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
mAllowedHorizontalScrollLength);
mUpdatesTab.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA /
mAllowedHorizontalScrollLength);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
mListener.onScrollChanged(l, t, oldl, oldt);
mLastScrollPosition = l;
updateAlphaLayers();
}
/**
* Reset the carousel to the start position (i.e. because new data will be loaded in for a
* different contact).
*/
public void reset() {
scrollTo(0, 0);
setCurrentTab(0);
moveToYCoordinate(0, 0);
}
/**
* Set the current tab that should be restored when the view is first laid out.
*/
public void restoreCurrentTab(int position) {
setCurrentTab(position);
// It is only possible to scroll the view after onMeasure() has been called (where the
// allowed horizontal scroll length is determined). Hence, set a flag that will be read
// in onLayout() after the children and this view have finished being laid out.
mScrollToCurrentTab = true;
}
/**
* Restore the Y position of this view to the last manually requested value. This can be done
* after the parent has been re-laid out again, where this view's position could have been
* lost if the view laid outside its parent's bounds.
*/
public void restoreYCoordinate() {
setY(getStoredYCoordinateForTab(mCurrentTab));
}
/**
* Request that the view move to the given Y coordinate. Also store the Y coordinate as the
* last requested Y coordinate for the given tabIndex.
*/
public void moveToYCoordinate(int tabIndex, float y) {
setY(y);
storeYCoordinate(tabIndex, y);
}
/**
* Store this information as the last requested Y coordinate for the given tabIndex.
*/
public void storeYCoordinate(int tabIndex, float y) {
mYCoordinateArray[tabIndex] = y;
}
/**
* Returns the stored Y coordinate of this view the last time the user was on the selected
* tab given by tabIndex.
*/
public float getStoredYCoordinateForTab(int tabIndex) {
return mYCoordinateArray[tabIndex];
}
/**
* Returns the number of pixels that this view can be scrolled horizontally.
*/
public int getAllowedHorizontalScrollLength() {
return mAllowedHorizontalScrollLength;
}
/**
* Returns the number of pixels that this view can be scrolled vertically while still allowing
* the tab labels to still show.
*/
public int getAllowedVerticalScrollLength() {
return mAllowedVerticalScrollLength;
}
/**
* Updates the tab selection.
*/
public void setCurrentTab(int position) {
switch (position) {
case TAB_INDEX_ABOUT:
mAboutTab.showSelectedState();
mUpdatesTab.showDeselectedState();
break;
case TAB_INDEX_UPDATES:
mUpdatesTab.showSelectedState();
mAboutTab.showDeselectedState();
break;
default:
throw new IllegalStateException("Invalid tab position " + position);
}
mCurrentTab = position;
}
/**
* Loads the data from the Loader-Result. This is the only function that has to be called
* from the outside to fully setup the View
*/
public void loadData(ContactLoader.Result contactData) {
if (contactData == null) {
return;
}
// TODO: Move this into the {@link CarouselTab} class when the updates fragment code is more
// finalized
ContactDetailDisplayUtils.setPhoto(mContext, contactData, mPhotoView);
ContactDetailDisplayUtils.setSocialSnippet(mContext, contactData, mStatusView,
mStatusPhotoView);
}
/**
* Set the given {@link Listener} to handle carousel events.
*/
public void setListener(Listener listener) {
mListener = listener;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mListener.onTouchDown();
return true;
case MotionEvent.ACTION_UP:
mListener.onTouchUp();
return true;
}
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean interceptTouch = super.onInterceptTouchEvent(ev);
if (interceptTouch) {
mListener.onTouchDown();
}
return interceptTouch;
}
}