RowPresenter.java revision 9de363b8db05106b03d115c266859fe200d41db7
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.support.v17.leanback.app.HeadersFragment; 17import android.view.View; 18import android.view.ViewGroup; 19 20/** 21 * An abstract {@link Presenter} that renders a {@link Row}. 22 * 23 * <h3>Customize UI widgets</h3> 24 * When a subclass of RowPresenter adds UI widgets, it should subclass 25 * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)} 26 * and {@link #initializeRowViewHolder(ViewHolder)}. The subclass must use layout id 27 * "row_content" for the widget that will be aligned to the title of any {@link HeadersFragment} 28 * that may exist in the parent fragment. RowPresenter contains an optional and 29 * replaceable {@link RowHeaderPresenter} that renders the header. You can disable 30 * the default rendering or replace the Presenter with a new header presenter 31 * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}. 32 * 33 * <h3>UI events from fragments</h3> 34 * RowPresenter receives calls from its parent (typically a Fragment) when: 35 * <ul> 36 * <li> 37 * A Row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}. The event 38 * is triggered immediately when there is a row selection change before the selection 39 * animation is started. 40 * Subclasses of RowPresenter may override {@link #onRowViewSelected(ViewHolder, boolean)}. 41 * </li> 42 * <li> 43 * A Row is expanded to full width via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}. 44 * The event is triggered immediately before the expand animation is started. 45 * Subclasses of RowPresenter may override {@link #onRowViewExpanded(ViewHolder, boolean)}. 46 * </li> 47 * </ul> 48 * 49 * <h3>User events</h3> 50 * RowPresenter provides {@link OnItemSelectedListener} and {@link OnItemClickedListener}. 51 * If a subclass wants to add its own {@link View.OnFocusChangeListener} or 52 * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)} 53 * to be properly chained by the library. Adding View listeners after 54 * {@link #createRowViewHolder(ViewGroup)} is undefined and may result in 55 * incorrect behavior by the library's listeners. 56 * 57 * <h3>Selection animation</h3> 58 * <p> 59 * When a user scrolls through rows, a fragment will initiate animation and call 60 * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value between 61 * 0 and 1. By default, the RowPresenter draws a dim overlay on top of the row 62 * view for views that are not selected. Subclasses may override this default effect 63 * by having {@link #isUsingDefaultSelectEffect()} return false and overriding 64 * {@link #onSelectLevelChanged(ViewHolder)} to apply a different selection effect. 65 * </p> 66 * <p> 67 * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable the select effect, 68 * This will not only enable/disable the default dim effect but also subclasses must 69 * respect this flag as well. 70 * </p> 71 */ 72public abstract class RowPresenter extends Presenter { 73 74 static class ContainerViewHolder extends Presenter.ViewHolder { 75 /** 76 * wrapped row view holder 77 */ 78 final ViewHolder mRowViewHolder; 79 80 public ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder) { 81 super(containerView); 82 containerView.addRowView(rowViewHolder.view); 83 if (rowViewHolder.mHeaderViewHolder != null) { 84 containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view); 85 } 86 mRowViewHolder = rowViewHolder; 87 mRowViewHolder.mContainerViewHolder = this; 88 } 89 } 90 91 /** 92 * A view holder for a {@link Row}. 93 */ 94 public static class ViewHolder extends Presenter.ViewHolder { 95 ContainerViewHolder mContainerViewHolder; 96 RowHeaderPresenter.ViewHolder mHeaderViewHolder; 97 Row mRow; 98 boolean mSelected; 99 boolean mExpanded; 100 boolean mInitialzed; 101 float mSelectLevel = 0f; // initially unselected 102 103 /** 104 * Constructor for ViewHolder. 105 * 106 * @param view The View bound to the Row. 107 */ 108 public ViewHolder(View view) { 109 super(view); 110 } 111 112 /** 113 * Returns the Row bound to the View in this ViewHolder. 114 */ 115 public final Row getRow() { 116 return mRow; 117 } 118 119 /** 120 * Returns whether the Row is in its expanded state. 121 * 122 * @return true if the Row is expanded, false otherwise. 123 */ 124 public final boolean isExpanded() { 125 return mExpanded; 126 } 127 128 /** 129 * Returns whether the Row is selected. 130 * 131 * @return true if the Row is selected, false otherwise. 132 */ 133 public final boolean isSelected() { 134 return mSelected; 135 } 136 137 /** 138 * Returns the current selection level of the Row. 139 */ 140 public final float getSelectLevel() { 141 return mSelectLevel; 142 } 143 144 /** 145 * Returns the view holder for the Row header for this Row. 146 */ 147 public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() { 148 return mHeaderViewHolder; 149 } 150 } 151 152 private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter(); 153 private OnItemSelectedListener mOnItemSelectedListener; 154 private OnItemClickedListener mOnItemClickedListener; 155 private OnItemViewSelectedListener mOnItemViewSelectedListener; 156 private OnItemViewClickedListener mOnItemViewClickedListener; 157 158 boolean mSelectEffectEnabled = true; 159 160 @Override 161 public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { 162 ViewHolder vh = createRowViewHolder(parent); 163 vh.mInitialzed = false; 164 Presenter.ViewHolder result; 165 if (needsRowContainerView()) { 166 RowContainerView containerView = new RowContainerView(parent.getContext()); 167 if (mHeaderPresenter != null) { 168 vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder) 169 mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view); 170 } 171 result = new ContainerViewHolder(containerView, vh); 172 } else { 173 result = vh; 174 } 175 initializeRowViewHolder(vh); 176 if (!vh.mInitialzed) { 177 throw new RuntimeException("super.initializeRowViewHolder() must be called"); 178 } 179 return result; 180 } 181 182 /** 183 * Called to create a ViewHolder object for a Row. Subclasses will override 184 * this method to return a different concrete ViewHolder object. 185 * 186 * @param parent The parent View for the Row's view holder. 187 * @return A ViewHolder for the Row's View. 188 */ 189 protected abstract ViewHolder createRowViewHolder(ViewGroup parent); 190 191 /** 192 * Called after a {@link RowPresenter.ViewHolder} is created for a Row. 193 * Subclasses may override this method and start by calling 194 * super.initializeRowViewHolder(ViewHolder). 195 * 196 * @param vh The ViewHolder to initialize for the Row. 197 */ 198 protected void initializeRowViewHolder(ViewHolder vh) { 199 vh.mInitialzed = true; 200 } 201 202 /** 203 * Set the Presenter used for rendering the header. Can be null to disable 204 * header rendering. The method must be called before creating any Row Views. 205 */ 206 public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) { 207 mHeaderPresenter = headerPresenter; 208 } 209 210 /** 211 * Get the Presenter used for rendering the header, or null if none has been 212 * set. 213 */ 214 public final RowHeaderPresenter getHeaderPresenter() { 215 return mHeaderPresenter; 216 } 217 218 /** 219 * Get the {@link RowPresenter.ViewHolder} from the given Presenter 220 * ViewHolder. 221 */ 222 public final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) { 223 if (holder instanceof ContainerViewHolder) { 224 return ((ContainerViewHolder) holder).mRowViewHolder; 225 } else { 226 return (ViewHolder) holder; 227 } 228 } 229 230 /** 231 * Set the expanded state of a Row view. 232 * 233 * @param holder The Row ViewHolder to set expanded state on. 234 * @param expanded True if the Row is expanded, false otherwise. 235 */ 236 public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) { 237 ViewHolder rowViewHolder = getRowViewHolder(holder); 238 rowViewHolder.mExpanded = expanded; 239 onRowViewExpanded(rowViewHolder, expanded); 240 } 241 242 /** 243 * Set the selected state of a Row view. 244 * 245 * @param holder The Row ViewHolder to set expanded state on. 246 * @param selected True if the Row is expanded, false otherwise. 247 */ 248 public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) { 249 ViewHolder rowViewHolder = getRowViewHolder(holder); 250 rowViewHolder.mSelected = selected; 251 onRowViewSelected(rowViewHolder, selected); 252 } 253 254 /** 255 * Subclass may override this to respond to expanded state changes of a Row. 256 * The default implementation will hide/show the header view. Subclasses may 257 * make visual changes to the Row View but must not create animation on the 258 * Row view. 259 */ 260 protected void onRowViewExpanded(ViewHolder vh, boolean expanded) { 261 updateHeaderViewVisibility(vh); 262 vh.view.setActivated(expanded); 263 } 264 265 /** 266 * Subclass may override this to respond to selected state changes of a Row. 267 * Subclass may make visual changes to Row view but must not create 268 * animation on the Row view. 269 */ 270 protected void onRowViewSelected(ViewHolder vh, boolean selected) { 271 if (selected) { 272 if (mOnItemViewSelectedListener != null) { 273 mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow()); 274 } 275 if (mOnItemSelectedListener != null) { 276 mOnItemSelectedListener.onItemSelected(null, vh.getRow()); 277 } 278 } 279 updateHeaderViewVisibility(vh); 280 } 281 282 private void updateHeaderViewVisibility(ViewHolder vh) { 283 if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) { 284 RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view); 285 containerView.showHeader(vh.isExpanded()); 286 } 287 } 288 289 /** 290 * Set the current select level to a value between 0 (unselected) and 1 (selected). 291 * Subclasses may override {@link #onSelectLevelChanged(ViewHolder)} to 292 * respond to changes in the selected level. 293 */ 294 public final void setSelectLevel(Presenter.ViewHolder vh, float level) { 295 ViewHolder rowViewHolder = getRowViewHolder(vh); 296 rowViewHolder.mSelectLevel = level; 297 onSelectLevelChanged(rowViewHolder); 298 } 299 300 /** 301 * Get the current select level. The value will be between 0 (unselected) 302 * and 1 (selected). 303 */ 304 public final float getSelectLevel(Presenter.ViewHolder vh) { 305 return getRowViewHolder(vh).mSelectLevel; 306 } 307 308 /** 309 * Callback when select level is changed. The default implementation applies 310 * the select level to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)} 311 * when {@link #getSelectEffectEnabled()} is true. Subclasses may override 312 * this function and implement a different select effect. In this case, you 313 * should also override {@link #isUsingDefaultSelectEffect()} to disable 314 * the default dimming effect applied by the library. 315 */ 316 protected void onSelectLevelChanged(ViewHolder vh) { 317 if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) { 318 mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel); 319 } 320 } 321 322 /** 323 * Enables or disables the row selection effect. 324 * This will not only affect the default dim effect, but subclasses must 325 * respect this flag as well. 326 */ 327 public final void setSelectEffectEnabled(boolean applyDimOnSelect) { 328 mSelectEffectEnabled = applyDimOnSelect; 329 } 330 331 /** 332 * Returns true if the row selection effect is enabled. 333 * This value not only determines whether the default dim implementation is 334 * used, but subclasses must also respect this flag. 335 */ 336 public final boolean getSelectEffectEnabled() { 337 return mSelectEffectEnabled; 338 } 339 340 /** 341 * Return whether this RowPresenter is using the default dimming effect 342 * provided by the library. Subclasses may(most likely) return false and 343 * override {@link #onSelectLevelChanged(ViewHolder)}. 344 */ 345 public boolean isUsingDefaultSelectEffect() { 346 return true; 347 } 348 349 final boolean needsDefaultSelectEffect() { 350 return isUsingDefaultSelectEffect() && getSelectEffectEnabled(); 351 } 352 353 final boolean needsRowContainerView() { 354 return mHeaderPresenter != null; 355 } 356 357 /** 358 * Return true if the Row view can draw outside its bounds. 359 */ 360 public boolean canDrawOutOfBounds() { 361 return false; 362 } 363 364 @Override 365 public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { 366 onBindRowViewHolder(getRowViewHolder(viewHolder), item); 367 } 368 369 protected void onBindRowViewHolder(ViewHolder vh, Object item) { 370 vh.mRow = (Row) item; 371 if (vh.mHeaderViewHolder != null) { 372 mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item); 373 } 374 } 375 376 @Override 377 public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { 378 onUnbindRowViewHolder(getRowViewHolder(viewHolder)); 379 } 380 381 protected void onUnbindRowViewHolder(ViewHolder vh) { 382 if (vh.mHeaderViewHolder != null) { 383 mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder); 384 } 385 vh.mRow = null; 386 } 387 388 @Override 389 public final void onViewAttachedToWindow(Presenter.ViewHolder holder) { 390 onRowViewAttachedToWindow(getRowViewHolder(holder)); 391 } 392 393 protected void onRowViewAttachedToWindow(ViewHolder vh) { 394 if (vh.mHeaderViewHolder != null) { 395 mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder); 396 } 397 } 398 399 @Override 400 public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) { 401 onRowViewDetachedFromWindow(getRowViewHolder(holder)); 402 } 403 404 protected void onRowViewDetachedFromWindow(ViewHolder vh) { 405 if (vh.mHeaderViewHolder != null) { 406 mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder); 407 } 408 } 409 410 /** 411 * Set the listener for item or row selection. A RowPresenter fires a row 412 * selection event with a null item. Subclasses (e.g. {@link ListRowPresenter}) 413 * can fire a selection event with the selected item. 414 */ 415 public final void setOnItemSelectedListener(OnItemSelectedListener listener) { 416 mOnItemSelectedListener = listener; 417 } 418 419 /** 420 * Get the listener for item or row selection. 421 */ 422 public final OnItemSelectedListener getOnItemSelectedListener() { 423 return mOnItemSelectedListener; 424 } 425 426 /** 427 * Set the listener for item click events. A RowPresenter does not use this 428 * listener, but a subclass may fire an item click event if it has the concept 429 * of an item. The {@link OnItemClickedListener} will override any 430 * {@link View.OnClickListener} that an item's Presenter sets during 431 * {@link Presenter#onCreateViewHolder(ViewGroup)}. So in general, you 432 * should choose to use an OnItemClickedListener or a {@link 433 * View.OnClickListener}, but not both. 434 */ 435 public final void setOnItemClickedListener(OnItemClickedListener listener) { 436 mOnItemClickedListener = listener; 437 } 438 439 /** 440 * Get the listener for item click events. 441 */ 442 public final OnItemClickedListener getOnItemClickedListener() { 443 return mOnItemClickedListener; 444 } 445 446 /** 447 * Set listener for item or row selection. RowPresenter fires row selection 448 * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can 449 * fire a selection event with selected item. 450 */ 451 public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) { 452 mOnItemViewSelectedListener = listener; 453 } 454 455 /** 456 * Get listener for item or row selection. 457 */ 458 public final OnItemViewSelectedListener getOnItemViewSelectedListener() { 459 return mOnItemViewSelectedListener; 460 } 461 462 /** 463 * Set listener for item click event. RowPresenter does nothing but subclass of 464 * RowPresenter may fire item click event if it does have a concept of item. 465 * OnItemViewClickedListener will override {@link View.OnClickListener} that 466 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 467 * So in general, developer should choose one of the listeners but not both. 468 */ 469 public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) { 470 mOnItemViewClickedListener = listener; 471 } 472 473 /** 474 * Set listener for item click event. 475 */ 476 public final OnItemViewClickedListener getOnItemViewClickedListener() { 477 return mOnItemViewClickedListener; 478 } 479} 480