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