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