BrowseFragment Header customization
– Android TV application hands on tutorial 17

More customization of BrowseFragment
– Showing icon beside the Header title

The basic implementation of BrowseFragment is already explained in this tutorial. I’m going to proceed some advanced customization in following chapters.

Aim is to change the design of header, to show icons on the left of header title text, as shown in the above image.

How to customize header design

Leanback support library adopts MVP (Model-View-Presenter) architecture*, and HeaderItem (Model) is not an exception. We can prepare our own View and Presenter to customize the design of header.
MVP is explained in Chapter 14

Step is as follows,

  1. Make own Model (If necessary): Java class which extends HeaderItem.
  2. Make own View: xml layout resource file
  3. Make own Presenter: Java class which extends RowHeaderPresenter, to use own view.
  4. Call setHeaderPresenterSelector method to specify which Presenter to be used.

Let’s take a look one by one.

Making own Model

HeaderItem class is used for Header model as default, however it has only 2 members id and name. We want to show image icon so we need a new class IconHeaderItem class to hold an image resource id.

Create new Java class IconHeaderItem in model package, extend HeaderItem class and declare a member mImageResId.

package com.corochann.androidtvapptutorial.model;

import android.support.v17.leanback.widget.HeaderItem;

import com.corochann.androidtvapptutorial.ui.presenter.IconHeaderItemPresenter;

/**
 * Subclass of {@link HeaderItem} to hold icon resource id
 * to show icon on header with {@link IconHeaderItemPresenter}
 */
public class IconHeaderItem extends HeaderItem {


    private static final String TAG = IconHeaderItem.class.getSimpleName();
    public static final int ICON_NONE = -1;

    /** Hold an icon resource id */
    private int mIconResId = ICON_NONE;

    public IconHeaderItem(long id, String name, int iconResId) {
        super(id, name);
        mIconResId = iconResId;
    }

    public IconHeaderItem(long id, String name) {
        this(id, name, ICON_NONE);
    }

    public IconHeaderItem(String name) {
        super(name);
    }

    public int getIconResId() {
        return mIconResId;
    }

    public void setIconResId(int iconResId) {
        this.mIconResId = iconResId;
    }
}

Icon resource id can be set at the constructor of IconHeaderItem.
It is also allowed to not specify icon resource id, if you don’t want to show icon.

Making own View

Create a layout file called “icon_header_item.xml”. (Right click on res/layout → [New] > [File] → type “icon_header_item.xml” and press [OK].

Here, I will just use the code provided by Google’s sample.

<?xml version="1.0" encoding="utf-8"?>
<!--
 Copyright (C) 2014 The Android Open Source Project

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
-->
<android.support.v17.leanback.widget.NonOverlappingLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/header_icon"
        android:layout_width="24dp"
        android:layout_height="24dp" />

    <TextView
        android:id="@+id/header_label"
        android:layout_marginTop="6dp"
        android:layout_width="140dp"
        android:layout_height="wrap_content" />
</android.support.v17.leanback.widget.NonOverlappingLinearLayout>
From https://github.com/googlesamples/androidtv-Leanback

I modified ImageView’s width & height to 24dp, and TextView’s width as 140dp.

Making own RowHeaderPresenter

Create a new class “IconHeaderPresenter” in ui.presenter package.

package com.corochann.androidtvapptutorial.ui.presenter;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.RowHeaderPresenter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.corochann.androidtvapptutorial.R;
import com.corochann.androidtvapptutorial.model.IconHeaderItem;

/**
 * Customized HeaderItem Presenter to show {@link IconHeaderItem}
 */
public class IconHeaderItemPresenter  extends RowHeaderPresenter {

    private static final String TAG = IconHeaderItemPresenter.class.getSimpleName();

    private float mUnselectedAlpha;

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
        mUnselectedAlpha = viewGroup.getResources()
                .getFraction(R.fraction.lb_browse_header_unselect_alpha, 1, 1);
        LayoutInflater inflater = (LayoutInflater) viewGroup.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        View view = inflater.inflate(R.layout.icon_header_item, null);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) {
        IconHeaderItem iconHeaderItem = (IconHeaderItem) ((ListRow) o).getHeaderItem();
        View rootView = viewHolder.view;

        ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
        int iconResId = iconHeaderItem.getIconResId();
        if( iconResId != IconHeaderItem.ICON_NONE) { // Show icon only when it is set.
            Drawable icon = rootView.getResources().getDrawable(iconResId, null);
            iconView.setImageDrawable(icon);
        }

        TextView label = (TextView) rootView.findViewById(R.id.header_label);
        label.setText(iconHeaderItem.getName());
    }

    @Override
    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
        // no op
    }

    // TODO: TEMP - remove me when leanback onCreateViewHolder no longer sets the mUnselectAlpha,AND
    // also assumes the xml inflation will return a RowHeaderView
    @Override
    protected void onSelectLevelChanged(RowHeaderPresenter.ViewHolder holder) {
        // this is a temporary fix
        holder.view.setAlpha(mUnselectedAlpha + holder.getSelectLevel() *
                (1.0f - mUnselectedAlpha));
    }

}

