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.recyclerview.widget.RecyclerView;
20 
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 
25 /**
26  * A Presenter is used to generate {@link View}s and bind Objects to them on
27  * demand. It is closely related to the concept of an {@link
28  * RecyclerView.Adapter RecyclerView.Adapter}, but is
29  * not position-based.  The leanback framework implements the adapter concept using
30  * {@link ObjectAdapter} which refers to a Presenter (or {@link PresenterSelector}) instance.
31  *
32  * <p>
33  * Presenters should be stateless.  Presenters typically extend {@link ViewHolder} to store all
34  * necessary view state information, such as references to child views to be used when
35  * binding to avoid expensive calls to {@link View#findViewById(int)}.
36  * </p>
37  *
38  * <p>
39  * A trivial Presenter that takes a string and renders it into a {@link
40  * android.widget.TextView TextView}:
41  *
42  * <pre class="prettyprint">
43  * public class StringTextViewPresenter extends Presenter {
44  *     // This class does not need a custom ViewHolder, since it does not use
45  *     // a complex layout.
46  *
47  *     {@literal @}Override
48  *     public ViewHolder onCreateViewHolder(ViewGroup parent) {
49  *         return new ViewHolder(new TextView(parent.getContext()));
50  *     }
51  *
52  *     {@literal @}Override
53  *     public void onBindViewHolder(ViewHolder viewHolder, Object item) {
54  *         String str = (String) item;
55  *         TextView textView = (TextView) viewHolder.mView;
56  *
57  *         textView.setText(item);
58  *     }
59  *
60  *     {@literal @}Override
61  *     public void onUnbindViewHolder(ViewHolder viewHolder) {
62  *         // Nothing to unbind for TextView, but if this viewHolder had
63  *         // allocated bitmaps, they can be released here.
64  *     }
65  * }
66  * </pre>
67  * In addition to view creation and binding, Presenter allows dynamic interface (facet) to
68  * be added: {@link #setFacet(Class, Object)}.  Supported facets:
69  * <li> {@link ItemAlignmentFacet} is used by {@link HorizontalGridView} and
70  * {@link VerticalGridView} to customize child alignment.
71  */
72 public abstract class Presenter implements FacetProvider {
73     /**
74      * ViewHolder can be subclassed and used to cache any view accessors needed
75      * to improve binding performance (for example, results of findViewById)
76      * without needing to subclass a View.
77      */
78     public static class ViewHolder implements FacetProvider {
79         public final View view;
80         private Map<Class, Object> mFacets;
81 
ViewHolder(View view)82         public ViewHolder(View view) {
83             this.view = view;
84         }
85 
86         @Override
getFacet(Class<?> facetClass)87         public final Object getFacet(Class<?> facetClass) {
88             if (mFacets == null) {
89                 return null;
90             }
91             return mFacets.get(facetClass);
92         }
93 
94         /**
95          * Sets dynamic implemented facet in addition to basic ViewHolder functions.
96          * @param facetClass   Facet classes to query,  can be class of {@link ItemAlignmentFacet}.
97          * @param facetImpl  Facet implementation.
98          */
setFacet(Class<?> facetClass, Object facetImpl)99         public final void setFacet(Class<?> facetClass, Object facetImpl) {
100             if (mFacets == null) {
101                 mFacets = new HashMap<Class, Object>();
102             }
103             mFacets.put(facetClass, facetImpl);
104         }
105     }
106 
107     /**
108      * Base class to perform a task on Presenter.ViewHolder.
109      */
110     public static abstract class ViewHolderTask {
111         /**
112          * Called to perform a task on view holder.
113          * @param holder The view holder to perform task.
114          */
run(Presenter.ViewHolder holder)115         public void run(Presenter.ViewHolder holder) {
116         }
117     }
118 
119     private Map<Class, Object> mFacets;
120 
121     /**
122      * Creates a new {@link View}.
123      */
onCreateViewHolder(ViewGroup parent)124     public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
125 
126     /**
127      * Binds a {@link View} to an item.
128      */
onBindViewHolder(ViewHolder viewHolder, Object item)129     public abstract void onBindViewHolder(ViewHolder viewHolder, Object item);
130 
131     /**
132      * Binds a {@link View} to an item with a list of payloads.
133      * @param viewHolder  The ViewHolder which should be updated to represent the contents of the
134      *                    item at the given position in the data set.
135      * @param item        The item which should be bound to view holder.
136      * @param payloads    A non-null list of merged payloads. Can be empty list if requires full
137      *                    update.
138      */
onBindViewHolder(ViewHolder viewHolder, Object item, List<Object> payloads)139     public void onBindViewHolder(ViewHolder viewHolder, Object item, List<Object> payloads) {
140         onBindViewHolder(viewHolder, item);
141     }
142 
143     /**
144      * Unbinds a {@link View} from an item. Any expensive references may be
145      * released here, and any fields that are not bound for every item should be
146      * cleared here.
147      */
onUnbindViewHolder(ViewHolder viewHolder)148     public abstract void onUnbindViewHolder(ViewHolder viewHolder);
149 
150     /**
151      * Called when a view created by this presenter has been attached to a window.
152      *
153      * <p>This can be used as a reasonable signal that the view is about to be seen
154      * by the user. If the adapter previously freed any resources in
155      * {@link #onViewDetachedFromWindow(ViewHolder)}
156      * those resources should be restored here.</p>
157      *
158      * @param holder Holder of the view being attached
159      */
onViewAttachedToWindow(ViewHolder holder)160     public void onViewAttachedToWindow(ViewHolder holder) {
161     }
162 
163     /**
164      * Called when a view created by this presenter has been detached from its window.
165      *
166      * <p>Becoming detached from the window is not necessarily a permanent condition;
167      * the consumer of an presenter's views may choose to cache views offscreen while they
168      * are not visible, attaching and detaching them as appropriate.</p>
169      *
170      * Any view property animations should be cancelled here or the view may fail
171      * to be recycled.
172      *
173      * @param holder Holder of the view being detached
174      */
onViewDetachedFromWindow(ViewHolder holder)175     public void onViewDetachedFromWindow(ViewHolder holder) {
176         // If there are view property animations running then RecyclerView won't recycle.
177         cancelAnimationsRecursive(holder.view);
178     }
179 
180     /**
181      * Utility method for removing all running animations on a view.
182      */
cancelAnimationsRecursive(View view)183     protected static void cancelAnimationsRecursive(View view) {
184         if (view != null && view.hasTransientState()) {
185             view.animate().cancel();
186             if (view instanceof ViewGroup) {
187                 final int count = ((ViewGroup) view).getChildCount();
188                 for (int i = 0; view.hasTransientState() && i < count; i++) {
189                     cancelAnimationsRecursive(((ViewGroup) view).getChildAt(i));
190                 }
191             }
192         }
193     }
194 
195     /**
196      * Called to set a click listener for the given view holder.
197      *
198      * The default implementation sets the click listener on the root view in the view holder.
199      * If the root view isn't focusable this method should be overridden to set the listener
200      * on the appropriate focusable child view(s).
201      *
202      * @param holder The view holder containing the view(s) on which the listener should be set.
203      * @param listener The click listener to be set.
204      */
setOnClickListener(ViewHolder holder, View.OnClickListener listener)205     public void setOnClickListener(ViewHolder holder, View.OnClickListener listener) {
206         holder.view.setOnClickListener(listener);
207     }
208 
209     @Override
getFacet(Class<?> facetClass)210     public final Object getFacet(Class<?> facetClass) {
211         if (mFacets == null) {
212             return null;
213         }
214         return mFacets.get(facetClass);
215     }
216 
217     /**
218      * Sets dynamic implemented facet in addition to basic Presenter functions.
219      * @param facetClass   Facet classes to query,  can be class of {@link ItemAlignmentFacet}.
220      * @param facetImpl  Facet implementation.
221      */
setFacet(Class<?> facetClass, Object facetImpl)222     public final void setFacet(Class<?> facetClass, Object facetImpl) {
223         if (mFacets == null) {
224             mFacets = new HashMap<Class, Object>();
225         }
226         mFacets.put(facetClass, facetImpl);
227     }
228 }
229