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 /** 532 * Get resource id to inflate the layout. The layout must match {@link #STATE_HALF} 533 */ 534 protected int getLayoutResourceId() { 535 return R.layout.lb_fullwidth_details_overview; 536 } 537 538 @Override 539 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 540 View v = LayoutInflater.from(parent.getContext()) 541 .inflate(getLayoutResourceId(), parent, false); 542 final ViewHolder vh = new ViewHolder(v, mDetailsPresenter, mDetailsOverviewLogoPresenter); 543 mDetailsOverviewLogoPresenter.setContext(vh.mDetailsLogoViewHolder, vh, this); 544 setState(vh, mInitialState); 545 546 vh.mActionBridgeAdapter = new ActionsItemBridgeAdapter(vh); 547 final View overview = vh.mOverviewFrame; 548 if (mBackgroundColorSet) { 549 overview.setBackgroundColor(mBackgroundColor); 550 } 551 if (mActionsBackgroundColorSet) { 552 overview.findViewById(R.id.details_overview_actions_background) 553 .setBackgroundColor(mActionsBackgroundColor); 554 } 555 RoundedRectHelper.getInstance().setClipToRoundedOutline(overview, true); 556 557 if (!getSelectEffectEnabled()) { 558 vh.mOverviewFrame.setForeground(null); 559 } 560 561 vh.mActionsRow.setOnUnhandledKeyListener(new BaseGridView.OnUnhandledKeyListener() { 562 @Override 563 public boolean onUnhandledKey(KeyEvent event) { 564 if (vh.getOnKeyListener() != null) { 565 if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) { 566 return true; 567 } 568 } 569 return false; 570 } 571 }); 572 return vh; 573 } 574 575 private static int getNonNegativeWidth(Drawable drawable) { 576 final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth(); 577 return (width > 0 ? width : 0); 578 } 579 580 private static int getNonNegativeHeight(Drawable drawable) { 581 final int height = (drawable == null) ? 0 : drawable.getIntrinsicHeight(); 582 return (height > 0 ? height : 0); 583 } 584 585 @Override 586 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 587 super.onBindRowViewHolder(holder, item); 588 589 DetailsOverviewRow row = (DetailsOverviewRow) item; 590 ViewHolder vh = (ViewHolder) holder; 591 592 mDetailsOverviewLogoPresenter.onBindViewHolder(vh.mDetailsLogoViewHolder, row); 593 mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem()); 594 vh.onBind(); 595 } 596 597 @Override 598 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 599 ViewHolder vh = (ViewHolder) holder; 600 vh.onUnbind(); 601 mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder); 602 mDetailsOverviewLogoPresenter.onUnbindViewHolder(vh.mDetailsLogoViewHolder); 603 super.onUnbindRowViewHolder(holder); 604 } 605 606 @Override 607 public final boolean isUsingDefaultSelectEffect() { 608 return false; 609 } 610 611 @Override 612 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 613 super.onSelectLevelChanged(holder); 614 if (getSelectEffectEnabled()) { 615 ViewHolder vh = (ViewHolder) holder; 616 int dimmedColor = vh.mColorDimmer.getPaint().getColor(); 617 ((ColorDrawable) vh.mOverviewFrame.getForeground().mutate()).setColor(dimmedColor); 618 } 619 } 620 621 @Override 622 protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) { 623 super.onRowViewAttachedToWindow(vh); 624 ViewHolder viewHolder = (ViewHolder) vh; 625 mDetailsPresenter.onViewAttachedToWindow(viewHolder.mDetailsDescriptionViewHolder); 626 mDetailsOverviewLogoPresenter.onViewAttachedToWindow(viewHolder.mDetailsLogoViewHolder); 627 } 628 629 @Override 630 protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) { 631 super.onRowViewDetachedFromWindow(vh); 632 ViewHolder viewHolder = (ViewHolder) vh; 633 mDetailsPresenter.onViewDetachedFromWindow(viewHolder.mDetailsDescriptionViewHolder); 634 mDetailsOverviewLogoPresenter.onViewDetachedFromWindow(viewHolder.mDetailsLogoViewHolder); 635 } 636 637 /** 638 * Called by {@link DetailsOverviewLogoPresenter} to notify logo was bound to view. 639 * Application should not directly call this method. 640 * @param viewHolder The row ViewHolder that has logo bound to view. 641 */ 642 public final void notifyOnBindLogo(ViewHolder viewHolder) { 643 onLayoutOverviewFrame(viewHolder, viewHolder.getState(), true); 644 onLayoutLogo(viewHolder, viewHolder.getState(), true); 645 if (mListener != null) { 646 mListener.onBindLogo(viewHolder); 647 } 648 } 649 650 /** 651 * Layout logo position based on current state. Subclass may override. 652 * The method is called when a logo is bound to view or state changes. 653 * @param viewHolder The row ViewHolder that contains the logo. 654 * @param oldState The old state, can be same as current viewHolder.getState() 655 * @param logoChanged Whether logo was changed. 656 */ 657 protected void onLayoutLogo(ViewHolder viewHolder, int oldState, boolean logoChanged) { 658 View v = viewHolder.getLogoViewHolder().view; 659 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) 660 v.getLayoutParams(); 661 switch (mAlignmentMode) { 662 case ALIGN_MODE_START: 663 default: 664 lp.setMarginStart(v.getResources().getDimensionPixelSize( 665 R.dimen.lb_details_v2_logo_margin_start)); 666 break; 667 case ALIGN_MODE_MIDDLE: 668 lp.setMarginStart(v.getResources().getDimensionPixelSize(R.dimen.lb_details_v2_left) 669 - lp.width); 670 break; 671 } 672 673 switch (viewHolder.getState()) { 674 case STATE_FULL: 675 default: 676 lp.topMargin = 677 v.getResources().getDimensionPixelSize(R.dimen.lb_details_v2_blank_height) 678 - lp.height / 2; 679 break; 680 case STATE_HALF: 681 lp.topMargin = v.getResources().getDimensionPixelSize( 682 R.dimen.lb_details_v2_blank_height) + v.getResources() 683 .getDimensionPixelSize(R.dimen.lb_details_v2_actions_height) + v 684 .getResources().getDimensionPixelSize( 685 R.dimen.lb_details_v2_description_margin_top); 686 break; 687 case STATE_SMALL: 688 lp.topMargin = 0; 689 break; 690 } 691 v.setLayoutParams(lp); 692 } 693 694 /** 695 * Layout overview frame based on current state. Subclass may override. 696 * The method is called when a logo is bound to view or state changes. 697 * @param viewHolder The row ViewHolder that contains the logo. 698 * @param oldState The old state, can be same as current viewHolder.getState() 699 * @param logoChanged Whether logo was changed. 700 */ 701 protected void onLayoutOverviewFrame(ViewHolder viewHolder, int oldState, boolean logoChanged) { 702 boolean wasBanner = oldState == STATE_SMALL; 703 boolean isBanner = viewHolder.getState() == STATE_SMALL; 704 if (wasBanner != isBanner || logoChanged) { 705 Resources res = viewHolder.view.getResources(); 706 707 int frameMarginStart; 708 int descriptionMarginStart = 0; 709 int logoWidth = 0; 710 if (mDetailsOverviewLogoPresenter.isBoundToImage(viewHolder.getLogoViewHolder(), 711 (DetailsOverviewRow) viewHolder.getRow())) { 712 logoWidth = viewHolder.getLogoViewHolder().view.getLayoutParams().width; 713 } 714 switch (mAlignmentMode) { 715 case ALIGN_MODE_START: 716 default: 717 if (isBanner) { 718 frameMarginStart = res.getDimensionPixelSize( 719 R.dimen.lb_details_v2_logo_margin_start); 720 descriptionMarginStart = logoWidth; 721 } else { 722 frameMarginStart = 0; 723 descriptionMarginStart = logoWidth + res.getDimensionPixelSize( 724 R.dimen.lb_details_v2_logo_margin_start); 725 } 726 break; 727 case ALIGN_MODE_MIDDLE: 728 if (isBanner) { 729 frameMarginStart = res.getDimensionPixelSize(R.dimen.lb_details_v2_left) 730 - logoWidth; 731 descriptionMarginStart = logoWidth; 732 } else { 733 frameMarginStart = 0; 734 descriptionMarginStart = res.getDimensionPixelSize( 735 R.dimen.lb_details_v2_left); 736 } 737 break; 738 } 739 MarginLayoutParams lpFrame = 740 (MarginLayoutParams) viewHolder.getOverviewView().getLayoutParams(); 741 lpFrame.topMargin = isBanner ? 0 742 : res.getDimensionPixelSize(R.dimen.lb_details_v2_blank_height); 743 lpFrame.leftMargin = lpFrame.rightMargin = frameMarginStart; 744 viewHolder.getOverviewView().setLayoutParams(lpFrame); 745 746 View description = viewHolder.getDetailsDescriptionFrame(); 747 MarginLayoutParams lpDesc = (MarginLayoutParams) description.getLayoutParams(); 748 lpDesc.setMarginStart(descriptionMarginStart); 749 description.setLayoutParams(lpDesc); 750 751 View action = viewHolder.getActionsRow(); 752 MarginLayoutParams lpActions = (MarginLayoutParams) action.getLayoutParams(); 753 lpActions.setMarginStart(descriptionMarginStart); 754 lpActions.height = 755 isBanner ? 0 : res.getDimensionPixelSize(R.dimen.lb_details_v2_actions_height); 756 action.setLayoutParams(lpActions); 757 } 758 } 759 760 /** 761 * Switch state of a ViewHolder. 762 * @param viewHolder The ViewHolder to change state. 763 * @param state New state, can be {@link #STATE_FULL}, {@link #STATE_HALF} 764 * or {@link #STATE_SMALL}. 765 */ 766 public final void setState(ViewHolder viewHolder, int state) { 767 if (viewHolder.getState() != state) { 768 int oldState = viewHolder.getState(); 769 viewHolder.mState = state; 770 onStateChanged(viewHolder, oldState); 771 } 772 } 773 774 /** 775 * Called when {@link ViewHolder#getState()} changes. Subclass may override. 776 * The default implementation calls {@link #onLayoutLogo(ViewHolder, int, boolean)} and 777 * {@link #onLayoutOverviewFrame(ViewHolder, int, boolean)}. 778 * @param viewHolder The ViewHolder which state changed. 779 * @param oldState The old state. 780 */ 781 protected void onStateChanged(ViewHolder viewHolder, int oldState) { 782 onLayoutOverviewFrame(viewHolder, oldState, false); 783 onLayoutLogo(viewHolder, oldState, false); 784 } 785 786 @Override 787 public void setEntranceTransitionState(RowPresenter.ViewHolder holder, 788 boolean afterEntrance) { 789 super.setEntranceTransitionState(holder, afterEntrance); 790 if (mParticipatingEntranceTransition) { 791 holder.view.setVisibility(afterEntrance? View.VISIBLE : View.INVISIBLE); 792 } 793 } 794} 795