[譯]Recommendation card (推薦卡)--Android TV 開發手冊十一

weixin_34208283發表於2017-06-03

版權宣告:本文為博主原創翻譯文章,轉載請註明出處。

推薦:
歡迎關注我建立的Android TV 簡書專題,會定期給大家分享一些AndroidTv相關的內容:
https://www.jianshu.com/c/3f0ab61a1322


1927803-815ab456e5ede77d.png
recommendation

建議

Android TV的home,LeanbackLauncher在第一行有一個推薦行。任何應用程式都可以為使用者建議推薦的內容。在本章中,我將介紹如何從您的應用程式向LeanbackLauncher顯示推薦卡。

該建議是通過使用Android手機/平板電腦SDK中已經存在結構的通知框架實現的。所以在LeanbackLauncher中顯示的建議其實是傳送通知。基本順序如下

1.宣告NotificationManager
2.使用您的自定義的RecommendationBuilder類(它是usingNotificationCompat類的自定義類)來準備推薦。
3.通過RecommendationBuilder建立通知
4.使用NotificationManager通知此通知。

本章將要實現什麼?

本章將實現兩個新的類,“RecommendationBuilder”和“RecommendationFactory”。 RecommendationBuilder是為您的應用程式建立通知的自定義類。RecommendationFactory實際上是使用RecommendationBuilder建立通知。

在本章中,我們將通過單擊MainFragment中的按鈕傳送通知(提出recommendation)。它將在RecommendationFactory中引用推薦方法來推薦。 (建議通常應該在服務中完成,但是我在onClick中實現了推薦,以便於理解。)

RecommendationBuilder

首先,RecommendationBuilder如下。

import android.app.Notification;
import android.app.PendingIntent;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
 * This class builds recommendations as notifications with videos as inputs.
 */
public class RecommendationBuilder {

    private static final String TAG = RecommendationBuilder.class.getSimpleName();
    private static final String BACKGROUND_URI_PREFIX = "content://com.corochann.androidtvapptutorial/";

    private Context mContext;

    private int mId;
    private int mPriority;
    private int mFastLaneColor;
    private int mSmallIcon;
    private String mTitle;
    private String mDescription;
    private Bitmap mCardImageBitmap;
    private String mBackgroundUri;
    private Bitmap mBackgroundBitmap;
    private String mGroupKey;
    private String mSort;
    private PendingIntent mIntent;


    public RecommendationBuilder(Context context) {
        mContext = context;
        // default fast lane color
        setFastLaneColor(mContext.getResources().getColor(R.color.fastlane_background));
    }

    public RecommendationBuilder setFastLaneColor(int color) {
        mFastLaneColor = color;
        return this;
    }

    /* context must not be null. It should be specified in constructor */
/*
    public RecommendationBuilder setContext(Context context) {
        mContext = context;
        return this;
    }
*/

    public RecommendationBuilder setId(int id) {
        mId = id;
        return this;
    }

    public RecommendationBuilder setPriority(int priority) {
        mPriority = priority;
        return this;
    }

    public RecommendationBuilder setTitle(String title) {
        mTitle = title;
        return this;
    }

    public RecommendationBuilder setDescription(String description) {
        mDescription = description;
        return this;
    }

    public RecommendationBuilder setBitmap(Bitmap bitmap) {
        mCardImageBitmap = bitmap;
        return this;
    }

    public RecommendationBuilder setBackground(String uri) {
        mBackgroundUri = uri;
        return this;
    }

    public RecommendationBuilder setBackground(Bitmap bitmap) {
        mBackgroundBitmap = bitmap;
        return this;
    }

    public RecommendationBuilder setIntent(PendingIntent intent) {
        mIntent = intent;
        return this;
    }

    public RecommendationBuilder setSmallIcon(int resourceId) {
        mSmallIcon = resourceId;
        return this;
    }

    public Notification build() {

        Bundle extras = new Bundle();
        File bitmapFile = getNotificationBackground(mContext, mId);

        if (mBackgroundBitmap != null) {
            Log.d(TAG, "making URI for mBackgroundBitmap");
            extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
                    Uri.parse(BACKGROUND_URI_PREFIX + Integer.toString(mId)).toString());
        } else {
            Log.w(TAG, "mBackgroundBitmap is null");
        }

        // the following simulates group assignment into "Top", "Middle", "Bottom"
        // by checking mId and similarly sort order
        mGroupKey = (mId < 3) ? "Group1" : (mId < 5) ? "Group2" : "Group3";
        mSort = (mId < 3) ? "1.0" : (mId < 5) ? "0.7" : "0.3";

