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