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