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