Construction of BrowseFragment
– Android TV application hands on tutorial 2

GridItemPresenter1

[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. 

RowsAdapter1
Set of ListRow constructs RowsAdapter, which makes main body UI of BrowseFragment.

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. 

ListRow1
Construction of each ListRow.

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.

GridItemPresenter1

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.

Leave a Comment

Your email address will not be published. Required fields are marked *