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 android.support.v17.leanback.widget;
15
16import android.view.View;
17import android.view.ViewGroup;
18
19import java.util.HashMap;
20import 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 */
69public 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
79        public ViewHolder(View view) {
80            this.view = view;
81        }
82
83        @Override
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         */
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    /**
105     * Base class to perform a task on Presenter.ViewHolder.
106     */
107    public static abstract class ViewHolderTask {
108        /**
109         * Called to perform a task on view holder.
110         * @param holder The view holder to perform task.
111         */
112        public void run(Presenter.ViewHolder holder) {
113        }
114    }
115
116    private Map<Class, Object> mFacets;
117
118    /**
119     * Creates a new {@link View}.
120     */
121    public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
122
123    /**
124     * Binds a {@link View} to an item.
125     */
126    public abstract void onBindViewHolder(ViewHolder viewHolder, Object item);
127
128    /**
129     * Unbinds a {@link View} from an item. Any expensive references may be
130     * released here, and any fields that are not bound for every item should be
131     * cleared here.
132     */
133    public abstract void onUnbindViewHolder(ViewHolder viewHolder);
134
135    /**
136     * Called when a view created by this presenter has been attached to a window.
137     *
138     * <p>This can be used as a reasonable signal that the view is about to be seen
139     * by the user. If the adapter previously freed any resources in
140     * {@link #onViewDetachedFromWindow(ViewHolder)}
141     * those resources should be restored here.</p>
142     *
143     * @param holder Holder of the view being attached
144     */
145    public void onViewAttachedToWindow(ViewHolder holder) {
146    }
147
148    /**
149     * Called when a view created by this presenter has been detached from its window.
150     *
151     * <p>Becoming detached from the window is not necessarily a permanent condition;
152     * the consumer of an presenter's views may choose to cache views offscreen while they
153     * are not visible, attaching and detaching them as appropriate.</p>
154     *
155     * Any view property animations should be cancelled here or the view may fail
156     * to be recycled.
157     *
158     * @param holder Holder of the view being detached
159     */
160    public void onViewDetachedFromWindow(ViewHolder holder) {
161        // If there are view property animations running then RecyclerView won't recycle.
162        cancelAnimationsRecursive(holder.view);
163    }
164
165    /**
166     * Utility method for removing all running animations on a view.
167     */
168    protected static void cancelAnimationsRecursive(View view) {
169        if (view != null && view.hasTransientState()) {
170            view.animate().cancel();
171            if (view instanceof ViewGroup) {
172                final int count = ((ViewGroup) view).getChildCount();
173                for (int i = 0; view.hasTransientState() && i < count; i++) {
174                    cancelAnimationsRecursive(((ViewGroup) view).getChildAt(i));
175                }
176            }
177        }
178    }
179
180    /**
181     * Called to set a click listener for the given view holder.
182     *
183     * The default implementation sets the click listener on the root view in the view holder.
184     * If the root view isn't focusable this method should be overridden to set the listener
185     * on the appropriate focusable child view(s).
186     *
187     * @param holder The view holder containing the view(s) on which the listener should be set.
188     * @param listener The click listener to be set.
189     */
190    public void setOnClickListener(ViewHolder holder, View.OnClickListener listener) {
191        holder.view.setOnClickListener(listener);
192    }
193
194    @Override
195    public final Object getFacet(Class<?> facetClass) {
196        if (mFacets == null) {
197            return null;
198        }
199        return mFacets.get(facetClass);
200    }
201
202    /**
203     * Sets dynamic implemented facet in addition to basic Presenter functions.
204     * @param facetClass   Facet classes to query,  can be class of {@link ItemAlignmentFacet}.
205     * @param facetImpl  Facet implementation.
206     */
207    public final void setFacet(Class<?> facetClass, Object facetImpl) {
208        if (mFacets == null) {
209            mFacets = new HashMap<Class, Object>();
210        }
211        mFacets.put(facetClass, facetImpl);
212    }
213}
214