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