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.res.Resources; 17 import android.graphics.Color; 18 import android.graphics.Rect; 19 import android.graphics.drawable.ColorDrawable; 20 import android.graphics.drawable.Drawable; 21 import android.os.Handler; 22 import android.util.Log; 23 import android.view.KeyEvent; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.view.ViewGroup.MarginLayoutParams; 28 import android.widget.FrameLayout; 29 30 import androidx.leanback.R; 31 import androidx.recyclerview.widget.RecyclerView; 32 33 /** 34 * Renders a {@link DetailsOverviewRow} to display an overview of an item. Typically this row will 35 * be the first row in a fragment such as the 36 * {@link androidx.leanback.app.DetailsFragment}. The View created by the 37 * FullWidthDetailsOverviewRowPresenter is made in three parts: logo view on the left, action list view on 38 * the top and a customizable detailed description view on the right. 39 * 40 * <p>The detailed description is rendered using a {@link Presenter} passed in 41 * {@link #FullWidthDetailsOverviewRowPresenter(Presenter)}. Typically this will be an instance of 42 * {@link AbstractDetailsDescriptionPresenter}. The application can access the detailed description 43 * ViewHolder from {@link ViewHolder#getDetailsDescriptionViewHolder()}. 44 * </p> 45 * 46 * <p>The logo view is rendered using a customizable {@link DetailsOverviewLogoPresenter} passed in 47 * {@link #FullWidthDetailsOverviewRowPresenter(Presenter, DetailsOverviewLogoPresenter)}. The application 48 * can access the logo ViewHolder from {@link ViewHolder#getLogoViewHolder()}. 49 * </p> 50 * 51 * <p> 52 * To support activity shared element transition, call {@link #setListener(Listener)} with 53 * {@link FullWidthDetailsOverviewSharedElementHelper} during Activity's onCreate(). Application is free to 54 * create its own "shared element helper" class using the Listener for image binding. 55 * Call {@link #setParticipatingEntranceTransition(boolean)} with false 56 * </p> 57 * 58 * <p> 59 * The view has three states: {@link #STATE_HALF} {@link #STATE_FULL} and {@link #STATE_SMALL}. See 60 * {@link androidx.leanback.app.DetailsFragment} where it switches states based on 61 * selected row position. 62 * </p> 63 */ 64 public class FullWidthDetailsOverviewRowPresenter extends RowPresenter { 65 66 static final String TAG = "FullWidthDetailsRP"; 67 static final boolean DEBUG = false; 68 69 private static Rect sTmpRect = new Rect(); 70 static final Handler sHandler = new Handler(); 71 72 /** 73 * This is the default state corresponding to layout file. The view takes full width 74 * of screen and covers bottom half of the screen. 75 */ 76 public static final int STATE_HALF = 0; 77 /** 78 * This is the state when the view covers full width and height of screen. 79 */ 80 public static final int STATE_FULL = 1; 81 /** 82 * This is the state where the view shrinks to a small banner. 83 */ 84 public static final int STATE_SMALL = 2; 85 86 /** 87 * This is the alignment mode that the logo and description align to the starting edge of the 88 * overview view. 89 */ 90 public static final int ALIGN_MODE_START = 0; 91 /** 92 * This is the alignment mode that the ending edge of logo and the starting edge of description 93 * align to the middle of the overview view. Note that this might not be the exact horizontal 94 * center of the overview view. 95 */ 96 public static final int ALIGN_MODE_MIDDLE = 1; 97 98 /** 99 * Listeners for events on ViewHolder. 100 */ 101 public static abstract class Listener { 102 103 /** 104 * {@link FullWidthDetailsOverviewRowPresenter#notifyOnBindLogo(ViewHolder)} is called. 105 * @param vh The ViewHolder that has bound logo view. 106 */ onBindLogo(ViewHolder vh)107 public void onBindLogo(ViewHolder vh) { 108 } 109 110 } 111 112 class ActionsItemBridgeAdapter extends ItemBridgeAdapter { 113 FullWidthDetailsOverviewRowPresenter.ViewHolder mViewHolder; 114 ActionsItemBridgeAdapter(FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder)115 ActionsItemBridgeAdapter(FullWidthDetailsOverviewRowPresenter.ViewHolder viewHolder) { 116 mViewHolder = viewHolder; 117 } 118 119 @Override onBind(final ItemBridgeAdapter.ViewHolder ibvh)120 public void onBind(final ItemBridgeAdapter.ViewHolder ibvh) { 121 if (mViewHolder.getOnItemViewClickedListener() != null 122 || mActionClickedListener != null) { 123 ibvh.getPresenter().setOnClickListener( 124 ibvh.getViewHolder(), new View.OnClickListener() { 125 @Override 126 public void onClick(View v) { 127 if (mViewHolder.getOnItemViewClickedListener() != null) { 128 mViewHolder.getOnItemViewClickedListener().onItemClicked( 129 ibvh.getViewHolder(), ibvh.getItem(), 130 mViewHolder, mViewHolder.getRow()); 131 } 132 if (mActionClickedListener != null) { 133 mActionClickedListener.onActionClicked((Action) ibvh.getItem()); 134 } 135 } 136 }); 137 } 138 } 139 @Override onUnbind(final ItemBridgeAdapter.ViewHolder ibvh)140 public void onUnbind(final ItemBridgeAdapter.ViewHolder ibvh) { 141 if (mViewHolder.getOnItemViewClickedListener() != null 142 || mActionClickedListener != null) { 143 ibvh.getPresenter().setOnClickListener(ibvh.getViewHolder(), null); 144 } 145 } 146 @Override onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder)147 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 148 // Remove first to ensure we don't add ourselves more than once. 149 viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener); 150 viewHolder.itemView.addOnLayoutChangeListener(mViewHolder.mLayoutChangeListener); 151 } 152 @Override onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder)153 public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 154 viewHolder.itemView.removeOnLayoutChangeListener(mViewHolder.mLayoutChangeListener); 155 mViewHolder.checkFirstAndLastPosition(false); 156 } 157 } 158 159 /** 160 * A ViewHolder for the DetailsOverviewRow. 161 */ 162 public class ViewHolder extends RowPresenter.ViewHolder { 163 164 protected final DetailsOverviewRow.Listener mRowListener = createRowListener(); 165 createRowListener()166 protected DetailsOverviewRow.Listener createRowListener() { 167 return new DetailsOverviewRowListener(); 168 } 169 170 public class DetailsOverviewRowListener extends DetailsOverviewRow.Listener { 171 @Override onImageDrawableChanged(DetailsOverviewRow row)172 public void onImageDrawableChanged(DetailsOverviewRow row) { 173 sHandler.removeCallbacks(mUpdateDrawableCallback); 174 sHandler.post(mUpdateDrawableCallback); 175 } 176 177 @Override onItemChanged(DetailsOverviewRow row)178 public void onItemChanged(DetailsOverviewRow row) { 179 if (mDetailsDescriptionViewHolder != null) { 180 mDetailsPresenter.onUnbindViewHolder(mDetailsDescriptionViewHolder); 181 } 182 mDetailsPresenter.onBindViewHolder(mDetailsDescriptionViewHolder, row.getItem()); 183 } 184 185 @Override onActionsAdapterChanged(DetailsOverviewRow row)186 public void onActionsAdapterChanged(DetailsOverviewRow row) { 187 bindActions(row.getActionsAdapter()); 188 } 189 }; 190 191 final ViewGroup mOverviewRoot; 192 final FrameLayout mOverviewFrame; 193 final ViewGroup mDetailsDescriptionFrame; 194 final HorizontalGridView mActionsRow; 195 final Presenter.ViewHolder mDetailsDescriptionViewHolder; 196 final DetailsOverviewLogoPresenter.ViewHolder mDetailsLogoViewHolder; 197 int mNumItems; 198 ItemBridgeAdapter mActionBridgeAdapter; 199 int mState = STATE_HALF; 200 201 final Runnable mUpdateDrawableCallback = new Runnable() { 202 @Override 203 public void run() { 204 Row row = getRow(); 205 if (row == null) { 206 return; 207 } 208 mDetailsOverviewLogoPresenter.onBindViewHolder(mDetailsLogoViewHolder, row); 209 } 210 }; 211 bindActions(ObjectAdapter adapter)212 void bindActions(ObjectAdapter adapter) { 213 mActionBridgeAdapter.setAdapter(adapter); 214 mActionsRow.setAdapter(mActionBridgeAdapter); 215 mNumItems = mActionBridgeAdapter.getItemCount(); 216 217 } 218 onBind()219 void onBind() { 220 DetailsOverviewRow row = (DetailsOverviewRow) getRow(); 221 bindActions(row.getActionsAdapter()); 222 row.addListener(mRowListener); 223 } 224 onUnbind()225 void onUnbind() { 226 DetailsOverviewRow row = (DetailsOverviewRow) getRow(); 227 row.removeListener(mRowListener); 228 sHandler.removeCallbacks(mUpdateDrawableCallback); 229 } 230 231 final View.OnLayoutChangeListener mLayoutChangeListener = 232 new View.OnLayoutChangeListener() { 233 234 @Override 235 public void onLayoutChange(View v, int left, int top, int right, 236 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 237 if (DEBUG) Log.v(TAG, "onLayoutChange " + v); 238 checkFirstAndLastPosition(false); 239 } 240 }; 241 242 final OnChildSelectedListener mChildSelectedListener = new OnChildSelectedListener() { 243 @Override 244 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 245 dispatchItemSelection(view); 246 } 247 }; 248 dispatchItemSelection(View view)249 void dispatchItemSelection(View view) { 250 if (!isSelected()) { 251 return; 252 } 253 ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) (view != null 254 ? mActionsRow.getChildViewHolder(view) 255 : mActionsRow.findViewHolderForPosition(mActionsRow.getSelectedPosition())); 256 if (ibvh == null) { 257 if (getOnItemViewSelectedListener() != null) { 258 getOnItemViewSelectedListener().onItemSelected(null, null, 259 ViewHolder.this, getRow()); 260 } 261 } else { 262 if (getOnItemViewSelectedListener() != null) { 263 getOnItemViewSelectedListener().onItemSelected(ibvh.getViewHolder(), ibvh.getItem(), 264 ViewHolder.this, getRow()); 265 } 266 } 267 }; 268 269 final RecyclerView.OnScrollListener mScrollListener = 270 new RecyclerView.OnScrollListener() { 271 272 @Override 273 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 274 } 275 @Override 276 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 277 checkFirstAndLastPosition(true); 278 } 279 }; 280 getViewCenter(View view)281 private int getViewCenter(View view) { 282 return (view.getRight() - view.getLeft()) / 2; 283 } 284 checkFirstAndLastPosition(boolean fromScroll)285 void checkFirstAndLastPosition(boolean fromScroll) { 286 RecyclerView.ViewHolder viewHolder; 287 288 viewHolder = mActionsRow.findViewHolderForPosition(mNumItems - 1); 289 boolean showRight = (viewHolder == null 290 || viewHolder.itemView.getRight() > mActionsRow.getWidth()); 291 292 viewHolder = mActionsRow.findViewHolderForPosition(0); 293 boolean showLeft = (viewHolder == null || viewHolder.itemView.getLeft() < 0); 294 295 if (DEBUG) { 296 Log.v(TAG, "checkFirstAndLast fromScroll " + fromScroll 297 + " showRight " + showRight + " showLeft " + showLeft); 298 } 299 300 } 301 302 /** 303 * Constructor for the ViewHolder. 304 * 305 * @param rootView The root View that this view holder will be attached 306 * to. 307 */ ViewHolder(View rootView, Presenter detailsPresenter, DetailsOverviewLogoPresenter logoPresenter)308 public ViewHolder(View rootView, Presenter detailsPresenter, 309 DetailsOverviewLogoPresenter logoPresenter) { 310 super(rootView); 311 mOverviewRoot = (ViewGroup) rootView.findViewById(R.id.details_root); 312 mOverviewFrame = (FrameLayout) rootView.findViewById(R.id.details_frame); 313 mDetailsDescriptionFrame = 314 (ViewGroup) rootView.findViewById(R.id.details_overview_description); 315 mActionsRow = 316 (HorizontalGridView) mOverviewFrame.findViewById(R.id.details_overview_actions); 317 mActionsRow.setHasOverlappingRendering(false); 318 mActionsRow.setOnScrollListener(mScrollListener); 319 mActionsRow.setAdapter(mActionBridgeAdapter); 320 mActionsRow.setOnChildSelectedListener(mChildSelectedListener); 321 322 final int fadeLength = rootView.getResources().getDimensionPixelSize( 323 R.dimen.lb_details_overview_actions_fade_size); 324 mActionsRow.setFadingRightEdgeLength(fadeLength); 325 mActionsRow.setFadingLeftEdgeLength(fadeLength); 326 mDetailsDescriptionViewHolder = 327 detailsPresenter.onCreateViewHolder(mDetailsDescriptionFrame); 328 mDetailsDescriptionFrame.addView(mDetailsDescriptionViewHolder.view); 329 mDetailsLogoViewHolder = (DetailsOverviewLogoPresenter.ViewHolder) 330 logoPresenter.onCreateViewHolder(mOverviewRoot); 331 mOverviewRoot.addView(mDetailsLogoViewHolder.view); 332 } 333 334 /** 335 * Returns the rectangle area with a color background. 336 */ getOverviewView()337 public final ViewGroup getOverviewView() { 338 return mOverviewFrame; 339 } 340 341 /** 342 * Returns the ViewHolder for logo. 343 */ getLogoViewHolder()344 public final DetailsOverviewLogoPresenter.ViewHolder getLogoViewHolder() { 345 return mDetailsLogoViewHolder; 346 } 347 348 /** 349 * Returns the ViewHolder for DetailsDescription. 350 */ getDetailsDescriptionViewHolder()351 public final Presenter.ViewHolder getDetailsDescriptionViewHolder() { 352 return mDetailsDescriptionViewHolder; 353 } 354 355 /** 356 * Returns the root view for inserting details description. 357 */ getDetailsDescriptionFrame()358 public final ViewGroup getDetailsDescriptionFrame() { 359 return mDetailsDescriptionFrame; 360 } 361 362 /** 363 * Returns the view of actions row. 364 */ getActionsRow()365 public final ViewGroup getActionsRow() { 366 return mActionsRow; 367 } 368 369 /** 370 * Returns current state of the ViewHolder set by 371 * {@link FullWidthDetailsOverviewRowPresenter#setState(ViewHolder, int)}. 372 */ getState()373 public final int getState() { 374 return mState; 375 } 376 } 377 378 protected int mInitialState = STATE_HALF; 379 380 final Presenter mDetailsPresenter; 381 final DetailsOverviewLogoPresenter mDetailsOverviewLogoPresenter; 382 OnActionClickedListener mActionClickedListener; 383 384 private int mBackgroundColor = Color.TRANSPARENT; 385 private int mActionsBackgroundColor = Color.TRANSPARENT; 386 private boolean mBackgroundColorSet; 387 private boolean mActionsBackgroundColorSet; 388 389 private Listener mListener; 390 private boolean mParticipatingEntranceTransition; 391 392 private int mAlignmentMode; 393 394 /** 395 * Constructor for a FullWidthDetailsOverviewRowPresenter. 396 * 397 * @param detailsPresenter The {@link Presenter} used to render the detailed 398 * description of the row. 399 */ FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter)400 public FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter) { 401 this(detailsPresenter, new DetailsOverviewLogoPresenter()); 402 } 403 404 /** 405 * Constructor for a FullWidthDetailsOverviewRowPresenter. 406 * 407 * @param detailsPresenter The {@link Presenter} used to render the detailed 408 * description of the row. 409 * @param logoPresenter The {@link Presenter} used to render the logo view. 410 */ FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter, DetailsOverviewLogoPresenter logoPresenter)411 public FullWidthDetailsOverviewRowPresenter(Presenter detailsPresenter, 412 DetailsOverviewLogoPresenter logoPresenter) { 413 setHeaderPresenter(null); 414 setSelectEffectEnabled(false); 415 mDetailsPresenter = detailsPresenter; 416 mDetailsOverviewLogoPresenter = logoPresenter; 417 } 418 419 /** 420 * Sets the listener for Action click events. 421 */ setOnActionClickedListener(OnActionClickedListener listener)422 public void setOnActionClickedListener(OnActionClickedListener listener) { 423 mActionClickedListener = listener; 424 } 425 426 /** 427 * Returns the listener for Action click events. 428 */ getOnActionClickedListener()429 public OnActionClickedListener getOnActionClickedListener() { 430 return mActionClickedListener; 431 } 432 433 /** 434 * Sets the background color. If not set, a default from the theme will be used. 435 */ setBackgroundColor(int color)436 public final void setBackgroundColor(int color) { 437 mBackgroundColor = color; 438 mBackgroundColorSet = true; 439 } 440 441 /** 442 * Returns the background color. If {@link #setBackgroundColor(int)}, transparent 443 * is returned. 444 */ getBackgroundColor()445 public final int getBackgroundColor() { 446 return mBackgroundColor; 447 } 448 449 /** 450 * Sets the background color for Action Bar. If not set, a default from the theme will be 451 * used. 452 */ setActionsBackgroundColor(int color)453 public final void setActionsBackgroundColor(int color) { 454 mActionsBackgroundColor = color; 455 mActionsBackgroundColorSet = true; 456 } 457 458 /** 459 * Returns the background color of actions. If {@link #setActionsBackgroundColor(int)} 460 * is not called, transparent is returned. 461 */ getActionsBackgroundColor()462 public final int getActionsBackgroundColor() { 463 return mActionsBackgroundColor; 464 } 465 466 /** 467 * Returns true if the overview should be part of shared element transition. 468 */ isParticipatingEntranceTransition()469 public final boolean isParticipatingEntranceTransition() { 470 return mParticipatingEntranceTransition; 471 } 472 473 /** 474 * Sets if the overview should be part of shared element transition. 475 */ setParticipatingEntranceTransition(boolean participating)476 public final void setParticipatingEntranceTransition(boolean participating) { 477 mParticipatingEntranceTransition = participating; 478 } 479 480 /** 481 * Change the initial state used to create ViewHolder. 482 */ setInitialState(int state)483 public final void setInitialState(int state) { 484 mInitialState = state; 485 } 486 487 /** 488 * Returns the initial state used to create ViewHolder. 489 */ getInitialState()490 public final int getInitialState() { 491 return mInitialState; 492 } 493 494 /** 495 * Set alignment mode of Description. 496 * 497 * @param alignmentMode One of {@link #ALIGN_MODE_MIDDLE} or {@link #ALIGN_MODE_START} 498 */ setAlignmentMode(int alignmentMode)499 public final void setAlignmentMode(int alignmentMode) { 500 mAlignmentMode = alignmentMode; 501 } 502 503 /** 504 * Returns alignment mode of Description. 505 * 506 * @return One of {@link #ALIGN_MODE_MIDDLE} or {@link #ALIGN_MODE_START}. 507 */ getAlignmentMode()508 public final int getAlignmentMode() { 509 return mAlignmentMode; 510 } 511 512 @Override isClippingChildren()513 protected boolean isClippingChildren() { 514 return true; 515 } 516 517 /** 518 * Set listener for details overview presenter. Must be called before creating 519 * ViewHolder. 520 */ setListener(Listener listener)521 public final void setListener(Listener listener) { 522 mListener = listener; 523 } 524 525 /** 526 * Get resource id to inflate the layout. The layout must match {@link #STATE_HALF} 527 */ getLayoutResourceId()528 protected int getLayoutResourceId() { 529 return R.layout.lb_fullwidth_details_overview; 530 } 531 532 @Override createRowViewHolder(ViewGroup parent)533 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 534 View v = LayoutInflater.from(parent.getContext()) 535 .inflate(getLayoutResourceId(), parent, false); 536 final ViewHolder vh = new ViewHolder(v, mDetailsPresenter, mDetailsOverviewLogoPresenter); 537 mDetailsOverviewLogoPresenter.setContext(vh.mDetailsLogoViewHolder, vh, this); 538 setState(vh, mInitialState); 539 540 vh.mActionBridgeAdapter = new ActionsItemBridgeAdapter(vh); 541 final View overview = vh.mOverviewFrame; 542 if (mBackgroundColorSet) { 543 overview.setBackgroundColor(mBackgroundColor); 544 } 545 if (mActionsBackgroundColorSet) { 546 overview.findViewById(R.id.details_overview_actions_background) 547 .setBackgroundColor(mActionsBackgroundColor); 548 } 549 RoundedRectHelper.setClipToRoundedOutline(overview, true); 550 551 if (!getSelectEffectEnabled()) { 552 vh.mOverviewFrame.setForeground(null); 553 } 554 555 vh.mActionsRow.setOnUnhandledKeyListener(new BaseGridView.OnUnhandledKeyListener() { 556 @Override 557 public boolean onUnhandledKey(KeyEvent event) { 558 if (vh.getOnKeyListener() != null) { 559 if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) { 560 return true; 561 } 562 } 563 return false; 564 } 565 }); 566 return vh; 567 } 568 getNonNegativeWidth(Drawable drawable)569 private static int getNonNegativeWidth(Drawable drawable) { 570 final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth(); 571 return (width > 0 ? width : 0); 572 } 573 getNonNegativeHeight(Drawable drawable)574 private static int getNonNegativeHeight(Drawable drawable) { 575 final int height = (drawable == null) ? 0 : drawable.getIntrinsicHeight(); 576 return (height > 0 ? height : 0); 577 } 578 579 @Override onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item)580 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 581 super.onBindRowViewHolder(holder, item); 582 583 DetailsOverviewRow row = (DetailsOverviewRow) item; 584 ViewHolder vh = (ViewHolder) holder; 585 586 mDetailsOverviewLogoPresenter.onBindViewHolder(vh.mDetailsLogoViewHolder, row); 587 mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem()); 588 vh.onBind(); 589 } 590 591 @Override onUnbindRowViewHolder(RowPresenter.ViewHolder holder)592 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 593 ViewHolder vh = (ViewHolder) holder; 594 vh.onUnbind(); 595 mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder); 596 mDetailsOverviewLogoPresenter.onUnbindViewHolder(vh.mDetailsLogoViewHolder); 597 super.onUnbindRowViewHolder(holder); 598 } 599 600 @Override isUsingDefaultSelectEffect()601 public final boolean isUsingDefaultSelectEffect() { 602 return false; 603 } 604 605 @Override onSelectLevelChanged(RowPresenter.ViewHolder holder)606 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 607 super.onSelectLevelChanged(holder); 608 if (getSelectEffectEnabled()) { 609 ViewHolder vh = (ViewHolder) holder; 610 int dimmedColor = vh.mColorDimmer.getPaint().getColor(); 611 ((ColorDrawable) vh.mOverviewFrame.getForeground().mutate()).setColor(dimmedColor); 612 } 613 } 614 615 @Override onRowViewAttachedToWindow(RowPresenter.ViewHolder vh)616 protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) { 617 super.onRowViewAttachedToWindow(vh); 618 ViewHolder viewHolder = (ViewHolder) vh; 619 mDetailsPresenter.onViewAttachedToWindow(viewHolder.mDetailsDescriptionViewHolder); 620 mDetailsOverviewLogoPresenter.onViewAttachedToWindow(viewHolder.mDetailsLogoViewHolder); 621 } 622 623 @Override onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh)624 protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) { 625 super.onRowViewDetachedFromWindow(vh); 626 ViewHolder viewHolder = (ViewHolder) vh; 627 mDetailsPresenter.onViewDetachedFromWindow(viewHolder.mDetailsDescriptionViewHolder); 628 mDetailsOverviewLogoPresenter.onViewDetachedFromWindow(viewHolder.mDetailsLogoViewHolder); 629 } 630 631 /** 632 * Called by {@link DetailsOverviewLogoPresenter} to notify logo was bound to view. 633 * Application should not directly call this method. 634 * @param viewHolder The row ViewHolder that has logo bound to view. 635 */ notifyOnBindLogo(ViewHolder viewHolder)636 public final void notifyOnBindLogo(ViewHolder viewHolder) { 637 onLayoutOverviewFrame(viewHolder, viewHolder.getState(), true); 638 onLayoutLogo(viewHolder, viewHolder.getState(), true); 639 if (mListener != null) { 640 mListener.onBindLogo(viewHolder); 641 } 642 } 643 644 /** 645 * Layout logo position based on current state. Subclass may override. 646 * The method is called when a logo is bound to view or state changes. 647 * @param viewHolder The row ViewHolder that contains the logo. 648 * @param oldState The old state, can be same as current viewHolder.getState() 649 * @param logoChanged Whether logo was changed. 650 */ onLayoutLogo(ViewHolder viewHolder, int oldState, boolean logoChanged)651 protected void onLayoutLogo(ViewHolder viewHolder, int oldState, boolean logoChanged) { 652 View v = viewHolder.getLogoViewHolder().view; 653 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) 654 v.getLayoutParams(); 655 switch (mAlignmentMode) { 656 case ALIGN_MODE_START: 657 default: 658 lp.setMarginStart(v.getResources().getDimensionPixelSize( 659 R.dimen.lb_details_v2_logo_margin_start)); 660 break; 661 case ALIGN_MODE_MIDDLE: 662 lp.setMarginStart(v.getResources().getDimensionPixelSize(R.dimen.lb_details_v2_left) 663 - lp.width); 664 break; 665 } 666 667 switch (viewHolder.getState()) { 668 case STATE_FULL: 669 default: 670 lp.topMargin = 671 v.getResources().getDimensionPixelSize(R.dimen.lb_details_v2_blank_height) 672 - lp.height / 2; 673 break; 674 case STATE_HALF: 675 lp.topMargin = v.getResources().getDimensionPixelSize( 676 R.dimen.lb_details_v2_blank_height) + v.getResources() 677 .getDimensionPixelSize(R.dimen.lb_details_v2_actions_height) + v 678 .getResources().getDimensionPixelSize( 679 R.dimen.lb_details_v2_description_margin_top); 680 break; 681 case STATE_SMALL: 682 lp.topMargin = 0; 683 break; 684 } 685 v.setLayoutParams(lp); 686 } 687 688 /** 689 * Layout overview frame based on current state. Subclass may override. 690 * The method is called when a logo is bound to view or state changes. 691 * @param viewHolder The row ViewHolder that contains the logo. 692 * @param oldState The old state, can be same as current viewHolder.getState() 693 * @param logoChanged Whether logo was changed. 694 */ onLayoutOverviewFrame(ViewHolder viewHolder, int oldState, boolean logoChanged)695 protected void onLayoutOverviewFrame(ViewHolder viewHolder, int oldState, boolean logoChanged) { 696 boolean wasBanner = oldState == STATE_SMALL; 697 boolean isBanner = viewHolder.getState() == STATE_SMALL; 698 if (wasBanner != isBanner || logoChanged) { 699 Resources res = viewHolder.view.getResources(); 700 701 int frameMarginStart; 702 int descriptionMarginStart = 0; 703 int logoWidth = 0; 704 if (mDetailsOverviewLogoPresenter.isBoundToImage(viewHolder.getLogoViewHolder(), 705 (DetailsOverviewRow) viewHolder.getRow())) { 706 logoWidth = viewHolder.getLogoViewHolder().view.getLayoutParams().width; 707 } 708 switch (mAlignmentMode) { 709 case ALIGN_MODE_START: 710 default: 711 if (isBanner) { 712 frameMarginStart = res.getDimensionPixelSize( 713 R.dimen.lb_details_v2_logo_margin_start); 714 descriptionMarginStart = logoWidth; 715 } else { 716 frameMarginStart = 0; 717 descriptionMarginStart = logoWidth + res.getDimensionPixelSize( 718 R.dimen.lb_details_v2_logo_margin_start); 719 } 720 break; 721 case ALIGN_MODE_MIDDLE: 722 if (isBanner) { 723 frameMarginStart = res.getDimensionPixelSize(R.dimen.lb_details_v2_left) 724 - logoWidth; 725 descriptionMarginStart = logoWidth; 726 } else { 727 frameMarginStart = 0; 728 descriptionMarginStart = res.getDimensionPixelSize( 729 R.dimen.lb_details_v2_left); 730 } 731 break; 732 } 733 MarginLayoutParams lpFrame = 734 (MarginLayoutParams) viewHolder.getOverviewView().getLayoutParams(); 735 lpFrame.topMargin = isBanner ? 0 736 : res.getDimensionPixelSize(R.dimen.lb_details_v2_blank_height); 737 lpFrame.leftMargin = lpFrame.rightMargin = frameMarginStart; 738 viewHolder.getOverviewView().setLayoutParams(lpFrame); 739 740 View description = viewHolder.getDetailsDescriptionFrame(); 741 MarginLayoutParams lpDesc = (MarginLayoutParams) description.getLayoutParams(); 742 lpDesc.setMarginStart(descriptionMarginStart); 743 description.setLayoutParams(lpDesc); 744 745 View action = viewHolder.getActionsRow(); 746 MarginLayoutParams lpActions = (MarginLayoutParams) action.getLayoutParams(); 747 lpActions.setMarginStart(descriptionMarginStart); 748 lpActions.height = 749 isBanner ? 0 : res.getDimensionPixelSize(R.dimen.lb_details_v2_actions_height); 750 action.setLayoutParams(lpActions); 751 } 752 } 753 754 /** 755 * Switch state of a ViewHolder. 756 * @param viewHolder The ViewHolder to change state. 757 * @param state New state, can be {@link #STATE_FULL}, {@link #STATE_HALF} 758 * or {@link #STATE_SMALL}. 759 */ setState(ViewHolder viewHolder, int state)760 public final void setState(ViewHolder viewHolder, int state) { 761 if (viewHolder.getState() != state) { 762 int oldState = viewHolder.getState(); 763 viewHolder.mState = state; 764 onStateChanged(viewHolder, oldState); 765 } 766 } 767 768 /** 769 * Called when {@link ViewHolder#getState()} changes. Subclass may override. 770 * The default implementation calls {@link #onLayoutLogo(ViewHolder, int, boolean)} and 771 * {@link #onLayoutOverviewFrame(ViewHolder, int, boolean)}. 772 * @param viewHolder The ViewHolder which state changed. 773 * @param oldState The old state. 774 */ onStateChanged(ViewHolder viewHolder, int oldState)775 protected void onStateChanged(ViewHolder viewHolder, int oldState) { 776 onLayoutOverviewFrame(viewHolder, oldState, false); 777 onLayoutLogo(viewHolder, oldState, false); 778 } 779 780 @Override setEntranceTransitionState(RowPresenter.ViewHolder holder, boolean afterEntrance)781 public void setEntranceTransitionState(RowPresenter.ViewHolder holder, 782 boolean afterEntrance) { 783 super.setEntranceTransitionState(holder, afterEntrance); 784 if (mParticipatingEntranceTransition) { 785 holder.view.setVisibility(afterEntrance? View.VISIBLE : View.INVISIBLE); 786 } 787 } 788 } 789