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