How to Use Presenter and ViewHolder--Android TV Application Development Tutorial 3

Copyright Statement: This article is an original translation of the blogger's articles. Please indicate the source of the reproduced articles.

Recommend:
Welcome to my Android TV profile, and I will regularly share some Android Tv related content with you.
http://www.jianshu.com/c/37efc6e9799b


I. Learning Objectives of this Chapter

In the previous chapter, we looked at GridItemPresenter. The relationship is as follows.

  • Presenter: GridItemPresenter
  • ViewHolder's View: TextView
  • CardInfo / Item: String

This is a very simple example. In this chapter, we continue to introduce another type of Presenter.

  • Presenter: CardPresenter
  • ViewHolderr's View: Image CardView
  • CardInfo / Item: Movie class

II. Image CardView

The ImageCardView class is provided by Android SDK, which provides a card design layout with the main image, title text and content text.

ImageCardView is a subclass of BaseCardView, so open source look at the BaseCardView class. This is the interpretation of BaseCardView:

android.support.v17.leanback.widget
public class BaseCardView
extends android.widget.FrameLayout
A card style layout that responds to certain state changes. Its children are arranged in vertical columns, and different areas are visible at different times.
BaseCardView draws its subitems according to their type, the area visibility of the subtype, and the status of the widget. A child may be labeled as belonging to one of three areas: master, information or extra. The main area is always visible, and the information and additional areas can be set to display according to the activation or selection status of the View. Card status is set by calling setActivated and setSelected.

BaseCardView itself does not provide a specific design layout. So when you want to use this, you can make BaseCardView subclasses with specific designs. ImageCardView is one of them. At present, I can only find the ImageCardView class as a subclass of BaseCardView provided by SDK.

In this chapter, we will add the ImageCardView to our code.

Implementing CardPresenter, Movie class

I will first place the necessary files. Right-click Packing,
1.New → class → CardPresenter
2.New → class → Movie
3. For the main image, we use movie.png.
Copy from the Android TV sample application res / drawable / movie.png.
4. We will use the utility functions provided by the Android TV sample application.
Copy the [package name] / Utils class from the Android TV sample application to your source code.

First, Utils.java is only replicated from AOSP, which will be below.

/*
 * 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.content.Context;
import android.graphics.Point;
import android.view.Display;
import android.view.WindowManager;
import android.widget.Toast;

/**
 * A collection of utility methods, all static.
 */
public class Utils {

    /*
     * Making sure public utility methods remain static
     */
    private Utils() {
    }

    /**
     * Returns the screen/display size
     */
    public static Point getDisplaySize(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Display display = wm.getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        return size;
    }

    /**
     * Shows a (long) toast
     */
    public static void showToast(Context context, String msg) {
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
    }

    /**
     * Shows a (long) toast.
     */
    public static void showToast(Context context, int resourceId) {
        Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
    }

    public static int convertDpToPixel(Context ctx, int dp) {
        float density = ctx.getResources().getDisplayMetrics().density;
        return Math.round((float) dp * density);
    }

    /**
     * Formats time in milliseconds to hh:mm:ss string format.
     */
    public static String formatMillis(int millis) {
        String result = "";
        int hr = millis / 3600000;
        millis %= 3600000;
        int min = millis / 60000;
        millis %= 60000;
        int sec = millis / 1000;
        if (hr > 0) {
            result += hr + ":";
        }
        if (min >= 0) {
            if (min > 9) {
                result += min + ":";
            } else {
                result += "0" + min + ":";
            }
        }
        if (sec > 9) {
            result += sec;
        } else {
            result += "0" + sec;
        }
        return result;
    }
}

Next, the Movie class defines that CardPresenter will use CardInfo / Item displayed by ImageCardView. It should have information.

  • main image
  • title text
  • content text (studio)
    But in the first stage, I used only "title" and "content" information.
/*
 * 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.util.Log;

import java.net.URI;
import java.net.URISyntaxException;

/**
 *  Modified from AOSP sample source code, by corochann on 2/7/2015.
 *  Movie class represents video entity with title, description, image thumbs and video url.
 */
public class Movie {

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

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

    public Movie() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getStudio() {
        return studio;
    }

    public void setStudio(String studio) {
        this.studio = studio;
    }

