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