        // save bitmap into files for content provider to serve later
        try {
            bitmapFile.createNewFile();
            FileOutputStream fOut = new FileOutputStream(bitmapFile);
            mBackgroundBitmap.compress(Bitmap.CompressFormat.PNG, 85, fOut); //

你可以將此ReccomendationBuilder類用作庫。 在這種情況下,您不需要知道此實現的細節,因此您可以跳過閱讀本節。 關於這個類的說明寫在本節的剩餘部分中。

最重要的部分是build()函式,它顯示瞭如何使Android TV推薦通知例項。 推薦Android TV確實是通知! 它通過使用NotificationCompat.BigPictureStyle方法建立Notification類的例項。

public Notification build() {
 
        ...
 
        Notification notification = new NotificationCompat.BigPictureStyle(
                new NotificationCompat.Builder(mContext)
                        .setAutoCancel(true)
                        .setContentTitle(mTitle)
                        .setContentText(mDescription)
                        .setPriority(mPriority)
                        .setLocalOnly(true)
                        .setOngoing(true)
                        .setGroup(mGroupKey)
                        .setSortKey(mSort)
                        .setColor(mContext.getResources().getColor(R.color.fastlane_background))
                        .setCategory(Notification.CATEGORY_RECOMMENDATION)
                        .setLargeIcon(mCardImageBitmap)
                        .setSmallIcon(mSmallIcon)
                        .setContentIntent(mIntent)
                        .setExtras(extras))
                .build();
 
        Log.d(TAG, "Building notification - " + this.toString());
 
        return notification;
    }

推薦卡中的許多功能及其相關部分是操作簡便的。

此功能中最棘手的部分是設定背景影像。 當使用者在LeanbackLauncher中選擇推薦卡時,將顯示背景影像。 該背景影像由“extra”欄位指定,通過使用Bundle。 key是Notification. EXTRA_BACKGROUND_IMAGE_URI ,值為backgroundimage的URI。 請注意,您可以為其指定點陣圖檔案

  • smallicon - 用作應用/公司標誌,顯示在推薦卡的右下方。
  • 大圖 - 用作推薦卡的主要影像。

但是您不能通過bitmap指定背景影像。 在這個實現中,我們通過使用內容提供者和指定這個快取的背景影像的URI來快取點陣圖背景影像。

儲存部分如下

public Notification build() {

        Bundle extras = new Bundle();
        File bitmapFile = getNotificationBackground(mContext, mId);

        ...

        // save bitmap into files for content provider to serve later
        try {
            bitmapFile.createNewFile();
            FileOutputStream fOut = new FileOutputStream(bitmapFile);
            mBackgroundBitmap.compress(Bitmap.CompressFormat.PNG, 85, fOut); // <- background bitmap must be created by mBackgroundUri, and not  mCardImageBitmap
            fOut.flush();
            fOut.close();
        } catch (IOException ioe) {
            Log.d(TAG, "Exception caught writing bitmap to file!", ioe);
        }

獲取背景圖片URI的部分如下。 當選擇推薦卡時,它將使用key為“Notification.EXTRA_BACKGROUND_IMAGE_URI”來引用額外的欄位。 該值將傳送到Content Provider的openFile方法。 因此,這些值在openFile方法中處理,以提取已儲存的背景影像的檔案路徑。

    public Notification build() {

        Bundle extras = new Bundle();
        File bitmapFile = getNotificationBackground(mContext, mId);

        if (mBackgroundBitmap != null) {
            Log.d(TAG, "making URI for mBackgroundBitmap");
            extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
                    Uri.parse(BACKGROUND_URI_PREFIX + Integer.toString(mId)).toString());
        } else {
            Log.w(TAG, "mBackgroundBitmap is null");
        }

    ...

    }

    public static class RecommendationBackgroundContentProvider extends ContentProvider {

        ...

        @Override
        /*
         * content provider serving files that are saved locally when recommendations are built
         */
        public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
            Log.i(TAG, "openFile");
            int backgroundId = Integer.parseInt(uri.getLastPathSegment());
            File bitmapFile = getNotificationBackground(getContext(), backgroundId);
            return ParcelFileDescriptor.open(bitmapFile, ParcelFileDescriptor.MODE_READ_ONLY);
        }
    }

RecommendationFactory

RecommendationFactory類使用推薦構建器建立推薦電影專案的通知。 程式碼實現如下。

import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.squareup.picasso.Picasso;

import java.net.URI;

public class RecommendationFactory {

    private static final String TAG = RecommendationFactory.class.getSimpleName();
    private static final int CARD_WIDTH = 500;
    private static final int CARD_HEIGHT = 500;
    private static final int BACKGROUND_WIDTH = 1920;
    private static final int BACKGROUND_HEIGHT = 1080;

    private Context mContext;
    private NotificationManager mNotificationManager;

    public RecommendationFactory(Context context) {
        mContext = context;
        if (mNotificationManager == null) {
            mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        }
    }


    public void recommend(int id, Movie movie) {
        recommend(id, movie, NotificationCompat.PRIORITY_DEFAULT);
    }

    /**
     * create a notification for recommending item of Movie class
     * @param movie
     */
    public void recommend(final int id, final Movie movie, final int priority) {
        Log.i(TAG, "recommend");
        /* Run in background thread, since bitmap loading must be done in background */
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "recommendation in progress");
                Bitmap backgroundBitmap = prepareBitmap(movie.getCardImageUrl(), BACKGROUND_WIDTH, BACKGROUND_HEIGHT);
                Bitmap cardImageBitmap = prepareBitmap(movie.getCardImageUrl(), CARD_WIDTH, CARD_HEIGHT);
                PendingIntent intent = buildPendingIntent(movie, id);

