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.content.res.TypedArray; 18import android.support.v17.leanback.R; 19import android.support.v17.leanback.system.Settings; 20import android.support.v17.leanback.transition.TransitionHelper; 21import android.support.v7.widget.RecyclerView; 22import android.util.Log; 23import android.view.KeyEvent; 24import android.view.View; 25import android.view.ViewGroup; 26 27import 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 */ 49public 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 127 public Presenter.ViewHolder getSelectedItemViewHolder() { 128 return getItemViewHolder(getSelectedPosition()); 129 } 130 131 @Override 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 201 public void setItemTask(Presenter.ViewHolderTask itemTask) { 202 mItemTask = itemTask; 203 } 204 205 @Override 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 232 ListRowPresenterItemBridgeAdapter(ListRowPresenter.ViewHolder rowViewHolder) { 233 mRowViewHolder = rowViewHolder; 234 } 235 236 @Override 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 247 public void onBind(final ItemBridgeAdapter.ViewHolder viewHolder) { 248 // Only when having an OnItemClickListner, 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 265 public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) { 266 if (mRowViewHolder.getOnItemViewClickedListener() != null) { 267 viewHolder.mHolder.view.setOnClickListener(null); 268 } 269 } 270 271 @Override 272 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 273 applySelectLevelToChild(mRowViewHolder, viewHolder.itemView); 274 mRowViewHolder.syncActivatedStatus(viewHolder.itemView); 275 } 276 277 @Override 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 378 public int getExpandedRowHeight() { 379 return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight; 380 } 381 382 /** 383 * Returns the zoom factor used for focus highlighting. 384 */ 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 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 */ 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 */ 409 public void setNumRows(int numRows) { 410 this.mNumRows = numRows; 411 } 412 413 @Override 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(areChildRoundedCornersEnabled()) 423 .preferZOrder(isUsingZOrder(context)) 424 .keepForegroundDrawable(mKeepChildForeground) 425 .options(createShadowOverlayOptions()) 426 .build(context); 427 if (mShadowOverlayHelper.needsWrapper()) { 428 mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper( 429 mShadowOverlayHelper); 430 } 431 } 432 rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder); 433 // set wrapper if needed 434 rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper); 435 mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView); 436 437 FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, 438 mFocusZoomFactor, mUseFocusDimmer); 439 rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType() 440 != ShadowOverlayHelper.SHADOW_DYNAMIC); 441 rowViewHolder.mGridView.setOnChildSelectedListener( 442 new OnChildSelectedListener() { 443 @Override 444 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 445 selectChildView(rowViewHolder, view, true); 446 } 447 }); 448 rowViewHolder.mGridView.setOnUnhandledKeyListener( 449 new BaseGridView.OnUnhandledKeyListener() { 450 @Override 451 public boolean onUnhandledKey(KeyEvent event) { 452 return rowViewHolder.getOnKeyListener() != null 453 && rowViewHolder.getOnKeyListener().onKey( 454 rowViewHolder.view, event.getKeyCode(), event); 455 } 456 }); 457 rowViewHolder.mGridView.setNumRows(mNumRows); 458 } 459 460 final boolean needsDefaultListSelectEffect() { 461 return isUsingDefaultListSelectEffect() && getSelectEffectEnabled(); 462 } 463 464 /** 465 * Sets the recycled pool size for the given presenter. 466 */ 467 public void setRecycledPoolSize(Presenter presenter, int size) { 468 mRecycledPoolSize.put(presenter, size); 469 } 470 471 /** 472 * Returns the recycled pool size for the given presenter. 473 */ 474 public int getRecycledPoolSize(Presenter presenter) { 475 return mRecycledPoolSize.containsKey(presenter) ? mRecycledPoolSize.get(presenter) : 476 DEFAULT_RECYCLED_POOL_SIZE; 477 } 478 479 /** 480 * Sets the {@link PresenterSelector} used for showing a select object in a hover card. 481 */ 482 public final void setHoverCardPresenterSelector(PresenterSelector selector) { 483 mHoverCardPresenterSelector = selector; 484 } 485 486 /** 487 * Returns the {@link PresenterSelector} used for showing a select object in a hover card. 488 */ 489 public final PresenterSelector getHoverCardPresenterSelector() { 490 return mHoverCardPresenterSelector; 491 } 492 493 /* 494 * Perform operations when a child of horizontal grid view is selected. 495 */ 496 void selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent) { 497 if (view != null) { 498 if (rowViewHolder.mSelected) { 499 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 500 rowViewHolder.mGridView.getChildViewHolder(view); 501 502 if (mHoverCardPresenterSelector != null) { 503 rowViewHolder.mHoverCardViewSwitcher.select( 504 rowViewHolder.mGridView, view, ibh.mItem); 505 } 506 if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) { 507 rowViewHolder.getOnItemViewSelectedListener().onItemSelected( 508 ibh.mHolder, ibh.mItem, rowViewHolder, rowViewHolder.mRow); 509 } 510 } 511 } else { 512 if (mHoverCardPresenterSelector != null) { 513 rowViewHolder.mHoverCardViewSwitcher.unselect(); 514 } 515 if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) { 516 rowViewHolder.getOnItemViewSelectedListener().onItemSelected( 517 null, null, rowViewHolder, rowViewHolder.mRow); 518 } 519 } 520 } 521 522 private static void initStatics(Context context) { 523 if (sSelectedRowTopPadding == 0) { 524 sSelectedRowTopPadding = context.getResources().getDimensionPixelSize( 525 R.dimen.lb_browse_selected_row_top_padding); 526 sExpandedSelectedRowTopPadding = context.getResources().getDimensionPixelSize( 527 R.dimen.lb_browse_expanded_selected_row_top_padding); 528 sExpandedRowNoHovercardBottomPadding = context.getResources().getDimensionPixelSize( 529 R.dimen.lb_browse_expanded_row_no_hovercard_bottom_padding); 530 } 531 } 532 533 private int getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh) { 534 RowHeaderPresenter.ViewHolder headerViewHolder = vh.getHeaderViewHolder(); 535 if (headerViewHolder != null) { 536 if (getHeaderPresenter() != null) { 537 return getHeaderPresenter().getSpaceUnderBaseline(headerViewHolder); 538 } 539 return headerViewHolder.view.getPaddingBottom(); 540 } 541 return 0; 542 } 543 544 private void setVerticalPadding(ListRowPresenter.ViewHolder vh) { 545 int paddingTop, paddingBottom; 546 // Note: sufficient bottom padding needed for card shadows. 547 if (vh.isExpanded()) { 548 int headerSpaceUnderBaseline = getSpaceUnderBaseline(vh); 549 if (DEBUG) Log.v(TAG, "headerSpaceUnderBaseline " + headerSpaceUnderBaseline); 550 paddingTop = (vh.isSelected() ? sExpandedSelectedRowTopPadding : vh.mPaddingTop) 551 - headerSpaceUnderBaseline; 552 paddingBottom = mHoverCardPresenterSelector == null 553 ? sExpandedRowNoHovercardBottomPadding : vh.mPaddingBottom; 554 } else if (vh.isSelected()) { 555 paddingTop = sSelectedRowTopPadding - vh.mPaddingBottom; 556 paddingBottom = sSelectedRowTopPadding; 557 } else { 558 paddingTop = 0; 559 paddingBottom = vh.mPaddingBottom; 560 } 561 vh.getGridView().setPadding(vh.mPaddingLeft, paddingTop, vh.mPaddingRight, 562 paddingBottom); 563 } 564 565 @Override 566 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 567 initStatics(parent.getContext()); 568 ListRowView rowView = new ListRowView(parent.getContext()); 569 setupFadingEffect(rowView); 570 if (mRowHeight != 0) { 571 rowView.getGridView().setRowHeight(mRowHeight); 572 } 573 return new ViewHolder(rowView, rowView.getGridView(), this); 574 } 575 576 /** 577 * Dispatch item selected event using current selected item in the {@link HorizontalGridView}. 578 * The method should only be called from onRowViewSelected(). 579 */ 580 @Override 581 protected void dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected) { 582 ViewHolder vh = (ViewHolder)holder; 583 ItemBridgeAdapter.ViewHolder itemViewHolder = (ItemBridgeAdapter.ViewHolder) 584 vh.mGridView.findViewHolderForPosition(vh.mGridView.getSelectedPosition()); 585 if (itemViewHolder == null) { 586 super.dispatchItemSelectedListener(holder, selected); 587 return; 588 } 589 590 if (selected) { 591 if (holder.getOnItemViewSelectedListener() != null) { 592 holder.getOnItemViewSelectedListener().onItemSelected( 593 itemViewHolder.getViewHolder(), itemViewHolder.mItem, vh, vh.getRow()); 594 } 595 } 596 } 597 598 @Override 599 protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) { 600 super.onRowViewSelected(holder, selected); 601 ViewHolder vh = (ViewHolder) holder; 602 setVerticalPadding(vh); 603 updateFooterViewSwitcher(vh); 604 } 605 606 /* 607 * Show or hide hover card when row selection or expanded state is changed. 608 */ 609 private void updateFooterViewSwitcher(ViewHolder vh) { 610 if (vh.mExpanded && vh.mSelected) { 611 if (mHoverCardPresenterSelector != null) { 612 vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view, 613 mHoverCardPresenterSelector); 614 } 615 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 616 vh.mGridView.findViewHolderForPosition( 617 vh.mGridView.getSelectedPosition()); 618 selectChildView(vh, ibh == null ? null : ibh.itemView, false); 619 } else { 620 if (mHoverCardPresenterSelector != null) { 621 vh.mHoverCardViewSwitcher.unselect(); 622 } 623 } 624 } 625 626 private void setupFadingEffect(ListRowView rowView) { 627 // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding. 628 HorizontalGridView gridView = rowView.getGridView(); 629 if (mBrowseRowsFadingEdgeLength < 0) { 630 TypedArray ta = gridView.getContext() 631 .obtainStyledAttributes(R.styleable.LeanbackTheme); 632 mBrowseRowsFadingEdgeLength = (int) ta.getDimension( 633 R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0); 634 ta.recycle(); 635 } 636 gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength); 637 } 638 639 @Override 640 protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) { 641 super.onRowViewExpanded(holder, expanded); 642 ViewHolder vh = (ViewHolder) holder; 643 if (getRowHeight() != getExpandedRowHeight()) { 644 int newHeight = expanded ? getExpandedRowHeight() : getRowHeight(); 645 vh.getGridView().setRowHeight(newHeight); 646 } 647 setVerticalPadding(vh); 648 updateFooterViewSwitcher(vh); 649 } 650 651 @Override 652 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 653 super.onBindRowViewHolder(holder, item); 654 ViewHolder vh = (ViewHolder) holder; 655 ListRow rowItem = (ListRow) item; 656 vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter()); 657 vh.mGridView.setAdapter(vh.mItemBridgeAdapter); 658 vh.mGridView.setContentDescription(rowItem.getContentDescription()); 659 } 660 661 @Override 662 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 663 ViewHolder vh = (ViewHolder) holder; 664 vh.mGridView.setAdapter(null); 665 vh.mItemBridgeAdapter.clear(); 666 super.onUnbindRowViewHolder(holder); 667 } 668 669 /** 670 * ListRowPresenter overrides the default select effect of {@link RowPresenter} 671 * and return false. 672 */ 673 @Override 674 public final boolean isUsingDefaultSelectEffect() { 675 return false; 676 } 677 678 /** 679 * Returns true so that default select effect is applied to each individual 680 * child of {@link HorizontalGridView}. Subclass may return false to disable 681 * the default implementation and implement {@link #applySelectLevelToChild(ViewHolder, View)}. 682 * @see #applySelectLevelToChild(ViewHolder, View) 683 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 684 */ 685 public boolean isUsingDefaultListSelectEffect() { 686 return true; 687 } 688 689 /** 690 * Default implementation returns true if SDK version >= 21, shadow (either static or z-order 691 * based) will be applied to each individual child of {@link HorizontalGridView}. 692 * Subclass may return false to disable default implementation of shadow and provide its own. 693 */ 694 public boolean isUsingDefaultShadow() { 695 return ShadowOverlayHelper.supportsShadow(); 696 } 697 698 /** 699 * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled 700 * on each child of horizontal list. If subclass returns false in isUsingDefaultShadow() 701 * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false. 702 */ 703 public boolean isUsingZOrder(Context context) { 704 return !Settings.getInstance(context).preferStaticShadows(); 705 } 706 707 /** 708 * Enables or disables child shadow. 709 * This is not only for enable/disable default shadow implementation but also subclass must 710 * respect this flag. 711 */ 712 public final void setShadowEnabled(boolean enabled) { 713 mShadowEnabled = enabled; 714 } 715 716 /** 717 * Returns true if child shadow is enabled. 718 * This is not only for enable/disable default shadow implementation but also subclass must 719 * respect this flag. 720 */ 721 public final boolean getShadowEnabled() { 722 return mShadowEnabled; 723 } 724 725 /** 726 * Enables or disabled rounded corners on children of this row. 727 * Supported on Android SDK >= L. 728 */ 729 public final void enableChildRoundedCorners(boolean enable) { 730 mRoundedCornersEnabled = enable; 731 } 732 733 /** 734 * Returns true if rounded corners are enabled for children of this row. 735 */ 736 public final boolean areChildRoundedCornersEnabled() { 737 return mRoundedCornersEnabled; 738 } 739 740 final boolean needsDefaultShadow() { 741 return isUsingDefaultShadow() && getShadowEnabled(); 742 } 743 744 /** 745 * When ListRowPresenter applies overlay color on the child, it may change child's foreground 746 * Drawable. If application uses child's foreground for other purposes such as ripple effect, 747 * it needs tell ListRowPresenter to keep the child's foreground. The default value is true. 748 * 749 * @param keep true if keep foreground of child of this row, false ListRowPresenter might change 750 * the foreground of the child. 751 */ 752 public final void setKeepChildForeground(boolean keep) { 753 mKeepChildForeground = keep; 754 } 755 756 /** 757 * Returns true if keeps foreground of child of this row, false otherwise. When 758 * 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 * @return true if keeps foreground of child of this row, false otherwise. 763 */ 764 public final boolean isKeepChildForeground() { 765 return mKeepChildForeground; 766 } 767 768 /** 769 * Create ShadowOverlayHelper Options. Subclass may override. 770 * e.g. 771 * <code> 772 * return new ShadowOverlayHelper.Options().roundedCornerRadius(10); 773 * </code> 774 * 775 * @return The options to be used for shadow, overlay and rounded corner. 776 */ 777 protected ShadowOverlayHelper.Options createShadowOverlayOptions() { 778 return ShadowOverlayHelper.Options.DEFAULT; 779 } 780 781 /** 782 * Applies select level to header and draws a default color dim over each child 783 * of {@link HorizontalGridView}. 784 * <p> 785 * Subclass may override this method and starts with calling super if it has views to apply 786 * select effect other than header and HorizontalGridView. 787 * To override the default color dim over each child of {@link HorizontalGridView}, 788 * app should override {@link #isUsingDefaultListSelectEffect()} to 789 * return false and override {@link #applySelectLevelToChild(ViewHolder, View)}. 790 * </p> 791 * @see #isUsingDefaultListSelectEffect() 792 * @see RowPresenter.ViewHolder#getSelectLevel() 793 * @see #applySelectLevelToChild(ViewHolder, View) 794 */ 795 @Override 796 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 797 super.onSelectLevelChanged(holder); 798 ViewHolder vh = (ViewHolder) holder; 799 for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) { 800 applySelectLevelToChild(vh, vh.mGridView.getChildAt(i)); 801 } 802 } 803 804 /** 805 * Applies select level to a child. Default implementation draws a default color 806 * dim over each child of {@link HorizontalGridView}. This method is called on all children in 807 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)} and when a child is attached to 808 * {@link HorizontalGridView}. 809 * <p> 810 * Subclass may disable the default implementation by override 811 * {@link #isUsingDefaultListSelectEffect()} to return false and deal with the individual item 812 * select level by itself. 813 * </p> 814 * @param rowViewHolder The ViewHolder of the Row 815 * @param childView The child of {@link HorizontalGridView} to apply select level. 816 * 817 * @see #isUsingDefaultListSelectEffect() 818 * @see RowPresenter.ViewHolder#getSelectLevel() 819 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 820 */ 821 protected void applySelectLevelToChild(ViewHolder rowViewHolder, View childView) { 822 if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) { 823 int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor(); 824 mShadowOverlayHelper.setOverlayColor(childView, dimmedColor); 825 } 826 } 827 828 @Override 829 public void freeze(RowPresenter.ViewHolder holder, boolean freeze) { 830 ViewHolder vh = (ViewHolder) holder; 831 vh.mGridView.setScrollEnabled(!freeze); 832 } 833 834 @Override 835 public void setEntranceTransitionState(RowPresenter.ViewHolder holder, 836 boolean afterEntrance) { 837 super.setEntranceTransitionState(holder, afterEntrance); 838 ((ViewHolder) holder).mGridView.setChildrenVisibility( 839 afterEntrance? View.VISIBLE : View.INVISIBLE); 840 } 841} 842