/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package android.support.v17.leanback.widget;
import android.view.View;
import android.view.ViewGroup;
import java.util.HashMap;
import java.util.Map;
/**
* A Presenter is used to generate {@link View}s and bind Objects to them on
* demand. It is closely related to the concept of an {@link
* android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, but is
* not position-based. The leanback framework implements the adapter concept using
* {@link ObjectAdapter} which refers to a Presenter (or {@link PresenterSelector}) instance.
*
* <p>
* Presenters should be stateless. Presenters typically extend {@link ViewHolder} to store all
* necessary view state information, such as references to child views to be used when
* binding to avoid expensive calls to {@link View#findViewById(int)}.
* </p>
*
* <p>
* A trivial Presenter that takes a string and renders it into a {@link
* android.widget.TextView TextView}:
*
* <pre class="prettyprint">
* public class StringTextViewPresenter extends Presenter {
* // This class does not need a custom ViewHolder, since it does not use
* // a complex layout.
*
* {@literal @}Override
* public ViewHolder onCreateViewHolder(ViewGroup parent) {
* return new ViewHolder(new TextView(parent.getContext()));
* }
*
* {@literal @}Override
* public void onBindViewHolder(ViewHolder viewHolder, Object item) {
* String str = (String) item;
* TextView textView = (TextView) viewHolder.mView;
*
* textView.setText(item);
* }
*
* {@literal @}Override
* public void onUnbindViewHolder(ViewHolder viewHolder) {
* // Nothing to unbind for TextView, but if this viewHolder had
* // allocated bitmaps, they can be released here.
* }
* }
* </pre>
* In addition to view creation and binding, Presenter allows dynamic interface (facet) to
* be added: {@link #setFacet(Class, Object)}. Supported facets:
* <li> {@link ItemAlignmentFacet} is used by {@link HorizontalGridView} and
* {@link VerticalGridView} to customize child alignment.
*/
public abstract class Presenter implements FacetProvider {
/**
* ViewHolder can be subclassed and used to cache any view accessors needed
* to improve binding performance (for example, results of findViewById)
* without needing to subclass a View.
*/
public static class ViewHolder implements FacetProvider {
public final View view;
private Map<Class, Object> mFacets;
public ViewHolder(View view) {
this.view = view;
}
@Override
public final Object getFacet(Class<?> facetClass) {
if (mFacets == null) {
return null;
}
return mFacets.get(facetClass);
}
/**
* Sets dynamic implemented facet in addition to basic ViewHolder functions.
* @param facetClass Facet classes to query, can be class of {@link ItemAlignmentFacet}.
* @param facetImpl Facet implementation.
*/
public final void setFacet(Class<?> facetClass, Object facetImpl) {
if (mFacets == null) {
mFacets = new HashMap<Class, Object>();
}
mFacets.put(facetClass, facetImpl);
}
}
private Map<Class, Object> mFacets;
/**
* Creates a new {@link View}.
*/
public abstract ViewHolder onCreateViewHolder(ViewGroup parent);
/**
* Binds a {@link View} to an item.
*/
public abstract void onBindViewHolder(ViewHolder viewHolder, Object item);
/**
* Unbinds a {@link View} from an item. Any expensive references may be
* released here, and any fields that are not bound for every item should be
* cleared here.
*/
public abstract void onUnbindViewHolder(ViewHolder viewHolder);
/**
* Called when a view created by this presenter has been attached to a window.
*
* <p>This can be used as a reasonable signal that the view is about to be seen
* by the user. If the adapter previously freed any resources in
* {@link #onViewDetachedFromWindow(ViewHolder)}
* those resources should be restored here.</p>
*
* @param holder Holder of the view being attached
*/
public void onViewAttachedToWindow(ViewHolder holder) {
}
/**
* Called when a view created by this presenter has been detached from its window.
*
* <p>Becoming detached from the window is not necessarily a permanent condition;
* the consumer of an presenter's views may choose to cache views offscreen while they
* are not visible, attaching and detaching them as appropriate.</p>
*
* Any view property animations should be cancelled here or the view may fail
* to be recycled.
*
* @param holder Holder of the view being detached
*/
public void onViewDetachedFromWindow(ViewHolder holder) {
// If there are view property animations running then RecyclerView won't recycle.
cancelAnimationsRecursive(holder.view);
}
/**
* Utility method for removing all running animations on a view.
*/
protected static void cancelAnimationsRecursive(View view) {
if (view != null && view.hasTransientState()) {
view.animate().cancel();
if (view instanceof ViewGroup) {
final int count = ((ViewGroup) view).getChildCount();
for (int i = 0; view.hasTransientState() && i < count; i++) {
cancelAnimationsRecursive(((ViewGroup) view).getChildAt(i));
}
}
}
}
/**
* Called to set a click listener for the given view holder.
*
* The default implementation sets the click listener on the root view in the view holder.
* If the root view isn't focusable this method should be overridden to set the listener
* on the appropriate focusable child view(s).
*
* @param holder The view holder containing the view(s) on which the listener should be set.
* @param listener The click listener to be set.
*/
public void setOnClickListener(ViewHolder holder, View.OnClickListener listener) {
holder.view.setOnClickListener(listener);
}
@Override
public final Object getFacet(Class<?> facetClass) {
if (mFacets == null) {
return null;
}
return mFacets.get(facetClass);
}
/**
* Sets dynamic implemented facet in addition to basic Presenter functions.
* @param facetClass Facet classes to query, can be class of {@link ItemAlignmentFacet}.
* @param facetImpl Facet implementation.
*/
public final void setFacet(Class<?> facetClass, Object facetImpl) {
if (mFacets == null) {
mFacets = new HashMap<Class, Object>();
}
mFacets.put(facetClass, facetImpl);
}
}