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<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"> 58 59 <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" /> 64 65</FrameLayout> 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 @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<HashMap<String, List<Movie>>> { 98 99... 100 101 @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 @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<?xml version="1.0" encoding="utf-8"?> 199<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"> 203 204 <ImageView 205 android:id="@+id/header_icon" 206 android:layout_width="32dp" 207 android:layout_height="32dp" /> 208 <TextView 209 android:id="@+id/header_label" 210 android:layout_marginTop="6dp" 211 android:layout_width="wrap_content" 212 android:layout_height="wrap_content" /> 213 214</LinearLayout> 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 @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 @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 @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 @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@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 < 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 @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