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 android.content.Context; 17import android.support.v17.leanback.R; 18import android.support.v17.leanback.system.Settings; 19import android.view.LayoutInflater; 20import android.view.View; 21import android.view.ViewGroup; 22import android.view.ViewGroup.LayoutParams; 23import android.util.Log; 24 25/** 26 * A presenter that renders objects in a {@link VerticalGridView}. 27 */ 28public 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 34 public void onBind(final ItemBridgeAdapter.ViewHolder itemViewHolder) { 35 // Only when having an OnItemClickListner, we attach the OnClickListener. 36 if (getOnItemViewClickedListener() != null) { 37 final View itemView = itemViewHolder.mHolder.view; 38 itemView.setOnClickListener(new View.OnClickListener() { 39 @Override 40 public void onClick(View view) { 41 if (getOnItemViewClickedListener() != null) { 42 // Row is always null 43 getOnItemViewClickedListener().onItemClicked( 44 itemViewHolder.mHolder, itemViewHolder.mItem, null, null); 45 } 46 } 47 }); 48 } 49 } 50 51 @Override 52 public void onUnbind(ItemBridgeAdapter.ViewHolder viewHolder) { 53 if (getOnItemViewClickedListener() != null) { 54 viewHolder.mHolder.view.setOnClickListener(null); 55 } 56 } 57 58 @Override 59 public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder viewHolder) { 60 viewHolder.itemView.setActivated(true); 61 } 62 } 63 64 /** 65 * ViewHolder for the VerticalGridPresenter. 66 */ 67 public static class ViewHolder extends Presenter.ViewHolder { 68 ItemBridgeAdapter mItemBridgeAdapter; 69 final VerticalGridView mGridView; 70 boolean mInitialized; 71 72 public ViewHolder(VerticalGridView view) { 73 super(view); 74 mGridView = view; 75 } 76 77 public VerticalGridView getGridView() { 78 return mGridView; 79 } 80 } 81 82 private int mNumColumns = -1; 83 private int mFocusZoomFactor; 84 private boolean mUseFocusDimmer; 85 private boolean mShadowEnabled = true; 86 private OnItemViewSelectedListener mOnItemViewSelectedListener; 87 private OnItemViewClickedListener mOnItemViewClickedListener; 88 private boolean mRoundedCornersEnabled = true; 89 90 /** 91 * Constructs a VerticalGridPresenter with defaults. 92 * Uses {@link FocusHighlight#ZOOM_FACTOR_MEDIUM} for focus zooming and 93 * enabled dimming on focus. 94 */ 95 public VerticalGridPresenter() { 96 this(FocusHighlight.ZOOM_FACTOR_LARGE); 97 } 98 99 /** 100 * Constructs a VerticalGridPresenter with the given parameters. 101 * 102 * @param focusZoomFactor Controls the zoom factor used when an item view is focused. One of 103 * {@link FocusHighlight#ZOOM_FACTOR_NONE}, 104 * {@link FocusHighlight#ZOOM_FACTOR_SMALL}, 105 * {@link FocusHighlight#ZOOM_FACTOR_XSMALL}, 106 * {@link FocusHighlight#ZOOM_FACTOR_MEDIUM}, 107 * {@link FocusHighlight#ZOOM_FACTOR_LARGE} 108 * enabled dimming on focus. 109 */ 110 public VerticalGridPresenter(int focusZoomFactor) { 111 this(focusZoomFactor, true); 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 * @param useFocusDimmer determines if the FocusHighlighter will use the dimmer 124 */ 125 public VerticalGridPresenter(int focusZoomFactor, boolean useFocusDimmer) { 126 mFocusZoomFactor = focusZoomFactor; 127 mUseFocusDimmer = useFocusDimmer; 128 } 129 130 /** 131 * Sets the number of columns in the vertical grid. 132 */ 133 public void setNumberOfColumns(int numColumns) { 134 if (numColumns < 0) { 135 throw new IllegalArgumentException("Invalid number of columns"); 136 } 137 if (mNumColumns != numColumns) { 138 mNumColumns = numColumns; 139 } 140 } 141 142 /** 143 * Returns the number of columns in the vertical grid. 144 */ 145 public int getNumberOfColumns() { 146 return mNumColumns; 147 } 148 149 /** 150 * Enable or disable child shadow. 151 * This is not only for enable/disable default shadow implementation but also subclass must 152 * respect this flag. 153 */ 154 public final void setShadowEnabled(boolean enabled) { 155 mShadowEnabled = enabled; 156 } 157 158 /** 159 * Returns true if child shadow is enabled. 160 * This is not only for enable/disable default shadow implementation but also subclass must 161 * respect this flag. 162 */ 163 public final boolean getShadowEnabled() { 164 return mShadowEnabled; 165 } 166 167 /** 168 * Returns true if opticalBounds is supported (SDK >= 18) so that default shadow 169 * is applied to each individual child of {@link VerticalGridView}. 170 * Subclass may return false to disable. 171 */ 172 public boolean isUsingDefaultShadow() { 173 return ShadowOverlayContainer.supportsShadow(); 174 } 175 176 /** 177 * Enables or disabled rounded corners on children of this row. 178 * Supported on Android SDK >= L. 179 */ 180 public final void enableChildRoundedCorners(boolean enable) { 181 mRoundedCornersEnabled = enable; 182 } 183 184 /** 185 * Returns true if rounded corners are enabled for children of this row. 186 */ 187 public final boolean areChildRoundedCornersEnabled() { 188 return mRoundedCornersEnabled; 189 } 190 191 /** 192 * Returns true if SDK >= L, where Z shadow is enabled so that Z order is enabled 193 * on each child of vertical grid. If subclass returns false in isUsingDefaultShadow() 194 * and does not use Z-shadow on SDK >= L, it should override isUsingZOrder() return false. 195 */ 196 public boolean isUsingZOrder(Context context) { 197 return ShadowOverlayContainer.supportsDynamicShadow() && 198 !Settings.getInstance(context).preferStaticShadows(); 199 } 200 201 final boolean needsDefaultShadow() { 202 return isUsingDefaultShadow() && getShadowEnabled(); 203 } 204 205 /** 206 * Returns the zoom factor used for focus highlighting. 207 */ 208 public final int getFocusZoomFactor() { 209 return mFocusZoomFactor; 210 } 211 212 /** 213 * Returns true if the focus dimmer is used for focus highlighting; false otherwise. 214 */ 215 public final boolean isFocusDimmerUsed() { 216 return mUseFocusDimmer; 217 } 218 219 220 @Override 221 public final ViewHolder onCreateViewHolder(ViewGroup parent) { 222 ViewHolder vh = createGridViewHolder(parent); 223 vh.mInitialized = false; 224 vh.mItemBridgeAdapter = new VerticalGridItemBridgeAdapter(); 225 initializeGridViewHolder(vh); 226 if (!vh.mInitialized) { 227 throw new RuntimeException("super.initializeGridViewHolder() must be called"); 228 } 229 return vh; 230 } 231 232 /** 233 * Subclass may override this to inflate a different layout. 234 */ 235 protected ViewHolder createGridViewHolder(ViewGroup parent) { 236 View root = LayoutInflater.from(parent.getContext()).inflate( 237 R.layout.lb_vertical_grid, parent, false); 238 return new ViewHolder((VerticalGridView) root.findViewById(R.id.browse_grid)); 239 } 240 241 private ItemBridgeAdapter.Wrapper mWrapper = new ItemBridgeAdapter.Wrapper() { 242 @Override 243 public View createWrapper(View root) { 244 ShadowOverlayContainer wrapper = new ShadowOverlayContainer(root.getContext()); 245 wrapper.setLayoutParams( 246 new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 247 wrapper.initialize(needsDefaultShadow(), true, areChildRoundedCornersEnabled()); 248 return wrapper; 249 } 250 @Override 251 public void wrap(View wrapper, View wrapped) { 252 ((ShadowOverlayContainer) wrapper).wrap(wrapped); 253 } 254 }; 255 256 /** 257 * Called after a {@link VerticalGridPresenter.ViewHolder} is created. 258 * Subclasses may override this method and start by calling 259 * super.initializeGridViewHolder(ViewHolder). 260 * 261 * @param vh The ViewHolder to initialize for the vertical grid. 262 */ 263 protected void initializeGridViewHolder(ViewHolder vh) { 264 if (mNumColumns == -1) { 265 throw new IllegalStateException("Number of columns must be set"); 266 } 267 if (DEBUG) Log.v(TAG, "mNumColumns " + mNumColumns); 268 vh.getGridView().setNumColumns(mNumColumns); 269 vh.mInitialized = true; 270 271 vh.mItemBridgeAdapter.setWrapper(mWrapper); 272 if (needsDefaultShadow() || areChildRoundedCornersEnabled()) { 273 ShadowOverlayContainer.prepareParentForShadow(vh.getGridView()); 274 ((ViewGroup) vh.view).setClipChildren(false); 275 } 276 vh.getGridView().setFocusDrawingOrderEnabled(!isUsingZOrder(vh.getGridView().getContext())); 277 FocusHighlightHelper.setupBrowseItemFocusHighlight(vh.mItemBridgeAdapter, 278 mFocusZoomFactor, mUseFocusDimmer); 279 280 final ViewHolder gridViewHolder = vh; 281 vh.getGridView().setOnChildSelectedListener(new OnChildSelectedListener() { 282 @Override 283 public void onChildSelected(ViewGroup parent, View view, int position, long id) { 284 selectChildView(gridViewHolder, view); 285 } 286 }); 287 } 288 289 @Override 290 public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { 291 if (DEBUG) Log.v(TAG, "onBindViewHolder " + item); 292 ViewHolder vh = (ViewHolder) viewHolder; 293 vh.mItemBridgeAdapter.setAdapter((ObjectAdapter) item); 294 vh.getGridView().setAdapter(vh.mItemBridgeAdapter); 295 } 296 297 @Override 298 public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { 299 if (DEBUG) Log.v(TAG, "onUnbindViewHolder"); 300 ViewHolder vh = (ViewHolder) viewHolder; 301 vh.mItemBridgeAdapter.setAdapter(null); 302 vh.getGridView().setAdapter(null); 303 } 304 305 /** 306 * Sets the item selected listener. 307 * Since this is a grid the row parameter is always null. 308 */ 309 public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) { 310 mOnItemViewSelectedListener = listener; 311 } 312 313 /** 314 * Returns the item selected listener. 315 */ 316 public final OnItemViewSelectedListener getOnItemViewSelectedListener() { 317 return mOnItemViewSelectedListener; 318 } 319 320 /** 321 * Sets the item clicked listener. 322 * OnItemViewClickedListener will override {@link View.OnClickListener} that 323 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 324 * So in general, developer should choose one of the listeners but not both. 325 */ 326 public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) { 327 mOnItemViewClickedListener = listener; 328 } 329 330 /** 331 * Returns the item clicked listener. 332 */ 333 public final OnItemViewClickedListener getOnItemViewClickedListener() { 334 return mOnItemViewClickedListener; 335 } 336 337 private void selectChildView(ViewHolder vh, View view) { 338 if (getOnItemViewSelectedListener() != null) { 339 ItemBridgeAdapter.ViewHolder ibh = (view == null) ? null : 340 (ItemBridgeAdapter.ViewHolder) vh.getGridView().getChildViewHolder(view); 341 if (ibh == null) { 342 getOnItemViewSelectedListener().onItemSelected(null, null, null, null); 343 } else { 344 getOnItemViewSelectedListener().onItemSelected(ibh.mHolder, ibh.mItem, null, null); 345 } 346 } 347 } 348} 349