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