                RecommendationBuilder recommendationBuilder = new RecommendationBuilder(mContext)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setBackground(backgroundBitmap)
                        .setId(id)
                        .setPriority(priority)
                        .setTitle(movie.getTitle())
                        .setDescription(movie.getDescription())
                        .setIntent(intent)
                        .setBitmap(cardImageBitmap)
                        .setFastLaneColor(mContext.getResources().getColor(R.color.fastlane_background));
                Notification recommendNotification = recommendationBuilder.build();
                mNotificationManager.notify(id, recommendNotification);
            }}).start();
    }

    /**
     * prepare bitmap from URL string
     * @param url
     * @return
     */
    public Bitmap prepareBitmap(String url, int width, int height) {
        Bitmap bitmap = null;
        try {
            URI uri = new URI(url);
            bitmap = Picasso.with(mContext)
                    .load(uri.toString())
                    .resize(width, height)
                    .get();
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
        return bitmap;
    }

    private PendingIntent buildPendingIntent(Movie movie, int id) {
        Intent detailsIntent = new Intent(mContext, DetailsActivity.class);
        detailsIntent.putExtra(DetailsActivity.MOVIE, movie);
        detailsIntent.putExtra(DetailsActivity.NOTIFICATION_ID, id);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(mContext);
        stackBuilder.addParentStack(DetailsActivity.class);
        stackBuilder.addNextIntent(detailsIntent);
        // Ensure a unique PendingIntents, otherwise all recommendations end up with the same
        // PendingIntent
        detailsIntent.setAction(Long.toString(movie.getId()));

        return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
    }
}

推薦方法是主要的方法,這裡建立recommend Notification。 程式如下

1.宣告NotificationManager
這是在RecommendationFactory的構造方法中完成的。

    public RecommendationFactory(Context context) {
        mContext = context;
        if (mNotificationManager == null) {
            mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        }
    }

2.使用您的RecommendationBuilder類(它是usingNotificationCompat類的自定義類)來準備推薦。
每個功能的簡要說明

  • Id - 此推薦卡的ID。
  • 優先順序 - 設定優先順序。高優先順序卡更有可能在LeanbackLauncher的推薦行中顯示。
  • 背景 - 設定背景點陣圖,當使用者選擇推薦卡時將顯示。
  • 標題 - 推薦卡的第一行文字。
  • 說明 - 推薦卡的第二行文字。
  • 點陣圖 - 推薦卡的主要影像。
  • SmallIcon - 公司/影像圖示。
  • FastLaneColor - 設定推薦卡中文字欄位的背景顏色
  • 意圖 - 設定PendingIntent(動作)以指示使用者單擊此推薦卡後會發生什麼。在本示例中,buildPendingIntent方法用於建立意圖。

3.通過建立RecommendationBuilder來建立通知。
4.使用NotificationManager通知此通知。
這些都是以recommend方法完成的。

    public void recommend(final int id, final Movie movie, final int priority) {
        Log.i(TAG, "recommend");
        /* Run in background thread, since bitmap loading must be done in background */
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG, "recommendation in progress");
                Bitmap backgroundBitmap = prepareBitmap(movie.getCardImageUrl(), BACKGROUND_WIDTH, BACKGROUND_HEIGHT);
                Bitmap cardImageBitmap = prepareBitmap(movie.getCardImageUrl(), CARD_WIDTH, CARD_HEIGHT);
                PendingIntent intent = buildPendingIntent(movie, id);
                // 2 prepare recommendation
                RecommendationBuilder recommendationBuilder = new RecommendationBuilder(mContext)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setBackground(backgroundBitmap)
                        .setId(id)
                        .setPriority(priority)
                        .setTitle(movie.getTitle())
                        .setDescription(movie.getDescription())
                        .setIntent(intent)
                        .setBitmap(cardImageBitmap)
                        .setFastLaneColor(mContext.getResources().getColor(R.color.fastlane_background));
                // 3 make notification
                Notification recommendNotification = recommendationBuilder.build();
                // 4 norify
                mNotificationManager.notify(id, recommendNotification);
            }}).start();
    }

編譯後執行

1927803-9fc2e98cd8909812.png
recommendation

您可以點選“推薦”按鈕傳送推薦卡。

原始碼在github上。

通過服務的方式執行推薦

推薦是應用程式建議使用者瀏覽下一個動作的概念。 所以大多數時候,建議可能會在後臺完成。 我將在剩下的時間跟進這一點。

官方文件推薦電視內容 - Android developers解釋了在後臺服務中如何推薦,Google的示例程式碼顯示了後臺服務使用的實際原始碼實現。 在此示例原始碼中,推薦服務將在獲得“BOOT_COMPLETED”的意圖之後啟動,並且將每30分鐘在後臺執行推薦。

關注微信公眾號,定期為你推薦移動開發相關文章。


1927803-0603f7d44dd403d0.jpg
songwenju

相關文章