DetailsOverviewRow.java revision 60bb6af2e336072921f5d3c3861e86b3cc6241b3
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 */ 14package android.support.v17.leanback.widget; 15 16import android.content.Context; 17import android.graphics.Bitmap; 18import android.graphics.drawable.BitmapDrawable; 19import android.graphics.drawable.Drawable; 20import android.view.KeyEvent; 21 22import java.lang.ref.WeakReference; 23import java.util.ArrayList; 24import java.util.List; 25 26/** 27 * An overview row for a details fragment. This row consists of an image, a 28 * description view, and optionally a series of {@link Action}s that can be taken for 29 * the item. 30 * 31 * <h3>Actions</h3> 32 * Application uses {@link #setActionsAdapter(ObjectAdapter)} to set actions on the overview 33 * row. {@link SparseArrayObjectAdapter} is recommended for easy updating actions while 34 * keeping the order. Application can add or remove actions on UI thread after the row is 35 * bound to view. 36 * 37 * <h3>Updating main item</h3> 38 * After the row is bound to view, application still can call ({@link #setItem(Object)}) 39 * on UI thread. 40 * 41 * <h3>Updating image</h3> 42 * After the row is bound to view, application still can change image by calling ({@link 43 * #setImageBitmap(Context, Bitmap)}) or {@link #setImageDrawable(Drawable)}) on UI thread. 44 */ 45public class DetailsOverviewRow extends Row { 46 47 /** 48 * Listener for changes of DetailsOverViewRow. 49 */ 50 static class Listener { 51 52 /** 53 * Called when DetailsOverviewRow has changed image drawable. 54 */ 55 public void onImageDrawableChanged(DetailsOverviewRow row) { 56 } 57 58 /** 59 * Called when DetailsOverviewRow has changed main item. 60 */ 61 public void onItemChanged(DetailsOverviewRow row) { 62 } 63 64 /** 65 * Called when DetailsOverviewRow has changed actions adapter. 66 */ 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 */ 83 public DetailsOverviewRow(Object item) { 84 super(null); 85 mItem = item; 86 verify(); 87 } 88 89 /** 90 * Adds listener for the details page. 91 */ 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 */ 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 */ 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 */ 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 */ 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 * Gets the main item for the details page. 184 */ 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 */ 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 */ 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 */ 220 public final void setImageBitmap(Context context, Bitmap bm) { 221 mImageDrawable = new BitmapDrawable(context.getResources(), bm); 222 notifyImageDrawableChanged(); 223 } 224 225 /** 226 * Gets 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 */ 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 */ 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 */ 250 public boolean isImageScaleUpAllowed() { 251 return mImageScaleUpAllowed; 252 } 253 254 /** 255 * Get array object adapter. Throws ClassCastException if the current ObjectAdapter is not 256 * ArrayObjectAdapter. 257 */ 258 private ArrayObjectAdapter getArrayObjectAdapter() { 259 return (ArrayObjectAdapter) mActionsAdapter; 260 } 261 262 /** 263 * Add an Action to the overview. It will throw ClassCastException if current actions adapter 264 * is not {@link ArrayObjectAdapter}. Must be called on UI thread. 265 * 266 * @param action The Action to add. 267 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 268 */ 269 public final void addAction(Action action) { 270 getArrayObjectAdapter().add(action); 271 } 272 273 /** 274 * Add an Action to the overview at the specified position. It will throw ClassCastException if 275 * current actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread. 276 * 277 * @param pos The position to insert the Action. 278 * @param action The Action to add. 279 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 280 */ 281 public final void addAction(int pos, Action action) { 282 getArrayObjectAdapter().add(pos, action); 283 } 284 285 /** 286 * Remove the given Action from the overview. It will throw ClassCastException if current 287 * actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread. 288 * 289 * @param action The Action to remove. 290 * @return true if the overview contained the specified Action. 291 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 292 */ 293 public final boolean removeAction(Action action) { 294 return getArrayObjectAdapter().remove(action); 295 } 296 297 /** 298 * Gets a read-only view of the list of Actions of this details overview. It will throw 299 * ClassCastException if current actions adapter is not {@link ArrayObjectAdapter}. Must be 300 * called on UI thread. 301 * 302 * @return An unmodifiable view of the list of Actions. 303 * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} 304 */ 305 public final List<Action> getActions() { 306 return getArrayObjectAdapter().unmodifiableList(); 307 } 308 309 /** 310 * Gets {@link ObjectAdapter} for actions. 311 */ 312 public final ObjectAdapter getActionsAdapter() { 313 return mActionsAdapter; 314 } 315 316 /** 317 * Sets {@link ObjectAdapter} for actions. 318 * @param adapter Adapter for actions, a default {@link PresenterSelector} will be attached 319 * to the adapter if it doesn't have one. 320 */ 321 public final void setActionsAdapter(ObjectAdapter adapter) { 322 if (adapter != mActionsAdapter) { 323 mActionsAdapter = adapter; 324 if (mActionsAdapter.getPresenterSelector() == null) { 325 mActionsAdapter.setPresenterSelector(mDefaultActionPresenter); 326 } 327 notifyActionsAdapterChanged(); 328 } 329 } 330 331 /** 332 * Returns the Action associated with the given keycode, or null if no associated action exists. 333 */ 334 public Action getActionForKeyCode(int keyCode) { 335 ObjectAdapter adapter = getActionsAdapter(); 336 if (adapter != null) { 337 for (int i = 0; i < adapter.size(); i++) { 338 Action action = (Action) adapter.get(i); 339 if (action.respondsToKeyCode(keyCode)) { 340 return action; 341 } 342 } 343 } 344 return null; 345 } 346 347 private void verify() { 348 if (mItem == null) { 349 throw new IllegalArgumentException("Object cannot be null"); 350 } 351 } 352} 353