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