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