blob: 89e426549e6170f6267c0f8035f3e11e82e8945f [file] [log] [blame]
/*
* Copyright (C) 2010 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;
import com.android.contacts.ui.widget.DontPressWithParentImageView;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.QuickContactBadge;
import android.widget.TextView;
import android.widget.ImageView.ScaleType;
/**
* A custom view for an item in the contact list.
*/
public class ContactListItemView extends ViewGroup {
private static final int QUICK_CONTACT_BADGE_STYLE =
com.android.internal.R.attr.quickContactBadgeStyleWindowMedium;
private final Context mContext;
private final int mPreferredHeight;
private final int mVerticalDividerMargin;
private final int mPaddingTop;
private final int mPaddingRight;
private final int mPaddingBottom;
private final int mPaddingLeft;
private final int mGapBetweenImageAndText;
private final int mGapBetweenLabelAndData;
private final int mCallButtonPadding;
private final int mPresenceIconMargin;
private final int mHeaderTextWidth;
private boolean mHorizontalDividerVisible;
private Drawable mHorizontalDividerDrawable;
private int mHorizontalDividerHeight;
private boolean mVerticalDividerVisible;
private Drawable mVerticalDividerDrawable;
private int mVerticalDividerWidth;
private boolean mHeaderVisible;
private Drawable mHeaderBackgroundDrawable;
private int mHeaderBackgroundHeight;
private TextView mHeaderTextView;
private QuickContactBadge mQuickContact;
private ImageView mPhotoView;
private TextView mNameTextView;
private DontPressWithParentImageView mCallButton;
private TextView mLabelView;
private TextView mDataView;
private TextView mSnippetView;
private ImageView mPresenceIcon;
private int mPhotoViewWidth;
private int mPhotoViewHeight;
private int mLine1Height;
private int mLine2Height;
private int mLine3Height;
private OnClickListener mCallButtonClickListener;
public ContactListItemView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// Obtain preferred item height from the current theme
TypedArray a = context.obtainStyledAttributes(null, com.android.internal.R.styleable.Theme);
mPreferredHeight =
a.getDimensionPixelSize(android.R.styleable.Theme_listPreferredItemHeight, 0);
a.recycle();
Resources resources = context.getResources();
mVerticalDividerMargin =
resources.getDimensionPixelOffset(R.dimen.list_item_vertical_divider_margin);
mPaddingTop =
resources.getDimensionPixelOffset(R.dimen.list_item_padding_top);
mPaddingBottom =
resources.getDimensionPixelOffset(R.dimen.list_item_padding_bottom);
mPaddingLeft =
resources.getDimensionPixelOffset(R.dimen.list_item_padding_left);
mPaddingRight =
resources.getDimensionPixelOffset(R.dimen.list_item_padding_right);
mGapBetweenImageAndText =
resources.getDimensionPixelOffset(R.dimen.list_item_gap_between_image_and_text);
mGapBetweenLabelAndData =
resources.getDimensionPixelOffset(R.dimen.list_item_gap_between_label_and_data);
mCallButtonPadding =
resources.getDimensionPixelOffset(R.dimen.list_item_call_button_padding);
mPresenceIconMargin =
resources.getDimensionPixelOffset(R.dimen.list_item_presence_icon_margin);
mHeaderTextWidth =
resources.getDimensionPixelOffset(R.dimen.list_item_header_text_width);
}
/**
* Installs a call button listener.
*/
public void setOnCallButtonClickListener(OnClickListener callButtonClickListener) {
mCallButtonClickListener = callButtonClickListener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We will match parent's width and wrap content vertically, but make sure
// height is no less than listPreferredItemHeight.
int width = resolveSize(0, widthMeasureSpec);
int height = 0;
mLine1Height = 0;
mLine2Height = 0;
mLine3Height = 0;
// Obtain the natural dimensions of the name text (we only care about height)
mNameTextView.measure(0, 0);
mLine1Height = mNameTextView.getMeasuredHeight();
if (isVisible(mLabelView)) {
mLabelView.measure(0, 0);
mLine2Height = mLabelView.getMeasuredHeight();
}
if (isVisible(mDataView)) {
mDataView.measure(0, 0);
mLine2Height = Math.max(mLine2Height, mDataView.getMeasuredHeight());
}
if (isVisible(mSnippetView)) {
mSnippetView.measure(0, 0);
mLine3Height = mSnippetView.getMeasuredHeight();
}
height += mLine1Height + mLine2Height + mLine3Height;
if (isVisible(mCallButton)) {
mCallButton.measure(0, 0);
}
if (isVisible(mPresenceIcon)) {
mPresenceIcon.measure(0, 0);
}
ensurePhotoViewSize();
height = Math.max(height, mPhotoViewHeight);
height = Math.max(height, mPreferredHeight);
if (mHeaderVisible) {
ensureHeaderBackground();
mHeaderTextView.measure(
MeasureSpec.makeMeasureSpec(mHeaderTextWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
height += mHeaderBackgroundDrawable.getIntrinsicHeight();
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int height = bottom - top;
int width = right - left;
// Determine the vertical bounds by laying out the header first.
int topBound = 0;
if (mHeaderVisible) {
mHeaderBackgroundDrawable.setBounds(
0,
0,
width,
mHeaderBackgroundHeight);
mHeaderTextView.layout(0, 0, width, mHeaderBackgroundHeight);
topBound += mHeaderBackgroundHeight;
}
// Positions of views on the left are fixed and so are those on the right side.
// The stretchable part of the layout is in the middle. So, we will start off
// by laying out the left and right sides. Then we will allocate the remainder
// to the text fields in the middle.
// Left side
int leftBound = mPaddingLeft;
View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
if (photoView != null) {
// Center the photo vertically
int photoTop = topBound + (height - topBound - mPhotoViewHeight) / 2;
photoView.layout(
leftBound,
photoTop,
leftBound + mPhotoViewWidth,
photoTop + mPhotoViewHeight);
leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
}
// Right side
int rightBound = right;
if (isVisible(mCallButton)) {
int buttonWidth = mCallButton.getMeasuredWidth();
rightBound -= buttonWidth;
mCallButton.layout(
rightBound,
topBound,
rightBound + buttonWidth,
height);
mVerticalDividerVisible = true;
ensureVerticalDivider();
rightBound -= mVerticalDividerWidth;
mVerticalDividerDrawable.setBounds(
rightBound,
topBound + mVerticalDividerMargin,
rightBound + mVerticalDividerWidth,
height - mVerticalDividerMargin);
} else {
mVerticalDividerVisible = false;
}
if (isVisible(mPresenceIcon)) {
int iconWidth = mPresenceIcon.getMeasuredWidth();
rightBound -= mPresenceIconMargin + iconWidth;
mPresenceIcon.layout(
rightBound,
topBound,
rightBound + iconWidth,
height);
}
if (mHorizontalDividerVisible) {
ensureHorizontalDivider();
mHorizontalDividerDrawable.setBounds(
0,
height - mHorizontalDividerHeight,
width,
height);
}
topBound += mPaddingTop;
int bottomBound = height - mPaddingBottom;
// Text lines, centered vertically
rightBound -= mPaddingRight;
// Center text vertically
int totalTextHeight = mLine1Height + mLine2Height + mLine3Height;
int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
mNameTextView.layout(leftBound,
textTopBound,
rightBound,
textTopBound + mLine1Height);
int dataLeftBound = leftBound;
if (isVisible(mLabelView)) {
dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
mLabelView.layout(leftBound,
textTopBound + mLine1Height,
dataLeftBound,
textTopBound + mLine1Height + mLine2Height);
dataLeftBound += mGapBetweenLabelAndData;
}
if (isVisible(mDataView)) {
mDataView.layout(dataLeftBound,
textTopBound + mLine1Height,
rightBound,
textTopBound + mLine1Height + mLine2Height);
}
if (isVisible(mSnippetView)) {
mSnippetView.layout(leftBound,
textTopBound + mLine1Height + mLine2Height,
rightBound,
textTopBound + mLine1Height + mLine2Height + mLine3Height);
}
}
private boolean isVisible(View view) {
return view != null && view.getVisibility() == View.VISIBLE;
}
/**
* Loads the drawable for the vertical divider if it has not yet been loaded.
*/
private void ensureVerticalDivider() {
if (mVerticalDividerDrawable == null) {
mVerticalDividerDrawable = mContext.getResources().getDrawable(
R.drawable.divider_vertical_dark);
mVerticalDividerWidth = mVerticalDividerDrawable.getIntrinsicWidth();
}
}
/**
* Loads the drawable for the horizontal divider if it has not yet been loaded.
*/
private void ensureHorizontalDivider() {
if (mHorizontalDividerDrawable == null) {
mHorizontalDividerDrawable = mContext.getResources().getDrawable(
com.android.internal.R.drawable.divider_horizontal_dark_opaque);
mHorizontalDividerHeight = mHorizontalDividerDrawable.getIntrinsicHeight();
}
}
/**
* Loads the drawable for the header background if it has not yet been loaded.
*/
private void ensureHeaderBackground() {
if (mHeaderBackgroundDrawable == null) {
mHeaderBackgroundDrawable = mContext.getResources().getDrawable(
android.R.drawable.dark_header);
mHeaderBackgroundHeight = mHeaderBackgroundDrawable.getIntrinsicHeight();
}
}
/**
* Extracts width and height from the style
*/
private void ensurePhotoViewSize() {
if (mPhotoViewWidth == 0 && mPhotoViewHeight == 0) {
TypedArray a = mContext.obtainStyledAttributes(null,
com.android.internal.R.styleable.ViewGroup_Layout,
QUICK_CONTACT_BADGE_STYLE, 0);
mPhotoViewWidth = a.getLayoutDimension(
android.R.styleable.ViewGroup_Layout_layout_width,
ViewGroup.LayoutParams.WRAP_CONTENT);
mPhotoViewHeight = a.getLayoutDimension(
android.R.styleable.ViewGroup_Layout_layout_height,
ViewGroup.LayoutParams.WRAP_CONTENT);
a.recycle();
}
}
@Override
public void dispatchDraw(Canvas canvas) {
if (mHeaderVisible) {
mHeaderBackgroundDrawable.draw(canvas);
}
if (mHorizontalDividerVisible) {
mHorizontalDividerDrawable.draw(canvas);
}
if (mVerticalDividerVisible) {
mVerticalDividerDrawable.draw(canvas);
}
super.dispatchDraw(canvas);
}
/**
* Sets the flag that determines whether a divider should drawn at the bottom
* of the view.
*/
public void setDividerVisible(boolean visible) {
mHorizontalDividerVisible = visible;
}
/**
* Sets section header or makes it invisible if the title is null.
*/
public void setSectionHeader(String title) {
if (!TextUtils.isEmpty(title)) {
if (mHeaderTextView == null) {
mHeaderTextView = new TextView(mContext);
mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD);
mHeaderTextView.setTextColor(mContext.getResources()
.getColor(com.android.internal.R.color.dim_foreground_dark));
mHeaderTextView.setTextSize(14);
mHeaderTextView.setGravity(Gravity.CENTER);
addView(mHeaderTextView);
}
mHeaderTextView.setText(title);
mHeaderTextView.setVisibility(View.VISIBLE);
mHeaderVisible = true;
} else {
if (mHeaderTextView != null) {
mHeaderTextView.setVisibility(View.GONE);
}
mHeaderVisible = false;
}
}
/**
* Returns the quick contact badge, creating it if necessary.
*/
public QuickContactBadge getQuickContact() {
if (mQuickContact == null) {
mQuickContact = new QuickContactBadge(mContext, null, QUICK_CONTACT_BADGE_STYLE);
mQuickContact.setExcludeMimes(new String[] { Contacts.CONTENT_ITEM_TYPE });
addView(mQuickContact);
}
return mQuickContact;
}
/**
* Returns the photo view, creating it if necessary.
*/
public ImageView getPhotoView() {
if (mPhotoView == null) {
mPhotoView = new ImageView(mContext, null, QUICK_CONTACT_BADGE_STYLE);
// Quick contact style used above will set a background - remove it
mPhotoView.setBackgroundDrawable(null);
addView(mPhotoView);
}
return mPhotoView;
}
/**
* Returns the text view for the contact name, creating it if necessary.
*/
public TextView getNameTextView() {
if (mNameTextView == null) {
mNameTextView = new TextView(mContext);
mNameTextView.setSingleLine(true);
mNameTextView.setEllipsize(TruncateAt.MARQUEE);
mNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Large);
mNameTextView.setGravity(Gravity.CENTER_VERTICAL);
addView(mNameTextView);
}
return mNameTextView;
}
/**
* Adds a call button using the supplied arguments as an id and tag.
*/
public void showCallButton(int id, int tag) {
if (mCallButton == null) {
mCallButton = new DontPressWithParentImageView(mContext, null);
mCallButton.setId(id);
mCallButton.setOnClickListener(mCallButtonClickListener);
mCallButton.setBackgroundResource(R.drawable.call_background);
mCallButton.setImageResource(android.R.drawable.sym_action_call);
mCallButton.setPadding(mCallButtonPadding, 0, mCallButtonPadding, 0);
mCallButton.setScaleType(ScaleType.CENTER);
addView(mCallButton);
}
mCallButton.setTag(tag);
mCallButton.setVisibility(View.VISIBLE);
}
public void hideCallButton() {
if (mCallButton != null) {
mCallButton.setVisibility(View.GONE);
}
}
/**
* Adds or updates a text view for the data label.
*/
public void setLabel(CharSequence text) {
if (TextUtils.isEmpty(text)) {
if (mLabelView != null) {
mLabelView.setVisibility(View.GONE);
}
} else {
getLabelView();
mLabelView.setText(text);
mLabelView.setVisibility(VISIBLE);
}
}
/**
* Adds or updates a text view for the data label.
*/
public void setLabel(char[] text, int size) {
if (text == null || size == 0) {
if (mLabelView != null) {
mLabelView.setVisibility(View.GONE);
}
} else {
getLabelView();
mLabelView.setText(text, 0, size);
mLabelView.setVisibility(VISIBLE);
}
}
/**
* Returns the text view for the data label, creating it if necessary.
*/
public TextView getLabelView() {
if (mLabelView == null) {
mLabelView = new TextView(mContext);
mLabelView.setSingleLine(true);
mLabelView.setEllipsize(TruncateAt.MARQUEE);
mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
addView(mLabelView);
}
return mLabelView;
}
/**
* Adds or updates a text view for the data element.
*/
public void setData(char[] text, int size) {
if (text == null || size == 0) {
if (mDataView != null) {
mDataView.setVisibility(View.GONE);
}
return;
} else {
getDataView();
mDataView.setText(text, 0, size);
mDataView.setVisibility(VISIBLE);
}
}
/**
* Returns the text view for the data text, creating it if necessary.
*/
public TextView getDataView() {
if (mDataView == null) {
mDataView = new TextView(mContext);
mDataView.setSingleLine(true);
mDataView.setEllipsize(TruncateAt.MARQUEE);
mDataView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
addView(mDataView);
}
return mDataView;
}
/**
* Adds or updates a text view for the search snippet.
*/
public void setSnippet(CharSequence text) {
if (TextUtils.isEmpty(text)) {
if (mSnippetView != null) {
mSnippetView.setVisibility(View.GONE);
}
} else {
getSnippetView();
mSnippetView.setText(text);
mSnippetView.setVisibility(VISIBLE);
}
}
/**
* Returns the text view for the search snippet, creating it if necessary.
*/
public TextView getSnippetView() {
if (mSnippetView == null) {
mSnippetView = new TextView(mContext);
mSnippetView.setSingleLine(true);
mSnippetView.setEllipsize(TruncateAt.MARQUEE);
mSnippetView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
mSnippetView.setTypeface(mSnippetView.getTypeface(), Typeface.BOLD);
addView(mSnippetView);
}
return mSnippetView;
}
/**
* Adds or updates the presence icon view.
*/
public void setPresence(Drawable icon) {
if (icon != null) {
if (mPresenceIcon == null) {
mPresenceIcon = new ImageView(mContext);
addView(mPresenceIcon);
}
mPresenceIcon.setImageDrawable(icon);
mPresenceIcon.setScaleType(ScaleType.CENTER);
mPresenceIcon.setVisibility(View.VISIBLE);
} else {
if (mPresenceIcon != null) {
mPresenceIcon.setVisibility(View.GONE);
}
}
}
}