1package androidx.car.widget; 2 3import android.car.drivingstate.CarUxRestrictions; 4import android.view.View; 5 6import androidx.annotation.CallSuper; 7import androidx.annotation.Nullable; 8import androidx.annotation.StyleRes; 9import androidx.car.R; 10import androidx.recyclerview.widget.RecyclerView; 11 12import java.util.ArrayList; 13import java.util.List; 14import java.util.function.Function; 15 16/** 17 * Definition of items that can be inserted into {@link ListItemAdapter}. 18 * 19 * @paramViewHolder that extends {@link ListItem.ViewHolder}. 20 */ 21public 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