    @Override
    public String toString() {
        return "Movie{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

The final implementation is CardPresenter, which is a subclass of Presenter. CardPresenter has ViewHolder extended from Parent's Presenter.ViewHolder. The ViewHolder saves the ImageCardView to display the UI of the Movie project.

/*
 * 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.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.Presenter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

/**
 * Modified from AOSP sample source code, by corochann on 2/7/2015.
 */
public class CardPresenter extends Presenter {

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

    private static Context mContext;
    private static int CARD_WIDTH = 313;
    private static int CARD_HEIGHT = 176;

    static class ViewHolder extends Presenter.ViewHolder {
        private Movie mMovie;
        private ImageCardView mCardView;
        private Drawable mDefaultCardImage;

        public ViewHolder(View view) {
            super(view);
            mCardView = (ImageCardView) view;
            mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
        }

        public void setMovie(Movie m) {
            mMovie = m;
        }

        public Movie getMovie() {
            return mMovie;
        }

        public ImageCardView getCardView() {
            return mCardView;
        }

        public Drawable getDefaultCardImage() {
            return mDefaultCardImage;
        }

    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        Log.d(TAG, "onCreateViewHolder");
        mContext = parent.getContext();

        ImageCardView cardView = new ImageCardView(mContext);
        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
        return new ViewHolder(cardView);
    }

    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
        Movie movie = (Movie) item;
        ((ViewHolder) viewHolder).setMovie(movie);

        Log.d(TAG, "onBindViewHolder");
        ((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
        ((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
        ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
        ((ViewHolder) viewHolder).mCardView.setMainImage(((ViewHolder) viewHolder).getDefaultCardImage());
    }

    @Override
    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
        Log.d(TAG, "onUnbindViewHolder");
    }

    @Override
    public void onViewAttachedToWindow(Presenter.ViewHolder viewHolder) {
        // TO DO
    }

}

The data model = Movie and presente= CardPresenter is complete. We can display the Movie project by putting the project on the adapter.

private void loadRows() {
        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
        ...

        /* CardPresenter */
        HeaderItem cardPresenterHeader = new HeaderItem(1, "CardPresenter");
        CardPresenter cardPresenter = new CardPresenter();
        ArrayObjectAdapter cardRowAdapter = new ArrayObjectAdapter(cardPresenter);

        for(int i=0; i<10; i++) {
            Movie movie = new Movie();
            movie.setTitle("title" + i);
            movie.setStudio("studio" + i);
            cardRowAdapter.add(movie);
        }
        mRowsAdapter.add(new ListRow(cardPresenterHeader, cardRowAdapter));

         ...
    }

IV. First run




The CardPresenter title will be displayed on the second line, and the ImageCardView will display the default card image. When you move from the title to the content (the item is "on Activated"), the title and the content text will appear.

The source code is in github Up.

Use Picasso to download pictures from the Internet and update the main map

The example shows the default image (which is static) that must be included in the ImageCardView in the application. However, sometimes you want to use images downloaded from web pages so that your application can display updated information.

Picasso Image Loading Library will help us to implement it easily. The following is a reference.

  • Picasso
  • Android SDK: Working with Picasso
  • How to Use Picasso Image Loader Library in Android
    In the CardPresenter class, we use the picasso library, which can be included by adding the following lines to the app / build.gradle file.

    dependencies {
      compile fileTree(dir: 'libs', include: ['*.jar'])
      compile 'com.android.support:recyclerview-v7:22.2.0'
      compile 'com.android.support:leanback-v17:22.2.0'
      compile 'com.squareup.picasso:picasso:2.3.2'
    }

    We will add the cardImageUrl member to the Movie class, which points to the URL of the main image.

      private String cardImageUrl;
    
      public String getCardImageUrl() {
          return cardImageUrl;
      }
    
      public void setCardImageUrl(String cardImageUrl) {
          this.cardImageUrl = cardImageUrl;
      }

Getter and setter can be automatically generated by Android studio. In the above modification, you just need to declare the cardImageUrl member, then press [Alt] + [Insert] and generate getter and setter. We also implemented a getImage URI function to convert the URL string to the URI format.
Movie.java

   public URI getCardImageURI() {
        try {
            return new URI(getCardImageUrl());
        } catch (URISyntaxException e) {
            return null;
        }
    }

CardPresenter is responsible for updating images using Picasso. This is done by implementing the updateCardViewImage function. Picasso makes the source code look more intuitive about loading and converting images.

        public ViewHolder(View view) {
            super(view);
            mCardView = (ImageCardView) view;
            mImageCardViewTarget = new PicassoImageCardViewTarget(mCardView);
            mDefaultCardImage = mContext.getResources().getDrawable(R.drawable.movie);
        }

           ...

        protected void updateCardViewImage(URI uri) {
            Picasso.with(mContext)
                    .load(uri.toString())
                    .resize(Utils.convertDpToPixel(mContext, CARD_WIDTH),
                            Utils.convertDpToPixel(mContext, CARD_HEIGHT))
                    .error(mDefaultCardImage)
                    .into(mImageCardViewTarget);
        }
    }


    ...

    @Override
    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
        Movie movie = (Movie) item;
        ((ViewHolder) viewHolder).setMovie(movie);

        Log.d(TAG, "onBindViewHolder");
        if (movie.getCardImageUrl() != null) {
            ((ViewHolder) viewHolder).mCardView.setTitleText(movie.getTitle());
            ((ViewHolder) viewHolder).mCardView.setContentText(movie.getStudio());
            ((ViewHolder) viewHolder).mCardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
            ((ViewHolder) viewHolder).updateCardViewImage(movie.getCardImageURI());
            //((ViewHolder) viewHolder).mCardView.setMainImage(((ViewHolder) viewHolder).getDefaultCardImage());
        }
    }

In the last line of updateCardViewImage, it calls the mImageCardViewTarget method to load the image into the image view. The achievement of this goal is as follows.

    public static class PicassoImageCardViewTarget implements Target {
        private ImageCardView mImageCardView;

        public PicassoImageCardViewTarget(ImageCardView imageCardView) {
            mImageCardView = imageCardView;
        }

        @Override
        public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom loadedFrom) {
            Drawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
            mImageCardView.setMainImage(bitmapDrawable);
        }

        @Override
        public void onBitmapFailed(Drawable drawable) {
            mImageCardView.setMainImage(drawable);
        }

        @Override
        public void onPrepareLoad(Drawable drawable) {
            // Do nothing, default_background manager has its own transitions
        }
    }

Interface goals are defined in the picasso Library

represents an arbitrary listener for image loading.

The target interface allows us to implement three listener functions.

