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