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 android.support.v17.leanback.widget;
15 
16 import android.support.v17.leanback.app.HeadersFragment;
17 import android.support.v17.leanback.graphics.ColorOverlayDimmer;
18 import android.view.View;
19 import android.view.ViewGroup;
20 
21 /**
22  * An abstract {@link Presenter} that renders a {@link Row}.
23  *
24  * <h3>Customize UI widgets</h3>
25  * When a subclass of RowPresenter adds UI widgets, it should subclass
26  * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)}
27  * and {@link #initializeRowViewHolder(ViewHolder)}. The subclass must use layout id
28  * "row_content" for the widget that will be aligned to the title of any {@link HeadersFragment}
29  * that may exist in the parent fragment. RowPresenter contains an optional and
30  * replaceable {@link RowHeaderPresenter} that renders the header. You can disable
31  * the default rendering or replace the Presenter with a new header presenter
32  * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}.
33  *
34  * <h3>UI events from fragments</h3>
35  * RowPresenter receives calls from its parent (typically a Fragment) when:
36  * <ul>
37  * <li>
38  * A row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}.  The event
39  * is triggered immediately when there is a row selection change before the selection
40  * animation is started.  Selected status may control activated status of the row (see
41  * "Activated status" below).
42  * Subclasses of RowPresenter may override {@link #onRowViewSelected(ViewHolder, boolean)}.
43  * </li>
44  * <li>
45  * A row is expanded to full height via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}
46  * when BrowseFragment hides fast lane on the left.
47  * The event is triggered immediately before the expand animation is started.
48  * Row title is shown when row is expanded.  Expanded status may control activated status
49  * of the row (see "Activated status" below).
50  * Subclasses of RowPresenter may override {@link #onRowViewExpanded(ViewHolder, boolean)}.
51  * </li>
52  * </ul>
53  *
54  * <h3>Activated status</h3>
55  * The activated status of a row is applied to the row view and it's children via
56  * {@link View#setActivated(boolean)}.
57  * The activated status is typically used to control {@link BaseCardView} info region visibility.
58  * The row's activated status can be controlled by selected status and/or expanded status.
59  * Call {@link #setSyncActivatePolicy(int)} and choose one of the four policies:
60  * <ul>
61  * <li>{@link #SYNC_ACTIVATED_TO_EXPANDED} Activated status is synced with row expanded status</li>
62  * <li>{@link #SYNC_ACTIVATED_TO_SELECTED} Activated status is synced with row selected status</li>
63  * <li>{@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED} Activated status is set to true
64  *     when both expanded and selected status are true</li>
65  * <li>{@link #SYNC_ACTIVATED_CUSTOM} Activated status is not controlled by selected status
66  *     or expanded status, application can control activated status by its own.
67  *     Application should call {@link RowPresenter.ViewHolder#setActivated(boolean)} to change
68  *     activated status of row view.
69  * </li>
70  * </ul>
71  *
72  * <h3>User events</h3>
73  * RowPresenter provides {@link OnItemViewSelectedListener} and {@link OnItemViewClickedListener}.
74  * If a subclass wants to add its own {@link View.OnFocusChangeListener} or
75  * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)}
76  * to be properly chained by the library.  Adding View listeners after
77  * {@link #createRowViewHolder(ViewGroup)} is undefined and may result in
78  * incorrect behavior by the library's listeners.
79  *
80  * <h3>Selection animation</h3>
81  * <p>
82  * When a user scrolls through rows, a fragment will initiate animation and call
83  * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value between
84  * 0 and 1.  By default, the RowPresenter draws a dim overlay on top of the row
85  * view for views that are not selected. Subclasses may override this default effect
86  * by having {@link #isUsingDefaultSelectEffect()} return false and overriding
87  * {@link #onSelectLevelChanged(ViewHolder)} to apply a different selection effect.
88  * </p>
89  * <p>
90  * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable the select effect,
91  * This will not only enable/disable the default dim effect but also subclasses must
92  * respect this flag as well.
93  * </p>
94  */
95 public abstract class RowPresenter extends Presenter {
96 
97     /**
98      * Don't synchronize row view activated status with selected status or expanded status,
99      * application will do its own through {@link RowPresenter.ViewHolder#setActivated(boolean)}.
100      */
101     public static final int SYNC_ACTIVATED_CUSTOM = 0;
102 
103     /**
104      * Synchronizes row view's activated status to expand status of the row view holder.
105      */
106     public static final int SYNC_ACTIVATED_TO_EXPANDED = 1;
107 
108     /**
109      * Synchronizes row view's activated status to selected status of the row view holder.
110      */
111     public static final int SYNC_ACTIVATED_TO_SELECTED = 2;
112 
113     /**
114      * Sets the row view's activated status to true when both expand and selected are true.
115      */
116     public static final int SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED = 3;
117 
118     static class ContainerViewHolder extends Presenter.ViewHolder {
119         /**
120          * wrapped row view holder
121          */
122         final ViewHolder mRowViewHolder;
123 
ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder)124         public ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder) {
125             super(containerView);
126             containerView.addRowView(rowViewHolder.view);
127             if (rowViewHolder.mHeaderViewHolder != null) {
128                 containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view);
129             }
130             mRowViewHolder = rowViewHolder;
131             mRowViewHolder.mContainerViewHolder = this;
132         }
133     }
134 
135     /**
136      * A ViewHolder for a {@link Row}.
137      */
138     public static class ViewHolder extends Presenter.ViewHolder {
139         private static final int ACTIVATED_NOT_ASSIGNED = 0;
140         private static final int ACTIVATED = 1;
141         private static final int NOT_ACTIVATED = 2;
142 
143         ContainerViewHolder mContainerViewHolder;
144         RowHeaderPresenter.ViewHolder mHeaderViewHolder;
145         Row mRow;
146         int mActivated = ACTIVATED_NOT_ASSIGNED;
147         boolean mSelected;
148         boolean mExpanded;
149         boolean mInitialzed;
150         float mSelectLevel = 0f; // initially unselected
151         protected final ColorOverlayDimmer mColorDimmer;
152         private View.OnKeyListener mOnKeyListener;
153         private OnItemViewSelectedListener mOnItemViewSelectedListener;
154         private OnItemViewClickedListener mOnItemViewClickedListener;
155 
156         /**
157          * Constructor for ViewHolder.
158          *
159          * @param view The View bound to the Row.
160          */
ViewHolder(View view)161         public ViewHolder(View view) {
162             super(view);
163             mColorDimmer = ColorOverlayDimmer.createDefault(view.getContext());
164         }
165 
166         /**
167          * Returns the Row bound to the View in this ViewHolder.
168          */
getRow()169         public final Row getRow() {
170             return mRow;
171         }
172 
173         /**
174          * Returns whether the Row is in its expanded state.
175          *
176          * @return true if the Row is expanded, false otherwise.
177          */
isExpanded()178         public final boolean isExpanded() {
179             return mExpanded;
180         }
181 
182         /**
183          * Returns whether the Row is selected.
184          *
185          * @return true if the Row is selected, false otherwise.
186          */
isSelected()187         public final boolean isSelected() {
188             return mSelected;
189         }
190 
191         /**
192          * Returns the current selection level of the Row.
193          */
getSelectLevel()194         public final float getSelectLevel() {
195             return mSelectLevel;
196         }
197 
198         /**
199          * Returns the view holder for the Row header for this Row.
200          */
getHeaderViewHolder()201         public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
202             return mHeaderViewHolder;
203         }
204 
205         /**
206          * Sets the row view's activated status.  The status will be applied to children through
207          * {@link #syncActivatedStatus(View)}.  Application should only call this function
208          * when {@link RowPresenter#getSyncActivatePolicy()} is
209          * {@link RowPresenter#SYNC_ACTIVATED_CUSTOM}; otherwise the value will
210          * be overwritten when expanded or selected status changes.
211          */
setActivated(boolean activated)212         public final void setActivated(boolean activated) {
213             mActivated = activated ? ACTIVATED : NOT_ACTIVATED;
214         }
215 
216         /**
217          * Synchronizes the activated status of view to the last value passed through
218          * {@link RowPresenter.ViewHolder#setActivated(boolean)}. No operation if
219          * {@link RowPresenter.ViewHolder#setActivated(boolean)} is never called.  Normally
220          * application does not need to call this method,  {@link ListRowPresenter} automatically
221          * calls this method when a child is attached to list row.   However if
222          * application writes its own custom RowPresenter, it should call this method
223          * when attaches a child to the row view.
224          */
syncActivatedStatus(View view)225         public final void syncActivatedStatus(View view) {
226             if (mActivated == ACTIVATED) {
227                 view.setActivated(true);
228             } else if (mActivated == NOT_ACTIVATED) {
229                 view.setActivated(false);
230             }
231         }
232 
233         /**
234          * Sets a key listener.
235          */
setOnKeyListener(View.OnKeyListener keyListener)236         public void setOnKeyListener(View.OnKeyListener keyListener) {
237             mOnKeyListener = keyListener;
238         }
239 
240         /**
241          * Returns the key listener.
242          */
getOnKeyListener()243         public View.OnKeyListener getOnKeyListener() {
244             return mOnKeyListener;
245         }
246 
247         /**
248          * Sets the listener for item or row selection.  RowPresenter fires row selection
249          * event with null item.  A subclass of RowPresenter e.g. {@link ListRowPresenter} may
250          * fire a selection event with selected item.
251          */
setOnItemViewSelectedListener(OnItemViewSelectedListener listener)252         public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
253             mOnItemViewSelectedListener = listener;
254         }
255 
256         /**
257          * Returns the listener for item or row selection.
258          */
getOnItemViewSelectedListener()259         public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
260             return mOnItemViewSelectedListener;
261         }
262 
263         /**
264          * Sets the listener for item click event.  RowPresenter does nothing but subclass of
265          * RowPresenter may fire item click event if it has the concept of item.
266          * OnItemViewClickedListener will override {@link View.OnClickListener} that
267          * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
268          */
setOnItemViewClickedListener(OnItemViewClickedListener listener)269         public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
270             mOnItemViewClickedListener = listener;
271         }
272 
273         /**
274          * Returns the listener for item click event.
275          */
getOnItemViewClickedListener()276         public final OnItemViewClickedListener getOnItemViewClickedListener() {
277             return mOnItemViewClickedListener;
278         }
279     }
280 
281     private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();
282 
283     boolean mSelectEffectEnabled = true;
284     int mSyncActivatePolicy = SYNC_ACTIVATED_TO_EXPANDED;
285 
286 
287     /**
288      * Constructs a RowPresenter.
289      */
RowPresenter()290     public RowPresenter() {
291         mHeaderPresenter.setNullItemVisibilityGone(true);
292     }
293 
294     @Override
onCreateViewHolder(ViewGroup parent)295     public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
296         ViewHolder vh = createRowViewHolder(parent);
297         vh.mInitialzed = false;
298         Presenter.ViewHolder result;
299         if (needsRowContainerView()) {
300             RowContainerView containerView = new RowContainerView(parent.getContext());
301             if (mHeaderPresenter != null) {
302                 vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
303                         mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
304             }
305             result = new ContainerViewHolder(containerView, vh);
306         } else {
307             result = vh;
308         }
309         initializeRowViewHolder(vh);
310         if (!vh.mInitialzed) {
311             throw new RuntimeException("super.initializeRowViewHolder() must be called");
312         }
313         return result;
314     }
315 
316     /**
317      * Called to create a ViewHolder object for a Row. Subclasses will override
318      * this method to return a different concrete ViewHolder object.
319      *
320      * @param parent The parent View for the Row's view holder.
321      * @return A ViewHolder for the Row's View.
322      */
createRowViewHolder(ViewGroup parent)323     protected abstract ViewHolder createRowViewHolder(ViewGroup parent);
324 
325     /**
326      * Returns true if the Row view should clip it's children.  The clipChildren
327      * flag is set on view in {@link #initializeRowViewHolder(ViewHolder)}.  Note that
328      * Slide transition or explode transition need turn off clipChildren.
329      * Default value is false.
330      */
isClippingChildren()331     protected boolean isClippingChildren() {
332         return false;
333     }
334 
335     /**
336      * Called after a {@link RowPresenter.ViewHolder} is created for a Row.
337      * Subclasses may override this method and start by calling
338      * super.initializeRowViewHolder(ViewHolder).
339      *
340      * @param vh The ViewHolder to initialize for the Row.
341      */
initializeRowViewHolder(ViewHolder vh)342     protected void initializeRowViewHolder(ViewHolder vh) {
343         vh.mInitialzed = true;
344         if (!isClippingChildren()) {
345             // set clip children to false for slide transition
346             if (vh.view instanceof ViewGroup) {
347                 ((ViewGroup) vh.view).setClipChildren(false);
348             }
349             if (vh.mContainerViewHolder != null) {
350                 ((ViewGroup) vh.mContainerViewHolder.view).setClipChildren(false);
351             }
352         }
353     }
354 
355     /**
356      * Sets the Presenter used for rendering the header. Can be null to disable
357      * header rendering. The method must be called before creating any Row Views.
358      */
setHeaderPresenter(RowHeaderPresenter headerPresenter)359     public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) {
360         mHeaderPresenter = headerPresenter;
361     }
362 
363     /**
364      * Returns the Presenter used for rendering the header, or null if none has been
365      * set.
366      */
getHeaderPresenter()367     public final RowHeaderPresenter getHeaderPresenter() {
368         return mHeaderPresenter;
369     }
370 
371     /**
372      * Returns the {@link RowPresenter.ViewHolder} from the given RowPresenter
373      * ViewHolder.
374      */
getRowViewHolder(Presenter.ViewHolder holder)375     public final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) {
376         if (holder instanceof ContainerViewHolder) {
377             return ((ContainerViewHolder) holder).mRowViewHolder;
378         } else {
379             return (ViewHolder) holder;
380         }
381     }
382 
383     /**
384      * Sets the expanded state of a Row view.
385      *
386      * @param holder The Row ViewHolder to set expanded state on.
387      * @param expanded True if the Row is expanded, false otherwise.
388      */
setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded)389     public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) {
390         ViewHolder rowViewHolder = getRowViewHolder(holder);
391         rowViewHolder.mExpanded = expanded;
392         onRowViewExpanded(rowViewHolder, expanded);
393     }
394 
395     /**
396      * Sets the selected state of a Row view.
397      *
398      * @param holder The Row ViewHolder to set expanded state on.
399      * @param selected True if the Row is expanded, false otherwise.
400      */
setRowViewSelected(Presenter.ViewHolder holder, boolean selected)401     public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) {
402         ViewHolder rowViewHolder = getRowViewHolder(holder);
403         rowViewHolder.mSelected = selected;
404         onRowViewSelected(rowViewHolder, selected);
405     }
406 
407     /**
408      * Called when the row view's expanded state changes.  A subclass may override this method to
409      * respond to expanded state changes of a Row.
410      * The default implementation will hide/show the header view. Subclasses may
411      * make visual changes to the Row View but must not create animation on the
412      * Row view.
413      */
onRowViewExpanded(ViewHolder vh, boolean expanded)414     protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
415         updateHeaderViewVisibility(vh);
416         updateActivateStatus(vh, vh.view);
417     }
418 
419     /**
420      * Updates the view's activate status according to {@link #getSyncActivatePolicy()} and the
421      * selected status and expanded status of the RowPresenter ViewHolder.
422      */
updateActivateStatus(ViewHolder vh, View view)423     private void updateActivateStatus(ViewHolder vh, View view) {
424         switch (mSyncActivatePolicy) {
425             case SYNC_ACTIVATED_TO_EXPANDED:
426                 vh.setActivated(vh.isExpanded());
427                 break;
428             case SYNC_ACTIVATED_TO_SELECTED:
429                 vh.setActivated(vh.isSelected());
430                 break;
431             case SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED:
432                 vh.setActivated(vh.isExpanded() && vh.isSelected());
433                 break;
434         }
435         vh.syncActivatedStatus(view);
436     }
437 
438     /**
439      * Sets the policy of updating row view activated status.  Can be one of:
440      * <li> Default value {@link #SYNC_ACTIVATED_TO_EXPANDED}
441      * <li> {@link #SYNC_ACTIVATED_TO_SELECTED}
442      * <li> {@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED}
443      * <li> {@link #SYNC_ACTIVATED_CUSTOM}
444      */
setSyncActivatePolicy(int syncActivatePolicy)445     public final void setSyncActivatePolicy(int syncActivatePolicy) {
446         mSyncActivatePolicy = syncActivatePolicy;
447     }
448 
449     /**
450      * Returns the policy of updating row view activated status.  Can be one of:
451      * <li> Default value {@link #SYNC_ACTIVATED_TO_EXPANDED}
452      * <li> {@link #SYNC_ACTIVATED_TO_SELECTED}
453      * <li> {@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED}
454      * <li> {@link #SYNC_ACTIVATED_CUSTOM}
455      */
getSyncActivatePolicy()456     public final int getSyncActivatePolicy() {
457         return mSyncActivatePolicy;
458     }
459 
460     /**
461      * This method is only called from
462      * {@link #onRowViewSelected(ViewHolder, boolean)} onRowViewSelected.
463      * The default behavior is to signal row selected events with a null item parameter.
464      * A Subclass of RowPresenter having child items should override this method and dispatch
465      * events with item information.
466      */
dispatchItemSelectedListener(ViewHolder vh, boolean selected)467     protected void dispatchItemSelectedListener(ViewHolder vh, boolean selected) {
468         if (selected) {
469             if (vh.mOnItemViewSelectedListener != null) {
470                 vh.mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
471             }
472         }
473     }
474 
475     /**
476      * Called when the given row view changes selection state.  A subclass may override this to
477      * respond to selected state changes of a Row.  A subclass may make visual changes to Row view
478      * but must not create animation on the Row view.
479      */
onRowViewSelected(ViewHolder vh, boolean selected)480     protected void onRowViewSelected(ViewHolder vh, boolean selected) {
481         dispatchItemSelectedListener(vh, selected);
482         updateHeaderViewVisibility(vh);
483         updateActivateStatus(vh, vh.view);
484     }
485 
updateHeaderViewVisibility(ViewHolder vh)486     private void updateHeaderViewVisibility(ViewHolder vh) {
487         if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) {
488             RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view);
489             containerView.showHeader(vh.isExpanded());
490         }
491     }
492 
493     /**
494      * Sets the current select level to a value between 0 (unselected) and 1 (selected).
495      * Subclasses may override {@link #onSelectLevelChanged(ViewHolder)} to
496      * respond to changes in the selected level.
497      */
setSelectLevel(Presenter.ViewHolder vh, float level)498     public final void setSelectLevel(Presenter.ViewHolder vh, float level) {
499         ViewHolder rowViewHolder = getRowViewHolder(vh);
500         rowViewHolder.mSelectLevel = level;
501         onSelectLevelChanged(rowViewHolder);
502     }
503 
504     /**
505      * Returns the current select level. The value will be between 0 (unselected)
506      * and 1 (selected).
507      */
getSelectLevel(Presenter.ViewHolder vh)508     public final float getSelectLevel(Presenter.ViewHolder vh) {
509         return getRowViewHolder(vh).mSelectLevel;
510     }
511 
512     /**
513      * Callback when the select level changes. The default implementation applies
514      * the select level to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
515      * when {@link #getSelectEffectEnabled()} is true. Subclasses may override
516      * this function and implement a different select effect. In this case,
517      * the method {@link #isUsingDefaultSelectEffect()} should also be overridden to disable
518      * the default dimming effect.
519      */
onSelectLevelChanged(ViewHolder vh)520     protected void onSelectLevelChanged(ViewHolder vh) {
521         if (getSelectEffectEnabled()) {
522             vh.mColorDimmer.setActiveLevel(vh.mSelectLevel);
523             if (vh.mHeaderViewHolder != null) {
524                 mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
525             }
526             if (isUsingDefaultSelectEffect()) {
527                 ((RowContainerView) vh.mContainerViewHolder.view).setForegroundColor(
528                         vh.mColorDimmer.getPaint().getColor());
529             }
530         }
531     }
532 
533     /**
534      * Enables or disables the row selection effect.
535      * This will not only affect the default dim effect, but subclasses must
536      * respect this flag as well.
537      */
setSelectEffectEnabled(boolean applyDimOnSelect)538     public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
539         mSelectEffectEnabled = applyDimOnSelect;
540     }
541 
542     /**
543      * Returns true if the row selection effect is enabled.
544      * This value not only determines whether the default dim implementation is
545      * used, but subclasses must also respect this flag.
546      */
getSelectEffectEnabled()547     public final boolean getSelectEffectEnabled() {
548         return mSelectEffectEnabled;
549     }
550 
551     /**
552      * Returns true if this RowPresenter is using the default dimming effect.
553      * A subclass may (most likely) return false and
554      * override {@link #onSelectLevelChanged(ViewHolder)}.
555      */
isUsingDefaultSelectEffect()556     public boolean isUsingDefaultSelectEffect() {
557         return true;
558     }
559 
needsDefaultSelectEffect()560     final boolean needsDefaultSelectEffect() {
561         return isUsingDefaultSelectEffect() && getSelectEffectEnabled();
562     }
563 
needsRowContainerView()564     final boolean needsRowContainerView() {
565         return mHeaderPresenter != null || needsDefaultSelectEffect();
566     }
567 
568     /**
569      * Returns true if the Row view can draw outside its bounds.
570      */
canDrawOutOfBounds()571     public boolean canDrawOutOfBounds() {
572         return false;
573     }
574 
575     @Override
onBindViewHolder(Presenter.ViewHolder viewHolder, Object item)576     public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
577         onBindRowViewHolder(getRowViewHolder(viewHolder), item);
578     }
579 
580     /**
581      * Binds the given row object to the given ViewHolder.
582      */
onBindRowViewHolder(ViewHolder vh, Object item)583     protected void onBindRowViewHolder(ViewHolder vh, Object item) {
584         vh.mRow = (Row) item;
585         if (vh.mHeaderViewHolder != null) {
586             mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
587         }
588     }
589 
590     @Override
onUnbindViewHolder(Presenter.ViewHolder viewHolder)591     public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
592         onUnbindRowViewHolder(getRowViewHolder(viewHolder));
593     }
594 
595     /**
596      * Unbinds the given ViewHolder.
597      */
onUnbindRowViewHolder(ViewHolder vh)598     protected void onUnbindRowViewHolder(ViewHolder vh) {
599         if (vh.mHeaderViewHolder != null) {
600             mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
601         }
602         vh.mRow = null;
603     }
604 
605     @Override
onViewAttachedToWindow(Presenter.ViewHolder holder)606     public final void onViewAttachedToWindow(Presenter.ViewHolder holder) {
607         onRowViewAttachedToWindow(getRowViewHolder(holder));
608     }
609 
610     /**
611      * Invoked when the row view is attached to the window.
612      */
onRowViewAttachedToWindow(ViewHolder vh)613     protected void onRowViewAttachedToWindow(ViewHolder vh) {
614         if (vh.mHeaderViewHolder != null) {
615             mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
616         }
617     }
618 
619     @Override
onViewDetachedFromWindow(Presenter.ViewHolder holder)620     public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
621         onRowViewDetachedFromWindow(getRowViewHolder(holder));
622     }
623 
624     /**
625      * Invoked when the row view is detached from the window.
626      */
onRowViewDetachedFromWindow(ViewHolder vh)627     protected void onRowViewDetachedFromWindow(ViewHolder vh) {
628         if (vh.mHeaderViewHolder != null) {
629             mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
630         }
631         cancelAnimationsRecursive(vh.view);
632     }
633 
634     /**
635      * Freezes/unfreezes the row, typically used when a transition starts/ends.
636      * This method is called by the fragment, it should not call it directly by the application.
637      */
freeze(ViewHolder holder, boolean freeze)638     public void freeze(ViewHolder holder, boolean freeze) {
639     }
640 
641     /**
642      * Changes the visibility of views.  The entrance transition will be run against the views that
643      * change visibilities.  A subclass may override and begin with calling
644      * super.setEntranceTransitionState().  This method is called by the fragment,
645      * it should not call it directly by the application.
646      */
setEntranceTransitionState(ViewHolder holder, boolean afterTransition)647     public void setEntranceTransitionState(ViewHolder holder, boolean afterTransition) {
648         if (holder.mHeaderViewHolder != null &&
649                 holder.mHeaderViewHolder.view.getVisibility() != View.GONE) {
650             holder.mHeaderViewHolder.view.setVisibility(afterTransition ?
651                     View.VISIBLE : View.INVISIBLE);
652         }
653     }
654 }
655