Contents
Showing icons on vertical alignment
We learned BrowseFragment
to show icons, where icons are aligned horizontally when the size increases. VerticalGridFragment
is another Fragment provided by leanback support library, where it shows the icons with vertical alignment.
For example, I guess Sideload Launcher – Android TV is using this VerticalGridFragment
to show installed app icons.
This post explains how to implement VerticalGridFragment
. The implementation is referenced from Google’s sample implementation in the same way as before.
Create parent Activity – VerticalGridActivity
Starting by creating Activity. right click on “ui” package, [New] → [Activity] → [Blank Activity], type Activity Name as “VerticalGridActivity
” and click [Finish]. It will automatically create VerticalGridActivity
class, activity_vertical_grid.xm
l layout resource and add this Activity information to AndroidManifest.xml
.
This Activity only shows VerticalGridFragment
, so modify layout/activity_vertical_grid.xml
(automatically generated file) as follows
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.corochann.androidtvapptutorial.ui.VerticalGridActivity"> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:name="com.corochann.androidtvapptutorial.ui.VerticalGridFragment" android:id="@+id/vertical_grid_fragment" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
No modification necessary for VerticalGridActivity
class.
Create VerticalGridFragment
Now we can proceed to make our VerticalGridFragment
class. right click on “ui” package, [New] → [Java Class] → type Name as “VerticalGridFragment
“.
This new Fragment needs to be a subclass of leanback support library’s VerticalGridFragment
class (android.support.v17.leanback.app.VerticalGridFragment
). Mock implementation looks like this.
package com.corochann.androidtvapptutorial.ui; import android.os.Bundle; import android.util.Log; /** * VerticalGridFragment shows contents with vertical alignment */ public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment { private static final String TAG = VerticalGridFragment.class.getSimpleName(); @Override public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); } }
When I check the source code of VerticalGridFragment
, it was not long. So tracking all the public set methods are not so difficult which are
setGridPresenter
setAdapter
setTitle
setOnItemViewSelectedListener
setOnItemViewClickedListener
setSelectedPosition
Each methods are explained one by one in the following. But if you want a quick minimum implementation, implementing setGridPresenter
& setAdapter
is enough (and setGridPresenter is MUST) for VerticalGridFragment
to work.
setGridPresenter (MUST)
Specify a Presenter for VerticalGridFragment. setGridPresenter
takes VerticalGridPresenter
class (provided by leanback library) as argument. Following simple template code suffices in most of the case.
private static final int NUM_COLUMNS = 4; VerticalGridPresenter gridPresenter = new VerticalGridPresenter(); gridPresenter.setNumberOfColumns(NUM_COLUMNS); setGridPresenter(gridPresenter);
You must implement this method, otherwise runtime exception happens.
setAdapter
Usage of setAdapter is similar to BrowseFragment
, but it’s more easy.
- Prepare instance of
ArrayObjectAdapter adapter
, by specifying Presenter
Ex. preparemAdapter
which usesCardPresenter
private ArrayObjectAdapter mAdapter; mAdapter = new ArrayObjectAdapter(new CardPresenter());
- Adding items (Model instance) to this adapter.
Ex. addingMovie movie
instance.mAdapter.add(movie);
- Finally, call
setAdapter
Ex.setAdapter(mAdapter);
After implementing setGridPresenter
and setAdapter
, VerticalGridFragment
source code will be following.
package com.corochann.androidtvapptutorial.ui; import android.os.Bundle; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.VerticalGridPresenter; import android.util.Log; import com.corochann.androidtvapptutorial.data.VideoProvider; import com.corochann.androidtvapptutorial.model.Movie; import com.corochann.androidtvapptutorial.ui.presenter.CardPresenter; import org.json.JSONException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * VerticalGridFragment shows contents with vertical alignment */ public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment { private static final String TAG = VerticalGridFragment.class.getSimpleName(); private static final int NUM_COLUMNS = 4; private LinkedHashMap<String, List<Movie>> mVideoLists = null; private ArrayObjectAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); setupFragment(); } private void setupFragment() { VerticalGridPresenter gridPresenter = new VerticalGridPresenter(); gridPresenter.setNumberOfColumns(NUM_COLUMNS); setGridPresenter(gridPresenter); mAdapter = new ArrayObjectAdapter(new CardPresenter()); /* Add movie items */ try { mVideoLists = VideoProvider.buildMedia(getActivity()); } catch (JSONException e) { Log.e(TAG, e.toString()); } for (int i = 0; i < 3; i++) { // This loop is to for increasing the number of contents. not necessary. for (Map.Entry<String, List<Movie>> entry : mVideoLists.entrySet()) { // String categoryName = entry.getKey(); List<Movie> list = entry.getValue(); for (int j = 0; j < list.size(); j++) { Movie movie = list.get(j); mAdapter.add(movie); } } } setAdapter(mAdapter); } }
Even without implemeting setAdapter,
VerticalGridFragment
appears with no contents. But there is no meaning for it if there is no contents set in adapter.
setTitle
Showing a title on the top-right of VerticalGridFragment
.
@Override public void onCreate(Bundle savedInstanceState) { ... setTitle("VerticalGridFragment"); setupFragment(); }
Instead of setTitle
, you may also call setBadgeDrawable
if you want to set Drawable
at the title.
setOnItemViewSelectedListener
This is same with BrowseFrament
, use a class which implements OnItemViewSelectedListener
in the argument.
Ex.
setOnItemViewSelectedListener(new ItemViewSelectedListener()); private final class ItemViewSelectedListener implements OnItemViewSelectedListener { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { // write program here } }
setOnItemViewClickedListener
This is same with BrowseFrament
, use a class which implements OnItemViewClickedListener
in the argument.
Ex.
setOnItemViewClickedListener(new ItemViewClickedListener()); private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { // write program here } }
setSelectedPosition
This method will change the current select focus to specified position.
The sample code after all the implementation is as follows
package com.corochann.androidtvapptutorial.ui; import android.content.Intent; import android.os.Bundle; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.OnItemViewSelectedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.support.v17.leanback.widget.VerticalGridPresenter; import android.util.Log; import com.corochann.androidtvapptutorial.data.VideoProvider; import com.corochann.androidtvapptutorial.model.Movie; import com.corochann.androidtvapptutorial.ui.background.PicassoBackgroundManager; import com.corochann.androidtvapptutorial.ui.presenter.CardPresenter; import org.json.JSONException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * VerticalGridFragment shows contents with vertical alignment */ public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment { private static final String TAG = VerticalGridFragment.class.getSimpleName(); private static final int NUM_COLUMNS = 4; private LinkedHashMap<String, List<Movie>> mVideoLists = null; private ArrayObjectAdapter mAdapter; private PicassoBackgroundManager picassoBackgroundManager; @Override public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); picassoBackgroundManager = new PicassoBackgroundManager(getActivity()); setTitle("VerticalGridFragment"); //setBadgeDrawable(getResources().getDrawable(R.drawable.app_icon_your_company)); setupFragment(); setupEventListeners(); // it will move current focus to specified position. Comment out it to see the behavior. // setSelectedPosition(5); } private void setupFragment() { VerticalGridPresenter gridPresenter = new VerticalGridPresenter(); gridPresenter.setNumberOfColumns(NUM_COLUMNS); setGridPresenter(gridPresenter); mAdapter = new ArrayObjectAdapter(new CardPresenter()); /* Add movie items */ try { mVideoLists = VideoProvider.buildMedia(getActivity()); } catch (JSONException e) { Log.e(TAG, e.toString()); } for (int i = 0; i < 3; i++) { // This loop is to for increasing the number of contents. not necessary. for (Map.Entry<String, List<Movie>> entry : mVideoLists.entrySet()) { // String categoryName = entry.getKey(); List<Movie> list = entry.getValue(); for (int j = 0; j < list.size(); j++) { Movie movie = list.get(j); mAdapter.add(movie); } } } setAdapter(mAdapter); } private void setupEventListeners() { setOnItemViewClickedListener(new ItemViewClickedListener()); setOnItemViewSelectedListener(new ItemViewSelectedListener()); } private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof Movie) { Movie movie = (Movie) item; Intent intent = new Intent(getActivity(), DetailsActivity.class); intent.putExtra(DetailsActivity.MOVIE, movie); getActivity().startActivity(intent); } } } private final class ItemViewSelectedListener implements OnItemViewSelectedListener { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { picassoBackgroundManager.updateBackgroundWithDelay(((Movie) item).getBackgroundImageUrl()); } } }
Launch from MainFragment
Launch VerticalGridActivity
from MainFragment
. I added grid item to launch VerticalGridActivity
, below is the added part.
private static final String GRID_STRING_VERTICAL_GRID_FRAGMENT = "VerticalGridFragment"; ... private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { ... } else if (item instanceof String){ ... } else if (item == GRID_STRING_VERTICAL_GRID_FRAGMENT) { Intent intent = new Intent(getActivity(), VerticalGridActivity.class); startActivity(intent); } ... } } } private void loadRows() { ... gridRowAdapter.add(GRID_STRING_GUIDED_STEP_FRAGMENT); gridRowAdapter.add(GRID_STRING_VERTICAL_GRID_FRAGMENT); gridRowAdapter.add(GRID_STRING_RECOMMENDATION); ... }
Build and run
When the number of contents increases VerticalGridFragment
grows to the bottom, not to the right like BrowseFragment
.
Source code is on github.