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 android.support.v17.leanback.widget; 15 16 import android.content.Context; 17 import android.content.res.TypedArray; 18 import android.support.v17.leanback.R; 19 import android.support.v17.leanback.system.Settings; 20 import android.support.v17.leanback.transition.TransitionHelper; 21 import android.support.v7.widget.RecyclerView; 22 import android.util.Log; 23 import android.view.KeyEvent; 24 import android.view.View; 25 import android.view.ViewGroup; 26 27 import java.util.HashMap; 28 29 /** 30 * ListRowPresenter renders {@link ListRow} using a 31 * {@link HorizontalGridView} hosted in a {@link ListRowView}. 32 * 33 * <h3>Hover card</h3> 34 * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to 35 * display a view for the currently focused list item below the rendered 36 * list. This view is known as a hover card. 37 * 38 * <h3>Row selection animation</h3> 39 * ListRowPresenter disables {@link RowPresenter}'s default full row dimming effect and draws 40 * a dim overlay on each child individually. A subclass may disable the overlay on each child 41 * by overriding {@link #isUsingDefaultListSelectEffect()} to return false and write its own child 42 * dim effect in {@link #applySelectLevelToChild(ViewHolder, View)}. 43 * 44 * <h3>Shadow</h3> 45 * ListRowPresenter applies a default shadow to each child view. Call 46 * {@link #setShadowEnabled(boolean)} to disable shadows. A subclass may override and return 47 * false in {@link #isUsingDefaultShadow()} and replace with its own shadow implementation. 48 */ 49 public class ListRowPresenter extends RowPresenter { 50 51 private static final String TAG = "ListRowPresenter"; 52 private static final boolean DEBUG = false; 53 54 private static final int DEFAULT_RECYCLED_POOL_SIZE = 24; 55 56 /** 57 * ViewHolder for the ListRowPresenter. 58 */ 59 public static class ViewHolder extends RowPresenter.ViewHolder { 60 final ListRowPresenter mListRowPresenter; 61 final HorizontalGridView mGridView; 62 ItemBridgeAdapter mItemBridgeAdapter; 63 final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher(); 64 final int mPaddingTop; 65 final int mPaddingBottom; 66 final int mPaddingLeft; 67 final int mPaddingRight; 68 ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p)69 public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) { 70 super(rootView); 71 mGridView = gridView; 72 mListRowPresenter = p; 73 mPaddingTop = mGridView.getPaddingTop(); 74 mPaddingBottom = mGridView.getPaddingBottom(); 75 mPaddingLeft = mGridView.getPaddingLeft(); 76 mPaddingRight = mGridView.getPaddingRight(); 77 } 78 79 /** 80 * Gets ListRowPresenter that creates this ViewHolder. 81 * @return ListRowPresenter that creates this ViewHolder. 82 */ getListRowPresenter()83 public final ListRowPresenter getListRowPresenter() { 84 return mListRowPresenter; 85 } 86 87 /** 88 * Gets HorizontalGridView that shows a list of items. 89 * @return HorizontalGridView that shows a list of items. 90 */ getGridView()91 public final HorizontalGridView getGridView() { 92 return mGridView; 93 } 94 95 /** 96 * Gets ItemBridgeAdapter that creates the list of items. 97 * @return ItemBridgeAdapter that creates the list of items. 98 */ getBridgeAdapter()99 public final ItemBridgeAdapter getBridgeAdapter() { 100 return mItemBridgeAdapter; 101 } 102 103 /** 104 * Gets selected item position in adapter. 105 * @return Selected item position in adapter. 106 */ getSelectedPosition()107 public int getSelectedPosition() { 108 return mGridView.getSelectedPosition(); 109 } 110 111 /** 112 * Gets ViewHolder at a position in adapter. Returns null if the item does not exist 113 * or the item is not bound to a view. 114 * @param position Position of the item in adapter. 115 * @return ViewHolder bounds to the item. 116 */ getItemViewHolder(int position)117 public Presenter.ViewHolder getItemViewHolder(int position) { 118 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) mGridView 119 .findViewHolderForAdapterPosition(position); 120 if (ibvh == null) { 121 return null; 122 } 123 return ibvh.getViewHolder(); 124 } 125 126 @Override getSelectedItemViewHolder()127 public Presenter.ViewHolder getSelectedItemViewHolder() { 128 return getItemViewHolder(getSelectedPosition()); 129 } 130 131 @Override getSelectedItem()132 public Object getSelectedItem() { 133 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) mGridView 134 .findViewHolderForAdapterPosition(getSelectedPosition()); 135 if (ibvh == null) { 136 return null; 137 } 138 return ibvh.getItem(); 139 } 140 } 141 142 /** 143 * A task on the ListRowPresenter.ViewHolder that can select an item by position in the 144 * HorizontalGridView and perform an optional item task on it. 145 */ 146 public static class SelectItemViewHolderTask extends Presenter.ViewHolderTask { 147 148 private int mItemPosition; 149 private boolean mSmoothScroll = true; 150 Presenter.ViewHolderTask mItemTask; 151 SelectItemViewHolderTask(int itemPosition)152 public SelectItemViewHolderTask(int itemPosition) { 153 setItemPosition(itemPosition); 154 } 155 156 /** 157 * Sets the adapter position of item to select. 158 * @param itemPosition Position of the item in adapter. 159 */ setItemPosition(int itemPosition)160 public void setItemPosition(int itemPosition) { 161 mItemPosition = itemPosition; 162 } 163 164 /** 165 * Returns the adapter position of item to select. 166 * @return The adapter position of item to select. 167 */ getItemPosition()168 public int getItemPosition() { 169 return mItemPosition; 170 } 171 172 /** 173 * Sets smooth scrolling to the item or jump to the item without scrolling. By default it is 174 * true. 175 * @param smoothScroll True for smooth scrolling to the item, false otherwise. 176 */ setSmoothScroll(boolean smoothScroll)177 public void setSmoothScroll(boolean smoothScroll) { 178 mSmoothScroll = smoothScroll; 179 } 180 181 /** 182 * Returns true if smooth scrolling to the item false otherwise. By default it is true. 183 * @return True for smooth scrolling to the item, false otherwise. 184 */ isSmoothScroll()185 public boolean isSmoothScroll() { 186 return mSmoothScroll; 187 } 188 189 /** 190 * Returns optional task to run when the item is selected, null for no task. 191 * @return Optional task to run when the item is selected, null for no task. 192 */ getItemTask()193 public Presenter.ViewHolderTask getItemTask() { 194 return mItemTask; 195 } 196 197 /** 198 * Sets task to run when the item is selected, null for no task. 199 * @param itemTask Optional task to run when the item is selected, null for no task. 200 */ setItemTask(Presenter.ViewHolderTask itemTask)201 public void setItemTask(Presenter.ViewHolderTask itemTask) { 202 mItemTask = itemTask; 203 } 204 205 @Override run(Presenter.ViewHolder holder)206 public void run(Presenter.ViewHolder holder) { 207 if (holder instanceof ListRowPresenter.ViewHolder) { 208 HorizontalGridView gridView = ((ListRowPresenter.ViewHolder) holder).getGridView(); 209 android.support.v17.leanback.widget.ViewHolderTask task = null; 210 if (mItemTask != null) { 211 task = new android.support.v17.leanback.widget.ViewHolderTask() { 212 final Presenter.ViewHolderTask itemTask = mItemTask; 213 @Override 214 public void run(RecyclerView.ViewHolder rvh) { 215 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) rvh; 216 itemTask.run(ibvh.getViewHolder()); 217 } 218 }; 219 } 220 if (isSmoothScroll()) { 221 gridView.setSelectedPositionSmooth(mItemPosition, task); 222 } else { 223 gridView.setSelectedPosition(mItemPosition, task); 224 } 225 } 226 } 227 } 228 229 class ListRowPresenterItemBridgeAdapter extends ItemBridgeAdapter { 230 ListRowPresenter.ViewHolder mRowViewHolder; 231 ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder)232 ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) { 233 mRowViewHolder = rowViewHolder; 234 } 235 236 @Override onCreate(ItemBridgeAdapter.ViewHolder viewHolder)237 protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) { 238 if (viewHolder.itemView instanceof ViewGroup) { 239 TransitionHelper.setTransitionGroup((ViewGroup) viewHolder.itemView, true); 240 } 241 if (mShadowOverlayHelper != null) { 242 mShadowOverlayHelper.onViewCreated(viewHolder.itemView); 243 } 244 } 245 246 @Override onBind(final ItemBridgeAdapter.ViewHolder viewHolder)247 public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) { 248 // Only when having an OnItemClickListener, we will attach the OnClickListener. 249 if (mRowViewHolder.getOnItemViewClickedListener() != null) { 250 viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() { 251 @Override 252 public void onClick(View v) { 253 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 254 mRowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView); 255 if (mRowViewHolder.getOnItemViewClickedListener() != null) { 256 mRowViewHolder.getOnItemViewClickedListener().onItemClicked(viewHolder.mHolder, 257 ibh.mItem, mRowViewHolder, (ListRow) mRowViewHolder.mRow); 258 } 259 } 260 }); 261 } 262 } 263 264 @Override onUnbind(ItemBridgeAdapter.ViewHolder viewHolder)265 public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) { 266 if (mRowViewHolder.getOnItemViewClickedListener() != null) { 267 viewHolder.mHolder.view.setOnClickListener(null); 268 } 269 } 270 271 @Override onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder)272 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 273 applySelectLevelToChild(mRowViewHolder, viewHolder.itemView); 274 mRowViewHolder.syncActivatedStatus(viewHolder.itemView); 275 } 276 277 @Override onAddPresenter(Presenter presenter, int type)278 public void onAddPresenter(Presenter presenter, int type) { 279 mRowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews( 280 type, getRecycledPoolSize(presenter)); 281 } 282 } 283 284 private int mNumRows = 1; 285 private int mRowHeight; 286 private int mExpandedRowHeight; 287 private PresenterSelector mHoverCardPresenterSelector; 288 private int mFocusZoomFactor; 289 private boolean mUseFocusDimmer; 290 private boolean mShadowEnabled = true; 291 private int mBrowseRowsFadingEdgeLength = -1; 292 private boolean mRoundedCornersEnabled = true; 293 private boolean mKeepChildForeground = true; 294 private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>(); 295 ShadowOverlayHelper mShadowOverlayHelper; 296 private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper; 297 298 private static int sSelectedRowTopPadding; 299 private static int sExpandedSelectedRowTopPadding; 300 private static int sExpandedRowNoHovercardBottomPadding; 301 302 /** 303 * Constructs a ListRowPresenter with defaults. 304 * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and 305 * disabled dimming on focus. 306 */ ListRowPresenter()307 public ListRowPresenter() { 308 this(FocusHighlight.ZOOM_FACTOR_MEDIUM); 309 } 310 311 /** 312 * Constructs a ListRowPresenter with the given parameters. 313 * 314 * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of 315 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 316 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 317 * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}, 318 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 319 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 320 * Dimming on focus defaults to disabled. 321 */ ListRowPresenter(int focusZoomFactor)322 public ListRowPresenter(int focusZoomFactor) { 323 this(focusZoomFactor, false); 324 } 325 326 /** 327 * Constructs a ListRowPresenter with the given parameters. 328 * 329 * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of 330 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 331 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 332 * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}, 333 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 334 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 335 * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer 336 */ ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer)337 public ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer) { 338 if (!FocusHighlightHelper.isValidZoomIndex(focusZoomFactor)) { 339 throw new IllegalArgumentException("Unhandled zoom factor"); 340 } 341 mFocusZoomFactor = focusZoomFactor; 342 mUseFocusDimmer = useFocusDimmer; 343 } 344 345 /** 346 * Sets the row height for rows created by this Presenter. Rows 347 * created before calling this method will not be updated. 348 * 349 * @param rowHeight Row height in pixels, or WRAP_CONTENT, or 0 350 * to use the default height. 351 */ setRowHeight(int rowHeight)352 public void setRowHeight(int rowHeight) { 353 mRowHeight = rowHeight; 354 } 355 356 /** 357 * Returns the row height for list rows created by this Presenter. 358 */ getRowHeight()359 public int getRowHeight() { 360 return mRowHeight; 361 } 362 363 /** 364 * Sets the expanded row height for rows created by this Presenter. 365 * If not set, expanded rows have the same height as unexpanded 366 * rows. 367 * 368 * @param rowHeight The row height in to use when the row is expanded, 369 * in pixels, or WRAP_CONTENT, or 0 to use the default. 370 */ setExpandedRowHeight(int rowHeight)371 public void setExpandedRowHeight(int rowHeight) { 372 mExpandedRowHeight = rowHeight; 373 } 374 375 /** 376 * Returns the expanded row height for rows created by this Presenter. 377 */ getExpandedRowHeight()378 public int getExpandedRowHeight() { 379 return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight; 380 } 381 382 /** 383 * Returns the zoom factor used for focus highlighting. 384 */ getFocusZoomFactor()385 public final int getFocusZoomFactor() { 386 return mFocusZoomFactor; 387 } 388 389 /** 390 * Returns the zoom factor used for focus highlighting. 391 * @deprecated use {@link #getFocusZoomFactor} instead. 392 */ 393 @Deprecated getZoomFactor()394 public final int getZoomFactor() { 395 return mFocusZoomFactor; 396 } 397 398 /** 399 * Returns true if the focus dimmer is used for focus highlighting; false otherwise. 400 */ isFocusDimmerUsed()401 public final boolean isFocusDimmerUsed() { 402 return mUseFocusDimmer; 403 } 404 405 /** 406 * Sets the numbers of rows for rendering the list of items. By default, it is 407 * set to 1. 408 */ setNumRows(int numRows)409 public void setNumRows(int numRows) { 410 this.mNumRows = numRows; 411 } 412 413 @Override initializeRowViewHolder(RowPresenter.ViewHolder holder)414 protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) { 415 super.initializeRowViewHolder(holder); 416 final ViewHolder rowViewHolder = (ViewHolder) holder; 417 Context context = holder.view.getContext(); 418 if (mShadowOverlayHelper == null) { 419 mShadowOverlayHelper = new ShadowOverlayHelper.Builder() 420 .needsOverlay(needsDefaultListSelectEffect()) 421 .needsShadow(needsDefaultShadow()) 422 .needsRoundedCorner(isUsingOutlineClipping(context) 423 && areChildRoundedCornersEnabled()) 424 .preferZOrder(isUsingZOrder(context)) 425 .keepForegroundDrawable(mKeepChildForeground) 426 .options(createShadowOverlayOptions()) 427 .build(context); 428 if (mShadowOverlayHelper.needsWrapper()) { 429 mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper( 430 mShadowOverlayHelper); 431 } 432 } 433 rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder); 434 // set wrapper if needed 435 rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper); 436 mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView); 437 438 FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, 439 mFocusZoomFactor, mUseFocusDimmer); 440 rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType() 441 != ShadowOverlayHelper.SHADOW_DYNAMIC); 442 rowViewHolder.mGridView.setOnChildSelectedListener( 443 new OnChildSelectedListener() { 444 @Override 445 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 446 selectChildView(rowViewHolder, view, true); 447 } 448 }); 449 rowViewHolder.mGridView.setOnUnhandledKeyListener( 450 new BaseGridView.OnUnhandledKeyListener() { 451 @Override 452 public boolean onUnhandledKey(KeyEvent event) { 453 return rowViewHolder.getOnKeyListener() != null 454 && rowViewHolder.getOnKeyListener().onKey( 455 rowViewHolder.view, event.getKeyCode(), event); 456 } 457 }); 458 rowViewHolder.mGridView.setNumRows(mNumRows); 459 } 460 needsDefaultListSelectEffect()461 final boolean needsDefaultListSelectEffect() { 462 return isUsingDefaultListSelectEffect() && getSelectEffectEnabled(); 463 } 464 465 /** 466 * Sets the recycled pool size for the given presenter. 467 */ setRecycledPoolSize(Presenter presenter, int size)468 public void setRecycledPoolSize(Presenter presenter, int size) { 469 mRecycledPoolSize.put(presenter, size); 470 } 471 472 /** 473 * Returns the recycled pool size for the given presenter. 474 */ getRecycledPoolSize(Presenter presenter)475 public int getRecycledPoolSize(Presenter presenter) { 476 return mRecycledPoolSize.containsKey(presenter) ? mRecycledPoolSize.get(presenter) : 477 DEFAULT_RECYCLED_POOL_SIZE; 478 } 479 480 /** 481 * Sets the {@link PresenterSelector} used for showing a select object in a hover card. 482 */ setHoverCardPresenterSelector(PresenterSelector selector)483 public final void setHoverCardPresenterSelector(PresenterSelector selector) { 484 mHoverCardPresenterSelector = selector; 485 } 486 487 /** 488 * Returns the {@link PresenterSelector} used for showing a select object in a hover card. 489 */ getHoverCardPresenterSelector()490 public final PresenterSelector getHoverCardPresenterSelector() { 491 return mHoverCardPresenterSelector; 492 } 493 494 /* 495 * Perform operations when a child of horizontal grid view is selected. 496 */ selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent)497 void selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent) { 498 if (view != null) { 499 if (rowViewHolder.mSelected) { 500 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 501 rowViewHolder.mGridView.getChildViewHolder(view); 502 503 if (mHoverCardPresenterSelector != null) { 504 rowViewHolder.mHoverCardViewSwitcher.select( 505 rowViewHolder.mGridView, view, ibh.mItem); 506 } 507 if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) { 508 rowViewHolder.getOnItemViewSelectedListener().onItemSelected( 509 ibh.mHolder, ibh.mItem, rowViewHolder, rowViewHolder.mRow); 510 } 511 } 512 } else { 513 if (mHoverCardPresenterSelector != null) { 514 rowViewHolder.mHoverCardViewSwitcher.unselect(); 515 } 516 if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) { 517 rowViewHolder.getOnItemViewSelectedListener().onItemSelected( 518 null, null, rowViewHolder, rowViewHolder.mRow); 519 } 520 } 521 } 522 initStatics(Context context)523 private static void initStatics(Context context) { 524 if (sSelectedRowTopPadding == 0) { 525 sSelectedRowTopPadding = context.getResources().getDimensionPixelSize( 526 R.dimen.lb_browse_selected_row_top_padding); 527 sExpandedSelectedRowTopPadding = context.getResources().getDimensionPixelSize( 528 R.dimen.lb_browse_expanded_selected_row_top_padding); 529 sExpandedRowNoHovercardBottomPadding = context.getResources().getDimensionPixelSize( 530 R.dimen.lb_browse_expanded_row_no_hovercard_bottom_padding); 531 } 532 } 533 getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh)534 private int getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh) { 535 RowHeaderPresenter.ViewHolder headerViewHolder = vh.getHeaderViewHolder(); 536 if (headerViewHolder != null) { 537 if (getHeaderPresenter() != null) { 538 return getHeaderPresenter().getSpaceUnderBaseline(headerViewHolder); 539 } 540 return headerViewHolder.view.getPaddingBottom(); 541 } 542 return 0; 543 } 544 setVerticalPadding(ListRowPresenter.ViewHolder vh)545 private void setVerticalPadding(ListRowPresenter.ViewHolder vh) { 546 int paddingTop, paddingBottom; 547 // Note: sufficient bottom padding needed for card shadows. 548 if (vh.isExpanded()) { 549 int headerSpaceUnderBaseline = getSpaceUnderBaseline(vh); 550 if (DEBUG) Log.v(TAG, "headerSpaceUnderBaseline " + headerSpaceUnderBaseline); 551 paddingTop = (vh.isSelected() ? sExpandedSelectedRowTopPadding : vh.mPaddingTop) 552 - headerSpaceUnderBaseline; 553 paddingBottom = mHoverCardPresenterSelector == null 554 ? sExpandedRowNoHovercardBottomPadding : vh.mPaddingBottom; 555 } else if (vh.isSelected()) { 556 paddingTop = sSelectedRowTopPadding - vh.mPaddingBottom; 557 paddingBottom = sSelectedRowTopPadding; 558 } else { 559 paddingTop = 0; 560 paddingBottom = vh.mPaddingBottom; 561 } 562 vh.getGridView().setPadding(vh.mPaddingLeft, paddingTop, vh.mPaddingRight, 563 paddingBottom); 564 } 565 566 @Override createRowViewHolder(ViewGroup parent)567 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 568 initStatics(parent.getContext()); 569 ListRowView rowView = new ListRowView(parent.getContext()); 570 setupFadingEffect(rowView); 571 if (mRowHeight != 0) { 572 rowView.getGridView().setRowHeight(mRowHeight); 573 } 574 return new ViewHolder(rowView, rowView.getGridView(), this); 575 } 576 577 /** 578 * Dispatch item selected event using current selected item in the {@link HorizontalGridView}. 579 * The method should only be called from onRowViewSelected(). 580 */ 581 @Override dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected)582 protected void dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected) { 583 ViewHolder vh = (ViewHolder)holder; 584 ItemBridgeAdapter.ViewHolder itemViewHolder = (ItemBridgeAdapter.ViewHolder) 585 vh.mGridView.findViewHolderForPosition(vh.mGridView.getSelectedPosition()); 586 if (itemViewHolder == null) { 587 super.dispatchItemSelectedListener(holder, selected); 588 return; 589 } 590 591 if (selected) { 592 if (holder.getOnItemViewSelectedListener() != null) { 593 holder.getOnItemViewSelectedListener().onItemSelected( 594 itemViewHolder.getViewHolder(), itemViewHolder.mItem, vh, vh.getRow()); 595 } 596 } 597 } 598 599 @Override onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected)600 protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) { 601 super.onRowViewSelected(holder, selected); 602 ViewHolder vh = (ViewHolder) holder; 603 setVerticalPadding(vh); 604 updateFooterViewSwitcher(vh); 605 } 606 607 /* 608 * Show or hide hover card when row selection or expanded state is changed. 609 */ updateFooterViewSwitcher(ViewHolder vh)610 private void updateFooterViewSwitcher(ViewHolder vh) { 611 if (vh.mExpanded && vh.mSelected) { 612 if (mHoverCardPresenterSelector != null) { 613 vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view, 614 mHoverCardPresenterSelector); 615 } 616 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 617 vh.mGridView.findViewHolderForPosition( 618 vh.mGridView.getSelectedPosition()); 619 selectChildView(vh, ibh == null ? null : ibh.itemView, false); 620 } else { 621 if (mHoverCardPresenterSelector != null) { 622 vh.mHoverCardViewSwitcher.unselect(); 623 } 624 } 625 } 626 setupFadingEffect(ListRowView rowView)627 private void setupFadingEffect(ListRowView rowView) { 628 // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding. 629 HorizontalGridView gridView = rowView.getGridView(); 630 if (mBrowseRowsFadingEdgeLength < 0) { 631 TypedArray ta = gridView.getContext() 632 .obtainStyledAttributes(R.styleable.LeanbackTheme); 633 mBrowseRowsFadingEdgeLength = (int) ta.getDimension( 634 R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0); 635 ta.recycle(); 636 } 637 gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength); 638 } 639 640 @Override onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded)641 protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) { 642 super.onRowViewExpanded(holder, expanded); 643 ViewHolder vh = (ViewHolder) holder; 644 if (getRowHeight() != getExpandedRowHeight()) { 645 int newHeight = expanded ? getExpandedRowHeight() : getRowHeight(); 646 vh.getGridView().setRowHeight(newHeight); 647 } 648 setVerticalPadding(vh); 649 updateFooterViewSwitcher(vh); 650 } 651 652 @Override onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item)653 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 654 super.onBindRowViewHolder(holder, item); 655 ViewHolder vh = (ViewHolder) holder; 656 ListRow rowItem = (ListRow) item; 657 vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter()); 658 vh.mGridView.setAdapter(vh.mItemBridgeAdapter); 659 vh.mGridView.setContentDescription(rowItem.getContentDescription()); 660 } 661 662 @Override onUnbindRowViewHolder(RowPresenter.ViewHolder holder)663 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 664 ViewHolder vh = (ViewHolder) holder; 665 vh.mGridView.setAdapter(null); 666 vh.mItemBridgeAdapter.clear(); 667 super.onUnbindRowViewHolder(holder); 668 } 669 670 /** 671 * ListRowPresenter overrides the default select effect of {@link RowPresenter} 672 * and return false. 673 */ 674 @Override isUsingDefaultSelectEffect()675 public final boolean isUsingDefaultSelectEffect() { 676 return false; 677 } 678 679 /** 680 * Returns true so that default select effect is applied to each individual 681 * child of {@link HorizontalGridView}. Subclass may return false to disable 682 * the default implementation and implement {@link #applySelectLevelToChild(ViewHolder, View)}. 683 * @see #applySelectLevelToChild(ViewHolder, View) 684 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 685 */ isUsingDefaultListSelectEffect()686 public boolean isUsingDefaultListSelectEffect() { 687 return true; 688 } 689 690 /** 691 * Default implementation returns true if SDK version >= 21, shadow (either static or z-order 692 * based) will be applied to each individual child of {@link HorizontalGridView}. 693 * Subclass may return false to disable default implementation of shadow and provide its own. 694 */ isUsingDefaultShadow()695 public boolean isUsingDefaultShadow() { 696 return ShadowOverlayHelper.supportsShadow(); 697 } 698 699 /** 700 * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled 701 * on each child of horizontal list. If subclass returns false in isUsingDefaultShadow() 702 * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false. 703 */ isUsingZOrder(Context context)704 public boolean isUsingZOrder(Context context) { 705 return !Settings.getInstance(context).preferStaticShadows(); 706 } 707 708 /** 709 * Returns true if leanback view outline is enabled on the system or false otherwise. When 710 * false, rounded corner will not be enabled even {@link #enableChildRoundedCorners(boolean)} 711 * is called with true. 712 * 713 * @param context Context to retrieve system settings. 714 * @return True if leanback view outline is enabled on the system or false otherwise. 715 */ isUsingOutlineClipping(Context context)716 public boolean isUsingOutlineClipping(Context context) { 717 return !Settings.getInstance(context).isOutlineClippingDisabled(); 718 } 719 720 /** 721 * Enables or disables child shadow. 722 * This is not only for enable/disable default shadow implementation but also subclass must 723 * respect this flag. 724 */ setShadowEnabled(boolean enabled)725 public final void setShadowEnabled(boolean enabled) { 726 mShadowEnabled = enabled; 727 } 728 729 /** 730 * Returns true if child shadow is enabled. 731 * This is not only for enable/disable default shadow implementation but also subclass must 732 * respect this flag. 733 */ getShadowEnabled()734 public final boolean getShadowEnabled() { 735 return mShadowEnabled; 736 } 737 738 /** 739 * Enables or disabled rounded corners on children of this row. 740 * Supported on Android SDK >= L. 741 */ enableChildRoundedCorners(boolean enable)742 public final void enableChildRoundedCorners(boolean enable) { 743 mRoundedCornersEnabled = enable; 744 } 745 746 /** 747 * Returns true if rounded corners are enabled for children of this row. 748 */ areChildRoundedCornersEnabled()749 public final boolean areChildRoundedCornersEnabled() { 750 return mRoundedCornersEnabled; 751 } 752 needsDefaultShadow()753 final boolean needsDefaultShadow() { 754 return isUsingDefaultShadow() && getShadowEnabled(); 755 } 756 757 /** 758 * When ListRowPresenter applies overlay color on the child, it may change child's foreground 759 * Drawable. If application uses child's foreground for other purposes such as ripple effect, 760 * it needs tell ListRowPresenter to keep the child's foreground. The default value is true. 761 * 762 * @param keep true if keep foreground of child of this row, false ListRowPresenter might change 763 * the foreground of the child. 764 */ setKeepChildForeground(boolean keep)765 public final void setKeepChildForeground(boolean keep) { 766 mKeepChildForeground = keep; 767 } 768 769 /** 770 * Returns true if keeps foreground of child of this row, false otherwise. When 771 * ListRowPresenter applies overlay color on the child, it may change child's foreground 772 * Drawable. If application uses child's foreground for other purposes such as ripple effect, 773 * it needs tell ListRowPresenter to keep the child's foreground. The default value is true. 774 * 775 * @return true if keeps foreground of child of this row, false otherwise. 776 */ isKeepChildForeground()777 public final boolean isKeepChildForeground() { 778 return mKeepChildForeground; 779 } 780 781 /** 782 * Create ShadowOverlayHelper Options. Subclass may override. 783 * e.g. 784 * <code> 785 * return new ShadowOverlayHelper.Options().roundedCornerRadius(10); 786 * </code> 787 * 788 * @return The options to be used for shadow, overlay and rounded corner. 789 */ createShadowOverlayOptions()790 protected ShadowOverlayHelper.Options createShadowOverlayOptions() { 791 return ShadowOverlayHelper.Options.DEFAULT; 792 } 793 794 /** 795 * Applies select level to header and draws a default color dim over each child 796 * of {@link HorizontalGridView}. 797 * <p> 798 * Subclass may override this method and starts with calling super if it has views to apply 799 * select effect other than header and HorizontalGridView. 800 * To override the default color dim over each child of {@link HorizontalGridView}, 801 * app should override {@link #isUsingDefaultListSelectEffect()} to 802 * return false and override {@link #applySelectLevelToChild(ViewHolder, View)}. 803 * </p> 804 * @see #isUsingDefaultListSelectEffect() 805 * @see RowPresenter.ViewHolder#getSelectLevel() 806 * @see #applySelectLevelToChild(ViewHolder, View) 807 */ 808 @Override onSelectLevelChanged(RowPresenter.ViewHolder holder)809 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 810 super.onSelectLevelChanged(holder); 811 ViewHolder vh = (ViewHolder) holder; 812 for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) { 813 applySelectLevelToChild(vh, vh.mGridView.getChildAt(i)); 814 } 815 } 816 817 /** 818 * Applies select level to a child. Default implementation draws a default color 819 * dim over each child of {@link HorizontalGridView}. This method is called on all children in 820 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)} and when a child is attached to 821 * {@link HorizontalGridView}. 822 * <p> 823 * Subclass may disable the default implementation by override 824 * {@link #isUsingDefaultListSelectEffect()} to return false and deal with the individual item 825 * select level by itself. 826 * </p> 827 * @param rowViewHolder The ViewHolder of the Row 828 * @param childView The child of {@link HorizontalGridView} to apply select level. 829 * 830 * @see #isUsingDefaultListSelectEffect() 831 * @see RowPresenter.ViewHolder#getSelectLevel() 832 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 833 */ applySelectLevelToChild(ViewHolder rowViewHolder, View childView)834 protected void applySelectLevelToChild(ViewHolder rowViewHolder, View childView) { 835 if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) { 836 int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor(); 837 mShadowOverlayHelper.setOverlayColor(childView, dimmedColor); 838 } 839 } 840 841 @Override freeze(RowPresenter.ViewHolder holder, boolean freeze)842 public void freeze(RowPresenter.ViewHolder holder, boolean freeze) { 843 ViewHolder vh = (ViewHolder) holder; 844 vh.mGridView.setScrollEnabled(!freeze); 845 vh.mGridView.setAnimateChildLayout(!freeze); 846 } 847 848 @Override setEntranceTransitionState(RowPresenter.ViewHolder holder, boolean afterEntrance)849 public void setEntranceTransitionState(RowPresenter.ViewHolder holder, 850 boolean afterEntrance) { 851 super.setEntranceTransitionState(holder, afterEntrance); 852 ((ViewHolder) holder).mGridView.setChildrenVisibility( 853 afterEntrance? View.VISIBLE : View.INVISIBLE); 854 } 855 } 856