  • onBitmapLoade - Callback when the image is successfully loaded.
  • onBitmapFailed - Callback when the image is successfully loaded. Link error ()
  • onPrepareLoad - Callback is invoked before your request is submitted. Connect to placeholder ()

The remaining task is to specify cardImageUrl from MainFragment, which is in

    private void loadRows() {

        ...

        for(int i=0; i<10; i++) {
            Movie movie = new Movie();
            movie.setCardImageUrl("http://heimkehrend.raindrop.jp/kl-hacker/wp-content/uploads/2014/08/DSC02580.jpg");
            movie.setTitle("title" + i);
            movie.setStudio("studio" + i);
            cardRowAdapter.add(movie);
        }
         ...
    }

Finally, you need to add permissions to use the Internet in AndroidManifest.xml before building.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.corochann.androidtvapptutorial" >

    <uses-permission android:name="android.permission.INTERNET" />

    ...

Run again


Now the main image is downloaded from the Internet.
Source code is on github.

Customize ImageCardView, BaseCardView

We can change the design style and the animation behavior of the card. First, I recommend referring to the BaseCardView description in the source code provided by Android SDK.

BaseCardView draws its subitems according to their type, the area visibility of the subtype, and the status of the widget. A child may be marked as belonging to one of three areas: main, info, extra. The main area is always visible, and info and extra can be set to display based on the activation or selection status of View. Card status is set by calling setActivated and setSelected.

In the BaseCardView class, you can check the options that can be used to change the design.

  • public void setCardType(int type)
  • public void setInfoVisibility(int visibility)
  • public void setExtraVisibility(int visibility)

setCardType(int type)

You can set the following parameters for card:

  • CARD_TYPE_MAIN_ONLY
  • CARD_TYPE_INFO_OVER
  • CARD_TYPE_INFO_UNDER
  • CARD_TYPE_INFO_UNDER_WITH_EXTRA
    Examples of ImageCardView:

    CARD_TYPE_MAIN_ONLY


    CARD_TYPE_INFO_OVER

CARD_TYPE_INFO_UNDER,CARD_TYPE_INFO_UNDER_WITH_EXTRA

You can view the layout of ImageCardView in the SDK folder sdk extras android support v17 leanback res layout lb_image_card_view. xml.

ImageCardView has imageView as its main area, and title and content text are located in the information area. No additional regions are set, so the behavior between CARD_TYPE_INFO_UNDER and CARD_TYPE_INFO_UNDER_WITH_EXTRA is the same.

setInfoVisibility(int visibility), setExtraVisibility(int visibility)

You can set the following parameters:

  • The CARD_REGION_VISIBLE_ALWAYS-area (title and content text area) will always be displayed.
  • CARD_REGION_VISIBLE_ACTIVATED - This area does not appear when the user selects the header. When the user moves to Rows Fragment, the area will be displayed.
  • CARD_REGION_VISIBLE_SELECTED - This area will not appear if this card/item is not selected. Only when the card/project is selected will the region appear.

A more detailed explanation of these options can refer to the SDK source code.

Here I change the settings by modifying onCreateViewHolder in the CardPresenter class.

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        Log.d(TAG, "onCreateViewHolder");
        mContext = parent.getContext();

        ImageCardView cardView = new ImageCardView(mContext);
        cardView.setCardType(BaseCardView.CARD_TYPE_INFO_UNDER);
        cardView.setInfoVisibility(BaseCardView.CARD_REGION_VISIBLE_ALWAYS);
        cardView.setFocusable(true);
        cardView.setFocusableInTouchMode(true);
        cardView.setBackgroundColor(mContext.getResources().getColor(R.color.fastlane_background));
        return new ViewHolder(cardView);
    }

Source code in github.

In the next chapter, Picasso background management - Android TV application manual tutorial 4, realizes the background image updating function.

Keywords: Android SDK Apache Java

Added by kubis on Mon, 08 Jul 2019 21:38:04 +0300