444 lines
18 KiB
Plaintext
444 lines
18 KiB
Plaintext
page.title=Creating a Catalog Browser
|
|
page.tags=tv, browsefragment, presenter, backgroundmanager
|
|
helpoutsWidget=true
|
|
|
|
trainingnavtop=true
|
|
|
|
@jd:body
|
|
|
|
<div id="tb-wrapper">
|
|
<div id="tb">
|
|
<h2>This lesson teaches you to</h2>
|
|
<ol>
|
|
<li><a href="#layout">Create a Media Browse Layout</a></li>
|
|
<li><a href="#header">Customize the Header Views</a></li>
|
|
<li><a href="#lists">Display Media Lists</a></li>
|
|
<li><a href="#background">Update the Background</a></li>
|
|
</ol>
|
|
<h2>Try it out</h2>
|
|
<ul>
|
|
<li><a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android
|
|
Leanback sample app</a></li>
|
|
</ul>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<p>
|
|
A media app that runs on a TV needs to allow users to browse its content offerings, make a
|
|
selection, and start playing content. The content browsing experience for apps of this type
|
|
should be simple and intuitive, as well as visually pleasing and engaging.
|
|
</p>
|
|
|
|
<p>
|
|
This lesson discusses how to use the classes provided by the <a href=
|
|
"{@docRoot}tools/support-library/features.html#v17-leanback">v17 leanback support library</a>
|
|
to implement a user interface for browsing music or videos from your app's media catalog.
|
|
</p>
|
|
|
|
<img itemprop="image" src="{@docRoot}images/tv/app-browse.png" alt="App main screen"/>
|
|
<p class="img-caption"><b>Figure 1.</b> The <a href="https://github.com/googlesamples/androidtv-Leanback">
|
|
Leanback sample app</a> browse fragment displays video catalog data.</p>
|
|
|
|
|
|
<h2 id="layout">Create a Media Browse Layout</h2>
|
|
|
|
<p>
|
|
The {@link android.support.v17.leanback.app.BrowseFragment} class in the leanback library
|
|
allows you to create a primary layout for browsing categories and rows of media items with a
|
|
minimum of code. The following example shows how to create a layout that contains a {@link
|
|
android.support.v17.leanback.app.BrowseFragment} object:
|
|
</p>
|
|
|
|
<pre>
|
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:id="@+id/main_frame"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent">
|
|
|
|
<fragment
|
|
android:name="com.example.android.tvleanback.ui.MainFragment"
|
|
android:id="@+id/main_browse_fragment"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent" />
|
|
|
|
</FrameLayout>
|
|
</pre>
|
|
|
|
<p>The application's main activity sets this view, as shown in the following example:</p>
|
|
|
|
<pre>
|
|
public class MainActivity extends Activity {
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.main);
|
|
}
|
|
...
|
|
</pre>
|
|
|
|
<p>The {@link android.support.v17.leanback.app.BrowseFragment} methods populate the view with the
|
|
video data and UI elements and set the layout parameters such as the icon, title, and whether
|
|
category headers are enabled.</p>
|
|
|
|
<ul>
|
|
<li>See <a href="#set-ui">Set UI Elements</a> for more information about setting up UI elements.</li>
|
|
<li>See <a href="#hide-heads">Hide or Disable Headers</a> for more information about hiding the
|
|
headers.</li>
|
|
</ul>
|
|
|
|
<p>The application's subclass that implements the
|
|
{@link android.support.v17.leanback.app.BrowseFragment} methods also sets
|
|
up event listeners for user actions on the UI elements, and prepares the background
|
|
manager, as shown in the following example:</p>
|
|
|
|
<pre>
|
|
public class MainFragment extends BrowseFragment implements
|
|
LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
|
|
|
|
...
|
|
|
|
@Override
|
|
public void onActivityCreated(Bundle savedInstanceState) {
|
|
super.onActivityCreated(savedInstanceState);
|
|
|
|
loadVideoData();
|
|
|
|
prepareBackgroundManager();
|
|
setupUIElements();
|
|
setupEventListeners();
|
|
}
|
|
...
|
|
|
|
private void prepareBackgroundManager() {
|
|
mBackgroundManager = BackgroundManager.getInstance(getActivity());
|
|
mBackgroundManager.attach(getActivity().getWindow());
|
|
mDefaultBackground = getResources()
|
|
.getDrawable(R.drawable.default_background);
|
|
mMetrics = new DisplayMetrics();
|
|
getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
|
|
}
|
|
|
|
private void setupUIElements() {
|
|
setBadgeDrawable(getActivity().getResources()
|
|
.getDrawable(R.drawable.videos_by_google_banner));
|
|
// Badge, when set, takes precedent over title
|
|
setTitle(getString(R.string.browse_title));
|
|
setHeadersState(HEADERS_ENABLED);
|
|
setHeadersTransitionOnBackEnabled(true);
|
|
// set headers background color
|
|
setBrandColor(getResources().getColor(R.color.fastlane_background));
|
|
// set search icon color
|
|
setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
|
|
}
|
|
|
|
private void loadVideoData() {
|
|
VideoProvider.setContext(getActivity());
|
|
mVideosUrl = getActivity().getResources().getString(R.string.catalog_url);
|
|
getLoaderManager().initLoader(0, null, this);
|
|
}
|
|
|
|
private void setupEventListeners() {
|
|
setOnSearchClickedListener(new View.OnClickListener() {
|
|
|
|
@Override
|
|
public void onClick(View view) {
|
|
Intent intent = new Intent(getActivity(), SearchActivity.class);
|
|
startActivity(intent);
|
|
}
|
|
});
|
|
|
|
setOnItemViewClickedListener(new ItemViewClickedListener());
|
|
setOnItemViewSelectedListener(new ItemViewSelectedListener());
|
|
}
|
|
...
|
|
</pre>
|
|
|
|
<h3 id="set-ui">Set UI Elements</h2>
|
|
|
|
<p>In the sample above, the private method <code>setupUIElements()</code> calls several of the
|
|
{@link android.support.v17.leanback.app.BrowseFragment} methods to style the media catalog browser:
|
|
</p>
|
|
|
|
<ul>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#setBadgeDrawable(android.graphics.drawable.Drawable) setBadgeDrawable()}
|
|
places the specified drawable resource in the upper-right corner of the browse fragment, as
|
|
shown in figures 1 and 2. This method replaces the title string with the
|
|
drawable resource, if {@code setTitle()} is also called. The drawable resource should be 52dps
|
|
tall.</li>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#setTitle(java.lang.CharSequence) setTitle()}
|
|
sets the title string in the upper-right corner of the browse fragment, unless
|
|
{@code setBadgeDrawable()} is called.</li>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) setHeadersState()}
|
|
and {@link android.support.v17.leanback.app.BrowseFragment#setHeadersTransitionOnBackEnabled(boolean) setHeadersTransitionOnBackEnabled()} hide or disable the headers. See
|
|
<a href="#hide-heads">Hide or Disable Headers</a> for more information.
|
|
</li>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#setBrandColor(int) setBrandColor()}
|
|
sets the background color for UI elements in the browse fragment, specifically the header
|
|
section background color, with the specified color value.</li>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#setSearchAffordanceColor(int) setSearchAffordanceColor()}
|
|
sets the color of the search icon with the specified color value. The search icon
|
|
appears in the upper-left corner of the browse fragment, as shown in figures 1 and 2.</li>
|
|
</ul>
|
|
|
|
<h2 id="header">Customize the Header Views</h2>
|
|
|
|
<p>The browse fragment shown in figure 1 lists the video category names (the row headers) in the
|
|
left pane. Text views display these category names from the video database. You can customize the
|
|
header to include additional views in a more complex layout. The following sections show how to
|
|
include an image view that displays an icon next to the category name, as shown in figure 2.</p>
|
|
|
|
<img itemprop="image" src="{@docRoot}images/tv/custom-head.png" alt="App main screen"/>
|
|
<p class="img-caption"><b>Figure 2.</b> The row headers in the browse fragment, with both an icon
|
|
and a text label.</p>
|
|
|
|
<p>The layout for the row header is defined as follows:</p>
|
|
|
|
<pre>
|
|
<?xml version="1.0" encoding="utf-8"?>
|
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
android:orientation="horizontal"
|
|
android:layout_width="match_parent"
|
|
android:layout_height="match_parent">
|
|
|
|
<ImageView
|
|
android:id="@+id/header_icon"
|
|
android:layout_width="32dp"
|
|
android:layout_height="32dp" />
|
|
<TextView
|
|
android:id="@+id/header_label"
|
|
android:layout_marginTop="6dp"
|
|
android:layout_width="wrap_content"
|
|
android:layout_height="wrap_content" />
|
|
|
|
</LinearLayout>
|
|
</pre>
|
|
|
|
<p>Use a {@link android.support.v17.leanback.widget.Presenter} and implement the
|
|
abstract methods to create, bind, and unbind the view holder. The following
|
|
example shows how to bind the viewholder with two views, an
|
|
{@link android.widget.ImageView} and a {@link android.widget.TextView}.
|
|
</p>
|
|
|
|
<pre>
|
|
public class IconHeaderItemPresenter extends Presenter {
|
|
@Override
|
|
public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
|
|
LayoutInflater inflater = (LayoutInflater) viewGroup.getContext()
|
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
|
|
View view = inflater.inflate(R.layout.icon_header_item, null);
|
|
|
|
return new ViewHolder(view);
|
|
}
|
|
|
|
@Override
|
|
public void onBindViewHolder(ViewHolder viewHolder, Object o) {
|
|
HeaderItem headerItem = ((ListRow) o).getHeaderItem();
|
|
View rootView = viewHolder.view;
|
|
|
|
ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
|
|
Drawable icon = rootView.getResources().getDrawable(R.drawable.ic_action_video, null);
|
|
iconView.setImageDrawable(icon);
|
|
|
|
TextView label = (TextView) rootView.findViewById(R.id.header_label);
|
|
label.setText(headerItem.getName());
|
|
}
|
|
|
|
@Override
|
|
public void onUnbindViewHolder(ViewHolder viewHolder) {
|
|
// no op
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>This example shows how to define the presenter for a complex layout with
|
|
multiple views, and you could use this pattern to do something even more complex.
|
|
However, an easier way to combine a {@link android.widget.TextView} with a
|
|
drawable resource is to use the <a href="{@docRoot}reference/android/widget/TextView.html#attr_android:drawableLeft">
|
|
{@code TextView.drawableLeft}</a> attribute. Doing it this way, you don't need the
|
|
{@link android.widget.ImageView} shown here.</p>
|
|
|
|
<p>In the {@link android.support.v17.leanback.app.BrowseFragment} implementation that displays the
|
|
catalog browser, use the {@link android.support.v17.leanback.app.BrowseFragment#setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector) setHeaderPresenterSelector()}
|
|
method to set the presenter for the row header, as shown in the following example.</p>
|
|
|
|
<pre>
|
|
setHeaderPresenterSelector(new PresenterSelector() {
|
|
@Override
|
|
public Presenter getPresenter(Object o) {
|
|
return new IconHeaderItemPresenter();
|
|
}
|
|
});
|
|
</pre>
|
|
|
|
<h3 id="hide-heads">Hide or Disable Headers</h3>
|
|
|
|
<p>Sometimes you may not want the row headers to appear: when there aren't enough categories to
|
|
require a scrollable list, for example. Call the {@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) BrowseFragment.setHeadersState()}
|
|
method during the fragment's {@link android.app.Fragment#onActivityCreated(android.os.Bundle) onActivityCreated()}
|
|
method to hide or disable the row headers. The {@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) setHeadersState()}
|
|
method sets the initial state of the headers in the browse fragment given one of the following
|
|
constants as a parameter:</p>
|
|
|
|
<ul>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_ENABLED} - When the browse
|
|
fragment activity is created, the headers are enabled and shown by default. The headers appear as
|
|
shown in figures 1 and 2 on this page.</li>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_HIDDEN} - When the browse
|
|
fragment activity is created, headers are enabled and hidden by default. The header section of the
|
|
screen is collapsed, as shown in <a href="{@docRoot}training/tv/playback/card.html#collapsed">
|
|
figure 1</a> of <a href="{@docRoot}training/tv/playback/card.html">Providing a Card View</a>. The
|
|
user can select the collapsed header section to expand it.</li>
|
|
<li>{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_DISABLED} - When the browse
|
|
fragment activity is created, headers are disabled by default and are never displayed.</li>
|
|
</ul>
|
|
|
|
<p>If either {@link android.support.v17.leanback.app.BrowseFragment#HEADERS_ENABLED} or
|
|
{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_HIDDEN} is set, you can call
|
|
{@link android.support.v17.leanback.app.BrowseFragment#setHeadersTransitionOnBackEnabled(boolean) setHeadersTransitionOnBackEnabled()}
|
|
to support moving back to the row header from a selected content item in the row. This is enabled by
|
|
default (if you don't call the method), but if you want to handle the back movement yourself, you
|
|
should pass the value <code>false</code> to {@link android.support.v17.leanback.app.BrowseFragment#setHeadersTransitionOnBackEnabled(boolean) setHeadersTransitionOnBackEnabled()}
|
|
and implement your own back stack handling.</p>
|
|
|
|
<h2 id="lists">Display Media Lists</h2>
|
|
|
|
<p>
|
|
The {@link android.support.v17.leanback.app.BrowseFragment} class allows you
|
|
to define and display browsable media content categories and media items from
|
|
a media catalog using adapters and presenters. Adapters enable you to connect
|
|
to local or online data sources that contain your media catalog information.
|
|
Adapters use presenters to create views and bind data to those views for
|
|
displaying an item on screen.
|
|
</p>
|
|
|
|
<p>
|
|
The following example code shows an implementation of a {@link
|
|
android.support.v17.leanback.widget.Presenter} for displaying string data:
|
|
</p>
|
|
|
|
<pre>
|
|
public class StringPresenter extends Presenter {
|
|
private static final String TAG = "StringPresenter";
|
|
|
|
public ViewHolder onCreateViewHolder(ViewGroup parent) {
|
|
TextView textView = new TextView(parent.getContext());
|
|
textView.setFocusable(true);
|
|
textView.setFocusableInTouchMode(true);
|
|
textView.setBackground(
|
|
parent.getContext().getResources().getDrawable(R.drawable.text_bg));
|
|
return new ViewHolder(textView);
|
|
}
|
|
|
|
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
|
|
((TextView) viewHolder.view).setText(item.toString());
|
|
}
|
|
|
|
public void onUnbindViewHolder(ViewHolder viewHolder) {
|
|
// no op
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>
|
|
Once you have constructed a presenter class for your media items, you can build
|
|
an adapter and attach it to the {@link android.support.v17.leanback.app.BrowseFragment}
|
|
to display those items on screen for browsing by the user. The following example
|
|
code demonstrates how to construct an adapter to display categories and items
|
|
in those categories using the {@code StringPresenter} class shown in the
|
|
previous code example:
|
|
</p>
|
|
|
|
<pre>
|
|
private ArrayObjectAdapter mRowsAdapter;
|
|
private static final int NUM_ROWS = 4;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
...
|
|
|
|
buildRowsAdapter();
|
|
}
|
|
|
|
private void buildRowsAdapter() {
|
|
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
|
|
|
|
for (int i = 0; i < NUM_ROWS; ++i) {
|
|
ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
|
|
new StringPresenter());
|
|
listRowAdapter.add("Media Item 1");
|
|
listRowAdapter.add("Media Item 2");
|
|
listRowAdapter.add("Media Item 3");
|
|
HeaderItem header = new HeaderItem(i, "Category " + i);
|
|
mRowsAdapter.add(new ListRow(header, listRowAdapter));
|
|
}
|
|
|
|
mBrowseFragment.setAdapter(mRowsAdapter);
|
|
}
|
|
</pre>
|
|
|
|
<p>
|
|
This example shows a static implementation of the adapters. A typical media browsing application
|
|
uses data from an online database or web service. For an example of a browsing application that
|
|
uses data retrieved from the web, see the
|
|
<a href="http://github.com/googlesamples/androidtv-leanback">Android Leanback sample app</a>.
|
|
</p>
|
|
|
|
<h2 id="background">Update the Background</h2>
|
|
|
|
<p>
|
|
In order to add visual interest to a media-browsing app on TV, you can update the background
|
|
image as users browse through content. This technique can make interaction with your app more
|
|
cinematic and enjoyable.
|
|
</p>
|
|
|
|
<p>
|
|
The Leanback support library provides a {@link android.support.v17.leanback.app.BackgroundManager}
|
|
class for changing the background of your TV app activity. The following example shows how to
|
|
create a simple method for updating the background within your TV app activity:
|
|
</p>
|
|
|
|
<pre>
|
|
protected void updateBackground(Drawable drawable) {
|
|
BackgroundManager.getInstance(this).setDrawable(drawable);
|
|
}
|
|
</pre>
|
|
|
|
<p>
|
|
Many of the existing media-browse apps automatically update the background as the user navigates
|
|
through media listings. In order to do this, you can set up a selection listener to automatically
|
|
update the background based on the user's current selection. The following example shows you how
|
|
to set up an {@link android.support.v17.leanback.widget.OnItemViewSelectedListener} class to
|
|
catch selection events and update the background:
|
|
</p>
|
|
|
|
<pre>
|
|
protected void clearBackground() {
|
|
BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
|
|
}
|
|
|
|
protected OnItemViewSelectedListener getDefaultItemViewSelectedListener() {
|
|
return new OnItemViewSelectedListener() {
|
|
@Override
|
|
public void onItemSelected(Object item, Row row) {
|
|
if (item instanceof Movie ) {
|
|
Drawable background = ((Movie)item).getBackdropDrawable();
|
|
updateBackground(background);
|
|
} else {
|
|
clearBackground();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
</pre>
|
|
|
|
<p class="note">
|
|
<strong>Note:</strong> The implementation above is a simple example shown for purposes of
|
|
illustration. When creating this function in your own app, you should consider running the
|
|
background update action in a separate thread for better performance. In addition, if you are
|
|
planning on updating the background in response to users scrolling through items, consider adding
|
|
a time to delay a background image update until the user settles on an item. This technique avoids
|
|
excessive background image updates.
|
|
</p>
|