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 */
14package androidx.leanback.widget;
15
16import android.view.View;
17import android.view.ViewGroup;
18
19import androidx.recyclerview.widget.RecyclerView;
20
21import java.util.HashMap;
22import java.util.List;
23import 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 */
72public 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
82        public ViewHolder(View view) {
83            this.view = view;
84        }
85
86        @Override
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         */
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         */
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     */
124    public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
125
126    /**
127     * Binds a {@link View} to an item.
128     */
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     */
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     */
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     */
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     */
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     */
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     */
205    public void setOnClickListener(ViewHolder holder, View.OnClickListener listener) {
206        holder.view.setOnClickListener(listener);
207    }
208
209    @Override
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     */
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