ListRowPresenter.java revision c4b1a043ab39a881b2a05d50e93c35e6f6ebfffb
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 ListRowView}. 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(), needsDefaultListSelectEffect()); 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 (needsDefaultListSelectEffect() || needsDefaultShadow()) { 130 rowViewHolder.mItemBridgeAdapter.setWrapper(mCardWrapper); 131 } 132 if (needsDefaultListSelectEffect()) { 133 ShadowOverlayContainer.prepareParentForShadow(rowViewHolder.mGridView); 134 ((ViewGroup) rowViewHolder.view).setClipChildren(false); 135 if (rowViewHolder.mContainerViewHolder != null) { 136 ((ViewGroup) rowViewHolder.mContainerViewHolder.view).setClipChildren(false); 137 } 138 } 139 FocusHighlightHelper.setupBrowseItemFocusHighlight(rowViewHolder.mItemBridgeAdapter, mZoomFactor); 140 rowViewHolder.mGridView.setOnChildSelectedListener( 141 new OnChildSelectedListener() { 142 @Override 143 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 144 selectChildView(rowViewHolder, view); 145 } 146 }); 147 rowViewHolder.mItemBridgeAdapter.setAdapterListener( 148 new ItemBridgeAdapter.AdapterListener() { 149 @Override 150 public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) { 151 // Only when having an OnItemClickListner, we will attach the OnClickListener. 152 if (getOnItemClickedListener() != null) { 153 viewHolder.mHolder.view.setOnClickListener(new View.OnClickListener() { 154 @Override 155 public void onClick(View v) { 156 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 157 rowViewHolder.mGridView.getChildViewHolder(viewHolder.itemView); 158 if (getOnItemClickedListener() != null) { 159 getOnItemClickedListener().onItemClicked(ibh.mItem, 160 (ListRow) rowViewHolder.mRow); 161 } 162 } 163 }); 164 } 165 } 166 167 @Override 168 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 169 if (viewHolder.itemView instanceof ShadowOverlayContainer) { 170 int dimmedColor = rowViewHolder.mColorDimmer.getPaint().getColor(); 171 ((ShadowOverlayContainer) viewHolder.itemView).setOverlayColor(dimmedColor); 172 } 173 } 174 }); 175 } 176 177 final boolean needsDefaultListSelectEffect() { 178 return isUsingDefaultListSelectEffect() && getSelectEffectEnabled(); 179 } 180 181 /** 182 * Set {@link PresenterSelector} used for showing a select object in a hover card. 183 */ 184 public final void setHoverCardPresenterSelector(PresenterSelector selector) { 185 mHoverCardPresenterSelector = selector; 186 } 187 188 /** 189 * Get {@link PresenterSelector} used for showing a select object in a hover card. 190 */ 191 public final PresenterSelector getHoverCardPresenterSelector() { 192 return mHoverCardPresenterSelector; 193 } 194 195 /* 196 * Perform operations when a child of horizontal grid view is selected. 197 */ 198 private void selectChildView(ViewHolder rowViewHolder, View view) { 199 ItemBridgeAdapter.ViewHolder ibh = null; 200 if (view != null) { 201 ibh = (ItemBridgeAdapter.ViewHolder) 202 rowViewHolder.mGridView.getChildViewHolder(view); 203 } 204 if (view == null) { 205 if (mHoverCardPresenterSelector != null) { 206 rowViewHolder.mHoverCardViewSwitcher.unselect(); 207 } 208 if (getOnItemSelectedListener() != null) { 209 getOnItemSelectedListener().onItemSelected(null, rowViewHolder.mRow); 210 } 211 } else if (rowViewHolder.mExpanded && rowViewHolder.mSelected) { 212 if (mHoverCardPresenterSelector != null) { 213 rowViewHolder.mHoverCardViewSwitcher.select(rowViewHolder.mGridView, view, 214 ibh.mItem); 215 } 216 if (getOnItemSelectedListener() != null) { 217 getOnItemSelectedListener().onItemSelected(ibh.mItem, rowViewHolder.mRow); 218 } 219 } 220 } 221 222 @Override 223 protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) { 224 ListRowView rowView = new ListRowView(parent.getContext()); 225 return new ViewHolder(rowView, rowView.getGridView(), this); 226 } 227 228 @Override 229 protected void onRowViewSelected(RowPresenter.ViewHolder holder, boolean selected) { 230 updateFooterViewSwitcher((ViewHolder) holder); 231 updateInitialChildSelection((ViewHolder) holder); 232 } 233 234 /* 235 * Show or hide hover card when row selection or expanded state is changed. 236 */ 237 private void updateFooterViewSwitcher(ViewHolder vh) { 238 if (vh.mExpanded && vh.mSelected) { 239 if (mHoverCardPresenterSelector != null) { 240 vh.mHoverCardViewSwitcher.init((ViewGroup) vh.view, 241 mHoverCardPresenterSelector); 242 } 243 } else { 244 if (mHoverCardPresenterSelector != null) { 245 vh.mHoverCardViewSwitcher.clear(); 246 } 247 } 248 } 249 250 /* 251 * Make initial child selection when row selection state is changed. 252 */ 253 private void updateInitialChildSelection(ViewHolder vh) { 254 if (vh.mExpanded && vh.mSelected) { 255 ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder) 256 vh.mGridView.findViewHolderForPosition( 257 vh.mGridView.getSelectedPosition()); 258 selectChildView(vh, ibh == null ? null : ibh.itemView); 259 } else { 260 selectChildView(vh, null); 261 } 262 } 263 264 @Override 265 protected void onRowViewExpanded(RowPresenter.ViewHolder holder, boolean expanded) { 266 super.onRowViewExpanded(holder, expanded); 267 ViewHolder vh = (ViewHolder) holder; 268 vh.mGridView.setClipToPadding(!expanded); 269 vh.mGridView.invalidate(); 270 updateFooterViewSwitcher(vh); 271 } 272 273 @Override 274 protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) { 275 super.onBindRowViewHolder(holder, item); 276 ViewHolder vh = (ViewHolder) holder; 277 ListRow rowItem = (ListRow) item; 278 vh.mItemBridgeAdapter.clear(); 279 vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter()); 280 vh.mGridView.setAdapter(vh.mItemBridgeAdapter); 281 } 282 283 @Override 284 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) { 285 ((ViewHolder) holder).mGridView.setAdapter(null); 286 super.onUnbindRowViewHolder(holder); 287 } 288 289 /** 290 * ListRowPresenter overrides the default select effect of {@link RowPresenter} 291 * and return false. 292 */ 293 @Override 294 public final boolean isUsingDefaultSelectEffect() { 295 return false; 296 } 297 298 /** 299 * Returns true so that default select effect is applied to each individual 300 * child of {@link HorizontalGridView}. Subclass may return false to disable 301 * the default implementation. 302 * @see #onSelectLevelChanged(RowPresenter.ViewHolder) 303 */ 304 public boolean isUsingDefaultListSelectEffect() { 305 return true; 306 } 307 308 /** 309 * Returns true if SDK >= 18, where default shadow 310 * is applied to each individual child of {@link HorizontalGridView}. 311 * Subclass may return false to disable. 312 */ 313 public boolean isUsingDefaultShadow() { 314 return ShadowOverlayContainer.supportsShadow(); 315 } 316 317 /** 318 * Enable or disable child shadow. 319 * This is not only for enable/disable default shadow implementation but also subclass must 320 * respect this flag. 321 */ 322 public final void setShadowEnabled(boolean enabled) { 323 mShadowEnabled = enabled; 324 } 325 326 /** 327 * Returns true if child shadow is enabled. 328 * This is not only for enable/disable default shadow implementation but also subclass must 329 * respect this flag. 330 */ 331 public final boolean getShadowEnabled() { 332 return mShadowEnabled; 333 } 334 335 final boolean needsDefaultShadow() { 336 return isUsingDefaultShadow() && getShadowEnabled(); 337 } 338 339 @Override 340 public boolean canDrawOutOfBounds() { 341 return needsDefaultShadow(); 342 } 343 344 /** 345 * Applies select level to header and draw a default color dim over each child 346 * of {@link HorizontalGridView}. 347 * <p> 348 * Subclass may override this method. A subclass 349 * needs to call super.onSelectLevelChanged() for applying header select level 350 * and optionally applying a default select level to each child view of 351 * {@link HorizontalGridView} if {@link #isUsingDefaultListSelectEffect()} 352 * is true. Subclass may override {@link #isUsingDefaultListSelectEffect()} to return 353 * false and deal with the individual item select level by itself. 354 * </p> 355 */ 356 @Override 357 protected void onSelectLevelChanged(RowPresenter.ViewHolder holder) { 358 super.onSelectLevelChanged(holder); 359 if (needsDefaultListSelectEffect()) { 360 ViewHolder vh = (ViewHolder) holder; 361 vh.mColorDimmer.setActiveLevel(holder.mSelectLevel); 362 int dimmedColor = vh.mColorDimmer.getPaint().getColor(); 363 for (int i = 0, count = vh.mGridView.getChildCount(); i < count; i++) { 364 ShadowOverlayContainer wrapper = (ShadowOverlayContainer) vh.mGridView.getChildAt(i); 365 wrapper.setOverlayColor(dimmedColor); 366 } 367 } 368 } 369 370} 371