/* * 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 android.support.v17.leanback.widget; import android.support.v17.leanback.app.HeadersFragment; import android.support.v17.leanback.graphics.ColorOverlayDimmer; import android.view.View; import android.view.ViewGroup; /** * 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. * *
* 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. *
** 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. *
*/ 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; } } 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 it's 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: *