Again, it is based on Google’s sample, but I slightly modified. Main point is following,

  • View: layout file, icon_header_item.xml, is extracted in onCreateViewHolder method as
    View view = inflater.inflate(R.layout.icon_header_item, null);
  • Model: IconHeaderItem instance is obtained at onBindViewHolder, and resource icon id is converted to Drawable to set image on iconView.
    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) {
        IconHeaderItem iconHeaderItem = (IconHeaderItem) ((ListRow) o).getHeaderItem();
        View rootView = viewHolder.view;

        ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
        int iconResId = iconHeaderItem.getIconResId();
        if( iconResId != IconHeaderItem.ICON_NONE) { // Show icon only when it is set.
            Drawable icon = rootView.getResources().getDrawable(iconResId, null);
            iconView.setImageDrawable(icon);
        }
        ...
    }

Implementation in MainFragment

Now we’ve done for preparing MVP of header class. Remaining task is update MainFragmentsetHeaderPresenterSelector method is used to specify Presenter class to be used for Header of BrowseFragment.

    private void setupUIElements() {

        ...

        setHeaderPresenterSelector(new PresenterSelector() {
            @Override
            public Presenter getPresenter(Object o) {
                return new IconHeaderItemPresenter();
            }
        });
    }

Change our header model from HeaderItem to IconHeaderItem.

1. GridItemListRow

    private void loadRows() {
        /* GridItemPresenter */
        IconHeaderItem gridItemPresenterHeader = new IconHeaderItem(0, "GridItemPresenter", R.drawable.ic_add_white_48dp);

        ...

        mGridItemListRow = new ListRow(gridItemPresenterHeader, gridRowAdapter);
    }

2. VideoListRow(s)

    private class MainFragmentLoaderCallbacks implements LoaderManager.LoaderCallbacks<LinkedHashMap<String, List<Movie>>> {

    ...

        @Override
        public void onLoadFinished(Loader<LinkedHashMap<String, List<Movie>>> loader, LinkedHashMap<String, List<Movie>> data) {

              ...
                    /* loadRows: videoListRow - CardPresenter */
                            IconHeaderItem header = new IconHeaderItem(index, entry.getKey(), R.drawable.ic_play_arrow_white_48dp);
                            index++;
                            mVideoListRowArray.add(new ListRow(header, cardRowAdapter));

              ...

            }
        }
    ...
    }

Icon images are downloaded from Material icon library. (Maybe icon and header title is not matching, but this is just workaround for developing practice)

Build and run

You can see icon on the left of header title if successful.

By modifying icon_header_item.xml, you can easily customize header’s layout.

Source code is on github.

Leave a Comment

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