RowPresenter.java

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package androidx.leanback.widget;

import android.view.View;
import android.view.ViewGroup;

import androidx.leanback.app.HeadersFragment;
import androidx.leanback.graphics.ColorOverlayDimmer;

/**
 * An abstract {@link Presenter} that renders an Object in RowsFragment, the object can be
 * subclass {@link Row} or a generic one.  When the object is not {@link Row} class,
 * {@link ViewHolder#getRow()} returns null.
 *
 * <h3>Customize UI widgets</h3>
 * When a subclass of RowPresenter adds UI widgets, it should subclass
 * {@link RowPresenter.ViewHolder} and override {@link #createRowViewHolder(ViewGroup)}
 * and {@link #initializeRowViewHolder(ViewHolder)}. The subclass must use layout id
 * "row_content" for the widget that will be aligned to the title of any {@link HeadersFragment}
 * that may exist in the parent fragment. RowPresenter contains an optional and
 * replaceable {@link RowHeaderPresenter} that renders the header. You can disable
 * the default rendering or replace the Presenter with a new header presenter
 * by calling {@link #setHeaderPresenter(RowHeaderPresenter)}.
 *
 * <h3>UI events from fragments</h3>
 * RowPresenter receives calls from its parent (typically a Fragment) when:
 * <ul>
 * <li>
 * A row is selected via {@link #setRowViewSelected(Presenter.ViewHolder, boolean)}.  The event
 * is triggered immediately when there is a row selection change before the selection
 * animation is started.  Selected status may control activated status of the row (see
 * "Activated status" below).
 * Subclasses of RowPresenter may override {@link #onRowViewSelected(ViewHolder, boolean)}.
 * </li>
 * <li>
 * A row is expanded to full height via {@link #setRowViewExpanded(Presenter.ViewHolder, boolean)}
 * when BrowseFragment hides fast lane on the left.
 * The event is triggered immediately before the expand animation is started.
 * Row title is shown when row is expanded.  Expanded status may control activated status
 * of the row (see "Activated status" below).
 * Subclasses of RowPresenter may override {@link #onRowViewExpanded(ViewHolder, boolean)}.
 * </li>
 * </ul>
 *
 * <h3>Activated status</h3>
 * The activated status of a row is applied to the row view and its children via
 * {@link View#setActivated(boolean)}.
 * The activated status is typically used to control {@link BaseCardView} info region visibility.
 * The row's activated status can be controlled by selected status and/or expanded status.
 * Call {@link #setSyncActivatePolicy(int)} and choose one of the four policies:
 * <ul>
 * <li>{@link #SYNC_ACTIVATED_TO_EXPANDED} Activated status is synced with row expanded status</li>
 * <li>{@link #SYNC_ACTIVATED_TO_SELECTED} Activated status is synced with row selected status</li>
 * <li>{@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED} Activated status is set to true
 *     when both expanded and selected status are true</li>
 * <li>{@link #SYNC_ACTIVATED_CUSTOM} Activated status is not controlled by selected status
 *     or expanded status, application can control activated status by its own.
 *     Application should call {@link RowPresenter.ViewHolder#setActivated(boolean)} to change
 *     activated status of row view.
 * </li>
 * </ul>
 *
 * <h3>User events</h3>
 * RowPresenter provides {@link OnItemViewSelectedListener} and {@link OnItemViewClickedListener}.
 * If a subclass wants to add its own {@link View.OnFocusChangeListener} or
 * {@link View.OnClickListener}, it must do that in {@link #createRowViewHolder(ViewGroup)}
 * to be properly chained by the library.  Adding View listeners after
 * {@link #createRowViewHolder(ViewGroup)} is undefined and may result in
 * incorrect behavior by the library's listeners.
 *
 * <h3>Selection animation</h3>
 * <p>
 * When a user scrolls through rows, a fragment will initiate animation and call
 * {@link #setSelectLevel(Presenter.ViewHolder, float)} with float value between
 * 0 and 1.  By default, the RowPresenter draws a dim overlay on top of the row
 * view for views that are not selected. Subclasses may override this default effect
 * by having {@link #isUsingDefaultSelectEffect()} return false and overriding
 * {@link #onSelectLevelChanged(ViewHolder)} to apply a different selection effect.
 * </p>
 * <p>
 * Call {@link #setSelectEffectEnabled(boolean)} to enable/disable the select effect,
 * This will not only enable/disable the default dim effect but also subclasses must
 * respect this flag as well.
 * </p>
 */
