DetailsOverviewRowPresenter & FullWidthDetailsOverviewRowPresenter
– Android TV application hands on tutorial 5

FullWidthDetailsOverviewRowPresenter1

[Update 2015.12.14]: revise & add DetailsOverviewRowPresenter sample implementation, see bottom of this post.

Aim of this chapter – DetailsActivity implementation

Implementing

  1. setOnItemViewClickedListener – onItemClicked callback function in MainFragment
    After implementation, we can click cards to go to next action. We show content detail information for each Movie item in this chapter.
  2. DetailsActivityVideoDetailsFragment, and DetailsDescriptionPresenter
    DetailsActivity is invoked by clicking card in MainActivity. It shows VideoDetailsFragment, which is a layout to show card content.

Implementing click listener in MainFragment

To register a next action when user clicked certain card, we can use setOnItemViewClickedListener method defined in BrowseFragment class (MainFragment class is a sub class of BrowseFragment). 

Sample implementation is as follows. This is almost same with setOnItemViewSelectedListener introduced in previous chapter.

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {

        ...

        setupEventListeners();

        picassoBackgroundManager = new PicassoBackgroundManager(getActivity());
    }

    private void setupEventListeners() {
        setOnItemViewSelectedListener(new ItemViewSelectedListener());
        setOnItemViewClickedListener(new ItemViewClickedListener());
    }

    private final class ItemViewClickedListener implements OnItemViewClickedListener {
        @Override
        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
            // each time the item is clicked, code inside here will be executed.

            }
        }
    }

DetailsActivity & VideoDetailsFragment – Explanation

Below is a picture from AOSP sample applicaton.  

DetailsFragment_combined_picture_explain

In the Android sample application, we have 2 rows in VideoDetailsFragment. First row is DetailsOverviewRow and second row is ListRow (which is already explained in MainFragment). DetailsOverviewRow shows the content details including picture in the lect, description and some actions are set in the left-bottom.

We can prepare our own Presenter to specify the design layout of DetailsOverviewRow. There are 2 pre-implemented presenters we can use in Leanback support library. 

  1. DetailsOverviewRowPresenter: Shown in the above picture, but it is already deprecated in leanback library version 22.2.0.
  2. FullWidthDetailsOverviewRowPresenter: Instead of DetailsOverviewRowPresenter, use this presenter is recommended in the AOSP document.

In the following, I will try to introduce this new FullWidthDetailsOverviewRowPresenter (*updated 2015.12.4: you can also check DetailsOverviewRowPresenter, see bottom of this post). It will specify the design layout of DetailsOverviewRow, which is usually used in the first row of your DetailsFragment to show item details information.

FullWidthDetailsOverviewRowPresenter is consisting of 3 parts, namely

  1. Logo view – customizable (option), by implementing DetailsOverViewLogoPresenter
  2. Action list view
  3. Detailed description view – customizable (MUST), implement subclass of AbstractDetailsDescriptionPresenter
FullWidthDetailsOverviewRowPresenter

We define “DetailsDescriptionPresenter” which extends AbstractDetailsDescriptionPresenter defined in Leanback libarary. AbstractDetailsDescriptionPresenter decides the design layout of descrption view.

DetailsActivity & VideoDetailsFragment – Implementation

We proceed to create DetailsActivity for showing the UI of content details. The design is specified in VideoDetailsFragment, which is a subclass of DetailsFragment.

Creating DetailsActivity & VideoDetailsFragment is done in the same way with MainActivity & MainFragment introduced in chapter 1.

DetailsActivity

New → Activity → BlankActivity

Activity Name: DetailsActivity
Layout Name: activity_details
Title: DetailsActivity
Menu Resource Name: menu_details
Hierarchical Parent: blank 

VideoDetailsFragment

New -> Java Class -> Name: VideoDetailsFragment

First, modify activity_details.xml as follows so that it only displays VideoDetailsFragment.

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/details_fragment"
    android:name="com.corochann.androidtvapptutorial.ui.VideoDetailsFragment"
    android:layout_width="match_parent" android:layout_height="match_parent"
    tools:context=".DetailsActivity" tools:deviceIds="tv" />

Second modify VideoDetailsFragment. We will make this VideoDetailsFragment as a sub-class of DetailsFragmentDetailsFragment class is in leanback support library to create UI for content details. In the VideoDetailsFragment, declared private member mFwdorPresenter is the instance of FullWidthDetailsOverviewRowPresenter.

