ListItem.java revision 5dbc4a83b9c6ea77dd85720a40de7dfc200ee872
1/*
2 * Copyright 2017 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.widget.RelativeLayout;
30
31import java.lang.annotation.Retention;
32import java.util.ArrayList;
33import java.util.List;
34
35import androidx.car.R;
36
37/**
38 * Class to build a list item.
39 *
40 * <p>An item supports primary action and supplemental action(s).
41 *
42 * <p>An item visually composes of 3 parts; each part may contain multiple views.
43 * <ul>
44 *     <li>{@code Primary Action}: represented by an icon of following types.
45 *     <ul>
46 *         <li>Primary Icon - icon size could be large or small.
47 *         <li>No Icon
48 *         <li>Empty Icon - different from No Icon by how much margin {@code Text} offsets
49 *     </ul>
50 *     <li>{@code Text}: supports any combination of the follow text views.
51 *     <ul>
52 *         <li>Title
53 *         <li>Body
54 *     </ul>
55 *     <li>{@code Supplemental Action(s)}: represented by one of the following types; aligned toward
56 *     the end of item.
57 *     <ul>
58 *         <li>Supplemental Icon
59 *         <li>One Action Button
60 *         <li>Two Action Buttons
61 *     </ul>
62 * </ul>
63 *
64 * {@link ListItem} can be built through its {@link ListItem.Builder}. It binds data
65 * to {@link ListItemAdapter.ViewHolder} based on components selected.
66 */
67public class ListItem {
68
69    private Builder mBuilder;
70
71    private ListItem(Builder builder) {
72        mBuilder = builder;
73    }
74
75    /**
76     * Applies all {@link ViewBinder} to {@code viewHolder}.
77     */
78    void bind(ListItemAdapter.ViewHolder viewHolder) {
79        setAllSubViewsGone(viewHolder);
80        for (ViewBinder binder : mBuilder.mBinders) {
81            binder.bind(viewHolder);
82        }
83    }
84
85    void setAllSubViewsGone(ListItemAdapter.ViewHolder vh) {
86        View[] subviews = new View[] {
87                vh.getPrimaryIcon(),
88                vh.getTitle(), vh.getBody(),
89                vh.getSupplementalIcon(), vh.getSupplementalIconDivider(),
90                vh.getAction1(), vh.getAction1Divider(), vh.getAction2(), vh.getAction2Divider()};
91        for (View v : subviews) {
92            v.setVisibility(View.GONE);
93        }
94    }
95
96    /**
97     * Functional interface to provide a way to interact with views in
98     * {@link ListItemAdapter.ViewHolder}. {@code ViewBinder}s added to a
99     * {@code ListItem} will be called when {@code ListItem} {@code bind}s to
100     * {@link ListItemAdapter.ViewHolder}.
101     */
102    public interface ViewBinder {
103        /**
104         * Provides a way to interact with views in view holder.
105         */
106        void bind(ListItemAdapter.ViewHolder viewHolder);
107    }
108
109    /**
110     * Builds a {@link ListItem}.
111     *
112     * <p>With conflicting methods are called, e.g. setting primary action to both primary icon and
113     * no icon, the last called method wins.
114     */
115    public static class Builder {
116
117        @Retention(SOURCE)
118        @IntDef({
119                PRIMARY_ACTION_TYPE_NO_ICON, PRIMARY_ACTION_TYPE_EMPTY_ICON,
120                PRIMARY_ACTION_TYPE_LARGE_ICON, PRIMARY_ACTION_TYPE_SMALL_ICON})
121        private @interface PrimaryActionType {}
122
123        private static final int PRIMARY_ACTION_TYPE_NO_ICON = 0;
124        private static final int PRIMARY_ACTION_TYPE_EMPTY_ICON = 1;
125        private static final int PRIMARY_ACTION_TYPE_LARGE_ICON = 2;
126        private static final int PRIMARY_ACTION_TYPE_SMALL_ICON = 3;
127
128        @Retention(SOURCE)
129        @IntDef({SUPPLEMENTAL_ACTION_NO_ACTION, SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON,
130                SUPPLEMENTAL_ACTION_ONE_ACTION, SUPPLEMENTAL_ACTION_TWO_ACTIONS})
131        private @interface SupplementalActionType {}
132
133        private static final int SUPPLEMENTAL_ACTION_NO_ACTION = 0;
134        private static final int SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON = 1;
135        private static final int SUPPLEMENTAL_ACTION_ONE_ACTION = 2;
136        private static final int SUPPLEMENTAL_ACTION_TWO_ACTIONS = 3;
137
138        private final Context mContext;
139        private final List<ViewBinder> mBinders = new ArrayList<>();
140        // Store custom binders separately so they will bind after binders are created in build().
141        private final List<ViewBinder> mCustomBinders = new ArrayList<>();
142
143        private View.OnClickListener mOnClickListener;
144
145        @PrimaryActionType private int mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
146        private int mPrimaryActionIconResId;
147        private Drawable mPrimaryActionIconDrawable;
148
149        private String mTitle;
150        private String mBody;
151        private boolean mIsBodyPrimary;
152
153        @SupplementalActionType private int mSupplementalActionType = SUPPLEMENTAL_ACTION_NO_ACTION;
154        private int mSupplementalIconResId;
155        private View.OnClickListener mSupplementalIconOnClickListener;
156        private boolean mShowSupplementalIconDivider;
157
158        private String mAction1Text;
159        private View.OnClickListener mAction1OnClickListener;
160        private boolean mShowAction1Divider;
161        private String mAction2Text;
162        private View.OnClickListener mAction2OnClickListener;
163        private boolean mShowAction2Divider;
164
165        public Builder(Context context) {
166            mContext = context;
167        }
168
169        /**
170         * Builds a {@link ListItem}. Adds {@link ViewBinder}s that will adjust layout in
171         * {@link ListItemAdapter.ViewHolder} depending on sub-views used.
172         */
173        public ListItem build() {
174            setItemLayoutHeight();
175            setPrimaryAction();
176            setText();
177            setSupplementalActions();
178            setOnClickListener();
179
180            mBinders.addAll(mCustomBinders);
181
182            return new ListItem(this);
183        }
184
185        /**
186         * Sets the height of item depending on which text field is set.
187         */
188        private void setItemLayoutHeight() {
189            if (TextUtils.isEmpty(mBody)) {
190                // If the item only has title or no text, it uses fixed-height as single line.
191                int height = (int) mContext.getResources().getDimension(
192                        R.dimen.car_single_line_list_item_height);
193                mBinders.add((vh) -> {
194                    RecyclerView.LayoutParams layoutParams =
195                            (RecyclerView.LayoutParams) vh.itemView.getLayoutParams();
196                    layoutParams.height = height;
197                    vh.itemView.setLayoutParams(layoutParams);
198                });
199            } else {
200                // If body is present, the item should be at least as tall as min height, and wraps
201                // content.
202                int minHeight = (int) mContext.getResources().getDimension(
203                        R.dimen.car_double_line_list_item_height);
204                mBinders.add((vh) -> {
205                    vh.itemView.setMinimumHeight(minHeight);
206                    vh.getContainerLayout().setMinimumHeight(minHeight);
207
208                    RecyclerView.LayoutParams layoutParams =
209                            (RecyclerView.LayoutParams) vh.itemView.getLayoutParams();
210                    layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT;
211                    vh.itemView.setLayoutParams(layoutParams);
212                });
213            }
214        }
215
216        private void setPrimaryAction() {
217            setPrimaryIconContent();
218            setPrimaryIconLayout();
219        }
220
221        private void setText() {
222            setTextContent();
223            setTextVerticalMargin();
224            // Only setting start margin because text end is relative to the start of supplemental
225            // actions.
226            setTextStartMargin();
227        }
228
229        private void setOnClickListener() {
230            if (mOnClickListener != null) {
231                mBinders.add(vh -> vh.itemView.setOnClickListener(mOnClickListener));
232            }
233        }
234
235        private void setPrimaryIconContent() {
236            switch (mPrimaryActionType) {
237                case PRIMARY_ACTION_TYPE_SMALL_ICON:
238                case PRIMARY_ACTION_TYPE_LARGE_ICON:
239                    mBinders.add((vh) -> {
240                        vh.getPrimaryIcon().setVisibility(View.VISIBLE);
241
242                        if (mPrimaryActionIconDrawable != null) {
243                            vh.getPrimaryIcon().setImageDrawable(mPrimaryActionIconDrawable);
244                        } else if (mPrimaryActionIconResId != 0) {
245                            vh.getPrimaryIcon().setImageResource(mPrimaryActionIconResId);
246                        }
247                    });
248                    break;
249                case PRIMARY_ACTION_TYPE_EMPTY_ICON:
250                case PRIMARY_ACTION_TYPE_NO_ICON:
251                    // Do nothing.
252                    break;
253                default:
254                    throw new IllegalStateException("Unrecognizable primary action type.");
255            }
256        }
257
258        /**
259         * Sets layout params of primary icon.
260         *
261         * <p>Large icon will have no start margin, and always align center vertically.
262         *
263         * <p>Small icon will have start margin. When body text is present small icon uses a top
264         * margin otherwise align center vertically.
265         */
266        private void setPrimaryIconLayout() {
267            // Set all relevant fields in layout params to avoid carried over params when the item
268            // gets bound to a recycled view holder.
269            switch (mPrimaryActionType) {
270                case PRIMARY_ACTION_TYPE_SMALL_ICON:
271                    mBinders.add(vh -> {
272                        int iconSize = mContext.getResources().getDimensionPixelSize(
273                                R.dimen.car_primary_icon_size);
274                        // Icon size.
275                        RelativeLayout.LayoutParams layoutParams =
276                                (RelativeLayout.LayoutParams) vh.getPrimaryIcon().getLayoutParams();
277                        layoutParams.height = iconSize;
278                        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().setLayoutParams(layoutParams);
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 = iconSize;
308                        layoutParams.width = iconSize;
309
310                        // No start margin.
311                        layoutParams.setMarginStart(0);
312
313                        // Always centered vertically.
314                        layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
315                        layoutParams.topMargin = 0;
316
317                        vh.getPrimaryIcon().setLayoutParams(layoutParams);
318                    });
319                    break;
320                case PRIMARY_ACTION_TYPE_EMPTY_ICON:
321                case PRIMARY_ACTION_TYPE_NO_ICON:
322                    // Do nothing.
323                    break;
324                default:
325                    throw new IllegalStateException("Unrecognizable primary action type.");
326            }
327        }
328
329        private void setTextContent() {
330            if (!TextUtils.isEmpty(mTitle)) {
331                mBinders.add(vh -> {
332                    vh.getTitle().setVisibility(View.VISIBLE);
333                    vh.getTitle().setText(mTitle);
334                });
335            }
336            if (!TextUtils.isEmpty(mBody)) {
337                mBinders.add(vh -> {
338                    vh.getBody().setVisibility(View.VISIBLE);
339                    vh.getBody().setText(mBody);
340                });
341            }
342
343            if (mIsBodyPrimary) {
344                mBinders.add((vh) -> {
345                    vh.getTitle().setTextAppearance(R.style.CarBody2);
346                    vh.getBody().setTextAppearance(R.style.CarBody1);
347                });
348            } else {
349                mBinders.add((vh) -> {
350                    vh.getTitle().setTextAppearance(R.style.CarBody1);
351                    vh.getBody().setTextAppearance(R.style.CarBody2);
352                });
353            }
354        }
355
356        /**
357         * Sets start margin of text view depending on icon type.
358         */
359        private void setTextStartMargin() {
360            final int startMarginResId;
361            switch (mPrimaryActionType) {
362                case PRIMARY_ACTION_TYPE_NO_ICON:
363                    startMarginResId = R.dimen.car_keyline_1;
364                    break;
365                case PRIMARY_ACTION_TYPE_EMPTY_ICON:
366                    startMarginResId = R.dimen.car_keyline_3;
367                    break;
368                case PRIMARY_ACTION_TYPE_SMALL_ICON:
369                    startMarginResId = R.dimen.car_keyline_3;
370                    break;
371                case PRIMARY_ACTION_TYPE_LARGE_ICON:
372                    startMarginResId = R.dimen.car_keyline_4;
373                    break;
374                default:
375                    throw new IllegalStateException("Unrecognizable primary action type.");
376            }
377            int startMargin = mContext.getResources().getDimensionPixelSize(startMarginResId);
378            mBinders.add(vh -> {
379                RelativeLayout.LayoutParams titleLayoutParams =
380                        (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
381                titleLayoutParams.setMarginStart(startMargin);
382                vh.getTitle().setLayoutParams(titleLayoutParams);
383
384                RelativeLayout.LayoutParams bodyLayoutParams =
385                        (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
386                bodyLayoutParams.setMarginStart(startMargin);
387                vh.getBody().setLayoutParams(bodyLayoutParams);
388            });
389        }
390
391        /**
392         * Sets top/bottom margins of {@code Title} and {@code Body}.
393         */
394        private void setTextVerticalMargin() {
395            // Set all relevant fields in layout params to avoid carried over params when the item
396            // gets bound to a recycled view holder.
397            if (!TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mBody)) {
398                // Title only - view is aligned center vertically by itself.
399                mBinders.add(vh -> {
400                    RelativeLayout.LayoutParams layoutParams =
401                            (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
402                    layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
403                    layoutParams.topMargin = 0;
404                    vh.getTitle().setLayoutParams(layoutParams);
405                });
406            } else if (TextUtils.isEmpty(mTitle) && !TextUtils.isEmpty(mBody)) {
407                mBinders.add(vh -> {
408                    // Body uses top and bottom margin.
409                    int margin = mContext.getResources().getDimensionPixelSize(
410                            R.dimen.car_padding_3);
411                    RelativeLayout.LayoutParams layoutParams =
412                            (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
413                    layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
414                    layoutParams.removeRule(RelativeLayout.BELOW);
415                    layoutParams.topMargin = margin;
416                    layoutParams.bottomMargin = margin;
417                    vh.getBody().setLayoutParams(layoutParams);
418                });
419            } else {
420                mBinders.add(vh -> {
421                    // Title has a top margin
422                    Resources resources = mContext.getResources();
423                    int padding1 = resources.getDimensionPixelSize(R.dimen.car_padding_1);
424                    int padding3 = resources.getDimensionPixelSize(R.dimen.car_padding_3);
425
426                    RelativeLayout.LayoutParams titleLayoutParams =
427                            (RelativeLayout.LayoutParams) vh.getTitle().getLayoutParams();
428                    titleLayoutParams.removeRule(RelativeLayout.CENTER_VERTICAL);
429                    titleLayoutParams.topMargin = padding3;
430                    vh.getTitle().setLayoutParams(titleLayoutParams);
431                    // Body is below title with a margin, and has bottom margin.
432                    RelativeLayout.LayoutParams bodyLayoutParams =
433                            (RelativeLayout.LayoutParams) vh.getBody().getLayoutParams();
434                    bodyLayoutParams.removeRule(RelativeLayout.CENTER_VERTICAL);
435                    bodyLayoutParams.addRule(RelativeLayout.BELOW, R.id.title);
436                    bodyLayoutParams.topMargin = padding1;
437                    bodyLayoutParams.bottomMargin = padding3;
438                    vh.getBody().setLayoutParams(bodyLayoutParams);
439                });
440            }
441        }
442
443        /**
444         * Sets up view(s) for supplemental action.
445         */
446        private void setSupplementalActions() {
447            switch (mSupplementalActionType) {
448                case SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON:
449                    mBinders.add((vh) -> {
450                        vh.getSupplementalIcon().setVisibility(View.VISIBLE);
451                        if (mShowSupplementalIconDivider) {
452                            vh.getSupplementalIconDivider().setVisibility(View.VISIBLE);
453                        }
454
455                        vh.getSupplementalIcon().setImageResource(mSupplementalIconResId);
456                        vh.getSupplementalIcon().setOnClickListener(
457                                mSupplementalIconOnClickListener);
458                        vh.getSupplementalIcon().setClickable(
459                                mSupplementalIconOnClickListener != null);
460                    });
461                    break;
462                case SUPPLEMENTAL_ACTION_TWO_ACTIONS:
463                    mBinders.add((vh) -> {
464                        vh.getAction2().setVisibility(View.VISIBLE);
465                        if (mShowAction2Divider) {
466                            vh.getAction2Divider().setVisibility(View.VISIBLE);
467                        }
468
469                        vh.getAction2().setText(mAction2Text);
470                        vh.getAction2().setOnClickListener(mAction2OnClickListener);
471                    });
472                    // Fall through
473                case SUPPLEMENTAL_ACTION_ONE_ACTION:
474                    mBinders.add((vh) -> {
475                        vh.getAction1().setVisibility(View.VISIBLE);
476                        if (mShowAction1Divider) {
477                            vh.getAction1Divider().setVisibility(View.VISIBLE);
478                        }
479
480                        vh.getAction1().setText(mAction1Text);
481                        vh.getAction1().setOnClickListener(mAction1OnClickListener);
482                    });
483                    break;
484                case SUPPLEMENTAL_ACTION_NO_ACTION:
485                    // Do nothing
486                    break;
487                default:
488                    throw new IllegalArgumentException("Unrecognized supplemental action type.");
489            }
490        }
491
492        /**
493         * Sets {@link View.OnClickListener} of {@code ListItem}.
494         *
495         * @return This Builder object to allow for chaining calls to set methods.
496         */
497        public Builder withOnClickListener(View.OnClickListener listener) {
498            mOnClickListener = listener;
499            return this;
500        }
501
502        /**
503         * Sets {@code Primary Action} to be represented by an icon.
504         *
505         * @param iconResId the resource identifier of the drawable.
506         * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item
507         *                     with only title set; useful for album cover art.
508         * @return This Builder object to allow for chaining calls to set methods.
509         */
510        public Builder withPrimaryActionIcon(@DrawableRes int iconResId, boolean useLargeIcon) {
511            return withPrimaryActionIcon(null, iconResId, useLargeIcon);
512        }
513
514        /**
515         * Sets {@code Primary Action} to be represented by an icon.
516         *
517         * @param drawable the Drawable to set, or null to clear the content.
518         * @param useLargeIcon the size of primary icon. Large Icon is a square as tall as an item
519         *                     with only title set; useful for album cover art.
520         * @return This Builder object to allow for chaining calls to set methods.
521         */
522        public Builder withPrimaryActionIcon(Drawable drawable, boolean useLargeIcon) {
523            return withPrimaryActionIcon(drawable, 0, useLargeIcon);
524        }
525
526        private Builder withPrimaryActionIcon(Drawable drawable, @DrawableRes int iconResId,
527                boolean useLargeIcon) {
528            mPrimaryActionType = useLargeIcon
529                    ? PRIMARY_ACTION_TYPE_LARGE_ICON
530                    : PRIMARY_ACTION_TYPE_SMALL_ICON;
531            mPrimaryActionIconResId = iconResId;
532            mPrimaryActionIconDrawable = drawable;
533            return this;
534        }
535
536        /**
537         * Sets {@code Primary Action} to be empty icon.
538         *
539         * {@code Text} would have a start margin as if {@code Primary Action} were set to
540         * primary icon.
541         *
542         * @return This Builder object to allow for chaining calls to set methods.
543         */
544        public Builder withPrimaryActionEmptyIcon() {
545            mPrimaryActionType = PRIMARY_ACTION_TYPE_EMPTY_ICON;
546            return this;
547        }
548
549        /**
550         * Sets {@code Primary Action} to have no icon. Text would align to the start of item.
551         *
552         * @return This Builder object to allow for chaining calls to set methods.
553         */
554        public Builder withPrimaryActionNoIcon() {
555            mPrimaryActionType = PRIMARY_ACTION_TYPE_NO_ICON;
556            return this;
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 #withBody(String, boolean)}
564         *
565         * @param title text to display as title.
566         * @return This Builder object to allow for chaining calls to set methods.
567         */
568        public Builder withTitle(String title) {
569            mTitle = title;
570            return this;
571        }
572
573        /**
574         * Sets the body text of item.
575         *
576         * <p>Text beyond length required by regulation will be truncated. Defaults {@code Title}
577         * text as the primary.
578         *
579         * @return This Builder object to allow for chaining calls to set methods.
580         */
581        public Builder withBody(String body) {
582            return withBody(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 asPrimary sets {@code Body Text} as primary text of item.
591         * @return This Builder object to allow for chaining calls to set methods.
592         */
593        public Builder withBody(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            return this;
603        }
604
605        /**
606         * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
607         *
608         * @return This Builder object to allow for chaining calls to set methods.
609         */
610        public Builder withSupplementalIcon(int iconResId, boolean showDivider) {
611            return withSupplementalIcon(iconResId, showDivider, null);
612        }
613
614        /**
615         * Sets {@code Supplemental Action} to be represented by an {@code Supplemental Icon}.
616         *
617         * @param iconResId drawable resource id.
618         * @return This Builder object to allow for chaining calls to set methods.
619         */
620        public Builder withSupplementalIcon(int iconResId, boolean showDivider,
621                View.OnClickListener listener) {
622            mSupplementalActionType = SUPPLEMENTAL_ACTION_SUPPLEMENTAL_ICON;
623
624            mSupplementalIconResId = iconResId;
625            mSupplementalIconOnClickListener = listener;
626            mShowSupplementalIconDivider = showDivider;
627            return this;
628        }
629
630        /**
631         * Sets {@code Supplemental Action} to be represented by an {@code Action Button}.
632         *
633         * @param text button text to display.
634         * @return This Builder object to allow for chaining calls to set methods.
635         */
636        public Builder withAction(String text, boolean showDivider, View.OnClickListener listener) {
637            if (TextUtils.isEmpty(text)) {
638                throw new IllegalArgumentException("Action text cannot be empty.");
639            }
640            if (listener == null) {
641                throw new IllegalArgumentException("Action OnClickListener cannot be null.");
642            }
643            mSupplementalActionType = SUPPLEMENTAL_ACTION_ONE_ACTION;
644
645            mAction1Text = text;
646            mAction1OnClickListener = listener;
647            mShowAction1Divider = showDivider;
648            return this;
649        }
650
651        /**
652         * Sets {@code Supplemental Action} to be represented by two {@code Action Button}s.
653         *
654         * <p>These two action buttons will be aligned towards item end.
655         *
656         * @param action1Text button text to display - this button will be closer to item end.
657         * @param action2Text button text to display.
658         */
659        public Builder withActions(String action1Text, boolean showAction1Divider,
660                View.OnClickListener action1OnClickListener,
661                String action2Text, boolean showAction2Divider,
662                View.OnClickListener action2OnClickListener) {
663            if (TextUtils.isEmpty(action1Text) || TextUtils.isEmpty(action2Text)) {
664                throw new IllegalArgumentException("Action text cannot be empty.");
665            }
666            if (action1OnClickListener == null || action2OnClickListener == null) {
667                throw new IllegalArgumentException("Action OnClickListener cannot be null.");
668            }
669            mSupplementalActionType = SUPPLEMENTAL_ACTION_TWO_ACTIONS;
670
671            mAction1Text = action1Text;
672            mAction1OnClickListener = action1OnClickListener;
673            mShowAction1Divider = showAction1Divider;
674            mAction2Text = action2Text;
675            mAction2OnClickListener = action2OnClickListener;
676            mShowAction2Divider = showAction2Divider;
677            return this;
678        }
679
680        /**
681         * Adds {@link ViewBinder} to interact with sub-views in
682         * {@link ListItemAdapter.ViewHolder}. These ViewBinders will always bind after
683         * other {@link Builder} methods have bond.
684         *
685         * <p>Make sure to call with...() method on the intended sub-view first.
686         *
687         * <p>Example:
688         * <pre>
689         * {@code
690         * new Builder()
691         *     .withTitle("title")
692         *     .withViewBinder((viewHolder) -> {
693         *         viewHolder.getTitle().doMoreStuff();
694         *     })
695         *     .build();
696         * }
697         * </pre>
698         */
699        public Builder withViewBinder(ViewBinder binder) {
700            mCustomBinders.add(binder);
701            return this;
702        }
703    }
704}
705