/*
* 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");
}
}
}