Note that AsyncTask is for executing some tasks in background thread (“doInBackground“)  followed by executing some tasks in UI thread (“onPostExecute“). Here, we load a picture image in background, and updating UI in UI thread. You can find information of AsyncTask below.

package com.corochann.androidtvapptutorial;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v17.leanback.app.DetailsFragment;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.DetailsOverviewRow;
import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
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.SparseArrayObjectAdapter;
import android.util.Log;

import com.squareup.picasso.Picasso;

import java.io.IOException;

/**
 * Created by corochann on 6/7/2015.
 */
public class VideoDetailsFragment extends DetailsFragment {

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

    private static final int DETAIL_THUMB_WIDTH = 274;
    private static final int DETAIL_THUMB_HEIGHT = 274;


    private static final String MOVIE = "Movie";

    private CustomFullWidthDetailsOverviewRowPresenter mFwdorPresenter;
    private PicassoBackgroundManager mPicassoBackgroundManager;

    private Movie mSelectedMovie;
    private DetailsRowBuilderTask mDetailsRowBuilderTask;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate");
        super.onCreate(savedInstanceState);

        mFwdorPresenter = new CustomFullWidthDetailsOverviewRowPresenter(new DetailsDescriptionPresenter());

        mPicassoBackgroundManager = new PicassoBackgroundManager(getActivity());
        mSelectedMovie = (Movie)getActivity().getIntent().getSerializableExtra(MOVIE);

        mDetailsRowBuilderTask = (DetailsRowBuilderTask) new DetailsRowBuilderTask().execute(mSelectedMovie);
        mPicassoBackgroundManager.updateBackgroundWithDelay(mSelectedMovie.getCardImageUrl());;
    }

    @Override
    public void onStop() {
        mDetailsRowBuilderTask.cancel(true);
        super.onStop();
    }

    private class DetailsRowBuilderTask extends AsyncTask<Movie, Integer, DetailsOverviewRow> {
        @Override
        protected DetailsOverviewRow doInBackground(Movie... params) {
            DetailsOverviewRow row = new DetailsOverviewRow(mSelectedMovie);
            try {
                Bitmap poster = Picasso.with(getActivity())
                        .load(mSelectedMovie.getCardImageUrl())
                        .resize(Utils.convertDpToPixel(getActivity().getApplicationContext(), DETAIL_THUMB_WIDTH),
                                Utils.convertDpToPixel(getActivity().getApplicationContext(), DETAIL_THUMB_HEIGHT))
                        .centerCrop()
                        .get();
                row.setImageBitmap(getActivity(), poster);
            } catch (IOException e) {
                Log.w(TAG, e.toString());
            }


            return row;
        }

        @Override
        protected void onPostExecute(DetailsOverviewRow row) {
                    /* 1st row: DetailsOverviewRow */
            SparseArrayObjectAdapter sparseArrayObjectAdapter = new SparseArrayObjectAdapter();
            for (int i = 0; i<10; i++){
                sparseArrayObjectAdapter.set(i, new Action(i, "label1", "label2"));
            }
            row.setActionsAdapter(sparseArrayObjectAdapter);

        /* 2nd row: ListRow */
            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter());
            for(int i = 0; i < 10; i++){
                Movie movie = new Movie();
                if(i%3 == 0) {
                    movie.setCardImageUrl("http://heimkehrend.raindrop.jp/kl-hacker/wp-content/uploads/2014/08/DSC02580.jpg");
                } else if (i%3 == 1) {
                    movie.setCardImageUrl("http://heimkehrend.raindrop.jp/kl-hacker/wp-content/uploads/2014/08/DSC02630.jpg");
                } else {
                    movie.setCardImageUrl("http://heimkehrend.raindrop.jp/kl-hacker/wp-content/uploads/2014/08/DSC02529.jpg");
                }
                movie.setTitle("title" + i);
                movie.setStudio("studio" + i);
                listRowAdapter.add(movie);
            }
            HeaderItem headerItem = new HeaderItem(0, "Related Videos");

            ClassPresenterSelector classPresenterSelector = new ClassPresenterSelector();
            mFwdorPresenter.setInitialState(FullWidthDetailsOverviewRowPresenter.STATE_SMALL);
            Log.e(TAG, "mFwdorPresenter.getInitialState: " +mFwdorPresenter.getInitialState());

            classPresenterSelector.addClassPresenter(DetailsOverviewRow.class, mFwdorPresenter);
            classPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());

            ArrayObjectAdapter adapter = new ArrayObjectAdapter(classPresenterSelector);
            /* 1st row */
            adapter.add(row);
            /* 2nd row */
            adapter.add(new ListRow(headerItem, listRowAdapter));
            /* 3rd row */
            //adapter.add(new ListRow(headerItem, listRowAdapter));
            setAdapter(adapter);

        }
    }
}

