[Update 2015.11.17: revise]
Construction of BrowseFragment
In this chapter, we will implement a combination of header and selectable objects (so called “cards”). But before going to real implementation, it’s good to understand the construction of BrowseFragment. You can also read the source code in the sdk (android/support/v17/leanback/app/
) by yourself.
Let’s start explaination by using Android TV sample application. When you launch application, the contents are aligned in a grid structure. Each header title on the left have a each contents row, and this header – contents row relationship is one to one. This “header + contents row” combination is represented by ListRow
. Body of BrowseFragment is a set of ListRow
(I will use the term RowsAdapter
in this post).
In the below picture, ListRow
is represented by blue circle. And a blue square is a RowsAdapter
, which is as set of blue circle.
Next, let’s look inside the ListRow
in more detail. The contents of the header is specified by ArrayObjectAdapter
(I call RowAdapter
in this post), which is a set of Object (I call CardInfo or Item in this post).
This CardInfo can be any object, and as it will be explained in detail later, how to show this CardInfo can be specified by Presenter
class.
To summarize,
ArrayObjectAdapter
(RowsAdapter
) ← A set of ListRow
ListRow
= HeaderItem
+ ArrayObjectAdapter (RowAdapter)
ArrayObjectAdapter (RowAdapter)
← A set of Object (CardInfo/Item)
Presenter class
The design of card is determined by Presenter. Presenter defines how to show/present cardInfo. Presenter class itself is an abstract class, so you need to extend this class for your app’s suitable UI design.
When you extend Presenter, you need to override at least below 3 methods.
onCreateViewHolder(Viewgroup parent)
onBindViewHolder(ViewHolder viewHolder, Object cardInfo/item)
onUnbindViewHolder(ViewHolder viewHolder)
For the details of methods, I encourage you to refer source code of Presenter
class. Presenter
has innerclass ViewHolder
which has the reference to the View
. You may access the View
via viewHolder at specific event (onBind, onUnbind, etc.) listener callback method.
Implement HeadersFragment & RowsFragment (GridItemPresenter)
Let’s proceed to hands on. Here, we will implement GridItemPresenter
class.
In this sample application, Object (CardInfo/item) is String
type and viewHolder
holds TextView
reference to show this String
.
The layout of view is defined in the onCreateViewHolder()
.
Argument of onBindViewHolder()
, we can access viewHolder
created by onCreateViewHolder
and also Object (CardInfo/item), which stores card information (In this example, just a String
).
private class GridItemPresenter extends Presenter { @Override public ViewHolder onCreateViewHolder(ViewGroup parent) { TextView view = new TextView(parent.getContext()); view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT)); view.setFocusable(true); view.setFocusableInTouchMode(true); view.setBackgroundColor(getResources().getColor(R.color.default_background)); view.setTextColor(Color.WHITE); view.setGravity(Gravity.CENTER); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder viewHolder, Object item) { ((TextView) viewHolder.view).setText((String) item); } @Override public void onUnbindViewHolder(ViewHolder viewHolder) { } } }
After defining your own Presenter
, you only need to set RowsAdapter
at the start time of Activity. You can do so in onActivityCreated()
in MainFragment
,
@Override public void onActivityCreated(Bundle savedInstanceState) { Log.i(TAG, "onActivityCreated"); super.onActivityCreated(savedInstanceState); setupUIElements(); loadRows(); } ... private void loadRows() { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); /* GridItemPresenter */ HeaderItem gridItemPresenterHeader = new HeaderItem(0, "GridItemPresenter"); GridItemPresenter mGridPresenter = new GridItemPresenter(); ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter); gridRowAdapter.add("ITEM 1"); gridRowAdapter.add("ITEM 2"); gridRowAdapter.add("ITEM 3"); mRowsAdapter.add(new ListRow(gridItemPresenterHeader, gridRowAdapter)); /* set */ setAdapter(mRowsAdapter); }
So whole source code of MainFragment
will be.
package com.corochann.androidtvapptutorial; import android.graphics.Color; import android.os.Bundle; import android.support.v17.leanback.app.BrowseFragment; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; import android.support.v17.leanback.widget.Presenter; import android.util.Log; import android.view.Gravity; import android.view.ViewGroup; import android.widget.TextView; /** * Created by corochann on 2015/06/28. */ public class MainFragment extends BrowseFragment { private static final String TAG = MainFragment.class.getSimpleName(); private ArrayObjectAdapter mRowsAdapter; private static final int GRID_ITEM_WIDTH = 300; private static final int GRID_ITEM_HEIGHT = 200; @Override public void onActivityCreated(Bundle savedInstanceState) { Log.i(TAG, "onActivityCreated"); super.onActivityCreated(savedInstanceState); setupUIElements(); loadRows(); } private void setupUIElements() { // setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner)); setTitle("Hello Android TV!"); // Badge, when set, takes precedent // over title setHeadersState(HEADERS_ENABLED); setHeadersTransitionOnBackEnabled(true); // set fastLane (or headers) background color setBrandColor(getResources().getColor(R.color.fastlane_background)); // set search icon color setSearchAffordanceColor(getResources().getColor(R.color.search_opaque)); } private void loadRows() { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); /* GridItemPresenter */ HeaderItem gridItemPresenterHeader = new HeaderItem(0, "GridItemPresenter"); GridItemPresenter mGridPresenter = new GridItemPresenter(); ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter); gridRowAdapter.add("ITEM 1"); gridRowAdapter.add("ITEM 2"); gridRowAdapter.add("ITEM 3"); mRowsAdapter.add(new ListRow(gridItemPresenterHeader, gridRowAdapter)); /* set */ setAdapter(mRowsAdapter); } private class GridItemPresenter extends Presenter { @Override public ViewHolder onCreateViewHolder(ViewGroup parent) { TextView view = new TextView(parent.getContext()); view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT)); view.setFocusable(true); view.setFocusableInTouchMode(true); view.setBackgroundColor(getResources().getColor(R.color.default_background)); view.setTextColor(Color.WHITE); view.setGravity(Gravity.CENTER); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder viewHolder, Object item) { ((TextView) viewHolder.view).setText((String) item); } @Override public void onUnbindViewHolder(ViewHolder viewHolder) { } } }
Please add following
<color name="default_background">#3d3d3d</color>
so that MainFragment
can refer the background color setting.
(III) Build and Run!
Now you can see header & contents combination is implemented.
Note that we only defined Presenter, and load items to show. Other animaiton, e.g. when you select item, it will be bigger, is already implemented in SDK. So even non-designer, it’s easy to make certain level of UI for Android TV application.
Source code is uploaded on github.
See next post, How to use Presenter and ViewHolder? – Android TV application hands on tutorial 3, for CardPresenter
implementation which uses ImageCardView
to present a card with main image, title, and sub-text.