public abstract class RowPresenter extends Presenter {

    /**
     * Don't synchronize row view activated status with selected status or expanded status,
     * application will do its own through {@link RowPresenter.ViewHolder#setActivated(boolean)}.
     */
    public static final int SYNC_ACTIVATED_CUSTOM = 0;

    /**
     * Synchronizes row view's activated status to expand status of the row view holder.
     */
    public static final int SYNC_ACTIVATED_TO_EXPANDED = 1;

    /**
     * Synchronizes row view's activated status to selected status of the row view holder.
     */
    public static final int SYNC_ACTIVATED_TO_SELECTED = 2;

    /**
     * Sets the row view's activated status to true when both expand and selected are true.
     */
    public static final int SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED = 3;

    static class ContainerViewHolder extends Presenter.ViewHolder {
        /**
         * wrapped row view holder
         */
        final ViewHolder mRowViewHolder;

        public ContainerViewHolder(RowContainerView containerView, ViewHolder rowViewHolder) {
            super(containerView);
            containerView.addRowView(rowViewHolder.view);
            if (rowViewHolder.mHeaderViewHolder != null) {
                containerView.addHeaderView(rowViewHolder.mHeaderViewHolder.view);
            }
            mRowViewHolder = rowViewHolder;
            mRowViewHolder.mContainerViewHolder = this;
        }
    }

    /**
     * A ViewHolder for a {@link Row}.
     */
    public static class ViewHolder extends Presenter.ViewHolder {
        private static final int ACTIVATED_NOT_ASSIGNED = 0;
        private static final int ACTIVATED = 1;
        private static final int NOT_ACTIVATED = 2;

        ContainerViewHolder mContainerViewHolder;
        RowHeaderPresenter.ViewHolder mHeaderViewHolder;
        Row mRow;
        Object mRowObject;
        int mActivated = ACTIVATED_NOT_ASSIGNED;
        boolean mSelected;
        boolean mExpanded;
        boolean mInitialzed;
        float mSelectLevel = 0f; // initially unselected
        protected final ColorOverlayDimmer mColorDimmer;
        private View.OnKeyListener mOnKeyListener;
        BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
        private BaseOnItemViewClickedListener mOnItemViewClickedListener;

        /**
         * Constructor for ViewHolder.
         *
         * @param view The View bound to the Row.
         */
        public ViewHolder(View view) {
            super(view);
            mColorDimmer = ColorOverlayDimmer.createDefault(view.getContext());
        }

        /**
         * Returns the row bound to this ViewHolder. Returns null if the row is not an instance of
         * {@link Row}.
         * @return The row bound to this ViewHolder. Returns null if the row is not an instance of
         * {@link Row}.
         */
        public final Row getRow() {
            return mRow;
        }

        /**
         * Returns the Row object bound to this ViewHolder.
         * @return The row object bound to this ViewHolder.
         */
        public final Object getRowObject() {
            return mRowObject;
        }

        /**
         * Returns whether the Row is in its expanded state.
         *
         * @return true if the Row is expanded, false otherwise.
         */
        public final boolean isExpanded() {
            return mExpanded;
        }

        /**
         * Returns whether the Row is selected.
         *
         * @return true if the Row is selected, false otherwise.
         */
        public final boolean isSelected() {
            return mSelected;
        }

        /**
         * Returns the current selection level of the Row.
         */
        public final float getSelectLevel() {
            return mSelectLevel;
        }

        /**
         * Returns the view holder for the Row header for this Row.
         */
        public final RowHeaderPresenter.ViewHolder getHeaderViewHolder() {
            return mHeaderViewHolder;
        }

