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