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