        /**
         * Sets the row view's activated status.  The status will be applied to children through
         * {@link #syncActivatedStatus(View)}.  Application should only call this function
         * when {@link RowPresenter#getSyncActivatePolicy()} is
         * {@link RowPresenter#SYNC_ACTIVATED_CUSTOM}; otherwise the value will
         * be overwritten when expanded or selected status changes.
         */
        public final void setActivated(boolean activated) {
            mActivated = activated ? ACTIVATED : NOT_ACTIVATED;
        }

        /**
         * Synchronizes the activated status of view to the last value passed through
         * {@link RowPresenter.ViewHolder#setActivated(boolean)}. No operation if
         * {@link RowPresenter.ViewHolder#setActivated(boolean)} is never called.  Normally
         * application does not need to call this method,  {@link ListRowPresenter} automatically
         * calls this method when a child is attached to list row.   However if
         * application writes its own custom RowPresenter, it should call this method
         * when attaches a child to the row view.
         */
        public final void syncActivatedStatus(View view) {
            if (mActivated == ACTIVATED) {
                view.setActivated(true);
            } else if (mActivated == NOT_ACTIVATED) {
                view.setActivated(false);
            }
        }

        /**
         * Sets a key listener.
         */
        public void setOnKeyListener(View.OnKeyListener keyListener) {
            mOnKeyListener = keyListener;
        }

        /**
         * Returns the key listener.
         */
        public View.OnKeyListener getOnKeyListener() {
            return mOnKeyListener;
        }

        /**
         * Sets the listener for item or row selection.  RowPresenter fires row selection
         * event with null item.  A subclass of RowPresenter e.g. {@link ListRowPresenter} may
         * fire a selection event with selected item.
         */
        public final void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
            mOnItemViewSelectedListener = listener;
        }

