[譯]Recommendation card (推薦卡)--Android TV 開發手冊十一
版權宣告:本文為博主原創翻譯文章,轉載請註明出處。
推薦:
歡迎關注我建立的Android TV 簡書專題,會定期給大家分享一些AndroidTv相關的內容:
https://www.jianshu.com/c/3f0ab61a1322
建議
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();
}
編譯後執行
您可以點選“推薦”按鈕傳送推薦卡。
原始碼在github上。
通過服務的方式執行推薦
推薦是應用程式建議使用者瀏覽下一個動作的概念。 所以大多數時候,建議可能會在後臺完成。 我將在剩下的時間跟進這一點。
官方文件推薦電視內容 - Android developers解釋了在後臺服務中如何推薦,Google的示例程式碼顯示了後臺服務使用的實際原始碼實現。 在此示例原始碼中,推薦服務將在獲得“BOOT_COMPLETED”的意圖之後啟動,並且將每30分鐘在後臺執行推薦。
關注微信公眾號,定期為你推薦移動開發相關文章。
相關文章
- [譯]SearchFragment --Android TV 開發手冊十二FragmentAndroid
- Android TV開發——RecyclerView For TVAndroidView
- Android 開發推薦Android
- 序列推薦模型梳理(Sequential Recommendation)模型
- Android開發文章推薦Android
- ZooKeeper開發手冊中文翻譯
- Android開發書籍推薦Android
- 推薦系統入門(Top-N recommendation)
- android TV - Creating a Catalog Browser,Providing a Card ViewAndroidView
- android TV-Building a Details View,Displaying a Now Playing CardAndroidUIAIView
- Android 應用開發推薦書單Android
- Android TV開發總結【RecycleView】AndroidView
- Android開發資源推薦第2季Android
- Android Jetpack - Android TV 應用開發教程AndroidJetpack
- 最佳Android Wear 智慧手錶推薦Android
- 【轉】值得推薦的android開發框架簡介Android框架
- 理性關卡設計手冊:如使用RLD理論指導遊戲關卡開發?遊戲
- 手遊歐洲生存手冊:應用商店展示及推薦
- [?]前端開發工具推薦前端
- 阿里java開發手冊阿里Java
- matrixone/ematrix開發手冊
- 阿里巴巴Android開發手冊(正式版)阿里Android
- Android文章與開源庫推薦Android
- phper 開發環境推薦PHP開發環境
- Linux 開發工具推薦Linux
- golang開發類庫推薦Golang
- 手機擴容不再難!OV NM Card高速儲存卡評測
- [譯]利用 Android 構建 TV 的未來Android
- [譯] 利用 Android 構建 TV 的未來Android
- Element-Ui元件(四十一)Card 卡片UI元件
- 【譯】JavaScript 完整手冊JavaScript
- [譯] fasthttp 文件手冊ASTHTTP
- 聊聊真實的 Android TV 開發技術棧Android
- 阿里巴巴Android開發手冊V1.0.0隨手筆記阿里Android筆記
- 海思hi3751 Android儲存開發指南手冊Android
- [開發文件]bootstrap中文手冊boot
- base業務框架開發手冊框架
- DATA CARTRIDGE開發手冊