ListItem.java revision 15c4392796f5bb261c632412953db3fe753f9dda
1package androidx.car.widget; 2 3import android.car.drivingstate.CarUxRestrictions; 4import android.support.annotation.CallSuper; 5import android.support.annotation.Nullable; 6import android.support.annotation.StyleRes; 7import android.support.v7.widget.RecyclerView; 8import android.view.View; 9 10import java.util.ArrayList; 11import java.util.List; 12import java.util.function.Function; 13 14import androidx.car.R; 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 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 /** 139 * Does the work that moves the ListItem from dirty state to clean state, i.e. the work required 140 * the first time this ListItem {@code bind}s to {@link ListItem.ViewHolder}. 141 * This method will transition ListItem to clean state. ListItem in clean state should move to 142 * dirty state when it is modified by calling {@link #markDirty()}. 143 */ 144 protected abstract void resolveDirtyState(); 145 146 /** 147 * Binds this ListItem to {@code viewHolder} by applying data in ListItem to sub-views. 148 * Assume {@link ViewHolder#cleanUp()} has already been invoked. 149 */ 150 protected abstract void onBind(VH viewHolder); 151 152 /** 153 * Same as {@link #addViewBinder(ViewBinder, ViewBinder)} when {@code cleanUp} ViewBinder 154 * is null. 155 * 156 * @param binder to interact with subviews in {@code ViewHolder}. 157 * 158 * @see #addViewBinder(ViewBinder, ViewBinder) 159 */ 160 public final void addViewBinder(ViewBinder<VH> binder) { 161 addViewBinder(binder, null); 162 } 163 164 /** 165 * Adds {@link ViewBinder} to interact with sub-views in {@link ViewHolder}. These ViewBinders 166 * will always be applied after {@link #onBind(ViewHolder)}. 167 * 168 * <p>To interact with a foobar sub-view in {@code ViewHolder}, make sure to first set its 169 * visibility, or call setFoobar() setter method. 170 * 171 * <p>Example: 172 * <pre> 173 * {@code 174 * TextListItem item = new TextListItem(context); 175 * item.setTitle("title"); 176 * item.addViewBinder((viewHolder) -> { 177 * viewHolder.getTitle().doFoobar(); 178 * }, (viewHolder) -> { 179 * viewHolder.getTitle().revertFoobar(); 180 * }); 181 * } 182 * </pre> 183 * 184 * @params binder to interact with subviews in {@code ViewHolder}. 185 * @params cleanUp view binder to revert the effect of {@code binder}. cleanUp binders will be 186 * stored in {@link ListItem.ViewHolder} and should be invoked via 187 * {@link ViewHolder#cleanUp()} before {@code ViewHolder} is recycled. 188 * This is to avoid changed made to ViewHolder lingers around when ViewHolder is 189 * recycled. Pass in null to skip. 190 */ 191 public final void addViewBinder(ViewBinder<VH> binder, @Nullable ViewBinder<VH> cleanUp) { 192 mCustomBinders.add(binder); 193 if (cleanUp != null) { 194 mCustomBinderCleanUps.add(cleanUp); 195 } 196 markDirty(); 197 } 198 199 /** 200 * Removes the first occurrence of the specified item. 201 * 202 * @param binder to be removed. 203 * @return {@code true} if {@code binder} exists. {@code false} otherwise. 204 */ 205 public boolean removeViewBinder(ViewBinder<VH> binder) { 206 return mCustomBinders.remove(binder); 207 } 208 209 /** 210 * Functional interface to provide a way to interact with views in {@code ViewHolder}. 211 * {@code ListItem} calls all added ViewBinders when it {@code bind}s to {@code ViewHolder}. 212 * 213 * @param class that extends {@link RecyclerView.ViewHolder}. 214 */ 215 public interface ViewBinder<VH> { 216 /** 217 * Provides a way to interact with views in view holder. 218 */ 219 void bind(VH viewHolder); 220 } 221 222 /** 223 * ViewHolder that supports {@link ViewBinder}. 224 */ 225 public abstract static class ViewHolder extends RecyclerView.ViewHolder { 226 private final List<ViewBinder> mCleanUps = new ArrayList<>(); 227 228 public ViewHolder(View itemView) { 229 super(itemView); 230 } 231 232 /** 233 * Removes customization from previous ListItem. Intended to be used when this ViewHolder is 234 * bound to a ListItem. 235 */ 236 public final void cleanUp() { 237 for (ViewBinder binder : mCleanUps) { 238 binder.bind(this); 239 } 240 } 241 242 /** 243 * Stores clean up ViewBinders that will be called in {@code cleanUp()}. 244 */ 245 public final void addCleanUp(@Nullable ViewBinder<ViewHolder> cleanUp) { 246 if (cleanUp != null) { 247 mCleanUps.add(cleanUp); 248 } 249 } 250 251 /** 252 * Update children views to comply with UX restriction changes. 253 * 254 * @param restrictions current car UX restrictions. 255 */ 256 abstract void complyWithUxRestrictions(CarUxRestrictions restrictions); 257 } 258} 259