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 android.content.Context; 17 import android.graphics.Bitmap; 18 import android.graphics.drawable.BitmapDrawable; 19 import android.graphics.drawable.Drawable; 20 21 import java.lang.ref.WeakReference; 22 import java.util.ArrayList; 23 import java.util.List; 24 25 /** 26 * An overview {@link Row} for a details fragment. This row consists of an image, a 27 * description view, and optionally a series of {@link Action}s that can be taken for 28 * the item. 29 * 30 * <h3>Actions</h3> 31 * Application uses {@link #setActionsAdapter(ObjectAdapter)} to set actions on the overview 32 * row. {@link SparseArrayObjectAdapter} is recommended for easily updating actions while 33 * maintaining the order. The application can add or remove actions on the UI thread after the 34 * row is bound to a view. 35 * 36 * <h3>Updating main item</h3> 37 * After the row is bound to a view, the application may call {@link #setItem(Object)} 38 * on UI thread and the view will be updated. 39 * 40 * <h3>Updating image</h3> 41 * After the row is bound to view, the application may change the image by calling {@link 42 * #setImageBitmap(Context, Bitmap)} or {@link #setImageDrawable(Drawable)} on the UI thread, 43 * and the view will be updated. 44 */ 45 public class DetailsOverviewRow extends Row { 46 47 /** 48 * Listener for changes of DetailsOverviewRow. 49 */ 50 public static class Listener { 51 52 /** 53 * Called when DetailsOverviewRow has changed image drawable. 54 */ onImageDrawableChanged(DetailsOverviewRow row)55 public void onImageDrawableChanged(DetailsOverviewRow row) { 56 } 57 58 /** 59 * Called when DetailsOverviewRow has changed main item. 60 */ onItemChanged(DetailsOverviewRow row)61 public void onItemChanged(DetailsOverviewRow row) { 62 } 63 64 /** 65 * Called when DetailsOverviewRow has changed actions adapter. 66 */ onActionsAdapterChanged(DetailsOverviewRow row)67 public void onActionsAdapterChanged(DetailsOverviewRow row) { 68 } 69 } 70 71 private Object mItem; 72 private Drawable mImageDrawable; 73 private boolean mImageScaleUpAllowed = true; 74 private ArrayList<WeakReference<Listener>> mListeners; 75 private PresenterSelector mDefaultActionPresenter = new ActionPresenterSelector(); 76 private ObjectAdapter mActionsAdapter = new ArrayObjectAdapter(mDefaultActionPresenter); 77 78 /** 79 * Constructor for a DetailsOverviewRow. 80 * 81 * @param item The main item for the details page. 82 */ DetailsOverviewRow(Object item)83 public DetailsOverviewRow(Object item) { 84 super(null); 85 mItem = item; 86 verify(); 87 } 88 89 /** 90 * Adds listener for the details page. 91 */ addListener(Listener listener)92 final void addListener(Listener listener) { 93 if (mListeners == null) { 94 mListeners = new ArrayList<WeakReference<Listener>>(); 95 } else { 96 for (int i = 0; i < mListeners.size();) { 97 Listener l = mListeners.get(i).get(); 98 if (l == null) { 99 mListeners.remove(i); 100 } else { 101 if (l == listener) { 102 return; 103 } 104 i++; 105 } 106 } 107 } 108 mListeners.add(new WeakReference<Listener>(listener)); 109 } 110 111 /** 112 * Removes listener of the details page. 113 */ removeListener(Listener listener)114 final void removeListener(Listener listener) { 115 if (mListeners != null) { 116 for (int i = 0; i < mListeners.size();) { 117 Listener l = mListeners.get(i).get(); 118 if (l == null) { 119 mListeners.remove(i); 120 } else { 121 if (l == listener) { 122 mListeners.remove(i); 123 return; 124 } 125 i++; 126 } 127 } 128 } 129 } 130 131 /** 132 * Notifies listeners for main item change on UI thread. 133 */ notifyItemChanged()134 final void notifyItemChanged() { 135 if (mListeners != null) { 136 for (int i = 0; i < mListeners.size();) { 137 Listener l = mListeners.get(i).get(); 138 if (l == null) { 139 mListeners.remove(i); 140 } else { 141 l.onItemChanged(this); 142 i++; 143 } 144 } 145 } 146 } 147 148 /** 149 * Notifies listeners for image related change on UI thread. 150 */ notifyImageDrawableChanged()151 final void notifyImageDrawableChanged() { 152 if (mListeners != null) { 153 for (int i = 0; i < mListeners.size();) { 154 Listener l = mListeners.get(i).get(); 155 if (l == null) { 156 mListeners.remove(i); 157 } else { 158 l.onImageDrawableChanged(this); 159 i++; 160 } 161 } 162 } 163 } 164 165 /** 166 * Notifies listeners for actions adapter changed on UI thread. 167 */ notifyActionsAdapterChanged()168 final void notifyActionsAdapterChanged() { 169 if (mListeners != null) { 170 for (int i = 0; i < mListeners.size();) { 171 Listener l = mListeners.get(i).get(); 172 if (l == null) { 173 mListeners.remove(i); 174 } else { 175 l.onActionsAdapterChanged(this); 176 i++; 177 } 178 } 179 } 180 } 181 182 /** 183 * Returns the main item for the details page. 184 */ getItem()185 public final Object getItem() { 186 return mItem; 187 } 188 189 /** 190 * Sets the main item for the details page. Must be called on UI thread after 191 * row is bound to view. 192 */ setItem(Object item)193 public final void setItem(Object item) { 194 if (item != mItem) { 195 mItem = item; 196 notifyItemChanged(); 197 } 198 } 199 200 /** 201 * Sets a drawable as the image of this details overview. Must be called on UI thread 202 * after row is bound to view. 203 * 204 * @param drawable The drawable to set. 205 */ setImageDrawable(Drawable drawable)206 public final void setImageDrawable(Drawable drawable) { 207 if (mImageDrawable != drawable) { 208 mImageDrawable = drawable; 209 notifyImageDrawableChanged(); 210 } 211 } 212 213 /** 214 * Sets a Bitmap as the image of this details overview. Must be called on UI thread 215 * after row is bound to view. 216 * 217 * @param context The context to retrieve display metrics from. 218 * @param bm The bitmap to set. 219 */ setImageBitmap(Context context, Bitmap bm)220 public final void setImageBitmap(Context context, Bitmap bm) { 221 mImageDrawable = new BitmapDrawable(context.getResources(), bm); 222 notifyImageDrawableChanged(); 223 } 224 225 /** 226 * Returns the image drawable of this details overview. 227 * 228 * @return The overview's image drawable, or null if no drawable has been 229 * assigned. 230 */ getImageDrawable()231 public final Drawable getImageDrawable() { 232 return mImageDrawable; 233 } 234 235 /** 236 * Allows or disallows scaling up of images. 237 * Images will always be scaled down if necessary. Must be called on UI thread 238 * after row is bound to view. 239 */ setImageScaleUpAllowed(boolean allowed)240 public void setImageScaleUpAllowed(boolean allowed) { 241 if (allowed != mImageScaleUpAllowed) { 242 mImageScaleUpAllowed = allowed; 243 notifyImageDrawableChanged(); 244 } 245 } 246 247 /** 248 * Returns true if the image may be scaled up; false otherwise. 249 */ isImageScaleUpAllowed()250 public boolean isImageScaleUpAllowed() { 251 return mImageScaleUpAllowed; 252 } 253 254 /** 255 * Returns the actions adapter. Throws ClassCastException if the current 256 * actions adapter is not an instance of {@link ArrayObjectAdapter}. 257 */ getArrayObjectAdapter()258 private ArrayObjectAdapter getArrayObjectAdapter() { 259 return (ArrayObjectAdapter) mActionsAdapter; 260 } 261 262 /** 263 * Adds an Action to the overview. It will throw ClassCastException if the current actions 264 * adapter is not an instance of {@link ArrayObjectAdapter}. Must be called on the UI thread. 265 * 266 * @param action The Action to add. 267 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 268 */ 269 @Deprecated addAction(Action action)270 public final void addAction(Action action) { 271 getArrayObjectAdapter().add(action); 272 } 273 274 /** 275 * Adds an Action to the overview at the specified position. It will throw ClassCastException if 276 * current actions adapter is not an instance of f{@link ArrayObjectAdapter}. Must be called 277 * on the UI thread. 278 * 279 * @param pos The position to insert the Action. 280 * @param action The Action to add. 281 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 282 */ 283 @Deprecated addAction(int pos, Action action)284 public final void addAction(int pos, Action action) { 285 getArrayObjectAdapter().add(pos, action); 286 } 287 288 /** 289 * Removes the given Action from the overview. It will throw ClassCastException if current 290 * actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread. 291 * 292 * @param action The Action to remove. 293 * @return true if the overview contained the specified Action. 294 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 295 */ 296 @Deprecated removeAction(Action action)297 public final boolean removeAction(Action action) { 298 return getArrayObjectAdapter().remove(action); 299 } 300 301 /** 302 * Returns a read-only view of the list of Actions of this details overview. It will throw 303 * ClassCastException if current actions adapter is not {@link ArrayObjectAdapter}. Must be 304 * called on UI thread. 305 * 306 * @return An unmodifiable view of the list of Actions. 307 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 308 */ 309 @Deprecated getActions()310 public final List<Action> getActions() { 311 return getArrayObjectAdapter().unmodifiableList(); 312 } 313 314 /** 315 * Returns the {@link ObjectAdapter} for actions. 316 */ getActionsAdapter()317 public final ObjectAdapter getActionsAdapter() { 318 return mActionsAdapter; 319 } 320 321 /** 322 * Sets the {@link ObjectAdapter} for actions. A default {@link PresenterSelector} will be 323 * attached to the adapter if it doesn't have one. 324 * 325 * @param adapter Adapter for actions. 326 */ setActionsAdapter(ObjectAdapter adapter)327 public final void setActionsAdapter(ObjectAdapter adapter) { 328 if (adapter != mActionsAdapter) { 329 mActionsAdapter = adapter; 330 if (mActionsAdapter.getPresenterSelector() == null) { 331 mActionsAdapter.setPresenterSelector(mDefaultActionPresenter); 332 } 333 notifyActionsAdapterChanged(); 334 } 335 } 336 337 /** 338 * Returns the Action associated with the given keycode, or null if no associated action exists. 339 */ getActionForKeyCode(int keyCode)340 public Action getActionForKeyCode(int keyCode) { 341 ObjectAdapter adapter = getActionsAdapter(); 342 if (adapter != null) { 343 for (int i = 0; i < adapter.size(); i++) { 344 Action action = (Action) adapter.get(i); 345 if (action.respondsToKeyCode(keyCode)) { 346 return action; 347 } 348 } 349 } 350 return null; 351 } 352 verify()353 private void verify() { 354 if (mItem == null) { 355 throw new IllegalArgumentException("Object cannot be null"); 356 } 357 } 358 } 359