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