RowPresenter.java revision 4df06cbe8f6dd087fc8f1068faa77923cb297365
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.app.HeadersFragment; 17import android.view.View; 18import android.view.ViewGroup; 19 20/** 21 * A presenter that renders {@link Row}. 22 * 23 * <h3>Customize UI widgets</h3> 24 * When subclass of RowPresenter adds UI widgets, it should subclass 25 * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)} 26 * and {@link #initializeRowViewHolder(ViewHolder)}. Subclass must use layout id 27 * "row_content" for the widget that will be aligned to title of {@link HeadersFragment}. 28 * RowPresenter contains an optional and replaceable {@link RowHeaderPresenter} that 29 * renders header. User can disable default rendering or replace with a new header presenter 30 * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}. 31 * 32 * <h3>UI events from fragments</h3> 33 * In addition to {@link Presenter} which defines how to render and bind data to row view, 34 * RowPresenter receives calls from upper level(typically a fragment) when: 35 * <ul> 36 * <li> 37 * Row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}. The event 38 * is triggered immediately when there is a row selection change before the selection 39 * animation is started. 40 * Subclass of RowPresenter may override and add more works in 41 * {@link #onRowViewSelected(ViewHolder, boolean)}. 42 * </li> 43 * <li> 44 * Row is expanded to full width via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}. 45 * The event is triggered immediately before the expand animation is started. 46 * Subclass of RowPresenter may override and add more works in 47 * {@link #onRowViewExpanded(ViewHolder, boolean)}. 48 * </li> 49 * </ul> 50 * 51 * <h3>User events:</h3> 52 * RowPresenter provides {@link OnItemSelectedListener} and {@link OnItemClickedListener}. 53 * If subclass wants to add its own {@link View.OnFocusChangeListener} or 54 * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)} 55 * to be properly chained by framework. Adding view listeners after 56 * {@link #createRowViewHolder(ViewGroup)} will interfere framework's listeners. 57 * 58 * <h3>Selection animation</h3> 59 * <p> 60 * When user scrolls through rows, fragment will initiate animation and call 61 * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value 0~1. By default, fragment 62 * draws a dim overlay on top of row view for views not selected. Subclass may override 63 * this default effect by having {@link #isUsingDefaultSelectEffect()} return false 64 * and override {@link #onSelectLevelChanged(ViewHolder)} to apply its own selection effect. 65 * </p> 66 * <p> 67 * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable select effect, 68 * This is not only for enable/disable default dim implementation but also subclass must 69 * respect this flag. 70 * </p> 71 */ 72public abstract class RowPresenter extends Presenter { 73 74 static class ContainerViewHolder extends Presenter.ViewHolder { 75 /** 76 * wrapped row view holder 77 */ 78 final ViewHolder mRowViewHolder; 79 80 public ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder) { 81 super(containerView); 82 containerView.addRowView(rowViewHolder.view); 83 if (rowViewHolder.mHeaderViewHolder != null) { 84 containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view); 85 } 86 mRowViewHolder = rowViewHolder; 87 mRowViewHolder.mContainerViewHolder = this; 88 } 89 } 90 91 public static class ViewHolder extends Presenter.ViewHolder { 92 ContainerViewHolder mContainerViewHolder; 93 RowHeaderPresenter.ViewHolder mHeaderViewHolder; 94 Row mRow; 95 boolean mSelected; 96 boolean mExpanded; 97 boolean mInitialzed; 98 float mSelectLevel = 0f; // initially unselected 99 public ViewHolder(View view) { 100 super(view); 101 } 102 public final Row getRow() { 103 return mRow; 104 } 105 public final boolean isExpanded() { 106 return mExpanded; 107 } 108 public final boolean isSelected() { 109 return mSelected; 110 } 111 public final float getSelectLevel() { 112 return mSelectLevel; 113 } 114 public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() { 115 return mHeaderViewHolder; 116 } 117 } 118 119 private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter(); 120 private OnItemSelectedListener mOnItemSelectedListener; 121 private OnItemClickedListener mOnItemClickedListener; 122 123 boolean mSelectEffectEnabled = true; 124 125 @Override 126 public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { 127 ViewHolder vh = createRowViewHolder(parent); 128 vh.mInitialzed = false; 129 Presenter.ViewHolder result; 130 if (needsRowContainerView()) { 131 RowContainerView containerView = new RowContainerView(parent.getContext()); 132 if (mHeaderPresenter != null) { 133 vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder) 134 mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view); 135 } 136 result = new ContainerViewHolder(containerView, vh); 137 } else { 138 result = vh; 139 } 140 initializeRowViewHolder(vh); 141 if (!vh.mInitialzed) { 142 throw new RuntimeException("super.initializeRowViewHolder() must be called"); 143 } 144 return result; 145 } 146 147 /** 148 * Called to create a ViewHolder object for row, subclass of {@link RowPresenter} 149 * should override and return a different concrete ViewHolder object. 150 */ 151 protected abstract ViewHolder createRowViewHolder(ViewGroup parent); 152 153 /** 154 * Called after a {@link RowPresenter.ViewHolder} is created, 155 * subclass of {@link RowPresenter} may override this method and start with calling 156 * super.initializeRowViewHolder(ViewHolder). 157 */ 158 protected void initializeRowViewHolder(ViewHolder vh) { 159 vh.mInitialzed = true; 160 } 161 162 /** 163 * Change the presenter used for rendering header. Can be null to disable header rendering. 164 * The method must be called before creating any row view. 165 */ 166 public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) { 167 mHeaderPresenter = headerPresenter; 168 } 169 170 /** 171 * Get optional presenter used for rendering header. May return null. 172 */ 173 public final RowHeaderPresenter getHeaderPresenter() { 174 return mHeaderPresenter; 175 } 176 177 /** 178 * Get wrapped {@link RowPresenter.ViewHolder} 179 */ 180 protected final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) { 181 if (holder instanceof ContainerViewHolder) { 182 return ((ContainerViewHolder) holder).mRowViewHolder; 183 } else { 184 return (ViewHolder) holder; 185 } 186 } 187 188 /** 189 * Change expanded state of row view. 190 */ 191 public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) { 192 ViewHolder rowViewHolder = getRowViewHolder(holder); 193 rowViewHolder.mExpanded = expanded; 194 onRowViewExpanded(rowViewHolder, expanded); 195 } 196 197 /** 198 * Change select state of row view. 199 */ 200 public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) { 201 ViewHolder rowViewHolder = getRowViewHolder(holder); 202 rowViewHolder.mSelected = selected; 203 onRowViewSelected(rowViewHolder, selected); 204 } 205 206 /** 207 * Subclass may override and respond to expanded state change of row in fragment. 208 * Default implementation hide/show header view depending on expanded state. 209 * Subclass may make visual changes to row view but not allowed to create 210 * animation on the row view. 211 */ 212 protected void onRowViewExpanded(ViewHolder vh, boolean expanded) { 213 if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) { 214 RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view); 215 View headerView = vh.mHeaderViewHolder.view; 216 if (expanded) { 217 containerView.addHeaderView(headerView); 218 } else { 219 containerView.removeHeaderView(headerView); 220 } 221 } 222 vh.view.setActivated(expanded); 223 } 224 225 /** 226 * Subclass may override and respond to event Row is selected. 227 * Subclass may make visual changes to row view but not allowed to create 228 * animation on the row view. 229 */ 230 protected void onRowViewSelected(ViewHolder vh, boolean selected) { 231 if (selected && mOnItemSelectedListener != null) { 232 mOnItemSelectedListener.onItemSelected(null, vh.getRow()); 233 } 234 } 235 236 /** 237 * Set current select level from 0(unselected) to 1(selected). 238 * Subclass should override {@link #onSelectLevelChanged(ViewHolder)}. 239 */ 240 public final void setSelectLevel(Presenter.ViewHolder vh, float level) { 241 ViewHolder rowViewHolder = getRowViewHolder(vh); 242 rowViewHolder.mSelectLevel = level; 243 onSelectLevelChanged(rowViewHolder); 244 } 245 246 /** 247 * Get current select level from 0(unselected) to 1(selected). 248 */ 249 public final float getSelectLevel(Presenter.ViewHolder vh) { 250 return getRowViewHolder(vh).mSelectLevel; 251 } 252 253 /** 254 * Callback when select level is changed. Default implementation applies select level 255 * to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)} 256 * when {@link #getSelectEffectEnabled()} is true. 257 * Subclass may override this function and implements its own select effect. When it 258 * overrides, it should also override {@link #isUsingDefaultSelectEffect()} to disable 259 * the default dimming effect applied by framework. 260 */ 261 protected void onSelectLevelChanged(ViewHolder vh) { 262 if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) { 263 mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel); 264 } 265 } 266 267 /** 268 * Enables or disables the row selection effect. 269 * This is not only for enable/disable default dim implementation but also subclass must 270 * respect this flag. 271 */ 272 public final void setSelectEffectEnabled(boolean applyDimOnSelect) { 273 mSelectEffectEnabled = applyDimOnSelect; 274 } 275 276 /** 277 * Returns true if row selection effect is enabled. 278 * This is not only for enable/disable default dim implementation but also subclass must 279 * respect this flag. 280 */ 281 public final boolean getSelectEffectEnabled() { 282 return mSelectEffectEnabled; 283 } 284 285 /** 286 * Return if using default dimming effect provided by framework (fragment). Subclass 287 * may(most likely) return false and override {@link #onSelectLevelChanged(ViewHolder)}. 288 */ 289 public boolean isUsingDefaultSelectEffect() { 290 return true; 291 } 292 293 final boolean needsDefaultSelectEffect() { 294 return isUsingDefaultSelectEffect() && getSelectEffectEnabled(); 295 } 296 297 final boolean needsRowContainerView() { 298 return mHeaderPresenter != null; 299 } 300 301 /** 302 * Return true if the Row view can draw outside bounds. 303 */ 304 public boolean canDrawOutOfBounds() { 305 return false; 306 } 307 308 @Override 309 public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { 310 onBindRowViewHolder(getRowViewHolder(viewHolder), item); 311 } 312 313 protected void onBindRowViewHolder(ViewHolder vh, Object item) { 314 vh.mRow = (Row) item; 315 if (vh.mHeaderViewHolder != null) { 316 mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item); 317 } 318 } 319 320 @Override 321 public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { 322 onUnbindRowViewHolder(getRowViewHolder(viewHolder)); 323 } 324 325 protected void onUnbindRowViewHolder(ViewHolder vh) { 326 if (vh.mHeaderViewHolder != null) { 327 mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder); 328 } 329 vh.mRow = null; 330 } 331 332 @Override 333 public final void onViewAttachedToWindow(Presenter.ViewHolder holder) { 334 onRowViewAttachedToWindow(getRowViewHolder(holder)); 335 } 336 337 protected void onRowViewAttachedToWindow(ViewHolder vh) { 338 if (vh.mHeaderViewHolder != null) { 339 mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder); 340 } 341 } 342 343 @Override 344 public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) { 345 onRowViewDetachedFromWindow(getRowViewHolder(holder)); 346 } 347 348 protected void onRowViewDetachedFromWindow(ViewHolder vh) { 349 if (vh.mHeaderViewHolder != null) { 350 mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder); 351 } 352 } 353 354 /** 355 * Set listener for item or row selection. RowPresenter fires row selection 356 * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can 357 * fire a selection event with selected item. 358 */ 359 public final void setOnItemSelectedListener(OnItemSelectedListener listener) { 360 mOnItemSelectedListener = listener; 361 } 362 363 /** 364 * Get listener for item or row selection. 365 */ 366 public final OnItemSelectedListener getOnItemSelectedListener() { 367 return mOnItemSelectedListener; 368 } 369 370 /** 371 * Set listener for item click event. RowPresenter does nothing but subclass of 372 * RowPresenter may fire item click event if it does have a concept of item. 373 * OnItemClickedListener will override {@link View.OnClickListener} that 374 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 375 * So in general, developer should choose one of the listeners but not both. 376 */ 377 public final void setOnItemClickedListener(OnItemClickedListener listener) { 378 mOnItemClickedListener = listener; 379 } 380 381 /** 382 * Set listener for item click event. 383 */ 384 public final OnItemClickedListener getOnItemClickedListener() { 385 return mOnItemClickedListener; 386 } 387} 388