Home | History | Annotate | Download | only in widget
      1 package androidx.car.widget;
      2 
      3 import android.car.drivingstate.CarUxRestrictions;
      4 import android.view.View;
      5 
      6 import androidx.annotation.CallSuper;
      7 import androidx.annotation.Nullable;
      8 import androidx.annotation.StyleRes;
      9 import androidx.car.R;
     10 import androidx.recyclerview.widget.RecyclerView;
     11 
     12 import java.util.ArrayList;
     13 import java.util.List;
     14 import java.util.function.Function;
     15 
     16 /**
     17  * Definition of items that can be inserted into {@link ListItemAdapter}.
     18  *
     19  * @param  ViewHolder that extends {@link ListItem.ViewHolder}.
     20  */
     21 public abstract class ListItem<VH extends ListItem.ViewHolder> {
     22 
     23     // Whether the item should calculate view layout params. This usually happens when the item is
     24     // updated after bind() is called. Calling bind() resets to false.
     25     private boolean mDirty;
     26 
     27     // Tag for indicating whether to hide the divider.
     28     private boolean mHideDivider;
     29 
     30     private final List<ViewBinder<VH>> mCustomBinders = new ArrayList<>();
     31     // Stores ViewBinders to revert customization. Does not guarantee to 1:1 match ViewBinders
     32     // in mCustomerBinders.
     33     private final List<ViewBinder<VH>> mCustomBinderCleanUps = new ArrayList<>();
     34 
     35     @StyleRes private int mTitleTextAppearance = R.style.TextAppearance_Car_Body1;
     36     @StyleRes private int mBodyTextAppearance = R.style.TextAppearance_Car_Body2;
     37 
     38     /**
     39      * Classes that extends {@code ListItem} should register its view type in
     40      * {@link ListItemAdapter#registerListItemViewType(int, int, Function)}.
     41      *
     42      * @return type of this ListItem.
     43      */
     44     public abstract int getViewType();
     45 
     46     /**
     47      * Called when ListItem is bound to its ViewHolder.
     48      */
     49     final void bind(VH viewHolder) {
     50         // Attempt to clean up custom view binder from previous item (if any).
     51         // Then save the clean up binders for next item.
     52         viewHolder.cleanUp();
     53         for (ViewBinder cleanUp : mCustomBinderCleanUps) {
     54             viewHolder.addCleanUp(cleanUp);
     55         }
     56 
     57         if (isDirty()) {
     58             resolveDirtyState();
     59             markClean();
     60         }
     61         onBind(viewHolder);
     62 
     63         // Custom view binders are applied after view layout.
     64         for (ViewBinder<VH> binder: mCustomBinders) {
     65             binder.bind(viewHolder);
     66         }
     67     }
     68 
     69     /** Sets the title text appearance from the specified style resource. */
     70     @CallSuper
     71     void setTitleTextAppearance(@StyleRes int titleTextAppearance) {
     72         mTitleTextAppearance = titleTextAppearance;
     73     }
     74 
     75     /** Sets the body text appearance from the specified style resource. */
     76     @CallSuper
     77     void setBodyTextAppearance(@StyleRes int bodyTextAppearance) {
     78         mBodyTextAppearance = bodyTextAppearance;
     79     }
     80 
     81     /** Returns the text appearance that should be used for title text. */
     82     @StyleRes
     83     final int getTitleTextAppearance() {
     84         return mTitleTextAppearance;
     85     }
     86 
     87     /** Returns the text appearance that should be used for body text. */
     88     @StyleRes
     89     final int getBodyTextAppearance() {
     90         return mBodyTextAppearance;
     91     }
     92 
     93 
     94     /**
     95      * Marks this item as dirty so {@link #resolveDirtyState()} is required in next bind() call.
     96      *
     97      * <p>This method should be called in each setter.
     98      */
     99     protected void markDirty() {
    100         mDirty = true;
    101     }
    102 
    103     /**
    104      * Marks this item as not dirty. No need to call {@link #resolveDirtyState()} in next bind().
    105      */
    106     protected void markClean() {
    107         mDirty = false;
    108     }
    109 
    110     /**
    111      * @return {@code true} if next bind() should call {@link #resolveDirtyState()}.
    112      */
    113     protected boolean isDirty() {
    114         return mDirty;
    115     }
    116 
    117     /**
    118      * Whether hide the item divider coming after this {@code ListItem}.
    119      *
    120      * <p>Note: For this to work, one must invoke
    121      * {@code PagedListView.setDividerVisibilityManager(adapter} for {@link ListItemAdapter} and
    122      * have dividers enabled on {@link PagedListView}.
    123      */
    124     public void setHideDivider(boolean hideDivider) {
    125         mHideDivider = hideDivider;
    126         markDirty();
    127     }
    128 
    129     /**
    130      * @return {@code true} if the divider that comes after this ListItem should be hidden.
    131      * Defaults to false.
    132      */
    133     public boolean shouldHideDivider() {
    134         return mHideDivider;
    135     };
    136 
    137     /**
    138      * Does the work that moves the ListItem from dirty state to clean state, i.e. the work required
    139      * the first time this ListItem {@code bind}s to {@link ListItem.ViewHolder}.
    140      * This method will transition ListItem to clean state. ListItem in clean state should move to
    141      * dirty state when it is modified by calling {@link #markDirty()}.
    142      */
    143     protected abstract void resolveDirtyState();
    144 
    145     /**
    146      * Binds this ListItem to {@code viewHolder} by applying data in ListItem to sub-views.
    147      * Assume {@link ViewHolder#cleanUp()} has already been invoked.
    148      */
    149     protected abstract void onBind(VH viewHolder);
    150 
    151     /**
    152      * Same as {@link #addViewBinder(ViewBinder, ViewBinder)} when {@code cleanUp} ViewBinder
    153      * is null.
    154      *
    155      * @param binder to interact with subviews in {@code ViewHolder}.
    156      *
    157      * @see #addViewBinder(ViewBinder, ViewBinder)
    158      */
    159     public final void addViewBinder(ViewBinder<VH> binder) {
    160         addViewBinder(binder, null);
    161     }
    162 
    163     /**
    164      * Adds {@link ViewBinder} to interact with sub-views in {@link ViewHolder}. These ViewBinders
    165      * will always be applied after {@link #onBind(ViewHolder)}.
    166      *
    167      * <p>To interact with a foobar sub-view in {@code ViewHolder}, make sure to first set its
    168      * visibility, or call setFoobar() setter method.
    169      *
    170      * <p>Example:
    171      * <pre>
    172      * {@code
    173      * TextListItem item = new TextListItem(context);
    174      * item.setTitle("title");
    175      * item.addViewBinder((viewHolder) -> {
    176      *     viewHolder.getTitle().doFoobar();
    177      * }, (viewHolder) -> {
    178      *     viewHolder.getTitle().revertFoobar();
    179      * });
    180      * }
    181      * </pre>
    182      *
    183      * @param binder to interact with subviews in {@code ViewHolder}.
    184      * @param cleanUp view binder to revert the effect of {@code binder}. cleanUp binders will be
    185      *                 stored in {@link ListItem.ViewHolder} and should be invoked via
    186      *                 {@link ViewHolder#cleanUp()} before {@code ViewHolder} is recycled.
    187      *                 This is to avoid changed made to ViewHolder lingers around when ViewHolder is
    188      *                 recycled. Pass in null to skip.
    189      */
    190     public final void addViewBinder(ViewBinder<VH> binder, @Nullable ViewBinder<VH> cleanUp) {
    191         mCustomBinders.add(binder);
    192         if (cleanUp != null) {
    193             mCustomBinderCleanUps.add(cleanUp);
    194         }
    195         markDirty();
    196     }
    197 
    198     /**
    199      * Removes the first occurrence of the specified item.
    200      *
    201      * @param binder to be removed.
    202      * @return {@code true} if {@code binder} exists. {@code false} otherwise.
    203      */
    204     public boolean removeViewBinder(ViewBinder<VH> binder) {
    205         return mCustomBinders.remove(binder);
    206     }
    207 
    208     /**
    209      * Functional interface to provide a way to interact with views in {@code ViewHolder}.
    210      * {@code ListItem} calls all added ViewBinders when it {@code bind}s to {@code ViewHolder}.
    211      *
    212      * @param  class that extends {@link RecyclerView.ViewHolder}.
    213      */
    214     public interface ViewBinder<VH> {
    215         /**
    216          * Provides a way to interact with views in view holder.
    217          */
    218         void bind(VH viewHolder);
    219     }
    220 
    221     /**
    222      * ViewHolder that supports {@link ViewBinder}.
    223      */
    224     public abstract static class ViewHolder extends RecyclerView.ViewHolder {
    225         private final List<ViewBinder> mCleanUps = new ArrayList<>();
    226 
    227         public ViewHolder(View itemView) {
    228             super(itemView);
    229         }
    230 
    231         /**
    232          * Removes customization from previous ListItem. Intended to be used when this ViewHolder is
    233          * bound to a ListItem.
    234          */
    235         public final void cleanUp() {
    236             for (ViewBinder binder : mCleanUps) {
    237                 binder.bind(this);
    238             }
    239         }
    240 
    241         /**
    242          * Stores clean up ViewBinders that will be called in {@code cleanUp()}.
    243          */
    244         public final void addCleanUp(@Nullable ViewBinder<ViewHolder> cleanUp) {
    245             if (cleanUp != null) {
    246                 mCleanUps.add(cleanUp);
    247             }
    248         }
    249 
    250         /**
    251          * Update children views to comply with UX restriction changes.
    252          *
    253          * @param restrictions current car UX restrictions.
    254          */
    255         protected abstract void complyWithUxRestrictions(CarUxRestrictions restrictions);
    256     }
    257 }
    258