DetailsOverviewRowPresenter.java revision 4f34a05cdf73b68c3b2eb8678f740ab15225126a
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.graphics.Bitmap; 19import android.graphics.Color; 20import android.graphics.drawable.Drawable; 21import android.graphics.drawable.BitmapDrawable; 22import android.support.v17.leanback.R; 23import android.support.v7.widget.RecyclerView; 24import android.util.Log; 25import android.util.TypedValue; 26import android.view.LayoutInflater; 27import android.view.View; 28import android.view.ViewGroup; 29import android.widget.FrameLayout; 30import android.widget.ImageView; 31 32import java.util.Collection; 33 34/** 35 * A DetailsOverviewRowPresenter renders a {@link DetailsOverviewRow} to display an 36 * overview of an item. Typically this row will be the first row in a fragment 37 * such as the {@link android.support.v17.leanback.app.DetailsFragment 38 * DetailsFragment}. View created by DetailsOverviewRowPresenter is made in three parts: 39 * ImageView on the left, action list view on the bottom and a customizable detailed 40 * description view on the right. 41 * 42 * <p>The detailed description is rendered using a {@link Presenter} passed in 43 * {@link #DetailsOverviewRowPresenter(Presenter)}. User can access detailed description 44 * ViewHolder from {@link ViewHolder#mDetailsDescriptionViewHolder}. 45 * </p> 46 * 47 * <p> 48 * To participate in activity transition, call {@link #setSharedElementEnterTransition(Activity, 49 * String)} during Activity's onCreate(). 50 * </p> 51 * 52 * <p> 53 * Because transition support and layout are fully controlled by DetailsOverviewRowPresenter, 54 * developer can not override DetailsOverviewRowPresenter.ViewHolder for adding/replacing views 55 * of DetailsOverviewRowPresenter. If developer wants more customization beyond replacing 56 * detailed description , he/she should write a new presenter class for row object. 57 * </p> 58 */ 59public class DetailsOverviewRowPresenter extends RowPresenter { 60 61 private static final String TAG = "DetailsOverviewRowPresenter"; 62 private static final boolean DEBUG = false; 63 64 private static final int MORE_ACTIONS_FADE_MS = 100; 65 66 /** 67 * A ViewHolder for the DetailsOverviewRow. 68 */ 69 public final static class ViewHolder extends RowPresenter.ViewHolder { 70 final ViewGroup mOverviewView; 71 final ImageView mImageView; 72 final ViewGroup mRightPanel; 73 final FrameLayout mDetailsDescriptionFrame; 74 final HorizontalGridView mActionsRow; 75 public final Presenter.ViewHolder mDetailsDescriptionViewHolder; 76 int mNumItems; 77 boolean mShowMoreRight; 78 boolean mShowMoreLeft; 79 80 void bind(ItemBridgeAdapter bridgeAdapter) { 81 mNumItems = bridgeAdapter.getItemCount(); 82 bridgeAdapter.setAdapterListener(mAdapterListener); 83 84 mShowMoreRight = false; 85 mShowMoreLeft = true; 86 showMoreLeft(false); 87 } 88 89 final View.OnLayoutChangeListener mLayoutChangeListener = 90 new View.OnLayoutChangeListener() { 91 92 @Override 93 public void onLayoutChange(View v, int left, int top, int right, 94 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 95 if (DEBUG) Log.v(TAG, "onLayoutChange " + v); 96 checkFirstAndLastPosition(false); 97 } 98 }; 99 100 final ItemBridgeAdapter.AdapterListener mAdapterListener = 101 new ItemBridgeAdapter.AdapterListener() { 102 103 @Override 104 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 105 // Remove first to ensure we don't add ourselves more than once. 106 viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener); 107 viewHolder.itemView.addOnLayoutChangeListener(mLayoutChangeListener); 108 } 109 @Override 110 public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 111 viewHolder.itemView.removeOnLayoutChangeListener(mLayoutChangeListener); 112 checkFirstAndLastPosition(false); 113 } 114 }; 115 116 final RecyclerView.OnScrollListener mScrollListener = 117 new RecyclerView.OnScrollListener() { 118 119 @Override 120 public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 121 } 122 @Override 123 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 124 checkFirstAndLastPosition(true); 125 } 126 }; 127 128 private int getViewCenter(View view) { 129 return (view.getRight() - view.getLeft()) / 2; 130 } 131 132 private void checkFirstAndLastPosition(boolean fromScroll) { 133 RecyclerView.ViewHolder viewHolder; 134 135 viewHolder = mActionsRow.findViewHolderForPosition(mNumItems - 1); 136 boolean showRight = (viewHolder == null || 137 viewHolder.itemView.getRight() > mActionsRow.getWidth()); 138 139 viewHolder = mActionsRow.findViewHolderForPosition(0); 140 boolean showLeft = (viewHolder == null || viewHolder.itemView.getLeft() < 0); 141 142 if (DEBUG) Log.v(TAG, "checkFirstAndLast fromScroll " + fromScroll + 143 " showRight " + showRight + " showLeft " + showLeft); 144 145 showMoreRight(showRight); 146 showMoreLeft(showLeft); 147 } 148 149 private void showMoreLeft(boolean show) { 150 if (show != mShowMoreLeft) { 151 mActionsRow.setFadingLeftEdge(show); 152 mShowMoreLeft = show; 153 } 154 } 155 156 private void showMoreRight(boolean show) { 157 if (show != mShowMoreRight) { 158 mActionsRow.setFadingRightEdge(show); 159 mShowMoreRight = show; 160 } 161 } 162 163 /** 164 * Constructor for the ViewHolder. 165 * 166 * @param rootView The root View that this view holder will be attached 167 * to. 168 */ 169 public ViewHolder(View rootView, Presenter detailsPresenter) { 170 super(rootView); 171 mOverviewView = (ViewGroup) rootView.findViewById(R.id.details_overview); 172 mImageView = (ImageView) rootView.findViewById(R.id.details_overview_image); 173 mRightPanel = (ViewGroup) rootView.findViewById(R.id.details_overview_right_panel); 174 mDetailsDescriptionFrame = 175 (FrameLayout) mRightPanel.findViewById(R.id.details_overview_description); 176 mActionsRow = 177 (HorizontalGridView) mRightPanel.findViewById(R.id.details_overview_actions); 178 mActionsRow.setOnScrollListener(mScrollListener); 179 180 final int fadeLength = rootView.getResources().getDimensionPixelSize( 181 R.dimen.lb_details_overview_actions_fade_size); 182 mActionsRow.setFadingRightEdgeLength(fadeLength); 183 mActionsRow.setFadingLeftEdgeLength(fadeLength); 184 mDetailsDescriptionViewHolder = 185 detailsPresenter.onCreateViewHolder(mDetailsDescriptionFrame); 186 mDetailsDescriptionFrame.addView(mDetailsDescriptionViewHolder.view); 187 } 188 } 189 190 private final Presenter mDetailsPresenter; 191 private final ActionPresenterSelector mActionPresenterSelector; 192 private final ItemBridgeAdapter mActionBridgeAdapter; 193 private int mBackgroundColor = Color.TRANSPARENT; 194 private boolean mBackgroundColorSet; 195 private boolean mIsStyleLarge = true; 196 197 private DetailsOverviewSharedElementHelper mSharedElementHelper; 198 199 /** 200 * Constructor for a DetailsOverviewRowPresenter. 201 * 202 * @param detailsPresenter The {@link Presenter} used to render the detailed 203 * description of the row. 204 */ 205 public DetailsOverviewRowPresenter(Presenter detailsPresenter) { 206 setHeaderPresenter(null); 207 setSelectEffectEnabled(false); 208 mDetailsPresenter = detailsPresenter; 209 mActionPresenterSelector = new ActionPresenterSelector(); 210 mActionBridgeAdapter = new ItemBridgeAdapter(); 211 } 212 213 /** 214 * Sets the listener for Action click events. 215 */ 216 public void setOnActionClickedListener(OnActionClickedListener listener) { 217 mActionPresenterSelector.setOnActionClickedListener(listener); 218 } 219 220 /** 221 * Gets the listener for Action click events. 222 */ 223 public OnActionClickedListener getOnActionClickedListener() { 224 return mActionPresenterSelector.getOnActionClickedListener(); 225 } 226 227 /** 228 * Sets the background color. If not set, a default from the theme will be used. 229 */ 230 public void setBackgroundColor(int color) { 231 mBackgroundColor = color; 232 mBackgroundColorSet = true; 233 } 234 235 /** 236 * Returns the background color. If no background color was set, transparent 237 * is returned. 238 */ 239 public int getBackgroundColor() { 240 return mBackgroundColor; 241 } 242 243 /** 244 * Sets the layout style to be large or small. This affects the height of 245 * the overview, including the text description. The default is large. 246 */ 247 public void setStyleLarge(boolean large) { 248 mIsStyleLarge = large; 249 } 250 251 /** 252 * Returns true if the layout style is large. 253 */ 254 public boolean isStyleLarge() { 255 return mIsStyleLarge; 256 } 257 258 /** 259 * Set enter transition of target activity (typically a DetailActivity) to be 260 * transiting into overview row created by this presenter. 261 * <p> 262 * It assumes shared element passed from calling activity is an ImageView; 263 * the shared element transits to overview image on the left of detail 264 * overview row, while bounds of overview row grows and reveals text 265 * and buttons on the right. 266 * <p> 267 * The method must be invoked in target Activity's onCreate(). 268 */ 269 public final void setSharedElementEnterTransition(Activity activity, 270 String sharedElementName) { 271 if (mSharedElementHelper == null) { 272 mSharedElementHelper = new DetailsOverviewSharedElementHelper(); 273 } 274 mSharedElementHelper.setSharedElementEnterTransition(activity, sharedElementName); 275 } 276 277 private int getDefaultBackgroundColor(Context context) { 278 TypedValue outValue = new TypedValue(); 279 context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true); 280 return context.getResources().getColor(outValue.resourceId); 281 } 282 283 @Override 284 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 285 View v = LayoutInflater.from(parent.getContext()) 286 .inflate(R.layout.lb_details_overview, parent, false); 287 ViewHolder vh = new ViewHolder(v, mDetailsPresenter); 288 289 initDetailsOverview(vh); 290 291 return vh; 292 } 293 294 private int getCardHeight(Context context) { 295 int resId = mIsStyleLarge ? R.dimen.lb_details_overview_height_large : 296 R.dimen.lb_details_overview_height_small; 297 return context.getResources().getDimensionPixelSize(resId); 298 } 299 300 private void initDetailsOverview(ViewHolder vh) { 301 final View overview = vh.mOverviewView; 302 ViewGroup.LayoutParams lp = overview.getLayoutParams(); 303 lp.height = getCardHeight(overview.getContext()); 304 overview.setLayoutParams(lp); 305 306 ShadowHelper.getInstance().setZ(overview, 0f); 307 } 308 309 private static int getNonNegativeWidth(Drawable drawable) { 310 final int width = (drawable == null) ? 0 : drawable.getIntrinsicWidth(); 311 return (width > 0 ? width : 0); 312 } 313 314 private static int getNonNegativeHeight(Drawable drawable) { 315 final int height = (drawable == null) ? 0 : drawable.getIntrinsicHeight(); 316 return (height > 0 ? height : 0); 317 } 318 319 @Override 320 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 321 super.onBindRowViewHolder(holder, item); 322 323 DetailsOverviewRow row = (DetailsOverviewRow) item; 324 ViewHolder vh = (ViewHolder) holder; 325 326 ViewGroup.MarginLayoutParams layoutParams = 327 (ViewGroup.MarginLayoutParams) vh.mImageView.getLayoutParams(); 328 final int cardHeight = getCardHeight(vh.mImageView.getContext()); 329 final int verticalMargin = vh.mImageView.getResources().getDimensionPixelSize( 330 R.dimen.lb_details_overview_image_margin_vertical); 331 final int horizontalMargin = vh.mImageView.getResources().getDimensionPixelSize( 332 R.dimen.lb_details_overview_image_margin_horizontal); 333 final int drawableWidth = getNonNegativeWidth(row.getImageDrawable()); 334 final int drawableHeight = getNonNegativeHeight(row.getImageDrawable()); 335 336 boolean scaleImage = row.isImageScaleUpAllowed(); 337 boolean useMargin = false; 338 339 if (row.getImageDrawable() != null) { 340 boolean landscape = false; 341 342 // If large style and landscape image we always use margin. 343 if (drawableWidth > drawableHeight) { 344 landscape = true; 345 if (mIsStyleLarge) { 346 useMargin = true; 347 } 348 } 349 // If long dimension bigger than the card height we scale down. 350 if ((landscape && drawableWidth > cardHeight) || 351 (!landscape && drawableHeight > cardHeight)) { 352 scaleImage = true; 353 } 354 // If we're not scaling to fit the card height then we always use margin. 355 if (!scaleImage) { 356 useMargin = true; 357 } 358 // If using margin than may need to scale down. 359 if (useMargin && !scaleImage) { 360 if (landscape && drawableWidth > cardHeight - horizontalMargin) { 361 scaleImage = true; 362 } else if (!landscape && drawableHeight > cardHeight - 2 * verticalMargin) { 363 scaleImage = true; 364 } 365 } 366 } 367 368 final int bgColor = mBackgroundColorSet ? mBackgroundColor : 369 getDefaultBackgroundColor(vh.mOverviewView.getContext()); 370 371 if (useMargin) { 372 layoutParams.leftMargin = horizontalMargin; 373 layoutParams.topMargin = layoutParams.bottomMargin = verticalMargin; 374 RoundedRectHelper.getInstance().setRoundedRectBackground(vh.mOverviewView, bgColor); 375 vh.mRightPanel.setBackground(null); 376 vh.mImageView.setBackground(null); 377 } else { 378 layoutParams.leftMargin = layoutParams.topMargin = layoutParams.bottomMargin = 0; 379 vh.mRightPanel.setBackgroundColor(bgColor); 380 vh.mImageView.setBackgroundColor(bgColor); 381 RoundedRectHelper.getInstance().setRoundedRectBackground(vh.mOverviewView, 382 Color.TRANSPARENT); 383 } 384 if (scaleImage) { 385 vh.mImageView.setScaleType(ImageView.ScaleType.FIT_START); 386 vh.mImageView.setAdjustViewBounds(true); 387 vh.mImageView.setMaxWidth(cardHeight); 388 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 389 layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; 390 } else { 391 vh.mImageView.setScaleType(ImageView.ScaleType.CENTER); 392 vh.mImageView.setAdjustViewBounds(false); 393 layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; 394 // Limit width to the card height 395 layoutParams.width = Math.min(cardHeight, drawableWidth); 396 } 397 vh.mImageView.setLayoutParams(layoutParams); 398 vh.mImageView.setImageDrawable(row.getImageDrawable()); 399 400 mDetailsPresenter.onBindViewHolder(vh.mDetailsDescriptionViewHolder, row.getItem()); 401 402 mActionBridgeAdapter.clear(); 403 ArrayObjectAdapter aoa = new ArrayObjectAdapter(mActionPresenterSelector); 404 aoa.addAll(0, (Collection)row.getActions()); 405 406 mActionBridgeAdapter.setAdapter(aoa); 407 vh.mActionsRow.setAdapter(mActionBridgeAdapter); 408 409 vh.bind(mActionBridgeAdapter); 410 411 if (row.getImageDrawable() != null && mSharedElementHelper != null) { 412 mSharedElementHelper.onBindToDrawable(vh); 413 } 414 } 415 416 @Override 417 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 418 super.onUnbindRowViewHolder(holder); 419 420 ViewHolder vh = (ViewHolder) holder; 421 if (vh.mDetailsDescriptionViewHolder != null) { 422 mDetailsPresenter.onUnbindViewHolder(vh.mDetailsDescriptionViewHolder); 423 } 424 425 vh.mActionsRow.setAdapter(null); 426 } 427} 428