/* * 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 android.view.KeyEvent; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * An overview {@link 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 easily updating actions while * maintaining the order. The application can add or remove actions on the UI thread after the * row is bound to a view. * *

Updating main item

* After the row is bound to a view, the application may call {@link #setItem(Object)} * on UI thread and the view will be updated. * *

Updating image

* After the row is bound to view, the application may change the image by calling {@link * #setImageBitmap(Context, Bitmap)} or {@link #setImageDrawable(Drawable)} on the UI thread, * and the view will be updated. */ public class DetailsOverviewRow extends Row { /** * Listener for changes of DetailsOverviewRow. */ public 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++; } } } } /** * Returns 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(); } /** * Returns 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; } /** * Returns the actions adapter. Throws ClassCastException if the current * actions adapter is not an instance of {@link ArrayObjectAdapter}. */ private ArrayObjectAdapter getArrayObjectAdapter() { return (ArrayObjectAdapter) mActionsAdapter; } /** * Adds an Action to the overview. It will throw ClassCastException if the current actions * adapter is not an instance of {@link ArrayObjectAdapter}. Must be called on the UI thread. * * @param action The Action to add. * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} */ @Deprecated public final void addAction(Action action) { getArrayObjectAdapter().add(action); } /** * Adds an Action to the overview at the specified position. It will throw ClassCastException if * current actions adapter is not an instance of f{@link ArrayObjectAdapter}. Must be called * on the UI thread. * * @param pos The position to insert the Action. * @param action The Action to add. * @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()} */ @Deprecated public final void addAction(int pos, Action action) { getArrayObjectAdapter().add(pos, action); } /** * Removes 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()} */ @Deprecated public final boolean removeAction(Action action) { return getArrayObjectAdapter().remove(action); } /** * Returns 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()} */ @Deprecated public final List getActions() { return getArrayObjectAdapter().unmodifiableList(); } /** * Returns the {@link ObjectAdapter} for actions. */ public final ObjectAdapter getActionsAdapter() { return mActionsAdapter; } /** * Sets the {@link ObjectAdapter} for actions. A default {@link PresenterSelector} will be * attached to the adapter if it doesn't have one. * * @param adapter Adapter for actions. */ public final void setActionsAdapter(ObjectAdapter adapter) { if (adapter != mActionsAdapter) { mActionsAdapter = adapter; if (mActionsAdapter.getPresenterSelector() == null) { mActionsAdapter.setPresenterSelector(mDefaultActionPresenter); } notifyActionsAdapterChanged(); } } /** * Returns the Action associated with the given keycode, or null if no associated action exists. */ public Action getActionForKeyCode(int keyCode) { ObjectAdapter adapter = getActionsAdapter(); if (adapter != null) { for (int i = 0; i < adapter.size(); i++) { Action action = (Action) adapter.get(i); if (action.respondsToKeyCode(keyCode)) { return action; } } } return null; } private void verify() { if (mItem == null) { throw new IllegalArgumentException("Object cannot be null"); } } }