        /**
         * Returns the listener for item or row selection.
         */
        public final BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
            return mOnItemViewSelectedListener;
        }

        /**
         * Sets the listener for item click event.  RowPresenter does nothing but subclass of
         * RowPresenter may fire item click event if it has the concept of item.
         * OnItemViewClickedListener will override {@link View.OnClickListener} that
         * item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
         */
        public final void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
            mOnItemViewClickedListener = listener;
        }

        /**
         * Returns the listener for item click event.
         */
        public final BaseOnItemViewClickedListener getOnItemViewClickedListener() {
            return mOnItemViewClickedListener;
        }
        /**
         * Return {@link ViewHolder} of currently selected item inside a row ViewHolder.
         * @return The selected item's ViewHolder.
         */
        public Presenter.ViewHolder getSelectedItemViewHolder() {
            return null;
        }

        /**
         * Return currently selected item inside a row ViewHolder.
         * @return The selected item.
         */
        public Object getSelectedItem() {
            return null;
        }
    }

    private RowHeaderPresenter mHeaderPresenter = new RowHeaderPresenter();

    boolean mSelectEffectEnabled = true;
    int mSyncActivatePolicy = SYNC_ACTIVATED_TO_EXPANDED;


    /**
     * Constructs a RowPresenter.
     */
    public RowPresenter() {
        mHeaderPresenter.setNullItemVisibilityGone(true);
    }

    @Override
    public final Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
        ViewHolder vh = createRowViewHolder(parent);
        vh.mInitialzed = false;
        Presenter.ViewHolder result;
        if (needsRowContainerView()) {
            RowContainerView containerView = new RowContainerView(parent.getContext());
            if (mHeaderPresenter != null) {
                vh.mHeaderViewHolder = (RowHeaderPresenter.ViewHolder)
                        mHeaderPresenter.onCreateViewHolder((ViewGroup) vh.view);
            }
            result = new ContainerViewHolder(containerView, vh);
        } else {
            result = vh;
        }
        initializeRowViewHolder(vh);
        if (!vh.mInitialzed) {
            throw new RuntimeException("super.initializeRowViewHolder() must be called");
        }
        return result;
    }

    /**
     * Called to create a ViewHolder object for a Row. Subclasses will override
     * this method to return a different concrete ViewHolder object.
     *
     * @param parent The parent View for the Row's view holder.
     * @return A ViewHolder for the Row's View.
     */
    protected abstract ViewHolder createRowViewHolder(ViewGroup parent);

    /**
     * Returns true if the Row view should clip its children.  The clipChildren
     * flag is set on view in {@link #initializeRowViewHolder(ViewHolder)}.  Note that
     * Slide transition or explode transition need turn off clipChildren.
     * Default value is false.
     */
    protected boolean isClippingChildren() {
        return false;
    }

    /**
     * Called after a {@link RowPresenter.ViewHolder} is created for a Row.
     * Subclasses may override this method and start by calling
     * super.initializeRowViewHolder(ViewHolder).
     *
     * @param vh The ViewHolder to initialize for the Row.
     */
    protected void initializeRowViewHolder(ViewHolder vh) {
        vh.mInitialzed = true;
        if (!isClippingChildren()) {
            // set clip children to false for slide transition
            if (vh.view instanceof ViewGroup) {
                ((ViewGroup) vh.view).setClipChildren(false);
            }
            if (vh.mContainerViewHolder != null) {
                ((ViewGroup) vh.mContainerViewHolder.view).setClipChildren(false);
            }
        }
    }

    /**
     * Sets the Presenter used for rendering the header. Can be null to disable
     * header rendering. The method must be called before creating any Row Views.
     */
    public final void setHeaderPresenter(RowHeaderPresenter headerPresenter) {
        mHeaderPresenter = headerPresenter;
    }

    /**
     * Returns the Presenter used for rendering the header, or null if none has been
     * set.
     */
    public final RowHeaderPresenter getHeaderPresenter() {
        return mHeaderPresenter;
    }

    /**
     * Returns the {@link RowPresenter.ViewHolder} from the given RowPresenter
     * ViewHolder.
     */
    public final ViewHolder getRowViewHolder(Presenter.ViewHolder holder) {
        if (holder instanceof ContainerViewHolder) {
            return ((ContainerViewHolder) holder).mRowViewHolder;
        } else {
            return (ViewHolder) holder;
        }
    }

    /**
     * Sets the expanded state of a Row view.
     *
     * @param holder The Row ViewHolder to set expanded state on.
     * @param expanded True if the Row is expanded, false otherwise.
     */
    public final void setRowViewExpanded(Presenter.ViewHolder holder, boolean expanded) {
        ViewHolder rowViewHolder = getRowViewHolder(holder);
        rowViewHolder.mExpanded = expanded;
        onRowViewExpanded(rowViewHolder, expanded);
    }

    /**
     * Sets the selected state of a Row view.
     *
     * @param holder The Row ViewHolder to set expanded state on.
     * @param selected True if the Row is expanded, false otherwise.
     */
    public final void setRowViewSelected(Presenter.ViewHolder holder, boolean selected) {
        ViewHolder rowViewHolder = getRowViewHolder(holder);
        rowViewHolder.mSelected = selected;
        onRowViewSelected(rowViewHolder, selected);
    }

    /**
     * Called when the row view's expanded state changes.  A subclass may override this method to
     * respond to expanded state changes of a Row.
     * The default implementation will hide/show the header view. Subclasses may
     * make visual changes to the Row View but must not create animation on the
     * Row view.
     */
    protected void onRowViewExpanded(ViewHolder vh, boolean expanded) {
        updateHeaderViewVisibility(vh);
        updateActivateStatus(vh, vh.view);
    }

    /**
     * Updates the view's activate status according to {@link #getSyncActivatePolicy()} and the
     * selected status and expanded status of the RowPresenter ViewHolder.
     */
    private void updateActivateStatus(ViewHolder vh, View view) {
        switch (mSyncActivatePolicy) {
            case SYNC_ACTIVATED_TO_EXPANDED:
                vh.setActivated(vh.isExpanded());
                break;
            case SYNC_ACTIVATED_TO_SELECTED:
                vh.setActivated(vh.isSelected());
                break;
            case SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED:
                vh.setActivated(vh.isExpanded() && vh.isSelected());
                break;
        }
        vh.syncActivatedStatus(view);
    }

    /**
     * Sets the policy of updating row view activated status.  Can be one of:
     * <li> Default value {@link #SYNC_ACTIVATED_TO_EXPANDED}
     * <li> {@link #SYNC_ACTIVATED_TO_SELECTED}
     * <li> {@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED}
     * <li> {@link #SYNC_ACTIVATED_CUSTOM}
     */
    public final void setSyncActivatePolicy(int syncActivatePolicy) {
        mSyncActivatePolicy = syncActivatePolicy;
    }

    /**
     * Returns the policy of updating row view activated status.  Can be one of:
     * <li> Default value {@link #SYNC_ACTIVATED_TO_EXPANDED}
     * <li> {@link #SYNC_ACTIVATED_TO_SELECTED}
     * <li> {@link #SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED}
     * <li> {@link #SYNC_ACTIVATED_CUSTOM}
     */
    public final int getSyncActivatePolicy() {
        return mSyncActivatePolicy;
    }

    /**
     * This method is only called from
     * {@link #onRowViewSelected(ViewHolder, boolean)} onRowViewSelected.
     * The default behavior is to signal row selected events with a null item parameter.
     * A Subclass of RowPresenter having child items should override this method and dispatch
     * events with item information.
     */
    protected void dispatchItemSelectedListener(ViewHolder vh, boolean selected) {
        if (selected) {
            if (vh.mOnItemViewSelectedListener != null) {
                vh.mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRowObject());
            }
        }
    }

    /**
     * Called when the given row view changes selection state.  A subclass may override this to
     * respond to selected state changes of a Row.  A subclass may make visual changes to Row view
     * but must not create animation on the Row view.
     */
    protected void onRowViewSelected(ViewHolder vh, boolean selected) {
        dispatchItemSelectedListener(vh, selected);
        updateHeaderViewVisibility(vh);
        updateActivateStatus(vh, vh.view);
    }

    private void updateHeaderViewVisibility(ViewHolder vh) {
        if (mHeaderPresenter != null && vh.mHeaderViewHolder != null) {
            RowContainerView containerView = ((RowContainerView) vh.mContainerViewHolder.view);
            containerView.showHeader(vh.isExpanded());
        }
    }

    /**
     * Sets the current select level to a value between 0 (unselected) and 1 (selected).
     * Subclasses may override {@link #onSelectLevelChanged(ViewHolder)} to
     * respond to changes in the selected level.
     */
    public final void setSelectLevel(Presenter.ViewHolder vh, float level) {
        ViewHolder rowViewHolder = getRowViewHolder(vh);
        rowViewHolder.mSelectLevel = level;
        onSelectLevelChanged(rowViewHolder);
    }

    /**
     * Returns the current select level. The value will be between 0 (unselected)
     * and 1 (selected).
     */
    public final float getSelectLevel(Presenter.ViewHolder vh) {
        return getRowViewHolder(vh).mSelectLevel;
    }

    /**
     * Callback when the select level changes. The default implementation applies
     * the select level to {@link RowHeaderPresenter#setSelectLevel(RowHeaderPresenter.ViewHolder, float)}
     * when {@link #getSelectEffectEnabled()} is true. Subclasses may override
     * this function and implement a different select effect. In this case,
     * the method {@link #isUsingDefaultSelectEffect()} should also be overridden to disable
     * the default dimming effect.
     */
    protected void onSelectLevelChanged(ViewHolder vh) {
        if (getSelectEffectEnabled()) {
            vh.mColorDimmer.setActiveLevel(vh.mSelectLevel);
            if (vh.mHeaderViewHolder != null) {
                mHeaderPresenter.setSelectLevel(vh.mHeaderViewHolder, vh.mSelectLevel);
            }
            if (isUsingDefaultSelectEffect()) {
                ((RowContainerView) vh.mContainerViewHolder.view).setForegroundColor(
                        vh.mColorDimmer.getPaint().getColor());
            }
        }
    }

    /**
     * Enables or disables the row selection effect.
     * This will not only affect the default dim effect, but subclasses must
     * respect this flag as well.
     */
    public final void setSelectEffectEnabled(boolean applyDimOnSelect) {
        mSelectEffectEnabled = applyDimOnSelect;
    }

    /**
     * Returns true if the row selection effect is enabled.
     * This value not only determines whether the default dim implementation is
     * used, but subclasses must also respect this flag.
     */
    public final boolean getSelectEffectEnabled() {
        return mSelectEffectEnabled;
    }

    /**
     * Returns true if this RowPresenter is using the default dimming effect.
     * A subclass may (most likely) return false and
     * override {@link #onSelectLevelChanged(ViewHolder)}.
     */
    public boolean isUsingDefaultSelectEffect() {
        return true;
    }

    final boolean needsDefaultSelectEffect() {
        return isUsingDefaultSelectEffect() && getSelectEffectEnabled();
    }

    final boolean needsRowContainerView() {
        return mHeaderPresenter != null || needsDefaultSelectEffect();
    }

    @Override
    public final void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
        onBindRowViewHolder(getRowViewHolder(viewHolder), item);
    }

    /**
     * Binds the given row object to the given ViewHolder.
     * Derived classes of {@link RowPresenter} overriding
     * {@link #onBindRowViewHolder(ViewHolder, Object)} must call through the super class's
     * implementation of this method.
     */
    protected void onBindRowViewHolder(ViewHolder vh, Object item) {
        vh.mRowObject = item;
        vh.mRow = item instanceof Row ? (Row) item : null;
        if (vh.mHeaderViewHolder != null && vh.getRow() != null) {
            mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
        }
    }

    @Override
    public final void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
        onUnbindRowViewHolder(getRowViewHolder(viewHolder));
    }

    /**
     * Unbinds the given ViewHolder.
     * Derived classes of {@link RowPresenter} overriding {@link #onUnbindRowViewHolder(ViewHolder)}
     * must call through the super class's implementation of this method.
     */
    protected void onUnbindRowViewHolder(ViewHolder vh) {
        if (vh.mHeaderViewHolder != null) {
            mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
        }
        vh.mRow = null;
        vh.mRowObject = null;
    }

    @Override
    public final void onViewAttachedToWindow(Presenter.ViewHolder holder) {
        onRowViewAttachedToWindow(getRowViewHolder(holder));
    }

    /**
     * Invoked when the row view is attached to the window.
     */
    protected void onRowViewAttachedToWindow(ViewHolder vh) {
        if (vh.mHeaderViewHolder != null) {
            mHeaderPresenter.onViewAttachedToWindow(vh.mHeaderViewHolder);
        }
    }

    @Override
    public final void onViewDetachedFromWindow(Presenter.ViewHolder holder) {
        onRowViewDetachedFromWindow(getRowViewHolder(holder));
    }

    /**
     * Invoked when the row view is detached from the window.
     */
    protected void onRowViewDetachedFromWindow(ViewHolder vh) {
        if (vh.mHeaderViewHolder != null) {
            mHeaderPresenter.onViewDetachedFromWindow(vh.mHeaderViewHolder);
        }
        cancelAnimationsRecursive(vh.view);
    }

    /**
     * Freezes/unfreezes the row, typically used when a transition starts/ends.
     * This method is called by the fragment, it should not call it directly by the application.
     */
    public void freeze(ViewHolder holder, boolean freeze) {
    }

    /**
     * Changes the visibility of views.  The entrance transition will be run against the views that
     * change visibilities.  A subclass may override and begin with calling
     * super.setEntranceTransitionState().  This method is called by the fragment,
     * it should not be called directly by the application.
     *
     * @param holder         The ViewHolder of the row.
     * @param afterEntrance  true if children of row participating in entrance transition
     *                       should be set to visible, false otherwise.
     */
    public void setEntranceTransitionState(ViewHolder holder, boolean afterEntrance) {
        if (holder.mHeaderViewHolder != null
                && holder.mHeaderViewHolder.view.getVisibility() != View.GONE) {
            holder.mHeaderViewHolder.view.setVisibility(afterEntrance
                    ? View.VISIBLE : View.INVISIBLE);
        }
    }
}