Contents
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,
- Make own Model (If necessary): Java class which extends
HeaderItem
. - Make own View: xml layout resource file
- Make own Presenter: Java class which extends
RowHeaderPresenter
, to use own view. - 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 atonBindViewHolder
, and resource icon id is converted toDrawable
to set image oniconView
.
@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 MainFragment
. setHeaderPresenterSelector
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.