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