/* * 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.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * An overview row for a details fragment. This row consists of an image, a * description view, and optionally a series of {@link Action}s that can be taken for * the item. * *

Actions

* Application uses {@link #setActionsAdapter(ObjectAdapter)} to set actions on the overview * row. {@link SparseArrayObjectAdapter} is recommended for easy updating actions while * keeping the order. Application can add or remove actions on UI thread after the row is * bound to view. * *

Updating main item

* After the row is bound to view, application still can call ({@link #setItem(Object)}) * on UI thread. * *

Updating image

* After the row is bound to view, application still can change image by calling ({@link * #setImageBitmap(Context, Bitmap)}) or {@link #setImageDrawable(Drawable)}) on UI thread. */ public class DetailsOverviewRow extends Row { /** * Listener for changes of DetailsOverViewRow. */ static class Listener { /** * Called when DetailsOverviewRow has changed image drawable. */ public void onImageDrawableChanged(DetailsOverviewRow row) { } /** * Called when DetailsOverviewRow has changed main item. */ public void onItemChanged(DetailsOverviewRow row) { } /** * Called when DetailsOverviewRow has changed actions adapter. */ public void onActionsAdapterChanged(DetailsOverviewRow row) { } } private Object mItem; private Drawable mImageDrawable; private boolean mImageScaleUpAllowed = true; private ArrayList> mListeners; private PresenterSelector mDefaultActionPresenter = new ActionPresenterSelector(); private ObjectAdapter mActionsAdapter = new ArrayObjectAdapter(mDefaultActionPresenter); /** * Constructor for a DetailsOverviewRow. * * @param item The main item for the details page. */ public DetailsOverviewRow(Object item) { super(null); mItem = item; verify(); } /** * Adds listener for the details page. */ final void addListener(Listener listener) { if (mListeners == null) { mListeners = new ArrayList>(); } else { for (int i = 0; i < mListeners.size();) { Listener l = mListeners.get(i).get(); if (l == null) { mListeners.remove(i); } else { if (l == listener) { return; } i++; } } } mListeners.add(new WeakReference(listener)); } /** * Removes listener of the details page. */ final void removeListener(Listener listener) { if (mListeners != null) { for (int i = 0; i < mListeners.size();) { Listener l = mListeners.get(i).get(); if (l == null) { mListeners.remove(i); } else { if (l == listener) { mListeners.remove(i); return; } i++; } } } } /** * Notifies listeners for main item change on UI thread. */ final void notifyItemChanged() { if (mListeners != null) { for (int i = 0; i < mListeners.size();) { Listener l = mListeners.get(i).get(); if (l == null) { mListeners.remove(i); } else { l.onItemChanged(this); i++; } } } } /** * Notifies listeners for image related change on UI thread. */ final void notifyImageDrawableChanged() { if (mListeners != null) { for (int i = 0; i < mListeners.size();) { Listener l = mListeners.get(i).get(); if (l == null) { mListeners.remove(i); } else { l.onImageDrawableChanged(this); i++; } } } } /** * Notifies listeners for actions adapter changed on UI thread. */ final void notifyActionsAdapterChanged() { if (mListeners != null) { for (int i = 0; i < mListeners.size();) { Listener l = mListeners.get(i).get(); if (l == null) { mListeners.remove(i); } else { l.onActionsAdapterChanged(this); i++; } } } } /** * Gets the main item for the details page. */ public final Object getItem() { return mItem; } /** * Sets the main item for the details page. Must be called on UI thread after * row is bound to view. */ public final void setItem(Object item) { if (item != mItem) { mItem = item; notifyItemChanged(); } } /** * Sets a drawable as the image of this details overview. Must be called on UI thread * after row is bound to view. * * @param drawable The drawable to set. */ public final void setImageDrawable(Drawable drawable) { if (mImageDrawable != drawable) { mImageDrawable = drawable; notifyImageDrawableChanged(); } } /** * Sets a Bitmap as the image of this details overview. Must be called on UI thread * after row is bound to view. * * @param context The context to retrieve display metrics from. * @param bm The bitmap to set. */ public final void setImageBitmap(Context context, Bitmap bm) { mImageDrawable = new BitmapDrawable(context.getResources(), bm); notifyImageDrawableChanged(); } /** * Gets the image drawable of this details overview. * * @return The overview's image drawable, or null if no drawable has been * assigned. */ public final Drawable getImageDrawable() { return mImageDrawable; } /** * Allows or disallows scaling up of images. * Images will always be scaled down if necessary. Must be called on UI thread * after row is bound to view. */ public void setImageScaleUpAllowed(boolean allowed) { if (allowed != mImageScaleUpAllowed) { mImageScaleUpAllowed = allowed; notifyImageDrawableChanged(); } } /** * Returns true if the image may be scaled up; false otherwise. */ public boolean isImageScaleUpAllowed() { return mImageScaleUpAllowed; } /** * Get array object adapter. Throws ClassCastException if the current ObjectAdapter is not * ArrayObjectAdapter. */ private ArrayObjectAdapter getArrayObjectAdapter() { return (ArrayObjectAdapter) mActionsAdapter; } /** * Add an Action to the overview. It will throw ClassCastException if current actions adapter * is not {@link ArrayObjectAdapter}. Must be called on UI thread. * * @param action The Action to add. * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} */ public final void addAction(Action action) { getArrayObjectAdapter().add(action); } /** * Add an Action to the overview at the specified position. It will throw ClassCastException if * current actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread. * * @param pos The position to insert the Action. * @param action The Action to add. * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} */ public final void addAction(int pos, Action action) { getArrayObjectAdapter().add(pos, action); } /** * Remove the given Action from the overview. It will throw ClassCastException if current * actions adapter is not {@link ArrayObjectAdapter}. Must be called on UI thread. * * @param action The Action to remove. * @return true if the overview contained the specified Action. * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} */ public final boolean removeAction(Action action) { return getArrayObjectAdapter().remove(action); } /** * Gets a read-only view of the list of Actions of this details overview. It will throw * ClassCastException if current actions adapter is not {@link ArrayObjectAdapter}. Must be * called on UI thread. * * @return An unmodifiable view of the list of Actions. * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} */ public final List getActions() { return getArrayObjectAdapter().unmodifiableList(); } /** * Gets {@link ObjectAdapter} for actions. */ public final ObjectAdapter getActionsAdapter() { return mActionsAdapter; } /** * Sets {@link ObjectAdapter} for actions. * @param adapter Adapter for actions, a default {@link PresenterSelector} will be attached * to the adapter if it doesn't have one. */ public final void setActionsAdapter(ObjectAdapter adapter) { if (adapter != mActionsAdapter) { mActionsAdapter = adapter; if (mActionsAdapter.getPresenterSelector() == null) { mActionsAdapter.setPresenterSelector(mDefaultActionPresenter); } notifyActionsAdapterChanged(); } } private void verify() { if (mItem == null) { throw new IllegalArgumentException("Object cannot be null"); } } }