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