版权声明:本文为博主原创翻译文章,转载请注明出处。
推荐:
欢迎关注我创建的Android TV 专题,会定期给大家分享一些AndroidTv相关的内容:
https://www.jianshu.com/c/3f0ab61a1322
建议
Android TV的home,LeanbackLauncher在第一行有一个推荐行。任何应用程序都可以为用户建议推荐的内容。在本章中,我将介绍如何从您的应用程序向LeanbackLauncher显示推荐卡。
- 推荐电视内容 - Android developers
- Android TV推荐:我的应用程序或游戏有什么用?
该建议是通过使用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分钟在后台执行推荐。
关注微信公众号,定期为你推荐移动开发相关文章。