Note that constructor of adapter is defferent between MainFragment and VideoDetailsFragment. We are only using ListRow – ListRowPresenter in the MainFragment. In that case we can instantiate adapter by setting Presenter itself like 

adapter = new ArrayObjectAdapter(new ListRowPresenter());

However, we are using DetailsOverviewRow – FullWidthDetailsOverviewRowPresenter & ListRow – ListRowPresenter in VideoDetails Fragment. ClassPresenterSelector defines this correspondence, and we can use it in the argument of constructor of adapter. 

ClassPresenterSelector classPresenterSelector = new ClassPresenterSelector();
classPresenterSelector.addClassPresenter(DetailsOverviewRow.class, mFwdorPresenter);
classPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
adapter = new ArrayObjectAdapter(classPresenterSelector);

Next, add description member in Movie class, you can implement Getter and Setter method by [Alt]+[Insert] in Android studio. Also, make Movie Serializable so that we can pass this object through intent. Because we pass Movie object through intent from MainActivity to DetailsActivity

* See also: Fast, easy Parcelable implementation with Android studio Parcelable plugin for better performance implementation.

public class Movie implements Serializable {

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

    static final long serialVersionUID = 727566175075960653L;
    private long id;
    private String title;
    private String studio;
    private String description;
    private String cardImageUrl;

    ...

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    ...

}

Copy DetailsDescriptionPresenter from AOSP sample source code as follows. Again, DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter, which decides the design layout of descrption view.

/*
 * 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.
 */

package com.corochann.androidtvapptutorial;

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

public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter {

    @Override
    protected void onBindDescription(ViewHolder viewHolder, Object item) {
        Movie movie = (Movie) item;

        if (movie != null) {
            viewHolder.getTitle().setText(movie.getTitle());
            viewHolder.getSubtitle().setText(movie.getStudio());
            viewHolder.getBody().setText(movie.getDescription());
        }

    }
}

Finally, modify MainFragment to send intent to launch DetailsActivity.

    private void setupEventListeners() {
        setOnItemViewSelectedListener(new ItemViewSelectedListener());
        setOnItemViewClickedListener(new ItemViewClickedListener());
    }

    private final class ItemViewClickedListener implements OnItemViewClickedListener {
        @Override
        public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
                                  RowPresenter.ViewHolder rowViewHolder, Row row) {
            // each time the item is clicked, code inside here will be executed.
            if (item instanceof Movie) {
                Movie movie = (Movie) item;
                Log.d(TAG, "Item: " + item.toString());
                Intent intent = new Intent(getActivity(), DetailsActivity.class);
                intent.putExtra(DetailsActivity.MOVIE, movie);

                getActivity().startActivity(intent);
            }
        }
    }

Build and Run!

Now you can see the content details by clicking card. 

Initial Display, Logo is on the left, background is on the top half, and description is on the bottom half.

When user press “down” key, description view will take full screen.

FullWidthDetailsOverviewRowPresenter2

When user press “down” key again, next row (ListRow in this example) will appear. 

FullWidthDetailsOverviewRowPresenter3

Source code is on github.

——- Update: 2015.12.14 Add DetailsOverviewRowPresenter implementation —————-

Build and Run 2!

detailsoverviewrowpresenter01
DetailsOverviewRowPresenter
fullwidthdetailsoverviewrowpresenter01
FullWidthDetailsOverviewRowPresenter

Please check updated source code on github if you are interested in (already deprecated) DetailsOverviewRowPresenter implementation. Why I updated this code? Because it seems still some developer prefers DetailsOverviewRowPresenter design, even if it was deprecated!

Leave a Comment

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