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