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