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