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