Home | History | Annotate | Download | only in playback
      1 page.title=Creating a Catalog Browser
      2 page.tags=tv, browsefragment, presenter, backgroundmanager
      3 helpoutsWidget=true
      4 
      5 trainingnavtop=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">
     41 Leanback 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>
     71 public 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
     81 video data and UI elements and set the layout parameters such as the icon, title, and whether
     82 category 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
     92 up event listeners for user actions on the UI elements, and prepares the background
     93 manager, as shown in the following example:</p>
     94 
     95 <pre>
     96 public 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
    187 left pane. Text views display these category names from the video database. You can customize the
    188 header to include additional views in a more complex layout. The following sections show how to
    189 include 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
    193 and 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
    218 abstract methods to create, bind, and unbind the view holder. The following
    219 example 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>
    224 public 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
    256 multiple views, and you could use this pattern to do something even more complex.
    257 However, an easier way to combine a {@link android.widget.TextView} with a
    258 drawable 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
    263 catalog browser, use the {@link android.support.v17.leanback.app.BrowseFragment#setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector) setHeaderPresenterSelector()}
    264 method to set the presenter for the row header, as shown in the following example.</p>
    265 
    266 <pre>
    267 setHeaderPresenterSelector(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
    278 require a scrollable list, for example. Call the {@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) BrowseFragment.setHeadersState()}
    279 method during the fragment's {@link android.app.Fragment#onActivityCreated(android.os.Bundle) onActivityCreated()}
    280 method to hide or disable the row headers. The {@link android.support.v17.leanback.app.BrowseFragment#setHeadersState(int) setHeadersState()}
    281 method sets the initial state of the headers in the browse fragment given one of the following
    282 constants 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()}
    300 to support moving back to the row header from a selected content item in the row. This is enabled by
    301 default (if you don't call the method), but if you want to handle the back movement yourself, you
    302 should pass the value <code>false</code> to {@link android.support.v17.leanback.app.BrowseFragment#setHeadersTransitionOnBackEnabled(boolean) setHeadersTransitionOnBackEnabled()}
    303 and 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>
    322 public 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>
    354 private ArrayObjectAdapter mRowsAdapter;
    355 private static final int NUM_ROWS = 4;
    356 
    357 &#64;Override
    358 protected void onCreate(Bundle savedInstanceState) {
    359     ...
    360 
    361     buildRowsAdapter();
    362 }
    363 
    364 private 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>
    403 protected 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>
    417 protected void clearBackground() {
    418     BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
    419 }
    420 
    421 protected 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