/* * 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); } } } }