ListRowPresenter.java revision 4df06cbe8f6dd087fc8f1068faa77923cb297365
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 java.util.ArrayList; 17 18import android.content.Context; 19import android.content.res.TypedArray; 20import android.graphics.Canvas; 21import android.support.v17.leanback.R; 22import android.support.v17.leanback.graphics.ColorOverlayDimmer; 23import android.support.v17.leanback.widget.Presenter.ViewHolder; 24import android.support.v7.widget.RecyclerView; 25import android.util.AttributeSet; 26import android.view.View; 27import android.view.ViewGroup; 28import android.view.ViewGroup.LayoutParams; 29import android.widget.FrameLayout; 30 31/** 32 * ListRowPresenter renders {@link ListRow} using a 33 * {@link HorizontalGridView} hosted in a {@link ListRowView}. 34 * 35 * <h3>Hover card</h3> 36 * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to 37 * display a view for the currently focused list item below the rendered 38 * list. This view is known as a hover card. 39 * 40 * <h3>Selection animation</h3> 41 * ListRowPresenter disables {@link RowPresenter}'s default dimming effect and draw 42 * a dim overlay on top of each individual child items. Subclass may override and disable 43 * {@link #isUsingDefaultListSelectEffect()} and write its own dim effect in 44 * {@link #onSelectLevelChanged(RowPresenter.ViewHolder)}. 45 * 46 * <h3>Shadow</h3> 47 * ListRowPresenter applies a default shadow to child of each view. Call 48 * {@link #setShadowEnabled(boolean)} to disable shadow. Subclass may override and return 49 * false in {@link #isUsingDefaultShadow()} and replace with its own shadow implementation. 50 */ 51public class ListRowPresenter extends RowPresenter { 52 53 private static final String TAG = "ListRowPresenter"; 54 private static final boolean DEBUG = false; 55 56 public static class ViewHolder extends RowPresenter.ViewHolder { 57 final ListRowPresenter mListRowPresenter; 58 final HorizontalGridView mGridView; 59 final ItemBridgeAdapter mItemBridgeAdapter = new ItemBridgeAdapter(); 60 final HorizontalHoverCardSwitcher mHoverCardViewSwitcher = new HorizontalHoverCardSwitcher(); 61 final ColorOverlayDimmer mColorDimmer; 62 63 public ViewHolder(View rootView, HorizontalGridView gridView, ListRowPresenter p) { 64 super(rootView); 65 mGridView = gridView; 66 mListRowPresenter = p; 67 mColorDimmer = ColorOverlayDimmer.createDefault(rootView.getContext()); 68 } 69 70 public final ListRowPresenter getListRowPresenter() { 71 return mListRowPresenter; 72 } 73 74 public final HorizontalGridView getGridView() { 75 return mGridView; 76 } 77 } 78 79 private PresenterSelector mHoverCardPresenterSelector; 80 private int mZoomFactor; 81 private boolean mShadowEnabled = true; 82 private int mBrowseRowsFadingEdgeLength = -1; 83 84 /** 85 * Constructs a ListRowPresenter with defaults. 86 * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming. 87 */ 88 public ListRowPresenter() { 89 this(FocusHighlight.ZOOM_FACTOR_MEDIUM); 90 } 91 92 /** 93 * Constructs a ListRowPresenter with the given parameters. 94 * 95 * @param zoomFactor Controls the zoom factor used when an item view is focused. One of 96 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 97 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 98 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 99 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 100 */ 101 public ListRowPresenter(int zoomFactor) { 102 mZoomFactor = zoomFactor; 103 } 104 105 /** 106 * Returns the zoom factor used for focus highlighting. 107 */ 108 public final int getZoomFactor() { 109 return mZoomFactor; 110 } 111 112 private ItemBridgeAdapter.Wrapper mCardWrapper = new ItemBridgeAdapter.Wrapper() { 113 @Override 114 public View createWrapper(View root) { 115 ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext()); 116 wrapper.setLayoutParams( 117 new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 118 wrapper.initialize(needsDefaultShadow(), needsDefaultListSelectEffect()); 119 return wrapper; 120 } 121 @Override 122 public void wrap(View wrapper, View wrapped) { 123 ((ShadowOverlayContainer) wrapper).wrap(wrapped); 124 } 125 }; 126 127 @Override 128 protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) { 129 super.initializeRowViewHolder(holder); 130 final ViewHolder rowViewHolder = (ViewHolder) holder; 131 if (needsDefaultListSelectEffect() || needsDefaultShadow()) { 132 rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper); 133 } 134 if (needsDefaultListSelectEffect()) { 135 ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView); 136 ((ViewGroup) rowViewHolder.view).setClipChildren(false); 137 if (rowViewHolder.mContainerViewHolder != null) { 138 ((ViewGroup) rowViewHolder.mContainerViewHolder.view).setClipChildren(false); 139 } 140 } 141 FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, mZoomFactor); 142 rowViewHolder.mGridView.setOnChildSelectedListener( 143 new OnChildSelectedListener() { 144 @Override 145 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 146 selectChildView(rowViewHolder, view); 147 } 148 }); 149 rowViewHolder.mItemBridgeAdapter.setAdapterListener( 150 new ItemBridgeAdapter.AdapterListener() { 151 @Override 152 public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) { 153 // Only when having an OnItemClickListner, we will attach the OnClickListener. 154 if (getOnItemClickedListener() != null) { 155 viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() { 156 @Override 157 public void onClick(View v) { 158 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 159 rowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView); 160 if (getOnItemClickedListener() != null) { 161 getOnItemClickedListener().onItemClicked(ibh.mItem, 162 (ListRow) rowViewHolder.mRow); 163 } 164 } 165 }); 166 } 167 } 168 169 @Override 170 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 171 if (viewHolder.itemView instanceof ShadowOverlayContainer) { 172 int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor(); 173 ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor); 174 } 175 viewHolder.itemView.setActivated(rowViewHolder.mExpanded); 176 } 177 }); 178 } 179 180 final boolean needsDefaultListSelectEffect() { 181 return isUsingDefaultListSelectEffect() && getSelectEffectEnabled(); 182 } 183 184 /** 185 * Set {@link PresenterSelector} used for showing a select object in a hover card. 186 */ 187 public final void setHoverCardPresenterSelector(PresenterSelector selector) { 188 mHoverCardPresenterSelector = selector; 189 } 190 191 /** 192 * Get {@link PresenterSelector} used for showing a select object in a hover card. 193 */ 194 public final PresenterSelector getHoverCardPresenterSelector() { 195 return mHoverCardPresenterSelector; 196 } 197 198 /* 199 * Perform operations when a child of horizontal grid view is selected. 200 */ 201 private void selectChildView(ViewHolder rowViewHolder, View view) { 202 ItemBridgeAdapter.ViewHolder ibh = null; 203 if (view != null) { 204 ibh = (ItemBridgeAdapter.ViewHolder) 205 rowViewHolder.mGridView.getChildViewHolder(view); 206 } 207 if (view == null) { 208 if (mHoverCardPresenterSelector != null) { 209 rowViewHolder.mHoverCardViewSwitcher.unselect(); 210 } 211 if (getOnItemSelectedListener() != null) { 212 getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow); 213 } 214 } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) { 215 if (mHoverCardPresenterSelector != null) { 216 rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view, 217 ibh.mItem); 218 } 219 if (getOnItemSelectedListener() != null) { 220 getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow); 221 } 222 } 223 } 224 225 @Override 226 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 227 ListRowView rowView = new ListRowView(parent.getContext()); 228 setupFadingEffect(rowView); 229 return new ViewHolder(rowView, rowView.getGridView(), this); 230 } 231 232 @Override 233 protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) { 234 updateFooterViewSwitcher((ViewHolder) holder); 235 } 236 237 /* 238 * Show or hide hover card when row selection or expanded state is changed. 239 */ 240 private void updateFooterViewSwitcher(ViewHolder vh) { 241 if (vh.mExpanded && vh.mSelected) { 242 if (mHoverCardPresenterSelector != null) { 243 vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view, 244 mHoverCardPresenterSelector); 245 } 246 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 247 vh.mGridView.findViewHolderForPosition( 248 vh.mGridView.getSelectedPosition()); 249 selectChildView(vh, ibh == null ? null : ibh.itemView); 250 } else { 251 if (mHoverCardPresenterSelector != null) { 252 vh.mHoverCardViewSwitcher.clear(); 253 } 254 } 255 } 256 257 private void setupFadingEffect(ListRowView rowView) { 258 // content is completely faded at 1/2 padding of left, fading length is 1/2 of padding. 259 HorizontalGridView gridView = rowView.getGridView(); 260 if (mBrowseRowsFadingEdgeLength < 0) { 261 TypedArray ta = gridView.getContext() 262 .obtainStyledAttributes(R.styleable.LeanbackTheme); 263 mBrowseRowsFadingEdgeLength = (int) ta.getDimension( 264 R.styleable.LeanbackTheme_browseRowsFadingEdgeLength, 0); 265 ta.recycle(); 266 } 267 gridView.setFadingLeftEdgeLength(mBrowseRowsFadingEdgeLength); 268 } 269 270 @Override 271 protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) { 272 super.onRowViewExpanded(holder, expanded); 273 ViewHolder vh = (ViewHolder) holder; 274 vh.getGridView().setFadingLeftEdge(!expanded); 275 updateFooterViewSwitcher(vh); 276 } 277 278 @Override 279 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 280 super.onBindRowViewHolder(holder, item); 281 ViewHolder vh = (ViewHolder) holder; 282 ListRow rowItem = (ListRow) item; 283 vh.mItemBridgeAdapter.clear(); 284 vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter()); 285 vh.mGridView.setAdapter(vh.mItemBridgeAdapter); 286 } 287 288 @Override 289 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 290 ((ViewHolder) holder).mGridView.setAdapter(null); 291 super.onUnbindRowViewHolder(holder); 292 } 293 294 /** 295 * ListRowPresenter overrides the default select effect of {@link RowPresenter} 296 * and return false. 297 */ 298 @Override 299 public final boolean isUsingDefaultSelectEffect() { 300 return false; 301 } 302 303 /** 304 * Returns true so that default select effect is applied to each individual 305 * child of {@link HorizontalGridView}. Subclass may return false to disable 306 * the default implementation. 307 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 308 */ 309 public boolean isUsingDefaultListSelectEffect() { 310 return true; 311 } 312 313 /** 314 * Returns true if SDK >= 18, where default shadow 315 * is applied to each individual child of {@link HorizontalGridView}. 316 * Subclass may return false to disable. 317 */ 318 public boolean isUsingDefaultShadow() { 319 return ShadowOverlayContainer.supportsShadow(); 320 } 321 322 /** 323 * Enable or disable child shadow. 324 * This is not only for enable/disable default shadow implementation but also subclass must 325 * respect this flag. 326 */ 327 public final void setShadowEnabled(boolean enabled) { 328 mShadowEnabled = enabled; 329 } 330 331 /** 332 * Returns true if child shadow is enabled. 333 * This is not only for enable/disable default shadow implementation but also subclass must 334 * respect this flag. 335 */ 336 public final boolean getShadowEnabled() { 337 return mShadowEnabled; 338 } 339 340 final boolean needsDefaultShadow() { 341 return isUsingDefaultShadow() && getShadowEnabled(); 342 } 343 344 @Override 345 public boolean canDrawOutOfBounds() { 346 return needsDefaultShadow(); 347 } 348 349 /** 350 * Applies select level to header and draw a default color dim over each child 351 * of {@link HorizontalGridView}. 352 * <p> 353 * Subclass may override this method. A subclass 354 * needs to call super.onSelectLevelChanged() for applying header select level 355 * and optionally applying a default select level to each child view of 356 * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()} 357 * is true. Subclass may override {@link #isUsingDefaultListSelectEffect()} to return 358 * false and deal with the individual item select level by itself. 359 * </p> 360 */ 361 @Override 362 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 363 super.onSelectLevelChanged(holder); 364 if (needsDefaultListSelectEffect()) { 365 ViewHolder vh = (ViewHolder) holder; 366 vh.mColorDimmer.setActiveLevel(holder.mSelectLevel); 367 int dimmedColor = vh.mColorDimmer.getPaint().getColor(); 368 for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) { 369 ShadowOverlayContainer wrapper = (ShadowOverlayContainer) vh.mGridView.getChildAt(i); 370 wrapper.setOverlayColor(dimmedColor); 371 } 372 if (vh.mGridView.getFadingLeftEdge()) { 373 vh.mGridView.invalidate(); 374 } 375 } 376 } 377 378} 379