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