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