RowPresenter.java revision 0946602a3f3815a5f7d46dfc571b3c60483f1ea4
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(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(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(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 public static class ViewHolder extends Presenter.ViewHolder { 75 RowHeaderPresenter.ViewHolder mHeaderViewHolder; 76 Row mRow; 77 boolean mSelected; 78 boolean mExpanded; 79 boolean mInitialzed; 80 float mSelectLevel = 0f; // initially unselected 81 public ViewHolder(View view) { 82 super(view); 83 } 84 public final Row getRow() { 85 return mRow; 86 } 87 public final boolean isExpanded() { 88 return mExpanded; 89 } 90 public final boolean isSelected() { 91 return mSelected; 92 } 93 public final float getSelectLevel() { 94 return mSelectLevel; 95 } 96 public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() { 97 return mHeaderViewHolder; 98 } 99 } 100 101 private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter(); 102 private OnItemSelectedListener mOnItemSelectedListener; 103 private OnItemClickedListener mOnItemClickedListener; 104 105 boolean mSelectEffectEnabled = true; 106 107 @Override 108 public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { 109 ViewHolder vh = createRowViewHolder(parent); 110 vh.mInitialzed = false; 111 initializeRowViewHolder(vh); 112 if (!vh.mInitialzed) { 113 throw new RuntimeException("super.initializeRowViewHolder() must be called"); 114 } 115 return vh; 116 } 117 118 /** 119 * Called to create a ViewHolder object for row, subclass of {@link RowPresenter} 120 * should override and return a different concrete ViewHolder object. 121 */ 122 protected abstract ViewHolder createRowViewHolder(ViewGroup parent); 123 124 /** 125 * Called after a {@link RowPresenter.ViewHolder} is created, 126 * subclass of {@link RowPresenter} may override this method and start with calling 127 * super.initializeRowViewHolder(ViewHolder). 128 */ 129 protected void initializeRowViewHolder(ViewHolder vh) { 130 if (mHeaderPresenter != null) { 131 vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder) 132 mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view); 133 ((ViewGroup) vh.view).addView(vh.mHeaderViewHolder.view, 0); 134 } 135 vh.mInitialzed = true; 136 } 137 138 /** 139 * Change the presenter used for rendering header. Can be null to disable header rendering. 140 * The method must be called before creating any row view. 141 */ 142 public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) { 143 mHeaderPresenter = headerPresenter; 144 } 145 146 /** 147 * Get optional presenter used for rendering header. May return null. 148 */ 149 public final RowHeaderPresenter getHeaderPresenter() { 150 return mHeaderPresenter; 151 } 152 153 /** 154 * Change expanded state of row view. 155 */ 156 public final void setRowViewExpanded(ViewHolder holder, boolean expanded) { 157 holder.mExpanded = expanded; 158 onRowViewExpanded(holder, expanded); 159 } 160 161 /** 162 * Change select state of row view. 163 */ 164 public final void setRowViewSelected(ViewHolder holder, boolean selected) { 165 holder.mSelected = selected; 166 onRowViewSelected(holder, selected); 167 } 168 169 /** 170 * Subclass may override and respond to expanded state change of row in fragment. 171 * Default implementation hide/show header view depending on expanded state. 172 * Subclass may make visual changes to row view but not allowed to create 173 * animation on the row view. 174 */ 175 protected void onRowViewExpanded(ViewHolder vh, boolean expanded) { 176 if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) { 177 mHeaderPresenter.setHidden(vh.mHeaderViewHolder, !expanded); 178 } 179 } 180 181 /** 182 * Subclass may override and respond to event Row is selected. 183 * Subclass may make visual changes to row view but not allowed to create 184 * animation on the row view. 185 */ 186 protected void onRowViewSelected(ViewHolder vh, boolean selected) { 187 if (selected && mOnItemSelectedListener != null) { 188 mOnItemSelectedListener.onItemSelected(null, vh.getRow()); 189 } 190 } 191 192 /** 193 * Set current select level from 0(unselected) to 1(selected). 194 * Subclass should override {@link #onSelectLevelChanged(ViewHolder)}. 195 */ 196 public final void setSelectLevel(ViewHolder vh, float level) { 197 vh.mSelectLevel = level; 198 onSelectLevelChanged(vh); 199 } 200 201 /** 202 * Get current select level from 0(unselected) to 1(selected). 203 */ 204 public final float getSelectLevel(ViewHolder vh) { 205 return vh.mSelectLevel; 206 } 207 208 /** 209 * Callback when select level is changed. Default implementation applies select level 210 * to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)} 211 * when {@link #getSelectEffectEnabled()} is true. 212 * Subclass may override this function and implements its own select effect. When it 213 * overrides, it should also override {@link #isUsingDefaultSelectEffect()} to disable 214 * the default dimming effect applied by framework. 215 */ 216 protected void onSelectLevelChanged(ViewHolder vh) { 217 if (getSelectEffectEnabled() && vh.mHeaderViewHolder != null) { 218 mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel); 219 } 220 } 221 222 /** 223 * Enables or disables the row selection effect. 224 * This is not only for enable/disable default dim implementation but also subclass must 225 * respect this flag. 226 */ 227 public final void setSelectEffectEnabled(boolean applyDimOnSelect) { 228 mSelectEffectEnabled = applyDimOnSelect; 229 } 230 231 /** 232 * Returns true if row selection effect is enabled. 233 * This is not only for enable/disable default dim implementation but also subclass must 234 * respect this flag. 235 */ 236 public final boolean getSelectEffectEnabled() { 237 return mSelectEffectEnabled; 238 } 239 240 /** 241 * Return if using default dimming effect provided by framework (fragment). Subclass 242 * may(most likely) return false and override {@link #onSelectLevelChanged(ViewHolder)}. 243 */ 244 public boolean isUsingDefaultSelectEffect() { 245 return true; 246 } 247 248 /** 249 * Return true if the Row view can draw outside bounds. 250 */ 251 public boolean canDrawOutOfBounds() { 252 return false; 253 } 254 255 @Override 256 public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) { 257 ViewHolder vh = (ViewHolder) viewHolder; 258 vh.mRow = (Row) item; 259 if (vh.mHeaderViewHolder != null) { 260 mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item); 261 } 262 } 263 264 @Override 265 public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { 266 ViewHolder vh = (ViewHolder) viewHolder; 267 if (vh.mHeaderViewHolder != null) { 268 mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder); 269 } 270 vh.mRow = null; 271 } 272 273 @Override 274 public void onViewAttachedToWindow(Presenter.ViewHolder holder) { 275 ViewHolder vh = (ViewHolder) holder; 276 if (vh.mHeaderViewHolder != null) { 277 mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder); 278 } 279 } 280 281 @Override 282 public void onViewDetachedFromWindow(Presenter.ViewHolder holder) { 283 ViewHolder vh = (ViewHolder) holder; 284 if (vh.mHeaderViewHolder != null) { 285 mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder); 286 } 287 } 288 289 /** 290 * Set listener for item or row selection. RowPresenter fires row selection 291 * event with null item, subclass of RowPresenter e.g. {@link ListRowPresenter} can 292 * fire a selection event with selected item. 293 */ 294 public final void setOnItemSelectedListener(OnItemSelectedListener listener) { 295 mOnItemSelectedListener = listener; 296 } 297 298 /** 299 * Get listener for item or row selection. 300 */ 301 public final OnItemSelectedListener getOnItemSelectedListener() { 302 return mOnItemSelectedListener; 303 } 304 305 /** 306 * Set listener for item click event. RowPresenter does nothing but subclass of 307 * RowPresenter may fire item click event if it does have a concept of item. 308 * OnItemClickedListener will override {@link View.OnClickListener} that 309 * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}. 310 * So in general, developer should choose one of the listeners but not both. 311 */ 312 public final void setOnItemClickedListener(OnItemClickedListener listener) { 313 mOnItemClickedListener = listener; 314 } 315 316 /** 317 * Set listener for item click event. 318 */ 319 public final OnItemClickedListener getOnItemClickedListener() { 320 return mOnItemClickedListener; 321 } 322} 323