• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1page.title=Creating a Catalog Browser
2page.tags=tv, browsefragment, presenter, backgroundmanager
3helpoutsWidget=true
4
5trainingnavtop=true
6
7@jd:body
8
9<div id="tb-wrapper">
10<div id="tb">
11  <h2>This lesson teaches you to</h2>
12  <ol>
13    <li><a href="#layout">Create a Media Browse Layout</a></li>
14    <li><a href="#header">Customize the Header Views</a></li>
15    <li><a href="#lists">Display Media Lists</a></li>
16    <li><a href="#background">Update the Background</a></li>
17  </ol>
18  <h2>Try it out</h2>
19  <ul>
20    <li><a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android
21    Leanback sample app</a></li>
22  </ul>
23
24</div>
25</div>
26
27<p>
28  A media app that runs on a TV needs to allow users to browse its content offerings, make a
29  selection, and start playing content. The content browsing experience for apps of this type
30  should be simple and intuitive, as well as visually pleasing and engaging.
31</p>
32
33<p>
34  This lesson discusses how to use the classes provided by the <a href=
35  "{@docRoot}tools/support-library/features.html#v17-leanback">v17 leanback support library</a>
36  to implement a user interface for browsing music or videos from your app's media catalog.
37</p>
38
39<img itemprop="image" src="{@docRoot}images/tv/app-browse.png" alt="App main screen"/>
40<p class="img-caption"><b>Figure 1.</b> The <a href="https://github.com/googlesamples/androidtv-Leanback">
41Leanback sample app</a> browse fragment displays video catalog data.</p>
42
43
44<h2 id="layout">Create a Media Browse Layout</h2>
45
46<p>
47  The {@link android.support.v17.leanback.app.BrowseFragment} class in the leanback library
48  allows you to create a primary layout for browsing categories and rows of media items with a
49  minimum of code. The following example shows how to create a layout that contains a {@link
50  android.support.v17.leanback.app.BrowseFragment} object:
51</p>
52
53<pre>
54&lt;FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
55    android:id="@+id/main_frame"
56    android:layout_width="match_parent"
57    android:layout_height="match_parent"&gt;
58
59    &lt;fragment
60        android:name="com.example.android.tvleanback.ui.MainFragment"
61        android:id="@+id/main_browse_fragment"
62        android:layout_width="match_parent"
63        android:layout_height="match_parent" /&gt;
64
65&lt;/FrameLayout&gt;
66</pre>
67
68<p>The application's main activity sets this view, as shown in the following example:</p>
69
70<pre>
71public class MainActivity extends Activity {
72    &#64;Override
73    public void onCreate(Bundle savedInstanceState) {
74        super.onCreate(savedInstanceState);
75        setContentView(R.layout.main);
76    }
77...
78</pre>
79
80<p>The {@link android.support.v17.leanback.app.BrowseFragment} methods populate the view with the
81video data and UI elements and set the layout parameters such as the icon, title, and whether
82category headers are enabled.</p>
83
84<ul>
85  <li>See <a href="#set-ui">Set UI Elements</a> for more information about setting up UI elements.</li>
86  <li>See <a href="#hide-heads">Hide or Disable Headers</a> for more information about hiding the
87  headers.</li>
88</ul>
89
90<p>The application's subclass that implements the
91{@link android.support.v17.leanback.app.BrowseFragment} methods also sets
92up event listeners for user actions on the UI elements, and prepares the background
93manager, as shown in the following example:</p>
94
95<pre>
96public class MainFragment extends BrowseFragment implements
97        LoaderManager.LoaderCallbacks&lt;HashMap&lt;String, List&lt;Movie&gt;&gt;&gt; {
98
99...
100
101    &#64;Override
102    public void onActivityCreated(Bundle savedInstanceState) {
103        super.onActivityCreated(savedInstanceState);
104
105        loadVideoData();
106
107        prepareBackgroundManager();
108        setupUIElements();
109        setupEventListeners();
110    }
111...
112
113    private void prepareBackgroundManager() {
114        mBackgroundManager = BackgroundManager.getInstance(getActivity());
115        mBackgroundManager.attach(getActivity().getWindow());
116        mDefaultBackground = getResources()
117            .getDrawable(R.drawable.default_background);
118        mMetrics = new DisplayMetrics();
119        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
120    }
121
122    private void setupUIElements() {
123        setBadgeDrawable(getActivity().getResources()
124            .getDrawable(R.drawable.videos_by_google_banner));
125        // Badge, when set, takes precedent over title
126        setTitle(getString(R.string.browse_title));
127        setHeadersState(HEADERS_ENABLED);
128        setHeadersTransitionOnBackEnabled(true);
129        // set headers background color
130        setBrandColor(getResources().getColor(R.color.fastlane_background));
131        // set search icon color
132        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
133    }
134
135    private void loadVideoData() {
136        VideoProvider.setContext(getActivity());
137        mVideosUrl = getActivity().getResources().getString(R.string.catalog_url);
138        getLoaderManager().initLoader(0, null, this);
139    }
140
141    private void setupEventListeners() {
142        setOnSearchClickedListener(new View.OnClickListener() {
143
144            &#64;Override
145            public void onClick(View view) {
146                Intent intent = new Intent(getActivity(), SearchActivity.class);
147                startActivity(intent);
148            }
149        });
150
151        setOnItemViewClickedListener(new ItemViewClickedListener());
152        setOnItemViewSelectedListener(new ItemViewSelectedListener());
153    }
154...
155</pre>
156
157<h3 id="set-ui">Set UI Elements</h2>
158
159<p>In the sample above, the private method <code>setupUIElements()</code> calls several of the
160{@link android.support.v17.leanback.app.BrowseFragment} methods to style the media catalog browser:
161</p>
162
163<ul>
164  <li>{@link android.support.v17.leanback.app.BrowseFragment#setBadgeDrawable(android.graphics.drawable.Drawable) setBadgeDrawable()}
165  places the specified drawable resource in the upper-right corner of the browse fragment, as
166  shown in figures 1 and 2. This method replaces the title string with the
167  drawable resource, if {@code setTitle()} is also called. The drawable resource should be 52dps
168  tall.</li>
169  <li>{@link android.support.v17.leanback.app.BrowseFragment#setTitle(java.lang.CharSequence) setTitle()}
170  sets the title string in the upper-right corner of the browse fragment, unless
171  {@code setBadgeDrawable()} is called.</li>
172  <li>{@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) setHeadersState()}
173  and {@link android.support.v17.leanback.app.BrowseFragment#setHeadersTransitionOnBackEnabled(boolean) setHeadersTransitionOnBackEnabled()} hide or disable the headers. See
174  <a href="#hide-heads">Hide or Disable Headers</a> for more information.
175  </li>
176  <li>{@link android.support.v17.leanback.app.BrowseFragment#setBrandColor(int) setBrandColor()}
177  sets the background color for UI elements in the browse fragment, specifically the header
178  section background color, with the specified color value.</li>
179  <li>{@link android.support.v17.leanback.app.BrowseFragment#setSearchAffordanceColor(int) setSearchAffordanceColor()}
180  sets the color of the search icon with the specified color value. The search icon
181  appears in the upper-left corner of the browse fragment, as shown in figures 1 and 2.</li>
182</ul>
183
184<h2 id="header">Customize the Header Views</h2>
185
186<p>The browse fragment shown in figure 1 lists the video category names (the row headers) in the
187left pane. Text views display these category names from the video database. You can customize the
188header to include additional views in a more complex layout. The following sections show how to
189include an image view that displays an icon next to the category name, as shown in figure 2.</p>
190
191<img itemprop="image" src="{@docRoot}images/tv/custom-head.png" alt="App main screen"/>
192<p class="img-caption"><b>Figure 2.</b> The row headers in the browse fragment, with both an icon
193and a text label.</p>
194
195<p>The layout for the row header is defined as follows:</p>
196
197<pre>
198&lt;?xml version="1.0" encoding="utf-8"?&gt;
199&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
200    android:orientation="horizontal"
201    android:layout_width="match_parent"
202    android:layout_height="match_parent"&gt;
203
204    &lt;ImageView
205        android:id="@+id/header_icon"
206        android:layout_width="32dp"
207        android:layout_height="32dp" /&gt;
208    &lt;TextView
209        android:id="@+id/header_label"
210        android:layout_marginTop="6dp"
211        android:layout_width="wrap_content"
212        android:layout_height="wrap_content" /&gt;
213
214&lt;/LinearLayout&gt;
215</pre>
216
217<p>Use a {@link android.support.v17.leanback.widget.Presenter} and implement the
218abstract methods to create, bind, and unbind the view holder. The following
219example shows how to bind the viewholder with two views, an
220{@link android.widget.ImageView} and a {@link android.widget.TextView}.
221</p>
222
223<pre>
224public class IconHeaderItemPresenter extends Presenter {
225    &#64;Override
226    public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
227        LayoutInflater inflater = (LayoutInflater) viewGroup.getContext()
228                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
229
230        View view = inflater.inflate(R.layout.icon_header_item, null);
231
232        return new ViewHolder(view);
233    }
234
235    &#64;Override
236    public void onBindViewHolder(ViewHolder viewHolder, Object o) {
237        HeaderItem headerItem = ((ListRow) o).getHeaderItem();
238        View rootView = viewHolder.view;
239
240        ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
241        Drawable icon = rootView.getResources().getDrawable(R.drawable.ic_action_video, null);
242        iconView.setImageDrawable(icon);
243
244        TextView label = (TextView) rootView.findViewById(R.id.header_label);
245        label.setText(headerItem.getName());
246    }
247
248    &#64;Override
249    public void onUnbindViewHolder(ViewHolder viewHolder) {
250    // no op
251    }
252}
253</pre>
254
255<p>This example shows how to define the presenter for a complex layout with
256multiple views, and you could use this pattern to do something even more complex.
257However, an easier way to combine a {@link android.widget.TextView} with a
258drawable resource is to use the <a href="{@docRoot}reference/android/widget/TextView.html#attr_android:drawableLeft">
259{@code TextView.drawableLeft}</a> attribute. Doing it this way, you don't need the
260{@link android.widget.ImageView} shown here.</p>
261
262<p>In the {@link android.support.v17.leanback.app.BrowseFragment} implementation that displays the
263catalog browser, use the {@link android.support.v17.leanback.app.BrowseFragment#setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector) setHeaderPresenterSelector()}
264method to set the presenter for the row header, as shown in the following example.</p>
265
266<pre>
267setHeaderPresenterSelector(new PresenterSelector() {
268    &#64;Override
269    public Presenter getPresenter(Object o) {
270        return new IconHeaderItemPresenter();
271    }
272});
273</pre>
274
275<h3 id="hide-heads">Hide or Disable Headers</h3>
276
277<p>Sometimes you may not want the row headers to appear: when there aren't enough categories to
278require a scrollable list, for example. Call the {@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) BrowseFragment.setHeadersState()}
279method during the fragment's {@link android.app.Fragment#onActivityCreated(android.os.Bundle) onActivityCreated()}
280method to hide or disable the row headers. The {@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) setHeadersState()}
281method sets the initial state of the headers in the browse fragment given one of the following
282constants as a parameter:</p>
283
284<ul>
285  <li>{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_ENABLED} - When the browse
286  fragment activity is created, the headers are enabled and shown by default. The headers appear as
287  shown in figures 1 and 2 on this page.</li>
288  <li>{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_HIDDEN} - When the browse
289  fragment activity is created, headers are enabled and hidden by default. The header section of the
290  screen is collapsed, as shown in <a href="{@docRoot}training/tv/playback/card.html#collapsed">
291  figure 1</a> of <a href="{@docRoot}training/tv/playback/card.html">Providing a Card View</a>. The
292  user can select the collapsed header section to expand it.</li>
293  <li>{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_DISABLED} - When the browse
294  fragment activity is created, headers are disabled by default and are never displayed.</li>
295</ul>
296
297<p>If either {@link android.support.v17.leanback.app.BrowseFragment#HEADERS_ENABLED} or
298{@link android.support.v17.leanback.app.BrowseFragment#HEADERS_HIDDEN} is set, you can call
299{@link android.support.v17.leanback.app.BrowseFragment#setHeadersTransitionOnBackEnabled(boolean) setHeadersTransitionOnBackEnabled()}
300to support moving back to the row header from a selected content item in the row. This is enabled by
301default (if you don't call the method), but if you want to handle the back movement yourself, you
302should pass the value <code>false</code> to {@link android.support.v17.leanback.app.BrowseFragment#setHeadersTransitionOnBackEnabled(boolean) setHeadersTransitionOnBackEnabled()}
303and implement your own back stack handling.</p>
304
305<h2 id="lists">Display Media Lists</h2>
306
307<p>
308  The {@link android.support.v17.leanback.app.BrowseFragment} class allows you
309  to define and display browsable media content categories and media items from
310  a media catalog using adapters and presenters. Adapters enable you to connect
311  to local or online data sources that contain your media catalog information.
312  Adapters use presenters to create views and bind data to those views for
313  displaying an item on screen.
314</p>
315
316<p>
317  The following example code shows an implementation of a {@link
318  android.support.v17.leanback.widget.Presenter} for displaying string data:
319</p>
320
321<pre>
322public class StringPresenter extends Presenter {
323    private static final String TAG = "StringPresenter";
324
325    public ViewHolder onCreateViewHolder(ViewGroup parent) {
326        TextView textView = new TextView(parent.getContext());
327        textView.setFocusable(true);
328        textView.setFocusableInTouchMode(true);
329        textView.setBackground(
330                parent.getContext().getResources().getDrawable(R.drawable.text_bg));
331        return new ViewHolder(textView);
332    }
333
334    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
335        ((TextView) viewHolder.view).setText(item.toString());
336    }
337
338    public void onUnbindViewHolder(ViewHolder viewHolder) {
339        // no op
340    }
341}
342</pre>
343
344<p>
345  Once you have constructed a presenter class for your media items, you can build
346  an adapter and attach it to the {@link android.support.v17.leanback.app.BrowseFragment}
347  to display those items on screen for browsing by the user. The following example
348  code demonstrates how to construct an adapter to display categories and items
349  in those categories using the {@code StringPresenter} class shown in the
350  previous code example:
351</p>
352
353<pre>
354private ArrayObjectAdapter mRowsAdapter;
355private static final int NUM_ROWS = 4;
356
357&#64;Override
358protected void onCreate(Bundle savedInstanceState) {
359    ...
360
361    buildRowsAdapter();
362}
363
364private void buildRowsAdapter() {
365    mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
366
367    for (int i = 0; i &lt; NUM_ROWS; ++i) {
368        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
369                new StringPresenter());
370        listRowAdapter.add("Media Item 1");
371        listRowAdapter.add("Media Item 2");
372        listRowAdapter.add("Media Item 3");
373        HeaderItem header = new HeaderItem(i, "Category " + i);
374        mRowsAdapter.add(new ListRow(header, listRowAdapter));
375    }
376
377    mBrowseFragment.setAdapter(mRowsAdapter);
378}
379</pre>
380
381<p>
382  This example shows a static implementation of the adapters. A typical media browsing application
383  uses data from an online database or web service. For an example of a browsing application that
384  uses data retrieved from the web, see the
385  <a href="http://github.com/googlesamples/androidtv-leanback">Android Leanback sample app</a>.
386</p>
387
388<h2 id="background">Update the Background</h2>
389
390<p>
391  In order to add visual interest to a media-browsing app on TV, you can update the background
392  image as users browse through content. This technique can make interaction with your app more
393  cinematic and enjoyable.
394</p>
395
396<p>
397  The Leanback support library provides a {@link android.support.v17.leanback.app.BackgroundManager}
398  class for changing the background of your TV app activity. The following example shows how to
399  create a simple method for updating the background within your TV app activity:
400</p>
401
402<pre>
403protected void updateBackground(Drawable drawable) {
404    BackgroundManager.getInstance(this).setDrawable(drawable);
405}
406</pre>
407
408<p>
409  Many of the existing media-browse apps automatically update the background as the user navigates
410  through media listings. In order to do this, you can set up a selection listener to automatically
411  update the background based on the user's current selection. The following example shows you how
412  to set up an {@link android.support.v17.leanback.widget.OnItemViewSelectedListener} class to
413  catch selection events and update the background:
414</p>
415
416<pre>
417protected void clearBackground() {
418    BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
419}
420
421protected OnItemViewSelectedListener getDefaultItemViewSelectedListener() {
422    return new OnItemViewSelectedListener() {
423        &#64;Override
424        public void onItemSelected(Object item, Row row) {
425            if (item instanceof Movie ) {
426                Drawable background = ((Movie)item).getBackdropDrawable();
427                updateBackground(background);
428            } else {
429                clearBackground();
430            }
431        }
432    };
433}
434</pre>
435
436<p class="note">
437  <strong>Note:</strong> The implementation above is a simple example shown for purposes of
438  illustration. When creating this function in your own app, you should consider running the
439  background update action in a separate thread for better performance. In addition, if you are
440  planning on updating the background in response to users scrolling through items, consider adding
441  a time to delay a background image update until the user settles on an item. This technique avoids
442  excessive background image updates.
443</p>
444