ListRowPresenter.java revision 29246e5ca814f17dcf368eeacd1b44a329592ae0
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 onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 179 if (viewHolder.itemView instanceof ListRowCardWrapper) { 180 int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor(); 181 ((ListRowCardWrapper) viewHolder.itemView) 182 .mColorDimOverlay.setBackgroundColor(dimmedColor); 183 } 184 } 185 }); 186 } 187 188 final boolean needsDefaultSelectEffect() { 189 return isUsingDefaultListSelectEffect() && getSelectEffectEnabled(); 190 } 191 192 /** 193 * Set {@link PresenterSelector} used for showing a select object in a hover card. 194 */ 195 public final void setHoverCardPresenterSelector(PresenterSelector selector) { 196 mHoverCardPresenterSelector = selector; 197 } 198 199 /** 200 * Get {@link PresenterSelector} used for showing a select object in a hover card. 201 */ 202 public final PresenterSelector getHoverCardPresenterSelector() { 203 return mHoverCardPresenterSelector; 204 } 205 206 /* 207 * Perform operations when a child of horizontal grid view is selected. 208 */ 209 private void selectChildView(ViewHolder rowViewHolder, View view) { 210 ItemBridgeAdapter.ViewHolder ibh = null; 211 if (view != null) { 212 ibh = (ItemBridgeAdapter.ViewHolder) 213 rowViewHolder.mGridView.getChildViewHolder(view); 214 } 215 if (view == null) { 216 if (mHoverCardPresenterSelector != null) { 217 rowViewHolder.mHoverCardViewSwitcher.unselect(); 218 } 219 if (getOnItemSelectedListener() != null) { 220 getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow); 221 } 222 } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) { 223 if (mHoverCardPresenterSelector != null) { 224 rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view, 225 ibh.mItem); 226 } 227 if (getOnItemSelectedListener() != null) { 228 getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow); 229 } 230 } 231 } 232 233 @Override 234 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 235 BrowseRowView rowView = new BrowseRowView(parent.getContext()); 236 return new ViewHolder(rowView, rowView.getGridView(), this); 237 } 238 239 @Override 240 protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) { 241 updateFooterViewSwitcher((ViewHolder) holder); 242 updateInitialChildSelection((ViewHolder) holder); 243 } 244 245 /* 246 * Show or hide hover card when row selection or expanded state is changed. 247 */ 248 private void updateFooterViewSwitcher(ViewHolder vh) { 249 if (vh.mExpanded && vh.mSelected) { 250 if (mHoverCardPresenterSelector != null) { 251 vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view, 252 mHoverCardPresenterSelector); 253 } 254 } else { 255 if (mHoverCardPresenterSelector != null) { 256 vh.mHoverCardViewSwitcher.clear(); 257 } 258 } 259 } 260 261 /* 262 * Make initial child selection when row selection state is changed. 263 */ 264 private void updateInitialChildSelection(ViewHolder vh) { 265 if (vh.mExpanded && vh.mSelected) { 266 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 267 vh.mGridView.findViewHolderForPosition( 268 vh.mGridView.getSelectedPosition()); 269 selectChildView(vh, ibh == null ? null : ibh.itemView); 270 } else { 271 selectChildView(vh, null); 272 } 273 } 274 275 @Override 276 protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) { 277 super.onRowViewExpanded(holder, expanded); 278 ViewHolder vh = (ViewHolder) holder; 279 vh.mGridView.setClipToPadding(!expanded); 280 vh.mGridView.invalidate(); 281 updateFooterViewSwitcher(vh); 282 } 283 284 @Override 285 public void onBindViewHolder(Presenter.ViewHolder holder, Object item) { 286 super.onBindViewHolder(holder, item); 287 ViewHolder vh = (ViewHolder)holder; 288 ListRow rowItem = (ListRow) item; 289 vh.mItemBridgeAdapter.clear(); 290 vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter()); 291 vh.mGridView.setAdapter(vh.mItemBridgeAdapter); 292 } 293 294 @Override 295 public void onUnbindViewHolder(Presenter.ViewHolder holder) { 296 ViewHolder vh = (ViewHolder)holder; 297 vh.mGridView.setAdapter(null); 298 super.onUnbindViewHolder(holder); 299 } 300 301 /** 302 * ListRowPresenter overrides the default select effect of {@link RowPresenter} 303 * and return false. 304 */ 305 @Override 306 public final boolean isUsingDefaultSelectEffect() { 307 return false; 308 } 309 310 /** 311 * Returns true so that default select effect is applied to each individual 312 * child of {@link HorizontalGridView}. Subclass may return false to disable 313 * the default implementation. 314 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 315 */ 316 public boolean isUsingDefaultListSelectEffect() { 317 return true; 318 } 319 320 /** 321 * Returns true if opticalBounds is supported (SDK >= 18) so that default shadow 322 * is applied to each individual child of {@link HorizontalGridView}. 323 * Subclass may return false to disable. 324 */ 325 public boolean isUsingDefaultShadow() { 326 return OpticalBoundsHelper.systemSupportsOpticalBounds(); 327 } 328 329 /** 330 * Enable or disable child shadow. 331 * This is not only for enable/disable default shadow implementation but also subclass must 332 * respect this flag. 333 */ 334 public final void setShadowEnabled(boolean enabled) { 335 mShadowEnabled = enabled; 336 } 337 338 /** 339 * Returns true if child shadow is enabled. 340 * This is not only for enable/disable default shadow implementation but also subclass must 341 * respect this flag. 342 */ 343 public final boolean getShadowEnabled() { 344 return mShadowEnabled; 345 } 346 347 final boolean needsDefaultShadow() { 348 return isUsingDefaultShadow() && getShadowEnabled(); 349 } 350 351 @Override 352 public boolean canDrawOutOfBounds() { 353 return needsDefaultShadow(); 354 } 355 356 /** 357 * Applies select level to header and draw a default color dim over each child 358 * of {@link HorizontalGridView}. 359 * <p> 360 * Subclass may override this method. A subclass 361 * needs to call super.onSelectLevelChanged() for applying header select level 362 * and optionally applying a default select level to each child view of 363 * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()} 364 * is true. Subclass may override {@link #isUsingDefaultListSelectEffect()} to return 365 * false and deal with the individual item select level by itself. 366 * </p> 367 */ 368 @Override 369 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 370 super.onSelectLevelChanged(holder); 371 if (needsDefaultSelectEffect()) { 372 ViewHolder vh = (ViewHolder) holder; 373 vh.mColorDimmer.setActiveLevel(holder.mSelectLevel); 374 int dimmedColor = vh.mColorDimmer.getPaint().getColor(); 375 for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) { 376 ListRowCardWrapper wrapper = (ListRowCardWrapper) vh.mGridView.getChildAt(i); 377 wrapper.mColorDimOverlay.setBackgroundColor(dimmedColor); 378 } 379 } 380 } 381 382} 383