ExpandingEntryCardView.java revision 2d48b5ae6664a7ae0ed9941f55fae9dc327bd640
1/* 2 * Copyright (C) 2014 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 */ 16package com.android.contacts.quickcontact; 17 18import com.android.contacts.R; 19 20import android.animation.Animator; 21import android.animation.AnimatorListenerAdapter; 22import android.animation.ValueAnimator; 23import android.content.Context; 24import android.content.Intent; 25import android.content.res.Resources; 26import android.graphics.drawable.Drawable; 27import android.net.Uri; 28import android.provider.ContactsContract; 29import android.provider.ContactsContract.QuickContact; 30import android.support.v4.text.TextUtilsCompat; 31import android.support.v4.view.ViewCompat; 32import android.text.TextUtils; 33import android.util.AttributeSet; 34import android.util.Log; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.view.View.OnClickListener; 38import android.view.ViewGroup; 39import android.view.animation.AccelerateDecelerateInterpolator; 40import android.widget.FrameLayout; 41import android.widget.ImageView; 42import android.widget.LinearLayout; 43import android.widget.TextView; 44 45import java.util.ArrayList; 46import java.util.List; 47import java.util.Locale; 48 49/** 50 * Display entries in a LinearLayout that can be expanded to show all entries. 51 */ 52public class ExpandingEntryCardView extends LinearLayout { 53 54 private static final String TAG = "ExpandingEntryCardView"; 55 56 /** 57 * Entry data. 58 */ 59 public static final class Entry { 60 61 private final Drawable mIcon; 62 private final String mHeader; 63 private final String mSubHeader; 64 private final Drawable mSubHeaderIcon; 65 private final String mText; 66 private final Drawable mTextIcon; 67 private final Intent mIntent; 68 private final boolean mIsEditable; 69 70 public Entry(Drawable icon, String header, String subHeader, String text, 71 Intent intent, boolean isEditable) { 72 this(icon, header, subHeader, null, text, null, intent, isEditable); 73 } 74 75 public Entry(Drawable mainIcon, String header, String subHeader, 76 Drawable subHeaderIcon, String text, Drawable textIcon, Intent intent, 77 boolean isEditable) { 78 mIcon = mainIcon; 79 mHeader = header; 80 mSubHeader = subHeader; 81 mSubHeaderIcon = subHeaderIcon; 82 mText = text; 83 mTextIcon = textIcon; 84 mIntent = intent; 85 mIsEditable = isEditable; 86 } 87 88 Drawable getIcon() { 89 return mIcon; 90 } 91 92 String getHeader() { 93 return mHeader; 94 } 95 96 String getSubHeader() { 97 return mSubHeader; 98 } 99 100 Drawable getSubHeaderIcon() { 101 return mSubHeaderIcon; 102 } 103 104 public String getText() { 105 return mText; 106 } 107 108 Drawable getTextIcon() { 109 return mTextIcon; 110 } 111 112 Intent getIntent() { 113 return mIntent; 114 } 115 116 boolean isEditable() { 117 return mIsEditable; 118 } 119 } 120 121 private View mExpandCollapseButton; 122 private TextView mExpandCollapseTextView; 123 private TextView mTitleTextView; 124 private CharSequence mExpandButtonText; 125 private CharSequence mCollapseButtonText; 126 private OnClickListener mOnClickListener; 127 private boolean mIsExpanded = false; 128 private int mCollapsedEntriesCount; 129 private List<View> mEntryViews; 130 private LinearLayout mEntriesViewGroup; 131 private int mThemeColor; 132 133 private final OnClickListener mExpandCollapseButtonListener = new OnClickListener() { 134 @Override 135 public void onClick(View v) { 136 if (mIsExpanded) { 137 collapse(); 138 } else { 139 expand(); 140 } 141 } 142 }; 143 144 public ExpandingEntryCardView(Context context) { 145 super(context); 146 LayoutInflater inflater = LayoutInflater.from(context); 147 View expandingEntryCardView = inflater.inflate(R.layout.expanding_entry_card_view, this); 148 mEntriesViewGroup = (LinearLayout) 149 expandingEntryCardView.findViewById(R.id.content_area_linear_layout); 150 mTitleTextView = (TextView) expandingEntryCardView.findViewById(R.id.title); 151 } 152 153 public ExpandingEntryCardView(Context context, AttributeSet attrs) { 154 super(context, attrs); 155 LayoutInflater inflater = LayoutInflater.from(context); 156 View expandingEntryCardView = inflater.inflate(R.layout.expanding_entry_card_view, this); 157 mEntriesViewGroup = (LinearLayout) 158 expandingEntryCardView.findViewById(R.id.content_area_linear_layout); 159 mTitleTextView = (TextView) expandingEntryCardView.findViewById(R.id.title); 160 } 161 162 /** 163 * Sets the Entry list to display. 164 * 165 * @param entries The Entry list to display. 166 */ 167 public void initialize(List<Entry> entries, int numInitialVisibleEntries, 168 boolean isExpanded, int themeColor) { 169 LayoutInflater layoutInflater = LayoutInflater.from(getContext()); 170 mIsExpanded = isExpanded; 171 mEntryViews = createEntryViews(layoutInflater, entries); 172 mThemeColor = themeColor; 173 mCollapsedEntriesCount = Math.min(numInitialVisibleEntries, entries.size()); 174 if (mExpandCollapseButton == null) { 175 createExpandButton(layoutInflater); 176 } 177 insertEntriesIntoViewGroup(); 178 } 179 180 /** 181 * Sets the text for the expand button. 182 * 183 * @param expandButtonText The expand button text. 184 */ 185 public void setExpandButtonText(CharSequence expandButtonText) { 186 mExpandButtonText = expandButtonText; 187 if (mExpandCollapseTextView != null && !mIsExpanded) { 188 mExpandCollapseTextView.setText(expandButtonText); 189 } 190 } 191 192 /** 193 * Sets the text for the expand button. 194 * 195 * @param expandButtonText The expand button text. 196 */ 197 public void setCollapseButtonText(CharSequence expandButtonText) { 198 mCollapseButtonText = expandButtonText; 199 if (mExpandCollapseTextView != null && mIsExpanded) { 200 mExpandCollapseTextView.setText(mCollapseButtonText); 201 } 202 } 203 204 @Override 205 public void setOnClickListener(OnClickListener listener) { 206 mOnClickListener = listener; 207 } 208 209 private void insertEntriesIntoViewGroup() { 210 mEntriesViewGroup.removeAllViews(); 211 for (int i = 0; i < mCollapsedEntriesCount; ++i) { 212 addEntry(mEntryViews.get(i)); 213 } 214 if (mIsExpanded) { 215 for (int i = mCollapsedEntriesCount; i < mEntryViews.size(); ++i) { 216 addEntry(mEntryViews.get(i)); 217 } 218 } 219 220 removeView(mExpandCollapseButton); 221 if (mCollapsedEntriesCount < mEntryViews.size() 222 && mExpandCollapseButton.getParent() == null) { 223 addView(mExpandCollapseButton, -1); 224 } 225 } 226 227 private void addEntry(View entry) { 228 if (mEntriesViewGroup.getChildCount() > 0) { 229 View separator = new View(getContext()); 230 separator.setBackgroundColor(getResources().getColor( 231 R.color.expanding_entry_card_item_separator_color)); 232 LayoutParams layoutParams = generateDefaultLayoutParams(); 233 Resources resources = getResources(); 234 layoutParams.height = resources.getDimensionPixelSize( 235 R.dimen.expanding_entry_card_item_separator_height); 236 if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 237 layoutParams.rightMargin = resources.getDimensionPixelSize( 238 R.dimen.expanding_entry_card_item_padding_start); 239 layoutParams.leftMargin = resources.getDimensionPixelSize( 240 R.dimen.expanding_entry_card_item_padding_end); 241 } else { 242 layoutParams.leftMargin = resources.getDimensionPixelSize( 243 R.dimen.expanding_entry_card_item_padding_start); 244 layoutParams.rightMargin = resources.getDimensionPixelSize( 245 R.dimen.expanding_entry_card_item_padding_end); 246 } 247 separator.setLayoutParams(layoutParams); 248 mEntriesViewGroup.addView(separator); 249 } 250 mEntriesViewGroup.addView(entry); 251 } 252 253 private CharSequence getExpandButtonText() { 254 if (!TextUtils.isEmpty(mExpandButtonText)) { 255 return mExpandButtonText; 256 } else { 257 // Default to "See more". 258 return getResources().getText(R.string.expanding_entry_card_view_see_more); 259 } 260 } 261 262 private CharSequence getCollapseButtonText() { 263 if (!TextUtils.isEmpty(mCollapseButtonText)) { 264 return mCollapseButtonText; 265 } else { 266 // Default to "See less". 267 return getResources().getText(R.string.expanding_entry_card_view_see_less); 268 } 269 } 270 271 private void createExpandButton(LayoutInflater layoutInflater) { 272 mExpandCollapseButton = layoutInflater.inflate( 273 R.layout.quickcontact_expanding_entry_card_button, this, false); 274 mExpandCollapseTextView = (TextView) mExpandCollapseButton.findViewById(R.id.text); 275 if (mIsExpanded) { 276 updateExpandCollapseButton(getCollapseButtonText()); 277 } else { 278 updateExpandCollapseButton(getExpandButtonText()); 279 } 280 mExpandCollapseButton.setOnClickListener(mExpandCollapseButtonListener); 281 } 282 283 private List<View> createEntryViews(LayoutInflater layoutInflater, List<Entry> entries) { 284 ArrayList<View> views = new ArrayList<View>(entries.size()); 285 for (int i = 0; i < entries.size(); ++i) { 286 Entry entry = entries.get(i); 287 views.add(createEntryView(layoutInflater, entry)); 288 } 289 return views; 290 } 291 292 private View createEntryView(LayoutInflater layoutInflater, Entry entry) { 293 View view = layoutInflater.inflate( 294 R.layout.expanding_entry_card_item, this, false); 295 296 ImageView icon = (ImageView) view.findViewById(R.id.icon); 297 icon.setImageDrawable(entry.getIcon()); 298 299 TextView header = (TextView) view.findViewById(R.id.header); 300 if (entry.getHeader() != null) { 301 header.setText(entry.getHeader()); 302 } else { 303 header.setVisibility(View.GONE); 304 } 305 306 TextView subHeader = (TextView) view.findViewById(R.id.sub_header); 307 if (entry.getSubHeader() != null) { 308 subHeader.setText(entry.getSubHeader()); 309 } else { 310 subHeader.setVisibility(View.GONE); 311 } 312 313 ImageView subHeaderIcon = (ImageView) view.findViewById(R.id.icon_sub_header); 314 if (entry.getSubHeaderIcon() != null) { 315 subHeaderIcon.setImageDrawable(entry.getSubHeaderIcon()); 316 } else { 317 subHeaderIcon.setVisibility(View.GONE); 318 } 319 320 TextView text = (TextView) view.findViewById(R.id.text); 321 if (entry.getText() != null) { 322 text.setText(entry.getText()); 323 } else { 324 text.setVisibility(View.GONE); 325 } 326 327 ImageView textIcon = (ImageView) view.findViewById(R.id.icon_text); 328 if (entry.getTextIcon() != null) { 329 textIcon.setImageDrawable(entry.getTextIcon()); 330 } else { 331 textIcon.setVisibility(View.GONE); 332 } 333 334 if (entry.getIntent() != null) { 335 View entryLayout = view.findViewById(R.id.entry_layout); 336 entryLayout.setOnClickListener(mOnClickListener); 337 entryLayout.setTag(entry.getIntent()); 338 } 339 340 return view; 341 } 342 343 private void updateExpandCollapseButton(CharSequence buttonText) { 344 int resId = mIsExpanded ? R.drawable.expanding_entry_card_collapse_white_24 345 : R.drawable.expanding_entry_card_expand_white_24; 346 // TODO: apply color theme to the drawable 347 Drawable drawable = getResources().getDrawable(resId); 348 if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 349 mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, drawable, 350 null); 351 } else { 352 mExpandCollapseTextView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, 353 null); 354 } 355 mExpandCollapseTextView.setText(buttonText); 356 } 357 358 private void expand() { 359 final int startingHeight = mEntriesViewGroup.getHeight(); 360 361 mIsExpanded = true; 362 insertEntriesIntoViewGroup(); 363 updateExpandCollapseButton(getCollapseButtonText()); 364 365 createExpandAnimator(startingHeight, measureContentAreaHeight()).start(); 366 } 367 368 private void collapse() { 369 int startingHeight = mEntriesViewGroup.getHeight(); 370 371 // Figure out the height the view will be after the animation is finished. 372 mIsExpanded = false; 373 insertEntriesIntoViewGroup(); 374 int finishHeight = measureContentAreaHeight(); 375 376 // During the animation, mEntriesViewGroup should contain the same views as it did before 377 // the animation. Otherwise, the animation will look very silly. 378 mIsExpanded = true; 379 insertEntriesIntoViewGroup(); 380 381 mIsExpanded = false; 382 updateExpandCollapseButton(getExpandButtonText()); 383 createExpandAnimator(startingHeight, finishHeight).start(); 384 } 385 386 private int measureContentAreaHeight() { 387 // Measure the LinearLayout, assuming no constraints from the parent. 388 final int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 389 final int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 390 mEntriesViewGroup.measure(widthSpec, heightSpec); 391 return mEntriesViewGroup.getMeasuredHeight(); 392 } 393 394 /** 395 * Create ValueAnimator that performs an expand animation on the content LinearLayout. 396 * 397 * The animation needs to be performed manually using a ValueAnimator, since LinearLayout 398 * doesn't have a single set-able height property (ie, no setHeight()). 399 */ 400 private ValueAnimator createExpandAnimator(int start, int end) { 401 ValueAnimator animator = ValueAnimator.ofInt(start, end); 402 animator.setInterpolator(new AccelerateDecelerateInterpolator()); 403 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 404 @Override 405 public void onAnimationUpdate(ValueAnimator valueAnimator) { 406 int value = (Integer) valueAnimator.getAnimatedValue(); 407 ViewGroup.LayoutParams layoutParams = mEntriesViewGroup.getLayoutParams(); 408 layoutParams.height = value; 409 mEntriesViewGroup.setLayoutParams(layoutParams); 410 } 411 }); 412 animator.addListener(new AnimatorListenerAdapter() { 413 @Override 414 public void onAnimationEnd(Animator animation) { 415 insertEntriesIntoViewGroup(); 416 } 417 }); 418 return animator; 419 } 420 421 /** 422 * Returns whether the view is currently in its expanded state. 423 */ 424 public boolean isExpanded() { 425 return mIsExpanded; 426 } 427 428 /** 429 * Sets the title text of this ExpandingEntryCardView. 430 * @param title The title to set. A null title will result in an empty string being set. 431 */ 432 public void setTitle(String title) { 433 if (mTitleTextView == null) { 434 Log.e(TAG, "mTitleTextView is null"); 435 } 436 if (title == null) { 437 mTitleTextView.setText(""); 438 } 439 mTitleTextView.setText(title); 440 } 441} 442