VerticalGridFragment
– Android TV application hands on tutorial 19

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.

“Sideload Launcher – Android TV” app shows installed app icons in 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.xml 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.

  1.  Prepare instance of ArrayObjectAdapter adapter, by specifying Presenter
    Ex. prepare mAdapter which uses CardPresenter
private ArrayObjectAdapter mAdapter;
mAdapter = new ArrayObjectAdapter(new CardPresenter());
  1. Adding items (Model instance) to this adapter.
    Ex. adding Movie movie instance.
    mAdapter.add(movie);
  2. Finally, call setAdapter
    Ex. 
    setAdapter(mAdapter);

After implementing setGridPresenter and setAdapterVerticalGridFragment 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.

Leave a Comment

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