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 */ 14 package android.support.v17.leanback.widget; 15 16 import android.content.Context; 17 import android.support.v17.leanback.R; 18 import android.support.v17.leanback.system.Settings; 19 import android.support.v17.leanback.transition.TransitionHelper; 20 import android.util.Log; 21 import android.view.LayoutInflater; 22 import android.view.View; 23 import android.view.ViewGroup; 24 25 /** 26 * A presenter that renders objects in a {@link VerticalGridView}. 27 */ 28 public class VerticalGridPresenter extends Presenter { 29 private static final String TAG = "GridPresenter"; 30 private static final boolean DEBUG = false; 31 32 class VerticalGridItemBridgeAdapter extends ItemBridgeAdapter { 33 @Override onCreate(ItemBridgeAdapter.ViewHolder viewHolder)34 protected void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) { 35 if (viewHolder.itemView instanceof ViewGroup) { 36 TransitionHelper.setTransitionGroup((ViewGroup) viewHolder.itemView, 37 true); 38 } 39 if (mShadowOverlayHelper != null) { 40 mShadowOverlayHelper.onViewCreated(viewHolder.itemView); 41 } 42 } 43 44 @Override onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder)45 public void onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder) { 46 // Only when having an OnItemClickListener, we attach the OnClickListener. 47 if (getOnItemViewClickedListener() != null) { 48 final View itemView = itemViewHolder.mHolder.view; 49 itemView.setOnClickListener(new View.OnClickListener() { 50 @Override 51 public void onClick(View view) { 52 if (getOnItemViewClickedListener() != null) { 53 // Row is always null 54 getOnItemViewClickedListener().onItemClicked( 55 itemViewHolder.mHolder, itemViewHolder.mItem, null, null); 56 } 57 } 58 }); 59 } 60 } 61 62 @Override onUnbind(ItemBridgeAdapter.ViewHolder viewHolder)63 public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) { 64 if (getOnItemViewClickedListener() != null) { 65 viewHolder.mHolder.view.setOnClickListener(null); 66 } 67 } 68 69 @Override onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder)70 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 71 viewHolder.itemView.setActivated(true); 72 } 73 } 74 75 /** 76 * ViewHolder for the VerticalGridPresenter. 77 */ 78 public static class ViewHolder extends Presenter.ViewHolder { 79 ItemBridgeAdapter mItemBridgeAdapter; 80 final VerticalGridView mGridView; 81 boolean mInitialized; 82 ViewHolder(VerticalGridView view)83 public ViewHolder(VerticalGridView view) { 84 super(view); 85 mGridView = view; 86 } 87 getGridView()88 public VerticalGridView getGridView() { 89 return mGridView; 90 } 91 } 92 93 private int mNumColumns = -1; 94 private int mFocusZoomFactor; 95 private boolean mUseFocusDimmer; 96 private boolean mShadowEnabled = true; 97 private boolean mKeepChildForeground = true; 98 private OnItemViewSelectedListener mOnItemViewSelectedListener; 99 private OnItemViewClickedListener mOnItemViewClickedListener; 100 private boolean mRoundedCornersEnabled = true; 101 ShadowOverlayHelper mShadowOverlayHelper; 102 private ItemBridgeAdapter.Wrapper mShadowOverlayWrapper; 103 104 /** 105 * Constructs a VerticalGridPresenter with defaults. 106 * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and 107 * enabled dimming on focus. 108 */ VerticalGridPresenter()109 public VerticalGridPresenter() { 110 this(FocusHighlight.ZOOM_FACTOR_LARGE); 111 } 112 113 /** 114 * Constructs a VerticalGridPresenter with the given parameters. 115 * 116 * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of 117 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 118 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 119 * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}, 120 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 121 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 122 * enabled dimming on focus. 123 */ VerticalGridPresenter(int focusZoomFactor)124 public VerticalGridPresenter(int focusZoomFactor) { 125 this(focusZoomFactor, true); 126 } 127 128 /** 129 * Constructs a VerticalGridPresenter with the given parameters. 130 * 131 * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of 132 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 133 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 134 * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}, 135 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 136 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 137 * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer 138 */ VerticalGridPresenter(int focusZoomFactor, boolean useFocusDimmer)139 public VerticalGridPresenter(int focusZoomFactor, boolean useFocusDimmer) { 140 mFocusZoomFactor = focusZoomFactor; 141 mUseFocusDimmer = useFocusDimmer; 142 } 143 144 /** 145 * Sets the number of columns in the vertical grid. 146 */ setNumberOfColumns(int numColumns)147 public void setNumberOfColumns(int numColumns) { 148 if (numColumns < 0) { 149 throw new IllegalArgumentException("Invalid number of columns"); 150 } 151 if (mNumColumns != numColumns) { 152 mNumColumns = numColumns; 153 } 154 } 155 156 /** 157 * Returns the number of columns in the vertical grid. 158 */ getNumberOfColumns()159 public int getNumberOfColumns() { 160 return mNumColumns; 161 } 162 163 /** 164 * Enable or disable child shadow. 165 * This is not only for enable/disable default shadow implementation but also subclass must 166 * respect this flag. 167 */ setShadowEnabled(boolean enabled)168 public final void setShadowEnabled(boolean enabled) { 169 mShadowEnabled = enabled; 170 } 171 172 /** 173 * Returns true if child shadow is enabled. 174 * This is not only for enable/disable default shadow implementation but also subclass must 175 * respect this flag. 176 */ getShadowEnabled()177 public final boolean getShadowEnabled() { 178 return mShadowEnabled; 179 } 180 181 /** 182 * Default implementation returns true if SDK version >= 21, shadow (either static or z-order 183 * based) will be applied to each individual child of {@link VerticalGridView}. 184 * Subclass may return false to disable default implementation of shadow and provide its own. 185 */ isUsingDefaultShadow()186 public boolean isUsingDefaultShadow() { 187 return ShadowOverlayHelper.supportsShadow(); 188 } 189 190 /** 191 * Enables or disabled rounded corners on children of this row. 192 * Supported on Android SDK >= L. 193 */ enableChildRoundedCorners(boolean enable)194 public final void enableChildRoundedCorners(boolean enable) { 195 mRoundedCornersEnabled = enable; 196 } 197 198 /** 199 * Returns true if rounded corners are enabled for children of this row. 200 */ areChildRoundedCornersEnabled()201 public final boolean areChildRoundedCornersEnabled() { 202 return mRoundedCornersEnabled; 203 } 204 205 /** 206 * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled 207 * on each child of vertical grid. If subclass returns false in isUsingDefaultShadow() 208 * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false. 209 */ isUsingZOrder(Context context)210 public boolean isUsingZOrder(Context context) { 211 return !Settings.getInstance(context).preferStaticShadows(); 212 } 213 needsDefaultShadow()214 final boolean needsDefaultShadow() { 215 return isUsingDefaultShadow() && getShadowEnabled(); 216 } 217 218 /** 219 * Returns the zoom factor used for focus highlighting. 220 */ getFocusZoomFactor()221 public final int getFocusZoomFactor() { 222 return mFocusZoomFactor; 223 } 224 225 /** 226 * Returns true if the focus dimmer is used for focus highlighting; false otherwise. 227 */ isFocusDimmerUsed()228 public final boolean isFocusDimmerUsed() { 229 return mUseFocusDimmer; 230 } 231 232 @Override onCreateViewHolder(ViewGroup parent)233 public final ViewHolder onCreateViewHolder(ViewGroup parent) { 234 ViewHolder vh = createGridViewHolder(parent); 235 vh.mInitialized = false; 236 vh.mItemBridgeAdapter = new VerticalGridItemBridgeAdapter(); 237 initializeGridViewHolder(vh); 238 if (!vh.mInitialized) { 239 throw new RuntimeException("super.initializeGridViewHolder() must be called"); 240 } 241 return vh; 242 } 243 244 /** 245 * Subclass may override this to inflate a different layout. 246 */ createGridViewHolder(ViewGroup parent)247 protected ViewHolder createGridViewHolder(ViewGroup parent) { 248 View root = LayoutInflater.from(parent.getContext()).inflate( 249 R.layout.lb_vertical_grid, parent, false); 250 return new ViewHolder((VerticalGridView) root.findViewById(R.id.browse_grid)); 251 } 252 253 /** 254 * Called after a {@link VerticalGridPresenter.ViewHolder} is created. 255 * Subclasses may override this method and start by calling 256 * super.initializeGridViewHolder(ViewHolder). 257 * 258 * @param vh The ViewHolder to initialize for the vertical grid. 259 */ initializeGridViewHolder(ViewHolder vh)260 protected void initializeGridViewHolder(ViewHolder vh) { 261 if (mNumColumns == -1) { 262 throw new IllegalStateException("Number of columns must be set"); 263 } 264 if (DEBUG) Log.v(TAG, "mNumColumns " + mNumColumns); 265 vh.getGridView().setNumColumns(mNumColumns); 266 vh.mInitialized = true; 267 268 Context context = vh.mGridView.getContext(); 269 if (mShadowOverlayHelper == null) { 270 mShadowOverlayHelper = new ShadowOverlayHelper.Builder() 271 .needsOverlay(mUseFocusDimmer) 272 .needsShadow(needsDefaultShadow()) 273 .needsRoundedCorner(areChildRoundedCornersEnabled()) 274 .preferZOrder(isUsingZOrder(context)) 275 .keepForegroundDrawable(mKeepChildForeground) 276 .options(createShadowOverlayOptions()) 277 .build(context); 278 if (mShadowOverlayHelper.needsWrapper()) { 279 mShadowOverlayWrapper = new ItemBridgeAdapterShadowOverlayWrapper( 280 mShadowOverlayHelper); 281 } 282 } 283 vh.mItemBridgeAdapter.setWrapper(mShadowOverlayWrapper); 284 mShadowOverlayHelper.prepareParentForShadow(vh.mGridView); 285 vh.getGridView().setFocusDrawingOrderEnabled(mShadowOverlayHelper.getShadowType() 286 != ShadowOverlayHelper.SHADOW_DYNAMIC); 287 FocusHighlightHelper.setupBrowseItemFocusHighlight(vh.mItemBridgeAdapter, 288 mFocusZoomFactor, mUseFocusDimmer); 289 290 final ViewHolder gridViewHolder = vh; 291 vh.getGridView().setOnChildSelectedListener(new OnChildSelectedListener() { 292 @Override 293 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 294 selectChildView(gridViewHolder, view); 295 } 296 }); 297 } 298 299 /** 300 * Set if keeps foreground of child of this grid, the foreground will not 301 * be used for overlay color. Default value is true. 302 * 303 * @param keep True if keep foreground of child of this grid. 304 */ setKeepChildForeground(boolean keep)305 public final void setKeepChildForeground(boolean keep) { 306 mKeepChildForeground = keep; 307 } 308 309 /** 310 * Returns true if keeps foreground of child of this grid, the foreground will not 311 * be used for overlay color. Default value is true. 312 * 313 * @return True if keeps foreground of child of this grid. 314 */ getKeepChildForeground()315 public final boolean getKeepChildForeground() { 316 return mKeepChildForeground; 317 } 318 319 /** 320 * Create ShadowOverlayHelper Options. Subclass may override. 321 * e.g. 322 * <code> 323 * return new ShadowOverlayHelper.Options().roundedCornerRadius(10); 324 * </code> 325 * 326 * @return The options to be used for shadow, overlay and rounded corner. 327 */ createShadowOverlayOptions()328 protected ShadowOverlayHelper.Options createShadowOverlayOptions() { 329 return ShadowOverlayHelper.Options.DEFAULT; 330 } 331 332 @Override onBindViewHolder(Presenter.ViewHolder viewHolder, Object item)333 public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { 334 if (DEBUG) Log.v(TAG, "onBindViewHolder " + item); 335 ViewHolder vh = (ViewHolder) viewHolder; 336 vh.mItemBridgeAdapter.setAdapter((ObjectAdapter) item); 337 vh.getGridView().setAdapter(vh.mItemBridgeAdapter); 338 } 339 340 @Override onUnbindViewHolder(Presenter.ViewHolder viewHolder)341 public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { 342 if (DEBUG) Log.v(TAG, "onUnbindViewHolder"); 343 ViewHolder vh = (ViewHolder) viewHolder; 344 vh.mItemBridgeAdapter.setAdapter(null); 345 vh.getGridView().setAdapter(null); 346 } 347 348 /** 349 * Sets the item selected listener. 350 * Since this is a grid the row parameter is always null. 351 */ setOnItemViewSelectedListener(OnItemViewSelectedListener listener)352 public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) { 353 mOnItemViewSelectedListener = listener; 354 } 355 356 /** 357 * Returns the item selected listener. 358 */ getOnItemViewSelectedListener()359 public final OnItemViewSelectedListener getOnItemViewSelectedListener() { 360 return mOnItemViewSelectedListener; 361 } 362 363 /** 364 * Sets the item clicked listener. 365 * OnItemViewClickedListener will override {@link View.OnClickListener} that 366 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 367 * So in general, developer should choose one of the listeners but not both. 368 */ setOnItemViewClickedListener(OnItemViewClickedListener listener)369 public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) { 370 mOnItemViewClickedListener = listener; 371 } 372 373 /** 374 * Returns the item clicked listener. 375 */ getOnItemViewClickedListener()376 public final OnItemViewClickedListener getOnItemViewClickedListener() { 377 return mOnItemViewClickedListener; 378 } 379 selectChildView(ViewHolder vh, View view)380 void selectChildView(ViewHolder vh, View view) { 381 if (getOnItemViewSelectedListener() != null) { 382 ItemBridgeAdapter.ViewHolder ibh = (view == null) ? null : 383 (ItemBridgeAdapter.ViewHolder) vh.getGridView().getChildViewHolder(view); 384 if (ibh == null) { 385 getOnItemViewSelectedListener().onItemSelected(null, null, null, null); 386 } else { 387 getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem, null, null); 388 } 389 } 390 } 391 392 /** 393 * Changes the visibility of views. The entrance transition will be run against the views that 394 * change visibilities. This method is called by the fragment, it should not be called 395 * directly by the application. 396 * 397 * @param holder The ViewHolder for the vertical grid. 398 * @param afterEntrance true if children of vertical grid participating in entrance transition 399 * should be set to visible, false otherwise. 400 */ setEntranceTransitionState(VerticalGridPresenter.ViewHolder holder, boolean afterEntrance)401 public void setEntranceTransitionState(VerticalGridPresenter.ViewHolder holder, 402 boolean afterEntrance) { 403 holder.mGridView.setChildrenVisibility(afterEntrance? View.VISIBLE : View.INVISIBLE); 404 } 405 } 406