Khi tương tác với TV, người dùng thường thích đưa ra ít dữ liệu đầu vào nhất trước khi xem nội dung. Một tình huống lý tưởng cho nhiều người dùng TV là: ngồi xuống, bật và xem. Nhìn chung, bước ít nhất để đưa người dùng đến nội dung mà họ yêu thích chính là cách họ thích.
Lưu ý: Chỉ sử dụng các API mô tả ở đây để đưa ra đề xuất trong các ứng dụng chạy trong các phiên bản Android cho đến và bao gồm Android 7.1 (API cấp 25). Để cung cấp nội dung đề xuất cho ứng dụng chạy trên Android 8.0 (API cấp 26) trở lên, ứng dụng của bạn phải sử dụng kênh đề xuất.
Khung Android hỗ trợ hoạt động tương tác đầu vào tối thiểu bằng cách cung cấp một hàng đề xuất trên màn hình chính. Nội dung đề xuất xuất hiện ở hàng đầu tiên trên màn hình chính của TV sau lần đầu tiên sử dụng thiết bị này. Việc đưa ra các đề xuất từ danh mục nội dung của ứng dụng có thể giúp đưa người dùng quay lại ứng dụng của bạn.
Hướng dẫn này chỉ cho bạn cách tạo và cung cấp các đề xuất cho khung Android để người dùng có thể dễ dàng khám phá và thưởng thức nội dung ứng dụng của bạn. Ngoài ra, hãy xem cách triển khai mẫu trong ứng dụng mẫu Leanback.
Các phương pháp hay nhất cho nội dung đề xuất
Nội dung đề xuất giúp người dùng nhanh chóng tìm thấy nội dung và ứng dụng mà họ yêu thích. Việc tạo các đề xuất có chất lượng cao và phù hợp với người dùng là một yếu tố quan trọng trong việc tạo ra trải nghiệm người dùng tuyệt vời cho ứng dụng TV. Vì lý do này, bạn nên xem xét cẩn thận những đề xuất mà bạn đưa ra cho người dùng và quản lý các đề xuất đó một cách chặt chẽ.
Các loại đề xuất
Khi tạo đề xuất, bạn nên liên kết người dùng quay lại các hoạt động xem chưa hoàn chỉnh hoặc đề xuất các hoạt động mở rộng hoạt động đó đến nội dung liên quan. Dưới đây là một số loại đề xuất cụ thể mà bạn nên cân nhắc:
- Các đề xuất về nội dung tiếp tục cho tập tiếp theo để người dùng tiếp tục xem chương trình dài tập. Bạn cũng có thể sử dụng các đề xuất tiếp tục xem phim, chương trình truyền hình hoặc podcast bị tạm dừng để người dùng có thể quay lại xem nội dung bị tạm dừng chỉ bằng vài cú nhấp chuột.
- Các đề xuất nội dung mới, chẳng hạn như cho tập mới chiếu trong tập đầu tiên, nếu người dùng xem xong một chương trình dài tập khác. Ngoài ra, nếu ứng dụng của bạn cho phép người dùng đăng ký, theo dõi hoặc theo dõi nội dung, hãy áp dụng các đề xuất mới về nội dung cho các mục chưa xem trong danh sách nội dung được theo dõi.
- Nội dung đề xuất về Nội dung có liên quan dựa trên thói quen xem trước đây của người dùng.
Để biết thêm thông tin về cách thiết kế thẻ đề xuất nhằm mang lại trải nghiệm tốt nhất cho người dùng, hãy xem Hàng Đề xuất trong Thông số kỹ thuật thiết kế của Android TV.
Làm mới đề xuất
Khi làm mới các đề xuất, đừng chỉ xoá rồi đăng lại, bởi làm như vậy sẽ khiến các đề xuất xuất hiện ở cuối hàng đề xuất. Sau khi một mục nội dung, chẳng hạn như phim, đã được phát, hãy xoá mục đó khỏi đề xuất.
Tuỳ chỉnh nội dung đề xuất
Bạn có thể tuỳ chỉnh thẻ đề xuất để truyền tải thông tin thương hiệu bằng cách đặt các thành phần trên giao diện người dùng (chẳng hạn như hình ảnh nền trước và nền sau, màu sắc, biểu tượng ứng dụng, tiêu đề và tiêu đề phụ của thẻ). Để tìm hiểu thêm, hãy xem Hàng Đề xuất trong Thông số kỹ thuật thiết kế của Android TV.
Đề xuất cho nhóm
Bạn có thể tuỳ ý nhóm các đề xuất dựa trên nguồn đề xuất. Ví dụ: ứng dụng của bạn có thể cung cấp 2 nhóm đề xuất: đề xuất về nội dung mà người dùng đã đăng ký và đề xuất về nội dung thịnh hành mới mà có thể người dùng chưa biết.
Hệ thống sẽ xếp hạng và sắp xếp các đề xuất cho từng nhóm một cách riêng biệt khi tạo hoặc cập nhật hàng đề xuất. Bằng cách cung cấp thông tin nhóm cho các đề xuất của bạn, bạn có thể đảm bảo rằng các đề xuất của bạn không được sắp xếp theo thứ tự đề xuất thấp hơn các đề xuất không liên quan.
Sử dụng NotificationCompat.Builder.setGroup()
để đặt chuỗi khoá nhóm của một đề xuất. Ví dụ: để đánh dấu một đề xuất là thuộc về nhóm chứa nội dung thịnh hành mới, bạn có thể gọi setGroup("trending")
.
Tạo dịch vụ đề xuất
Đề xuất về nội dung được tạo bằng tính năng xử lý nền. Để ứng dụng của bạn đóng góp vào các đề xuất, hãy tạo một dịch vụ giúp định kỳ thêm các trang thông tin từ danh mục ứng dụng vào danh sách đề xuất của hệ thống.
Ví dụ về mã sau đây minh hoạ cách mở rộng IntentService
để tạo dịch vụ đề xuất cho ứng dụng của bạn:
Kotlin
class UpdateRecommendationsService : IntentService("RecommendationService") { override protected fun onHandleIntent(intent: Intent) { Log.d(TAG, "Updating recommendation cards") val recommendations = VideoProvider.getMovieList() if (recommendations == null) return var count = 0 try { val builder = RecommendationBuilder() .setContext(applicationContext) .setSmallIcon(R.drawable.videos_by_google_icon) for (entry in recommendations.entrySet()) { for (movie in entry.getValue()) { Log.d(TAG, "Recommendation - " + movie.getTitle()) builder.setBackground(movie.getCardImageUrl()) .setId(count + 1) .setPriority(MAX_RECOMMENDATIONS - count) .setTitle(movie.getTitle()) .setDescription(getString(R.string.popular_header)) .setImage(movie.getCardImageUrl()) .setIntent(buildPendingIntent(movie)) .build() if (++count >= MAX_RECOMMENDATIONS) { break } } if (++count >= MAX_RECOMMENDATIONS) { break } } } catch (e: IOException) { Log.e(TAG, "Unable to update recommendation", e) } } private fun buildPendingIntent(movie: Movie): PendingIntent { val detailsIntent = Intent(this, DetailsActivity::class.java) detailsIntent.putExtra("Movie", movie) val stackBuilder = TaskStackBuilder.create(this) stackBuilder.addParentStack(DetailsActivity::class.java) stackBuilder.addNextIntent(detailsIntent) // Ensure a unique PendingIntents, otherwise all // recommendations end up with the same PendingIntent detailsIntent.setAction(movie.getId().toString()) val intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) return intent } companion object { private val TAG = "UpdateRecommendationsService" private val MAX_RECOMMENDATIONS = 3 } }
Java
public class UpdateRecommendationsService extends IntentService { private static final String TAG = "UpdateRecommendationsService"; private static final int MAX_RECOMMENDATIONS = 3; public UpdateRecommendationsService() { super("RecommendationService"); } @Override protected void onHandleIntent(Intent intent) { Log.d(TAG, "Updating recommendation cards"); HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList(); if (recommendations == null) return; int count = 0; try { RecommendationBuilder builder = new RecommendationBuilder() .setContext(getApplicationContext()) .setSmallIcon(R.drawable.videos_by_google_icon); for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet()) { for (Movie movie : entry.getValue()) { Log.d(TAG, "Recommendation - " + movie.getTitle()); builder.setBackground(movie.getCardImageUrl()) .setId(count + 1) .setPriority(MAX_RECOMMENDATIONS - count) .setTitle(movie.getTitle()) .setDescription(getString(R.string.popular_header)) .setImage(movie.getCardImageUrl()) .setIntent(buildPendingIntent(movie)) .build(); if (++count >= MAX_RECOMMENDATIONS) { break; } } if (++count >= MAX_RECOMMENDATIONS) { break; } } } catch (IOException e) { Log.e(TAG, "Unable to update recommendation", e); } } private PendingIntent buildPendingIntent(Movie movie) { Intent detailsIntent = new Intent(this, DetailsActivity.class); detailsIntent.putExtra("Movie", movie); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); 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())); PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); return intent; } }
Để hệ thống nhận dạng và chạy dịch vụ này, hãy đăng ký dịch vụ bằng cách sử dụng tệp kê khai ứng dụng của bạn. Đoạn mã sau minh hoạ cách khai báo lớp này là một dịch vụ:
<manifest ... > <application ... > ... <service android:name="com.example.android.tvleanback.UpdateRecommendationsService" android:enabled="true" /> </application> </manifest>
Xây dựng các đề xuất
Khi dịch vụ đề xuất bắt đầu chạy, dịch vụ đó phải tạo các đề xuất và chuyển các đề xuất đó đến khung Android. Khung này nhận được các đề xuất dưới dạng các đối tượng Notification
sử dụng một mẫu cụ thể và được đánh dấu bằng một danh mục cụ thể.
Đặt giá trị
Để đặt giá trị thành phần trên giao diện người dùng cho thẻ đề xuất, bạn hãy tạo một lớp trình tạo tuân theo mẫu trình tạo được mô tả như sau. Trước tiên, bạn đặt giá trị cho các phần tử của thẻ đề xuất.
Kotlin
class RecommendationBuilder { ... fun setTitle(title: String): RecommendationBuilder { this.title = title return this } fun setDescription(description: String): RecommendationBuilder { this.description = description return this } fun setImage(uri: String): RecommendationBuilder { imageUri = uri return this } fun setBackground(uri: String): RecommendationBuilder { backgroundUri = uri return this } ...
Java
public class RecommendationBuilder { ... public RecommendationBuilder setTitle(String title) { this.title = title; return this; } public RecommendationBuilder setDescription(String description) { this.description = description; return this; } public RecommendationBuilder setImage(String uri) { imageUri = uri; return this; } public RecommendationBuilder setBackground(String uri) { backgroundUri = uri; return this; } ...
Tạo thông báo
Sau khi đặt giá trị, hãy tạo thông báo, gán giá trị từ lớp trình tạo cho thông báo và gọi NotificationCompat.Builder.build()
.
Ngoài ra, hãy nhớ gọi setLocalOnly()
để thông báo NotificationCompat.BigPictureStyle
không xuất hiện trên các thiết bị khác.
Ví dụ về mã sau đây minh hoạ cách xây dựng một đề xuất.
Kotlin
class RecommendationBuilder { ... @Throws(IOException::class) fun build(): Notification { ... val notification = NotificationCompat.BigPictureStyle( NotificationCompat.Builder(context) .setContentTitle(title) .setContentText(description) .setPriority(priority) .setLocalOnly(true) .setOngoing(true) .setColor(context.resources.getColor(R.color.fastlane_background)) .setCategory(Notification.CATEGORY_RECOMMENDATION) .setLargeIcon(image) .setSmallIcon(smallIcon) .setContentIntent(intent) .setExtras(extras)) .build() return notification } }
Java
public class RecommendationBuilder { ... public Notification build() throws IOException { ... Notification notification = new NotificationCompat.BigPictureStyle( new NotificationCompat.Builder(context) .setContentTitle(title) .setContentText(description) .setPriority(priority) .setLocalOnly(true) .setOngoing(true) .setColor(context.getResources().getColor(R.color.fastlane_background)) .setCategory(Notification.CATEGORY_RECOMMENDATION) .setLargeIcon(image) .setSmallIcon(smallIcon) .setContentIntent(intent) .setExtras(extras)) .build(); return notification; } }
Chạy dịch vụ đề xuất
Dịch vụ đề xuất dành cho ứng dụng của bạn phải chạy định kỳ để tạo các đề xuất hiện có. Để chạy dịch vụ, hãy tạo một lớp chạy bộ tính giờ và gọi lớp đó theo định kỳ. Ví dụ về mã sau đây sẽ mở rộng lớp BroadcastReceiver
để bắt đầu thực thi định kỳ một dịch vụ đề xuất nửa giờ một lần:
Kotlin
class BootupActivity : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Log.d(TAG, "BootupActivity initiated") if (intent.action.endsWith(Intent.ACTION_BOOT_COMPLETED)) { scheduleRecommendationUpdate(context) } } private fun scheduleRecommendationUpdate(context: Context) { Log.d(TAG, "Scheduling recommendations update") val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val recommendationIntent = Intent(context, UpdateRecommendationsService::class.java) val alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0) alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, INITIAL_DELAY, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent ) } companion object { private val TAG = "BootupActivity" private val INITIAL_DELAY:Long = 5000 } }
Java
public class BootupActivity extends BroadcastReceiver { private static final String TAG = "BootupActivity"; private static final long INITIAL_DELAY = 5000; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "BootupActivity initiated"); if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) { scheduleRecommendationUpdate(context); } } private void scheduleRecommendationUpdate(Context context) { Log.d(TAG, "Scheduling recommendations update"); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class); PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0); alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, INITIAL_DELAY, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent); } }
Quá trình triển khai lớp BroadcastReceiver
này phải chạy sau khi khởi động thiết bị TV chứa lớp này. Để thực hiện việc này, hãy đăng ký lớp này trong tệp kê khai ứng dụng bằng bộ lọc ý định để theo dõi việc hoàn tất quá trình khởi động thiết bị. Mã mẫu sau đây minh hoạ cách thêm cấu hình này vào tệp kê khai:
<manifest ... > <application ... > <receiver android:name="com.example.android.tvleanback.BootupActivity" android:enabled="true" android:exported="false"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> </application> </manifest>
Lưu ý quan trọng: Để nhận được thông báo khởi động xong, ứng dụng của bạn phải yêu cầu quyền RECEIVE_BOOT_COMPLETED
.
Để biết thêm thông tin, hãy xem ACTION_BOOT_COMPLETED
.
Trong phương thức onHandleIntent()
của lớp dịch vụ đề xuất, hãy đăng đề xuất cho người quản lý như sau:
Kotlin
val notification = notificationBuilder.build() notificationManager.notify(id, notification)
Java
Notification notification = notificationBuilder.build(); notificationManager.notify(id, notification);