TextListItem.java revision 20e9f85df9325d7dc79750be2527bd8dd1c38fcb
1/* 2 * Copyright 2018 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 17package androidx.car.widget; 18 19import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21import android.content.Context; 22import android.content.res.Resources; 23import android.graphics.drawable.Drawable; 24import android.support.annotation.DrawableRes; 25import android.support.annotation.IntDef; 26import android.support.v7.widget.RecyclerView; 27import android.text.TextUtils; 28import android.view.View; 29import android.view.ViewGroup; 30import android.widget.Button; 31import android.widget.CompoundButton; 32import android.widget.ImageView; 33import android.widget.RelativeLayout; 34import android.widget.Switch; 35import android.widget.TextView; 36 37import java.lang.annotation.Retention; 38import java.util.ArrayList; 39import java.util.List; 40 41import androidx.car.R; 42 43/** 44 * Class to build a list item of text. 45 * 46 * <p>An item supports primary action and supplemental action(s). 47 * 48 * <p>An item visually composes of 3 parts; each part may contain multiple views. 49 * <ul> 50 * <li>{@code Primary Action}: represented by an icon of following types. 51 * <ul> 52 * <li>Primary Icon - icon size could be large or small. 53 * <li>No Icon - no icon is shown. 54 * <li>Empty Icon - {@code Text} offsets start space as if there was an icon. 55 * </ul> 56 * <li>{@code Text}: supports any combination of the following text views. 57 * <ul> 58 * <li>Title 59 * <li>Body 60 * </ul> 61 * <li>{@code Supplemental Action}: represented by one of the following types; aligned toward 62 * the end of item. 63 * <ul> 64 * <li>Supplemental Icon 65 * <li>One Action Button 66 * <li>Two Action Buttons 67 * <li>Switch 68 * </ul> 69 * </ul> 70 * 71 * <p>{@code TextListItem} binds data to {@link ViewHolder} based on components selected. 72 * 73 * <p>When conflicting setter methods are called (e.g. setting primary action to both primary icon 74 * and no icon), the last called method wins. 75 */ 76public class TextListItem extends ListItem<TextListItem.ViewHolder> { 77 78 @Retention(SOURCE) 79 @IntDef({ 80 PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON, 81 PRIMARY_ACTION_TYPE_LARGE_ICON, PRIMARY_ACTION_TYPE_SMALL_ICON}) 82 private @interface PrimaryActionType {} 83 84 private static final int PRIMARY_ACTION_TYPE_NO_ICON = 0; 85 private static final int PRIMARY_ACTION_TYPE_EMPTY_ICON = 1; 86 private static final int PRIMARY_ACTION_TYPE_LARGE_ICON = 2; 87 private static final int PRIMARY_ACTION_TYPE_SMALL_ICON = 3; 88 89 @Retention(SOURCE) 90 @IntDef({SUPPLEMENTAL_ACTION_NO_ACTION, SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON, 91 SUPPLEMENTAL_ACTION_ONE_ACTION, SUPPLEMENTAL_ACTION_TWO_ACTIONS, 92 SUPPLEMENTAL_ACTION_SWITCH}) 93 private @interface SupplementalActionType {} 94 95 private static final int SUPPLEMENTAL_ACTION_NO_ACTION = 0; 96 private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON = 1; 97 private static final int SUPPLEMENTAL_ACTION_ONE_ACTION = 2; 98 private static final int SUPPLEMENTAL_ACTION_TWO_ACTIONS = 3; 99 private static final int SUPPLEMENTAL_ACTION_SWITCH = 4; 100 101 private final Context mContext; 102 103 private final List<ViewBinder<ViewHolder>> mBinders = new ArrayList<>(); 104 105 private View.OnClickListener mOnClickListener; 106 107 @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON; 108 private int mPrimaryActionIconResId; 109 private Drawable mPrimaryActionIconDrawable; 110 111 private String mTitle; 112 private String mBody; 113 private boolean mIsBodyPrimary; 114 115 @SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION; 116 private int mSupplementalIconResId; 117 private View.OnClickListener mSupplementalIconOnClickListener; 118 private boolean mShowSupplementalIconDivider; 119 120 private boolean mSwitchChecked; 121 private boolean mShowSwitchDivider; 122 private CompoundButton.OnCheckedChangeListener mSwitchOnCheckedChangeListener; 123 124 private String mAction1Text; 125 private View.OnClickListener mAction1OnClickListener; 126 private boolean mShowAction1Divider; 127 private String mAction2Text; 128 private View.OnClickListener mAction2OnClickListener; 129 private boolean mShowAction2Divider; 130 131 /** 132 * Creates a {@link TextListItem.ViewHolder}. 133 */ 134 public static ViewHolder createViewHolder(View itemView) { 135 return new ViewHolder(itemView); 136 } 137 138 public TextListItem(Context context) { 139 mContext = context; 140 markDirty(); 141 } 142 143 /** 144 * Used by {@link ListItemAdapter} to choose layout to inflate for view holder. 145 */ 146 @Override 147 public int getViewType() { 148 return ListItemAdapter.LIST_ITEM_TYPE_TEXT; 149 } 150 151 /** 152 * Calculates the layout params for views in {@link ViewHolder}. 153 */ 154 @Override 155 protected void resolveDirtyState() { 156 mBinders.clear(); 157 158 // Create binders that adjust layout params of each view. 159 setItemLayoutHeight(); 160 setPrimaryAction(); 161 setText(); 162 setSupplementalActions(); 163 setOnClickListener(); 164 } 165 166 /** 167 * Hides all views in {@link ViewHolder} then applies ViewBinders to adjust view layout params. 168 */ 169 @Override 170 public void onBind(ViewHolder viewHolder) { 171 hideSubViews(viewHolder); 172 for (ViewBinder binder : mBinders) { 173 binder.bind(viewHolder); 174 } 175 } 176 177 private void hideSubViews(ViewHolder vh) { 178 View[] subviews = new View[] { 179 vh.getPrimaryIcon(), 180 vh.getTitle(), vh.getBody(), 181 vh.getSupplementalIcon(), vh.getSupplementalIconDivider(), 182 vh.getSwitch(), vh.getSwitchDivider(), 183 vh.getAction1(), vh.getAction1Divider(), vh.getAction2(), vh.getAction2Divider()}; 184 for (View v : subviews) { 185 v.setVisibility(View.GONE); 186 } 187 } 188 189 /** 190 * Sets the height of item depending on which text field is set. 191 */ 192 private void setItemLayoutHeight() { 193 if (TextUtils.isEmpty(mBody)) { 194 // If the item only has title or no text, it uses fixed-height as single line. 195 int height = mContext.getResources().getDimensionPixelSize( 196 R.dimen.car_single_line_list_item_height); 197 mBinders.add(vh -> { 198 ViewGroup.LayoutParams layoutParams = vh.itemView.getLayoutParams(); 199 layoutParams.height = height; 200 vh.itemView.requestLayout(); 201 }); 202 } else { 203 // If body is present, the item should be at least as tall as min height, and wraps 204 // content. 205 int minHeight = mContext.getResources().getDimensionPixelSize( 206 R.dimen.car_double_line_list_item_height); 207 mBinders.add(vh -> { 208 vh.itemView.setMinimumHeight(minHeight); 209 vh.getContainerLayout().setMinimumHeight(minHeight); 210 211 ViewGroup.LayoutParams layoutParams = vh.itemView.getLayoutParams(); 212 layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT; 213 vh.itemView.requestLayout(); 214 }); 215 } 216 } 217 218 private void setPrimaryAction() { 219 setPrimaryIconContent(); 220 setPrimaryIconLayout(); 221 } 222 223 private void setText() { 224 setTextContent(); 225 setTextVerticalMargin(); 226 // Only set start margin because text end is relative to the start of supplemental actions. 227 setTextStartMargin(); 228 } 229 230 private void setOnClickListener() { 231 if (mOnClickListener != null) { 232 mBinders.add(vh -> vh.itemView.setOnClickListener(mOnClickListener)); 233 } 234 } 235 236 private void setPrimaryIconContent() { 237 switch (mPrimaryActionType) { 238 case PRIMARY_ACTION_TYPE_SMALL_ICON: 239 case PRIMARY_ACTION_TYPE_LARGE_ICON: 240 mBinders.add(vh -> { 241 vh.getPrimaryIcon().setVisibility(View.VISIBLE); 242 243 if (mPrimaryActionIconDrawable != null) { 244 vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable); 245 } else if (mPrimaryActionIconResId != 0) { 246 vh.getPrimaryIcon().setImageResource(mPrimaryActionIconResId); 247 } 248 }); 249 break; 250 case PRIMARY_ACTION_TYPE_EMPTY_ICON: 251 case PRIMARY_ACTION_TYPE_NO_ICON: 252 // Do nothing. 253 break; 254 default: 255 throw new IllegalStateException("Unrecognizable primary action type."); 256 } 257 } 258 259 /** 260 * Sets layout params of primary icon. 261 * 262 * <p>Large icon will have no start margin, and always align center vertically. 263 * 264 * <p>Small icon will have start margin. When body text is present small icon uses a top 265 * margin otherwise align center vertically. 266 */ 267 private void setPrimaryIconLayout() { 268 // Set all relevant fields in layout params to avoid carried over params when the item 269 // gets bound to a recycled view holder. 270 switch (mPrimaryActionType) { 271 case PRIMARY_ACTION_TYPE_SMALL_ICON: 272 mBinders.add(vh -> { 273 int iconSize = mContext.getResources().getDimensionPixelSize( 274 R.dimen.car_primary_icon_size); 275 // Icon size. 276 RelativeLayout.LayoutParams layoutParams = 277 (RelativeLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams(); 278 layoutParams.height = layoutParams.width = iconSize; 279 280 // Start margin. 281 layoutParams.setMarginStart(mContext.getResources().getDimensionPixelSize( 282 R.dimen.car_keyline_1)); 283 284 if (!TextUtils.isEmpty(mBody)) { 285 // Set icon top margin so that the icon remains in the same position it 286 // would've been in for non-long-text item, namely so that the center 287 // line of icon matches that of line item. 288 layoutParams.removeRule(RelativeLayout.CENTER_VERTICAL); 289 int itemHeight = mContext.getResources().getDimensionPixelSize( 290 R.dimen.car_double_line_list_item_height); 291 layoutParams.topMargin = (itemHeight - iconSize) / 2; 292 } else { 293 // If the icon can be centered vertically, leave the work for framework. 294 layoutParams.addRule(RelativeLayout.CENTER_VERTICAL); 295 layoutParams.topMargin = 0; 296 } 297 vh.getPrimaryIcon().requestLayout(); 298 }); 299 break; 300 case PRIMARY_ACTION_TYPE_LARGE_ICON: 301 mBinders.add(vh -> { 302 int iconSize = mContext.getResources().getDimensionPixelSize( 303 R.dimen.car_single_line_list_item_height); 304 // Icon size. 305 RelativeLayout.LayoutParams layoutParams = 306 (RelativeLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams(); 307 layoutParams.height = layoutParams.width = iconSize; 308 309 // No start margin. 310 layoutParams.setMarginStart(0); 311 312 // Always centered vertically. 313 layoutParams.addRule(RelativeLayout.CENTER_VERTICAL); 314 layoutParams.topMargin = 0; 315 316 vh.getPrimaryIcon().requestLayout(); 317 }); 318 break; 319 case PRIMARY_ACTION_TYPE_EMPTY_ICON: 320 case PRIMARY_ACTION_TYPE_NO_ICON: 321 // Do nothing. 322 break; 323 default: 324 throw new IllegalStateException("Unrecognizable primary action type."); 325 } 326 } 327 328 private void setTextContent() { 329 if (!TextUtils.isEmpty(mTitle)) { 330 mBinders.add(vh -> { 331 vh.getTitle().setVisibility(View.VISIBLE); 332 vh.getTitle().setText(mTitle); 333 }); 334 } 335 if (!TextUtils.isEmpty(mBody)) { 336 mBinders.add(vh -> { 337 vh.getBody().setVisibility(View.VISIBLE); 338 vh.getBody().setText(mBody); 339 }); 340 } 341 342 if (mIsBodyPrimary) { 343 mBinders.add(vh -> { 344 vh.getTitle().setTextAppearance(R.style.CarBody2); 345 vh.getBody().setTextAppearance(R.style.CarBody1); 346 }); 347 } else { 348 mBinders.add(vh -> { 349 vh.getTitle().setTextAppearance(R.style.CarBody1); 350 vh.getBody().setTextAppearance(R.style.CarBody2); 351 }); 352 } 353 } 354 355 /** 356 * Sets start margin of text view depending on icon type. 357 */ 358 private void setTextStartMargin() { 359 final int startMarginResId; 360 switch (mPrimaryActionType) { 361 case PRIMARY_ACTION_TYPE_NO_ICON: 362 startMarginResId = R.dimen.car_keyline_1; 363 break; 364 case PRIMARY_ACTION_TYPE_EMPTY_ICON: 365 startMarginResId = R.dimen.car_keyline_3; 366 break; 367 case PRIMARY_ACTION_TYPE_SMALL_ICON: 368 startMarginResId = R.dimen.car_keyline_3; 369 break; 370 case PRIMARY_ACTION_TYPE_LARGE_ICON: 371 startMarginResId = R.dimen.car_keyline_4; 372 break; 373 default: 374 throw new IllegalStateException("Unrecognizable primary action type."); 375 } 376 int startMargin = mContext.getResources().getDimensionPixelSize(startMarginResId); 377 mBinders.add(vh -> { 378 RelativeLayout.LayoutParams titleLayoutParams = 379 (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams(); 380 titleLayoutParams.setMarginStart(startMargin); 381 vh.getTitle().requestLayout(); 382 383 RelativeLayout.LayoutParams bodyLayoutParams = 384 (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams(); 385 bodyLayoutParams.setMarginStart(startMargin); 386 vh.getBody().requestLayout(); 387 }); 388 } 389 390 /** 391 * Sets top/bottom margins of {@code Title} and {@code Body}. 392 */ 393 private void setTextVerticalMargin() { 394 // Set all relevant fields in layout params to avoid carried over params when the item 395 // gets bound to a recycled view holder. 396 if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mBody)) { 397 // Title only - view is aligned center vertically by itself. 398 mBinders.add(vh -> { 399 RelativeLayout.LayoutParams layoutParams = 400 (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams(); 401 layoutParams.addRule(RelativeLayout.CENTER_VERTICAL); 402 layoutParams.topMargin = 0; 403 vh.getTitle().requestLayout(); 404 }); 405 } else if (TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mBody)) { 406 mBinders.add(vh -> { 407 // Body uses top and bottom margin. 408 int margin = mContext.getResources().getDimensionPixelSize( 409 R.dimen.car_padding_3); 410 RelativeLayout.LayoutParams layoutParams = 411 (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams(); 412 layoutParams.addRule(RelativeLayout.CENTER_VERTICAL); 413 layoutParams.removeRule(RelativeLayout.BELOW); 414 layoutParams.topMargin = margin; 415 layoutParams.bottomMargin = margin; 416 vh.getBody().requestLayout(); 417 }); 418 } else { 419 mBinders.add(vh -> { 420 // Title has a top margin 421 Resources resources = mContext.getResources(); 422 int padding1 = resources.getDimensionPixelSize(R.dimen.car_padding_1); 423 int padding3 = resources.getDimensionPixelSize(R.dimen.car_padding_3); 424 425 RelativeLayout.LayoutParams titleLayoutParams = 426 (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams(); 427 titleLayoutParams.removeRule(RelativeLayout.CENTER_VERTICAL); 428 titleLayoutParams.topMargin = padding3; 429 vh.getTitle().requestLayout(); 430 // Body is below title with a margin, and has bottom margin. 431 RelativeLayout.LayoutParams bodyLayoutParams = 432 (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams(); 433 bodyLayoutParams.removeRule(RelativeLayout.CENTER_VERTICAL); 434 bodyLayoutParams.addRule(RelativeLayout.BELOW, R.id.title); 435 bodyLayoutParams.topMargin = padding1; 436 bodyLayoutParams.bottomMargin = padding3; 437 vh.getBody().requestLayout(); 438 }); 439 } 440 } 441 442 /** 443 * Sets up view(s) for supplemental action. 444 */ 445 private void setSupplementalActions() { 446 switch (mSupplementalActionType) { 447 case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON: 448 mBinders.add(vh -> { 449 vh.getSupplementalIcon().setVisibility(View.VISIBLE); 450 if (mShowSupplementalIconDivider) { 451 vh.getSupplementalIconDivider().setVisibility(View.VISIBLE); 452 } 453 454 vh.getSupplementalIcon().setImageResource(mSupplementalIconResId); 455 vh.getSupplementalIcon().setOnClickListener( 456 mSupplementalIconOnClickListener); 457 vh.getSupplementalIcon().setClickable( 458 mSupplementalIconOnClickListener != null); 459 }); 460 break; 461 case SUPPLEMENTAL_ACTION_TWO_ACTIONS: 462 mBinders.add(vh -> { 463 vh.getAction2().setVisibility(View.VISIBLE); 464 if (mShowAction2Divider) { 465 vh.getAction2Divider().setVisibility(View.VISIBLE); 466 } 467 468 vh.getAction2().setText(mAction2Text); 469 vh.getAction2().setOnClickListener(mAction2OnClickListener); 470 }); 471 // Fall through 472 case SUPPLEMENTAL_ACTION_ONE_ACTION: 473 mBinders.add(vh -> { 474 vh.getAction1().setVisibility(View.VISIBLE); 475 if (mShowAction1Divider) { 476 vh.getAction1Divider().setVisibility(View.VISIBLE); 477 } 478 479 vh.getAction1().setText(mAction1Text); 480 vh.getAction1().setOnClickListener(mAction1OnClickListener); 481 }); 482 break; 483 case SUPPLEMENTAL_ACTION_NO_ACTION: 484 // Do nothing 485 break; 486 case SUPPLEMENTAL_ACTION_SWITCH: 487 mBinders.add(vh -> { 488 vh.getSwitch().setVisibility(View.VISIBLE); 489 vh.getSwitch().setChecked(mSwitchChecked); 490 vh.getSwitch().setOnCheckedChangeListener(mSwitchOnCheckedChangeListener); 491 if (mShowSwitchDivider) { 492 vh.getSwitchDivider().setVisibility(View.VISIBLE); 493 } 494 }); 495 break; 496 default: 497 throw new IllegalArgumentException("Unrecognized supplemental action type."); 498 } 499 } 500 501 /** 502 * Sets {@link View.OnClickListener} of {@code TextListItem}. 503 */ 504 public void setOnClickListener(View.OnClickListener listener) { 505 mOnClickListener = listener; 506 markDirty(); 507 } 508 509 /** 510 * Sets {@code Primary Action} to be represented by an icon. 511 * 512 * @param iconResId the resource identifier of the drawable. 513 * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item. 514 */ 515 public void setPrimaryActionIcon(@DrawableRes int iconResId, boolean useLargeIcon) { 516 setPrimaryActionIcon(null, iconResId, useLargeIcon); 517 } 518 519 /** 520 * Sets {@code Primary Action} to be represented by an icon. 521 * 522 * @param drawable the Drawable to set, or null to clear the content. 523 * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item. 524 */ 525 public void setPrimaryActionIcon(Drawable drawable, boolean useLargeIcon) { 526 setPrimaryActionIcon(drawable, 0, useLargeIcon); 527 } 528 529 private void setPrimaryActionIcon(Drawable drawable, @DrawableRes int iconResId, 530 boolean useLargeIcon) { 531 mPrimaryActionType = useLargeIcon 532 ? PRIMARY_ACTION_TYPE_LARGE_ICON 533 : PRIMARY_ACTION_TYPE_SMALL_ICON; 534 mPrimaryActionIconResId = iconResId; 535 mPrimaryActionIconDrawable = drawable; 536 537 markDirty(); 538 } 539 540 /** 541 * Sets {@code Primary Action} to be empty icon. 542 * 543 * <p>{@code Text} would have a start margin as if {@code Primary Action} were set to primary 544 * icon. 545 */ 546 public void setPrimaryActionEmptyIcon() { 547 mPrimaryActionType = PRIMARY_ACTION_TYPE_EMPTY_ICON; 548 markDirty(); 549 } 550 551 /** 552 * Sets {@code Primary Action} to have no icon. Text would align to the start of item. 553 */ 554 public void setPrimaryActionNoIcon() { 555 mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON; 556 markDirty(); 557 } 558 559 /** 560 * Sets the title of item. 561 * 562 * <p>Primary text is {@code Title} by default. It can be set by 563 * {@link #setBody(String, boolean)} 564 * 565 * <p>{@code Title} text is limited to one line, and ellipses at the end. 566 * 567 * @param title text to display as title. 568 */ 569 public void setTitle(String title) { 570 mTitle = title; 571 markDirty(); 572 } 573 574 /** 575 * Sets the body text of item. 576 * 577 * <p>Text beyond length required by regulation will be truncated. Defaults {@code Title} 578 * text as the primary. 579 * @param body text to be displayed. 580 */ 581 public void setBody(String body) { 582 setBody(body, false); 583 } 584 585 /** 586 * Sets the body text of item. 587 * 588 * <p>Text beyond length required by regulation will be truncated. 589 * 590 * @param body text to be displayed. 591 * @param asPrimary sets {@code Body Text} as primary text of item. 592 */ 593 public void setBody(String body, boolean asPrimary) { 594 int limit = mContext.getResources().getInteger( 595 R.integer.car_list_item_text_length_limit); 596 if (body.length() < limit) { 597 mBody = body; 598 } else { 599 mBody = body.substring(0, limit) + mContext.getString(R.string.ellipsis); 600 } 601 mIsBodyPrimary = asPrimary; 602 603 markDirty(); 604 } 605 606 /** 607 * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}. 608 * 609 * @param iconResId drawable resource id. 610 * @param showDivider whether to display a vertical bar that separates {@code text} and 611 * {@code Supplemental Icon}. 612 */ 613 public void setSupplementalIcon(int iconResId, boolean showDivider) { 614 setSupplementalIcon(iconResId, showDivider, null); 615 } 616 617 /** 618 * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}. 619 * 620 * @param iconResId drawable resource id. 621 * @param showDivider whether to display a vertical bar that separates {@code text} and 622 * {@code Supplemental Icon}. 623 * @param listener the callback that will run when icon is clicked. 624 */ 625 public void setSupplementalIcon(int iconResId, boolean showDivider, 626 View.OnClickListener listener) { 627 mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON; 628 629 mSupplementalIconResId = iconResId; 630 mSupplementalIconOnClickListener = listener; 631 mShowSupplementalIconDivider = showDivider; 632 markDirty(); 633 } 634 635 /** 636 * Sets {@code Supplemental Action} to be represented by an {@code Action Button}. 637 * 638 * @param text button text to display. 639 * @param showDivider whether to display a vertical bar that separates {@code Text} and 640 * {@code Action Button}. 641 * @param listener the callback that will run when action button is clicked. 642 */ 643 public void setAction(String text, boolean showDivider, View.OnClickListener listener) { 644 if (TextUtils.isEmpty(text)) { 645 throw new IllegalArgumentException("Action text cannot be empty."); 646 } 647 if (listener == null) { 648 throw new IllegalArgumentException("Action OnClickListener cannot be null."); 649 } 650 mSupplementalActionType = SUPPLEMENTAL_ACTION_ONE_ACTION; 651 652 mAction1Text = text; 653 mAction1OnClickListener = listener; 654 mShowAction1Divider = showDivider; 655 656 markDirty(); 657 } 658 659 /** 660 * Sets {@code Supplemental Action} to be represented by two {@code Action Button}s. 661 * 662 * <p>These two action buttons will be aligned towards item end. 663 * 664 * @param action1Text button text to display - this button will be closer to item end. 665 * @param action2Text button text to display. 666 */ 667 public void setActions(String action1Text, boolean showAction1Divider, 668 View.OnClickListener action1OnClickListener, 669 String action2Text, boolean showAction2Divider, 670 View.OnClickListener action2OnClickListener) { 671 if (TextUtils.isEmpty(action1Text) || TextUtils.isEmpty(action2Text)) { 672 throw new IllegalArgumentException("Action text cannot be empty."); 673 } 674 if (action1OnClickListener == null || action2OnClickListener == null) { 675 throw new IllegalArgumentException("Action OnClickListener cannot be null."); 676 } 677 mSupplementalActionType = SUPPLEMENTAL_ACTION_TWO_ACTIONS; 678 679 mAction1Text = action1Text; 680 mAction1OnClickListener = action1OnClickListener; 681 mShowAction1Divider = showAction1Divider; 682 mAction2Text = action2Text; 683 mAction2OnClickListener = action2OnClickListener; 684 mShowAction2Divider = showAction2Divider; 685 686 markDirty(); 687 } 688 689 /** 690 * Sets {@code Supplemental Action} to be represented by a {@link android.widget.Switch}. 691 * 692 * @param checked initial value for switched. 693 * @param showDivider whether to display a vertical bar between switch and text. 694 * @param listener callback to be invoked when the checked state is markDirty. 695 */ 696 public void setSwitch(boolean checked, boolean showDivider, 697 CompoundButton.OnCheckedChangeListener listener) { 698 mSupplementalActionType = SUPPLEMENTAL_ACTION_SWITCH; 699 700 mSwitchChecked = checked; 701 mShowSwitchDivider = showDivider; 702 mSwitchOnCheckedChangeListener = listener; 703 704 markDirty(); 705 } 706 707 /** 708 * Holds views of TextListItem. 709 */ 710 public static class ViewHolder extends ListItem.ViewHolder { 711 712 private RelativeLayout mContainerLayout; 713 714 private ImageView mPrimaryIcon; 715 716 private TextView mTitle; 717 private TextView mBody; 718 719 private View mSupplementalIconDivider; 720 private ImageView mSupplementalIcon; 721 722 private Button mAction1; 723 private View mAction1Divider; 724 725 private Button mAction2; 726 private View mAction2Divider; 727 728 private Switch mSwitch; 729 private View mSwitchDivider; 730 731 public ViewHolder(View itemView) { 732 super(itemView); 733 734 mContainerLayout = itemView.findViewById(R.id.container); 735 736 mPrimaryIcon = itemView.findViewById(R.id.primary_icon); 737 738 mTitle = itemView.findViewById(R.id.title); 739 mBody = itemView.findViewById(R.id.body); 740 741 mSupplementalIcon = itemView.findViewById(R.id.supplemental_icon); 742 mSupplementalIconDivider = itemView.findViewById(R.id.supplemental_icon_divider); 743 744 mSwitch = itemView.findViewById(R.id.switch_widget); 745 mSwitchDivider = itemView.findViewById(R.id.switch_divider); 746 747 mAction1 = itemView.findViewById(R.id.action1); 748 mAction1Divider = itemView.findViewById(R.id.action1_divider); 749 mAction2 = itemView.findViewById(R.id.action2); 750 mAction2Divider = itemView.findViewById(R.id.action2_divider); 751 } 752 753 public RelativeLayout getContainerLayout() { 754 return mContainerLayout; 755 } 756 757 public ImageView getPrimaryIcon() { 758 return mPrimaryIcon; 759 } 760 761 public TextView getTitle() { 762 return mTitle; 763 } 764 765 public TextView getBody() { 766 return mBody; 767 } 768 769 public ImageView getSupplementalIcon() { 770 return mSupplementalIcon; 771 } 772 773 public View getSupplementalIconDivider() { 774 return mSupplementalIconDivider; 775 } 776 777 public View getSwitchDivider() { 778 return mSwitchDivider; 779 } 780 781 public Switch getSwitch() { 782 return mSwitch; 783 } 784 785 public Button getAction1() { 786 return mAction1; 787 } 788 789 public View getAction1Divider() { 790 return mAction1Divider; 791 } 792 793 public Button getAction2() { 794 return mAction2; 795 } 796 797 public View getAction2Divider() { 798 return mAction2Divider; 799 } 800 } 801} 802