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