ListRowPresenter.java revision 181c8847d5a1169e26755ed690131333b7fff7e9
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>Selection animation</h3> 39 * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draws 40 * a dim overlay on each view individually. A subclass may override and disable 41 * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in 42 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}. 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 if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) { 274 int dimmedColor = mRowViewHolder.mColorDimmer.getPaint().getColor(); 275 mShadowOverlayHelper.setOverlayColor(viewHolder.itemView, dimmedColor); 276 } 277 mRowViewHolder.syncActivatedStatus(viewHolder.itemView); 278 } 279 280 @Override 281 public void onAddPresenter(Presenter presenter, int type) { 282 mRowViewHolder.getGridView().getRecycledViewPool().setMaxRecycledViews( 283 type, getRecycledPoolSize(presenter)); 284 } 285 } 286 287 private int mNumRows = 1; 288 private int mRowHeight; 289 private int mExpandedRowHeight; 290 private PresenterSelector mHoverCardPresenterSelector; 291 private int mFocusZoomFactor; 292 private boolean mUseFocusDimmer; 293 private boolean mShadowEnabled = true; 294 private int mBrowseRowsFadingEdgeLength = -1; 295 private boolean mRoundedCornersEnabled = true; 296 private boolean mKeepChildForeground = true; 297 private HashMap<Presenter, Integer> mRecycledPoolSize = new HashMap<Presenter, Integer>(); 298 ShadowOverlayHelper mShadowOverlayHelper; 299 private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper; 300 301 private static int sSelectedRowTopPadding; 302 private static int sExpandedSelectedRowTopPadding; 303 private static int sExpandedRowNoHovercardBottomPadding; 304 305 /** 306 * Constructs a ListRowPresenter with defaults. 307 * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and 308 * disabled dimming on focus. 309 */ 310 public ListRowPresenter() { 311 this(FocusHighlight.ZOOM_FACTOR_MEDIUM); 312 } 313 314 /** 315 * Constructs a ListRowPresenter with the given parameters. 316 * 317 * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of 318 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 319 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 320 * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}, 321 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 322 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 323 * Dimming on focus defaults to disabled. 324 */ 325 public ListRowPresenter(int focusZoomFactor) { 326 this(focusZoomFactor, false); 327 } 328 329 /** 330 * Constructs a ListRowPresenter with the given parameters. 331 * 332 * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of 333 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 334 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 335 * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}, 336 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 337 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 338 * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer 339 */ 340 public ListRowPresenter(int focusZoomFactor, boolean useFocusDimmer) { 341 if (!FocusHighlightHelper.isValidZoomIndex(focusZoomFactor)) { 342 throw new IllegalArgumentException("Unhandled zoom factor"); 343 } 344 mFocusZoomFactor = focusZoomFactor; 345 mUseFocusDimmer = useFocusDimmer; 346 } 347 348 /** 349 * Sets the row height for rows created by this Presenter. Rows 350 * created before calling this method will not be updated. 351 * 352 * @param rowHeight Row height in pixels, or WRAP_CONTENT, or 0 353 * to use the default height. 354 */ 355 public void setRowHeight(int rowHeight) { 356 mRowHeight = rowHeight; 357 } 358 359 /** 360 * Returns the row height for list rows created by this Presenter. 361 */ 362 public int getRowHeight() { 363 return mRowHeight; 364 } 365 366 /** 367 * Sets the expanded row height for rows created by this Presenter. 368 * If not set, expanded rows have the same height as unexpanded 369 * rows. 370 * 371 * @param rowHeight The row height in to use when the row is expanded, 372 * in pixels, or WRAP_CONTENT, or 0 to use the default. 373 */ 374 public void setExpandedRowHeight(int rowHeight) { 375 mExpandedRowHeight = rowHeight; 376 } 377 378 /** 379 * Returns the expanded row height for rows created by this Presenter. 380 */ 381 public int getExpandedRowHeight() { 382 return mExpandedRowHeight != 0 ? mExpandedRowHeight : mRowHeight; 383 } 384 385 /** 386 * Returns the zoom factor used for focus highlighting. 387 */ 388 public final int getFocusZoomFactor() { 389 return mFocusZoomFactor; 390 } 391 392 /** 393 * Returns the zoom factor used for focus highlighting. 394 * @deprecated use {@link #getFocusZoomFactor} instead. 395 */ 396 @Deprecated 397 public final int getZoomFactor() { 398 return mFocusZoomFactor; 399 } 400 401 /** 402 * Returns true if the focus dimmer is used for focus highlighting; false otherwise. 403 */ 404 public final boolean isFocusDimmerUsed() { 405 return mUseFocusDimmer; 406 } 407 408 /** 409 * Sets the numbers of rows for rendering the list of items. By default, it is 410 * set to 1. 411 */ 412 public void setNumRows(int numRows) { 413 this.mNumRows = numRows; 414 } 415 416 @Override 417 protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) { 418 super.initializeRowViewHolder(holder); 419 final ViewHolder rowViewHolder = (ViewHolder) holder; 420 Context context = holder.view.getContext(); 421 if (mShadowOverlayHelper == null) { 422 mShadowOverlayHelper = new ShadowOverlayHelper.Builder() 423 .needsOverlay(needsDefaultListSelectEffect()) 424 .needsShadow(needsDefaultShadow()) 425 .needsRoundedCorner(areChildRoundedCornersEnabled()) 426 .preferZOrder(isUsingZOrder(context)) 427 .keepForegroundDrawable(mKeepChildForeground) 428 .options(createShadowOverlayOptions()) 429 .build(context); 430 if (mShadowOverlayHelper.needsWrapper()) { 431 mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper( 432 mShadowOverlayHelper); 433 } 434 } 435 rowViewHolder.mItemBridgeAdapter = new ListRowPresenterItemBridgeAdapter(rowViewHolder); 436 // set wrapper if needed 437 rowViewHolder.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper); 438 mShadowOverlayHelper.prepareParentForShadow(rowViewHolder.mGridView); 439 440 FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, 441 mFocusZoomFactor, mUseFocusDimmer); 442 rowViewHolder.mGridView.setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType() 443 != ShadowOverlayHelper.SHADOW_DYNAMIC); 444 rowViewHolder.mGridView.setOnChildSelectedListener( 445 new OnChildSelectedListener() { 446 @Override 447 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 448 selectChildView(rowViewHolder, view, true); 449 } 450 }); 451 rowViewHolder.mGridView.setOnUnhandledKeyListener( 452 new BaseGridView.OnUnhandledKeyListener() { 453 @Override 454 public boolean onUnhandledKey(KeyEvent event) { 455 if (rowViewHolder.getOnKeyListener() != null 456 && rowViewHolder.getOnKeyListener().onKey( 457 rowViewHolder.view, event.getKeyCode(), event)) { 458 return true; 459 } 460 return false; 461 } 462 }); 463 rowViewHolder.mGridView.setNumRows(mNumRows); 464 } 465 466 final boolean needsDefaultListSelectEffect() { 467 return isUsingDefaultListSelectEffect() && getSelectEffectEnabled(); 468 } 469 470 /** 471 * Sets the recycled pool size for the given presenter. 472 */ 473 public void setRecycledPoolSize(Presenter presenter, int size) { 474 mRecycledPoolSize.put(presenter, size); 475 } 476 477 /** 478 * Returns the recycled pool size for the given presenter. 479 */ 480 public int getRecycledPoolSize(Presenter presenter) { 481 return mRecycledPoolSize.containsKey(presenter) ? mRecycledPoolSize.get(presenter) : 482 DEFAULT_RECYCLED_POOL_SIZE; 483 } 484 485 /** 486 * Sets the {@link PresenterSelector} used for showing a select object in a hover card. 487 */ 488 public final void setHoverCardPresenterSelector(PresenterSelector selector) { 489 mHoverCardPresenterSelector = selector; 490 } 491 492 /** 493 * Returns the {@link PresenterSelector} used for showing a select object in a hover card. 494 */ 495 public final PresenterSelector getHoverCardPresenterSelector() { 496 return mHoverCardPresenterSelector; 497 } 498 499 /* 500 * Perform operations when a child of horizontal grid view is selected. 501 */ 502 void selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent) { 503 if (view != null) { 504 if (rowViewHolder.mSelected) { 505 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 506 rowViewHolder.mGridView.getChildViewHolder(view); 507 508 if (mHoverCardPresenterSelector != null) { 509 rowViewHolder.mHoverCardViewSwitcher.select( 510 rowViewHolder.mGridView, view, ibh.mItem); 511 } 512 if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) { 513 rowViewHolder.getOnItemViewSelectedListener().onItemSelected( 514 ibh.mHolder, ibh.mItem, rowViewHolder, rowViewHolder.mRow); 515 } 516 } 517 } else { 518 if (mHoverCardPresenterSelector != null) { 519 rowViewHolder.mHoverCardViewSwitcher.unselect(); 520 } 521 if (fireEvent && rowViewHolder.getOnItemViewSelectedListener() != null) { 522 rowViewHolder.getOnItemViewSelectedListener().onItemSelected( 523 null, null, rowViewHolder, rowViewHolder.mRow); 524 } 525 } 526 } 527 528 private static void initStatics(Context context) { 529 if (sSelectedRowTopPadding == 0) { 530 sSelectedRowTopPadding = context.getResources().getDimensionPixelSize( 531 R.dimen.lb_browse_selected_row_top_padding); 532 sExpandedSelectedRowTopPadding = context.getResources().getDimensionPixelSize( 533 R.dimen.lb_browse_expanded_selected_row_top_padding); 534 sExpandedRowNoHovercardBottomPadding = context.getResources().getDimensionPixelSize( 535 R.dimen.lb_browse_expanded_row_no_hovercard_bottom_padding); 536 } 537 } 538 539 private int getSpaceUnderBaseline(ListRowPresenter.ViewHolder vh) { 540 RowHeaderPresenter.ViewHolder headerViewHolder = vh.getHeaderViewHolder(); 541 if (headerViewHolder != null) { 542 if (getHeaderPresenter() != null) { 543 return getHeaderPresenter().getSpaceUnderBaseline(headerViewHolder); 544 } 545 return headerViewHolder.view.getPaddingBottom(); 546 } 547 return 0; 548 } 549 550 private void setVerticalPadding(ListRowPresenter.ViewHolder vh) { 551 int paddingTop, paddingBottom; 552 // Note: sufficient bottom padding needed for card shadows. 553 if (vh.isExpanded()) { 554 int headerSpaceUnderBaseline = getSpaceUnderBaseline(vh); 555 if (DEBUG) Log.v(TAG, "headerSpaceUnderBaseline " + headerSpaceUnderBaseline); 556 paddingTop = (vh.isSelected() ? sExpandedSelectedRowTopPadding : vh.mPaddingTop) 557 - headerSpaceUnderBaseline; 558 paddingBottom = mHoverCardPresenterSelector == null 559 ? sExpandedRowNoHovercardBottomPadding : vh.mPaddingBottom; 560 } else if (vh.isSelected()) { 561 paddingTop = sSelectedRowTopPadding - vh.mPaddingBottom; 562 paddingBottom = sSelectedRowTopPadding; 563 } else { 564 paddingTop = 0; 565 paddingBottom = vh.mPaddingBottom; 566 } 567 vh.getGridView().setPadding(vh.mPaddingLeft, paddingTop, vh.mPaddingRight, 568 paddingBottom); 569 } 570 571 @Override 572 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 573 initStatics(parent.getContext()); 574 ListRowView rowView = new ListRowView(parent.getContext()); 575 setupFadingEffect(rowView); 576 if (mRowHeight != 0) { 577 rowView.getGridView().setRowHeight(mRowHeight); 578 } 579 return new ViewHolder(rowView, rowView.getGridView(), this); 580 } 581 582 /** 583 * Dispatch item selected event using current selected item in the {@link HorizontalGridView}. 584 * The method should only be called from onRowViewSelected(). 585 */ 586 @Override 587 protected void dispatchItemSelectedListener(RowPresenter.ViewHolder holder, boolean selected) { 588 ViewHolder vh = (ViewHolder)holder; 589 ItemBridgeAdapter.ViewHolder itemViewHolder = (ItemBridgeAdapter.ViewHolder) 590 vh.mGridView.findViewHolderForPosition(vh.mGridView.getSelectedPosition()); 591 if (itemViewHolder == null) { 592 super.dispatchItemSelectedListener(holder, selected); 593 return; 594 } 595 596 if (selected) { 597 if (holder.getOnItemViewSelectedListener() != null) { 598 holder.getOnItemViewSelectedListener().onItemSelected( 599 itemViewHolder.getViewHolder(), itemViewHolder.mItem, vh, vh.getRow()); 600 } 601 } 602 } 603 604 @Override 605 protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) { 606 super.onRowViewSelected(holder, selected); 607 ViewHolder vh = (ViewHolder) holder; 608 setVerticalPadding(vh); 609 updateFooterViewSwitcher(vh); 610 } 611 612 /* 613 * Show or hide hover card when row selection or expanded state is changed. 614 */ 615 private void updateFooterViewSwitcher(ViewHolder vh) { 616 if (vh.mExpanded && vh.mSelected) { 617 if (mHoverCardPresenterSelector != null) { 618 vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view, 619 mHoverCardPresenterSelector); 620 } 621 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 622 vh.mGridView.findViewHolderForPosition( 623 vh.mGridView.getSelectedPosition()); 624 selectChildView(vh, ibh == null ? null : ibh.itemView, false); 625 } else { 626 if (mHoverCardPresenterSelector != null) { 627 vh.mHoverCardViewSwitcher.unselect(); 628 } 629 } 630 } 631 632 private void setupFadingEffect(ListRowView rowView) { 633 // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding. 634 HorizontalGridView gridView = rowView.getGridView(); 635 if (mBrowseRowsFadingEdgeLength < 0) { 636 TypedArray ta = gridView.getContext() 637 .obtainStyledAttributes(R.styleable.LeanbackTheme); 638 mBrowseRowsFadingEdgeLength = (int) ta.getDimension( 639 R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0); 640 ta.recycle(); 641 } 642 gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength); 643 } 644 645 @Override 646 protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) { 647 super.onRowViewExpanded(holder, expanded); 648 ViewHolder vh = (ViewHolder) holder; 649 if (getRowHeight() != getExpandedRowHeight()) { 650 int newHeight = expanded ? getExpandedRowHeight() : getRowHeight(); 651 vh.getGridView().setRowHeight(newHeight); 652 } 653 setVerticalPadding(vh); 654 updateFooterViewSwitcher(vh); 655 } 656 657 @Override 658 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 659 super.onBindRowViewHolder(holder, item); 660 ViewHolder vh = (ViewHolder) holder; 661 ListRow rowItem = (ListRow) item; 662 vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter()); 663 vh.mGridView.setAdapter(vh.mItemBridgeAdapter); 664 vh.mGridView.setContentDescription(rowItem.getContentDescription()); 665 } 666 667 @Override 668 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 669 ViewHolder vh = (ViewHolder) holder; 670 vh.mGridView.setAdapter(null); 671 vh.mItemBridgeAdapter.clear(); 672 super.onUnbindRowViewHolder(holder); 673 } 674 675 /** 676 * ListRowPresenter overrides the default select effect of {@link RowPresenter} 677 * and return false. 678 */ 679 @Override 680 public final boolean isUsingDefaultSelectEffect() { 681 return false; 682 } 683 684 /** 685 * Returns true so that default select effect is applied to each individual 686 * child of {@link HorizontalGridView}. Subclass may return false to disable 687 * the default implementation. 688 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 689 */ 690 public boolean isUsingDefaultListSelectEffect() { 691 return true; 692 } 693 694 /** 695 * Default implementation returns true if SDK version >= 21, shadow (either static or z-order 696 * based) will be applied to each individual child of {@link HorizontalGridView}. 697 * Subclass may return false to disable default implementation of shadow and provide its own. 698 */ 699 public boolean isUsingDefaultShadow() { 700 return ShadowOverlayHelper.supportsShadow(); 701 } 702 703 /** 704 * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled 705 * on each child of horizontal list. If subclass returns false in isUsingDefaultShadow() 706 * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false. 707 */ 708 public boolean isUsingZOrder(Context context) { 709 return !Settings.getInstance(context).preferStaticShadows(); 710 } 711 712 /** 713 * Enables or disables child shadow. 714 * This is not only for enable/disable default shadow implementation but also subclass must 715 * respect this flag. 716 */ 717 public final void setShadowEnabled(boolean enabled) { 718 mShadowEnabled = enabled; 719 } 720 721 /** 722 * Returns true if child shadow is enabled. 723 * This is not only for enable/disable default shadow implementation but also subclass must 724 * respect this flag. 725 */ 726 public final boolean getShadowEnabled() { 727 return mShadowEnabled; 728 } 729 730 /** 731 * Enables or disabled rounded corners on children of this row. 732 * Supported on Android SDK >= L. 733 */ 734 public final void enableChildRoundedCorners(boolean enable) { 735 mRoundedCornersEnabled = enable; 736 } 737 738 /** 739 * Returns true if rounded corners are enabled for children of this row. 740 */ 741 public final boolean areChildRoundedCornersEnabled() { 742 return mRoundedCornersEnabled; 743 } 744 745 final boolean needsDefaultShadow() { 746 return isUsingDefaultShadow() && getShadowEnabled(); 747 } 748 749 /** 750 * When ListRowPresenter applies overlay color on the child, it may change child's foreground 751 * Drawable. If application uses child's foreground for other purposes such as ripple effect, 752 * it needs tell ListRowPresenter to keep the child's foreground. The default value is true. 753 * 754 * @param keep true if keep foreground of child of this row, false ListRowPresenter might change 755 * the foreground of the child. 756 */ 757 public final void setKeepChildForeground(boolean keep) { 758 mKeepChildForeground = keep; 759 } 760 761 /** 762 * Returns true if keeps foreground of child of this row, false otherwise. When 763 * ListRowPresenter applies overlay color on the child, it may change child's foreground 764 * Drawable. If application uses child's foreground for other purposes such as ripple effect, 765 * it needs tell ListRowPresenter to keep the child's foreground. The default value is true. 766 * 767 * @return true if keeps foreground of child of this row, false otherwise. 768 */ 769 public final boolean isKeepChildForeground() { 770 return mKeepChildForeground; 771 } 772 773 /** 774 * Create ShadowOverlayHelper Options. Subclass may override. 775 * e.g. 776 * <code> 777 * return new ShadowOverlayHelper.Options().roundedCornerRadius(10); 778 * </code> 779 * 780 * @return The options to be used for shadow, overlay and rounded corner. 781 */ 782 protected ShadowOverlayHelper.Options createShadowOverlayOptions() { 783 return ShadowOverlayHelper.Options.DEFAULT; 784 } 785 786 /** 787 * Applies select level to header and draw a default color dim over each child 788 * of {@link HorizontalGridView}. 789 * <p> 790 * Subclass may override this method. A subclass 791 * needs to call super.onSelectLevelChanged() for applying header select level 792 * and optionally applying a default select level to each child view of 793 * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()} 794 * is true. Subclass may override {@link #isUsingDefaultListSelectEffect()} to return 795 * false and deal with the individual item select level by itself. 796 * </p> 797 */ 798 @Override 799 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 800 super.onSelectLevelChanged(holder); 801 if (mShadowOverlayHelper != null && mShadowOverlayHelper.needsOverlay()) { 802 ViewHolder vh = (ViewHolder) holder; 803 int dimmedColor = vh.mColorDimmer.getPaint().getColor(); 804 for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) { 805 mShadowOverlayHelper.setOverlayColor(vh.mGridView.getChildAt(i), dimmedColor); 806 } 807 if (vh.mGridView.getFadingLeftEdge()) { 808 vh.mGridView.invalidate(); 809 } 810 } 811 } 812 813 @Override 814 public void freeze(RowPresenter.ViewHolder holder, boolean freeze) { 815 ViewHolder vh = (ViewHolder) holder; 816 vh.mGridView.setScrollEnabled(!freeze); 817 } 818 819 @Override 820 public void setEntranceTransitionState(RowPresenter.ViewHolder holder, 821 boolean afterEntrance) { 822 super.setEntranceTransitionState(holder, afterEntrance); 823 ((ViewHolder) holder).mGridView.setChildrenVisibility( 824 afterEntrance? View.VISIBLE : View.INVISIBLE); 825 } 826} 827