1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 package androidx.leanback.widget; 15 16 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 17 18 import android.database.Observable; 19 20 import androidx.annotation.RestrictTo; 21 22 /** 23 * Base class adapter to be used in leanback activities. Provides access to a data model and is 24 * decoupled from the presentation of the items via {@link PresenterSelector}. 25 */ 26 public abstract class ObjectAdapter { 27 28 /** Indicates that an id has not been set. */ 29 public static final int NO_ID = -1; 30 31 /** 32 * A DataObserver can be notified when an ObjectAdapter's underlying data 33 * changes. Separate methods provide notifications about different types of 34 * changes. 35 */ 36 public static abstract class DataObserver { 37 /** 38 * Called whenever the ObjectAdapter's data has changed in some manner 39 * outside of the set of changes covered by the other range-based change 40 * notification methods. 41 */ 42 public void onChanged() { 43 } 44 45 /** 46 * Called when a range of items in the ObjectAdapter has changed. The 47 * basic ordering and structure of the ObjectAdapter has not changed. 48 * 49 * @param positionStart The position of the first item that changed. 50 * @param itemCount The number of items changed. 51 */ 52 public void onItemRangeChanged(int positionStart, int itemCount) { 53 onChanged(); 54 } 55 56 /** 57 * Called when a range of items in the ObjectAdapter has changed. The 58 * basic ordering and structure of the ObjectAdapter has not changed. 59 * 60 * @param positionStart The position of the first item that changed. 61 * @param itemCount The number of items changed. 62 * @param payload Optional parameter, use null to identify a "full" update. 63 */ 64 public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { 65 onChanged(); 66 } 67 68 /** 69 * Called when a range of items is inserted into the ObjectAdapter. 70 * 71 * @param positionStart The position of the first inserted item. 72 * @param itemCount The number of items inserted. 73 */ 74 public void onItemRangeInserted(int positionStart, int itemCount) { 75 onChanged(); 76 } 77 78 /** 79 * Called when an item is moved from one position to another position 80 * 81 * @param fromPosition Previous position of the item. 82 * @param toPosition New position of the item. 83 */ 84 public void onItemMoved(int fromPosition, int toPosition) { 85 onChanged(); 86 } 87 88 /** 89 * Called when a range of items is removed from the ObjectAdapter. 90 * 91 * @param positionStart The position of the first removed item. 92 * @param itemCount The number of items removed. 93 */ 94 public void onItemRangeRemoved(int positionStart, int itemCount) { 95 onChanged(); 96 } 97 } 98 99 private static final class DataObservable extends Observable<DataObserver> { 100 101 DataObservable() { 102 } 103 104 public void notifyChanged() { 105 for (int i = mObservers.size() - 1; i >= 0; i--) { 106 mObservers.get(i).onChanged(); 107 } 108 } 109 110 public void notifyItemRangeChanged(int positionStart, int itemCount) { 111 for (int i = mObservers.size() - 1; i >= 0; i--) { 112 mObservers.get(i).onItemRangeChanged(positionStart, itemCount); 113 } 114 } 115 116 public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { 117 for (int i = mObservers.size() - 1; i >= 0; i--) { 118 mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); 119 } 120 } 121 122 public void notifyItemRangeInserted(int positionStart, int itemCount) { 123 for (int i = mObservers.size() - 1; i >= 0; i--) { 124 mObservers.get(i).onItemRangeInserted(positionStart, itemCount); 125 } 126 } 127 128 public void notifyItemRangeRemoved(int positionStart, int itemCount) { 129 for (int i = mObservers.size() - 1; i >= 0; i--) { 130 mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); 131 } 132 } 133 134 public void notifyItemMoved(int positionStart, int toPosition) { 135 for (int i = mObservers.size() - 1; i >= 0; i--) { 136 mObservers.get(i).onItemMoved(positionStart, toPosition); 137 } 138 } 139 140 boolean hasObserver() { 141 return mObservers.size() > 0; 142 } 143 } 144 145 private final DataObservable mObservable = new DataObservable(); 146 private boolean mHasStableIds; 147 private PresenterSelector mPresenterSelector; 148 149 /** 150 * Constructs an adapter with the given {@link PresenterSelector}. 151 */ 152 public ObjectAdapter(PresenterSelector presenterSelector) { 153 setPresenterSelector(presenterSelector); 154 } 155 156 /** 157 * Constructs an adapter that uses the given {@link Presenter} for all items. 158 */ 159 public ObjectAdapter(Presenter presenter) { 160 setPresenterSelector(new SinglePresenterSelector(presenter)); 161 } 162 163 /** 164 * Constructs an adapter. 165 */ 166 public ObjectAdapter() { 167 } 168 169 /** 170 * Sets the presenter selector. May not be null. 171 */ 172 public final void setPresenterSelector(PresenterSelector presenterSelector) { 173 if (presenterSelector == null) { 174 throw new IllegalArgumentException("Presenter selector must not be null"); 175 } 176 final boolean update = (mPresenterSelector != null); 177 final boolean selectorChanged = update && mPresenterSelector != presenterSelector; 178 179 mPresenterSelector = presenterSelector; 180 181 if (selectorChanged) { 182 onPresenterSelectorChanged(); 183 } 184 if (update) { 185 notifyChanged(); 186 } 187 } 188 189 /** 190 * Called when {@link #setPresenterSelector(PresenterSelector)} is called 191 * and the PresenterSelector differs from the previous one. 192 */ 193 protected void onPresenterSelectorChanged() { 194 } 195 196 /** 197 * Returns the presenter selector for this ObjectAdapter. 198 */ 199 public final PresenterSelector getPresenterSelector() { 200 return mPresenterSelector; 201 } 202 203 /** 204 * Registers a DataObserver for data change notifications. 205 */ 206 public final void registerObserver(DataObserver observer) { 207 mObservable.registerObserver(observer); 208 } 209 210 /** 211 * Unregisters a DataObserver for data change notifications. 212 */ 213 public final void unregisterObserver(DataObserver observer) { 214 mObservable.unregisterObserver(observer); 215 } 216 217 /** 218 * @hide 219 */ 220 @RestrictTo(LIBRARY_GROUP) 221 public final boolean hasObserver() { 222 return mObservable.hasObserver(); 223 } 224 225 /** 226 * Unregisters all DataObservers for this ObjectAdapter. 227 */ 228 public final void unregisterAllObservers() { 229 mObservable.unregisterAll(); 230 } 231 232 /** 233 * Notifies UI that some items has changed. 234 * 235 * @param positionStart Starting position of the changed items. 236 * @param itemCount Total number of items that changed. 237 */ 238 public final void notifyItemRangeChanged(int positionStart, int itemCount) { 239 mObservable.notifyItemRangeChanged(positionStart, itemCount); 240 } 241 242 /** 243 * Notifies UI that some items has changed. 244 * 245 * @param positionStart Starting position of the changed items. 246 * @param itemCount Total number of items that changed. 247 * @param payload Optional parameter, use null to identify a "full" update. 248 */ 249 public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { 250 mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); 251 } 252 253 /** 254 * Notifies UI that new items has been inserted. 255 * 256 * @param positionStart Position where new items has been inserted. 257 * @param itemCount Count of the new items has been inserted. 258 */ 259 final protected void notifyItemRangeInserted(int positionStart, int itemCount) { 260 mObservable.notifyItemRangeInserted(positionStart, itemCount); 261 } 262 263 /** 264 * Notifies UI that some items that has been removed. 265 * 266 * @param positionStart Starting position of the removed items. 267 * @param itemCount Total number of items that has been removed. 268 */ 269 final protected void notifyItemRangeRemoved(int positionStart, int itemCount) { 270 mObservable.notifyItemRangeRemoved(positionStart, itemCount); 271 } 272 273 /** 274 * Notifies UI that item at fromPosition has been moved to toPosition. 275 * 276 * @param fromPosition Previous position of the item. 277 * @param toPosition New position of the item. 278 */ 279 protected final void notifyItemMoved(int fromPosition, int toPosition) { 280 mObservable.notifyItemMoved(fromPosition, toPosition); 281 } 282 283 /** 284 * Notifies UI that the underlying data has changed. 285 */ 286 final protected void notifyChanged() { 287 mObservable.notifyChanged(); 288 } 289 290 /** 291 * Returns true if the item ids are stable across changes to the 292 * underlying data. When this is true, clients of the ObjectAdapter can use 293 * {@link #getId(int)} to correlate Objects across changes. 294 */ 295 public final boolean hasStableIds() { 296 return mHasStableIds; 297 } 298 299 /** 300 * Sets whether the item ids are stable across changes to the underlying 301 * data. 302 */ 303 public final void setHasStableIds(boolean hasStableIds) { 304 boolean changed = mHasStableIds != hasStableIds; 305 mHasStableIds = hasStableIds; 306 307 if (changed) { 308 onHasStableIdsChanged(); 309 } 310 } 311 312 /** 313 * Called when {@link #setHasStableIds(boolean)} is called and the status 314 * of stable ids has changed. 315 */ 316 protected void onHasStableIdsChanged() { 317 } 318 319 /** 320 * Returns the {@link Presenter} for the given item from the adapter. 321 */ 322 public final Presenter getPresenter(Object item) { 323 if (mPresenterSelector == null) { 324 throw new IllegalStateException("Presenter selector must not be null"); 325 } 326 return mPresenterSelector.getPresenter(item); 327 } 328 329 /** 330 * Returns the number of items in the adapter. 331 */ 332 public abstract int size(); 333 334 /** 335 * Returns the item for the given position. 336 */ 337 public abstract Object get(int position); 338 339 /** 340 * Returns the id for the given position. 341 */ 342 public long getId(int position) { 343 return NO_ID; 344 } 345 346 /** 347 * Returns true if the adapter pairs each underlying data change with a call to notify and 348 * false otherwise. 349 */ 350 public boolean isImmediateNotifySupported() { 351 return false; 352 } 353 } 354