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