在 TV 上运行的媒体应用需要允许用户浏览其提供的内容、选择要播放的内容,以及开始播放内容。此类应用的内容浏览体验应简单直观,并且视觉上要赏心悦目。

本节课介绍如何利用 Leanback androidx 库提供的类来实现一个用户界面,该界面可让用户浏览应用的媒体目录中包含的音乐或视频。

注意:此处显示的实现示例使用的是 BrowseSupportFragment 扩展了 AndroidX

图 1. Leanback 示例应用浏览 Fragment 中显示视频目录数据。

创建媒体浏览布局

通过 Leanback 库中的

android:id="@+id/main_frame"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:name="com.example.android.tvleanback.ui.MainFragment"

android:id="@+id/main_browse_fragment"

android:layout_width="match_parent"

android:layout_height="match_parent" />

应用的主 Activity 用于设置此视图,如下例所示:

Kotlin

class MainActivity : Activity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.main)

}

...Java

public class MainActivity extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}

...

如需详细了解如何设置界面元素,请参阅设置界面元素。

如需详细了解如何隐藏标题,请参阅隐藏或停用标题。

实现

Kotlin

class MainFragment : BrowseSupportFragment(),

LoaderManager.LoaderCallbacks>> {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

loadVideoData()

}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

prepareBackgroundManager()

setupUIElements()

setupEventListeners()

}

...

private fun prepareBackgroundManager() {

backgroundManager = BackgroundManager.getInstance(activity).apply {

attach(activity?.window)

}

defaultBackground = resources.getDrawable(R.drawable.default_background)

metrics = DisplayMetrics()

activity?.windowManager?.defaultDisplay?.getMetrics(metrics)

}

private fun setupUIElements() {

badgeDrawable = resources.getDrawable(R.drawable.videos_by_google_banner)

// Badge, when set, takes precedent over title

title = getString(R.string.browse_title)

headersState = BrowseSupportFragment.HEADERS_ENABLED

isHeadersTransitionOnBackEnabled = true

// set headers background color

brandColor = ContextCompat.getColor(requireContext(), R.color.fastlane_background)

// set search icon color

searchAffordanceColor = ContextCompat.getColor(requireContext(), R.color.search_opaque)

}

private fun loadVideoData() {

VideoProvider.setContext(activity)

videosUrl = getString(R.string.catalog_url)

loaderManager.initLoader(0, null, this)

}

private fun setupEventListeners() {

setOnSearchClickedListener {

Intent(activity, SearchActivity::class.java).also { intent ->

startActivity(intent)

}

}

onItemViewClickedListener = ItemViewClickedListener()

onItemViewSelectedListener = ItemViewSelectedListener()

}

...Java

public class MainFragment extends BrowseSupportFragment implements

LoaderManager.LoaderCallbacks>> {

}

...

@Override

public void onCreate(@Nullable Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

loadVideoData();

}

@Override

public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {

super.onViewCreated(view, savedInstanceState);

prepareBackgroundManager();

setupUIElements();

setupEventListeners();

}

...

private void prepareBackgroundManager() {

backgroundManager = BackgroundManager.getInstance(getActivity());

backgroundManager.attach(getActivity().getWindow());

defaultBackground = getResources()

.getDrawable(R.drawable.default_background);

metrics = new DisplayMetrics();

getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);

}

private void setupUIElements() {

setBadgeDrawable(getActivity().getResources()

.getDrawable(R.drawable.videos_by_google_banner));

// Badge, when set, takes precedent over title

setTitle(getString(R.string.browse_title));

setHeadersState(HEADERS_ENABLED);

setHeadersTransitionOnBackEnabled(true);

// set headers background color

setBrandColor(ContextCompat.getColor(requireContext(), R.color.fastlane_background));

// set search icon color

setSearchAffordanceColor(ContextCompat.getColor(requireContext(), R.color.search_opaque));

}

private void loadVideoData() {

VideoProvider.setContext(getActivity());

videosUrl = getString(R.string.catalog_url);

getLoaderManager().initLoader(0, null, this);

}

private void setupEventListeners() {

setOnSearchClickedListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

Intent intent = new Intent(getActivity(), SearchActivity.class);

startActivity(intent);

}

});

setOnItemViewClickedListener(new ItemViewClickedListener());

setOnItemViewSelectedListener(new ItemViewSelectedListener());

}

...

设置界面元素

在上例中,私有方法 setupUIElements() 调用了几个

setTitle(),此方法会将标题字符串替换为可绘制资源。可绘制资源的高度应为 52dp。

如果未调用 setBadgeDrawable(),

自定义标题视图

图 1 中所示的浏览 Fragment 在左侧窗格中列出了视频类别名称(行标题)。文本视图显示的这些类别名称来自视频数据库。您可以自定义标题,以便在更复杂的布局中添加其他视图。下面几部分展示了如何添加图片视图,以在类别名称旁边显示一个图标,如图 2 中所示。

图 2. 浏览 Fragment 中的行标题,同时具有图标和文本标签。

行标题的布局定义如下:

android:orientation="horizontal"

android:layout_width="match_parent"

android:layout_height="match_parent">

android:id="@+id/header_icon"

android:layout_width="32dp"

android:layout_height="32dp" />

android:id="@+id/header_label"

android:layout_marginTop="6dp"

android:layout_width="wrap_content"

android:layout_height="wrap_content" />

可以使用

Kotlin

class IconHeaderItemPresenter : Presenter() {

override fun onCreateViewHolder(viewGroup: ViewGroup): Presenter.ViewHolder {

val view = LayoutInflater.from(viewGroup.context).run {

inflate(R.layout.icon_header_item, null)

}

return Presenter.ViewHolder(view)

}

override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, o: Any) {

val headerItem = (o as ListRow).headerItem

val rootView = viewHolder.view

rootView.findViewById(R.id.header_icon).apply {

rootView.resources.getDrawable(R.drawable.ic_action_video, null).also { icon ->

setImageDrawable(icon)

}

}

rootView.findViewById(R.id.header_label).apply {

text = headerItem.name

}

}

override fun onUnbindViewHolder(viewHolder: Presenter.ViewHolder) {

// no op

}

}Java

public class IconHeaderItemPresenter extends Presenter {

@Override

public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {

LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());

View view = inflater.inflate(R.layout.icon_header_item, null);

return new ViewHolder(view);

}

@Override

public void onBindViewHolder(ViewHolder viewHolder, Object o) {

HeaderItem headerItem = ((ListRow) o).getHeaderItem();

View rootView = viewHolder.view;

ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);

Drawable icon = rootView.getResources().getDrawable(R.drawable.ic_action_video, null);

iconView.setImageDrawable(icon);

TextView label = (TextView) rootView.findViewById(R.id.header_label);

label.setText(headerItem.getName());

}

@Override

public void onUnbindViewHolder(ViewHolder viewHolder) {

// no op

}

}

您的标题必须可聚焦,以便可以使用方向键来滚动浏览。有两个备选方式:

Kotlin

override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, o: Any) {

val headerItem = (o as ListRow).headerItem

val rootView = viewHolder.view

rootView.focusable = View.FOCUSABLE

//...

}Java

@Override

public void onBindViewHolder(ViewHolder viewHolder, Object o) {

HeaderItem headerItem = ((ListRow) o).getHeaderItem();

View rootView = viewHolder.view;

rootView.setFocusable(View.FOCUSABLE) // Allows the D-Pad to navigate to this header item

//...

}

将您的布局设为可聚焦:

...

android:focusable="true">

Kotlin

setHeaderPresenterSelector(object : PresenterSelector() {

override fun getPresenter(o: Any): Presenter {

return IconHeaderItemPresenter()

}

})Java

setHeaderPresenterSelector(new PresenterSelector() {

@Override

public Presenter getPresenter(Object o) {

return new IconHeaderItemPresenter();

}

});

如需查看完整示例,请参阅 Android TV GitHub 代码库中的 Android Leanback 示例应用。

隐藏或停用标题

有时,您可能不希望显示行标题。例如:当类别数量不是很多,无需使用可滚动列表时。在 Fragment 的

提供卡片视图的图 1 中所示。用户可选择收起的标题部分,以将其展开。

显示媒体列表

通过

以下示例代码展示了一个用于显示字符串数据的

Kotlin

private const val TAG = "StringPresenter"

class StringPresenter : Presenter() {

override fun onCreateViewHolder(parent: ViewGroup): Presenter.ViewHolder {

val textView = TextView(parent.context).apply {

isFocusable = true

isFocusableInTouchMode = true

background = parent.resources.getDrawable(R.drawable.text_bg)

}

return Presenter.ViewHolder(textView)

}

override fun onBindViewHolder(viewHolder: Presenter.ViewHolder, item: Any) {

(viewHolder.view as TextView).text = item.toString()

}

override fun onUnbindViewHolder(viewHolder: Presenter.ViewHolder) {

// no op

}

}Java

public class StringPresenter extends Presenter {

private static final String TAG = "StringPresenter";

public ViewHolder onCreateViewHolder(ViewGroup parent) {

TextView textView = new TextView(parent.getContext());

textView.setFocusable(true);

textView.setFocusableInTouchMode(true);

textView.setBackground(

parent.getResources().getDrawable(R.drawable.text_bg));

return new ViewHolder(textView);

}

public void onBindViewHolder(ViewHolder viewHolder, Object item) {

((TextView) viewHolder.view).setText(item.toString());

}

public void onUnbindViewHolder(ViewHolder viewHolder) {

// no op

}

}

为媒体内容构建 Presenter 类后,您可以构建 Adapter 并将其附加到 StringPresenter 类来显示类别以及这些类别的内容:

Kotlin

private const val NUM_ROWS = 4

...

private lateinit var rowsAdapter: ArrayObjectAdapter

override fun onCreate(savedInstanceState: Bundle?) {

...

buildRowsAdapter()

}

private fun buildRowsAdapter() {

rowsAdapter = ArrayObjectAdapter(ListRowPresenter())

for (i in 0 until NUM_ROWS) {

val listRowAdapter = ArrayObjectAdapter(StringPresenter()).apply {

add("Media Item 1")

add("Media Item 2")

add("Media Item 3")

}

HeaderItem(i.toLong(), "Category $i").also { header ->

rowsAdapter.add(ListRow(header, listRowAdapter))

}

}

browseSupportFragment.adapter = rowsAdapter

}Java

private ArrayObjectAdapter rowsAdapter;

private static final int NUM_ROWS = 4;

@Override

protected void onCreate(Bundle savedInstanceState) {

...

buildRowsAdapter();

}

private void buildRowsAdapter() {

rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

for (int i = 0; i < NUM_ROWS; ++i) {

ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(

new StringPresenter());

listRowAdapter.add("Media Item 1");

listRowAdapter.add("Media Item 2");

listRowAdapter.add("Media Item 3");

HeaderItem header = new HeaderItem(i, "Category " + i);

rowsAdapter.add(new ListRow(header, listRowAdapter));

}

browseSupportFragment.setAdapter(rowsAdapter);

}

此示例展示了 Adapter 的一个静态实现。典型的媒体浏览应用会使用来自在线数据库或网络服务的数据。如需查看使用从网络检索的数据的浏览应用示例,请参阅 Android TV GitHub 代码库中的 Android Leanback 示例应用。

更新背景

为了增强 TV 上媒体浏览应用的视觉吸引力,您可以在用户浏览内容时更新背景图片。此方法可令用户与应用的交互更加赏心悦目。

Leanback 支持库提供了一个

Kotlin

protected fun updateBackground(drawable: Drawable) {

BackgroundManager.getInstance(this).drawable = drawable

}Java

protected void updateBackground(Drawable drawable) {

BackgroundManager.getInstance(this).setDrawable(drawable);

}

许多现有的媒体浏览应用都会在用户浏览媒体列表时自动更新背景。为了实现此项更新,您可以设置一个选择监听器,以根据用户的当前选择自动更新背景。以下示例展示了如何设置一个

Kotlin

protected fun clearBackground() {

BackgroundManager.getInstance(this).drawable = defaultBackground

}

protected fun getDefaultItemViewSelectedListener(): OnItemViewSelectedListener =

OnItemViewSelectedListener { _, item, _, _ ->

if (item is Movie) {

item.getBackdropDrawable().also { background ->

updateBackground(background)

}

} else {

clearBackground()

}

}Java

protected void clearBackground() {

BackgroundManager.getInstance(this).setDrawable(defaultBackground);

}

protected OnItemViewSelectedListener getDefaultItemViewSelectedListener() {

return new OnItemViewSelectedListener() {

@Override

public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,

RowPresenter.ViewHolder rowViewHolder, Row row) {

if (item instanceof Movie ) {

Drawable background = ((Movie)item).getBackdropDrawable();

updateBackground(background);

} else {

clearBackground();

}

}

};

}

注意:以上实现是出于说明目的而展示的一个简单示例。在您自己的应用中创建此功能时,您应考虑在一个单独的线程内运行背景更新操作,以便获得更好的性能。此外,如果您计划在用户滚动浏览内容时更新背景,不妨考虑添加一个延迟时间,使背景图片更新延迟到用户选择某个内容后再执行。此方法可以避免背景图片更新过于频繁。

目录创建 android,创建目录浏览器  |  Android 开发者  |  Android Developers相关推荐

  1. android 文件浏览器源码,android 文件管理器源码

    [实例简介] [实例截图] [核心代码] package com.android.FileBrowser; import java.io.File; import java.util.ArrayLis ...

  2. android 平板安装程序开发者,android – 限制平板电脑中的应用安装

    我有一个 Android应用程序,我想在Tablet中停止安装此应用程序.我搜索了很多网站.我有一些想法并遵循了.但这不是限制. 以下是我访问过的一些网站. 我已尝试使用支持屏幕仍然应用程序正在平板电 ...

  3. 最新android studio创建项目,创建项目  |  Android 开发者  |  Android Developers

    利用 Android Studio,您可以轻松地为各种类型的设备(例如手机.平板电脑.电视和 Wear 设备)创建 Android 应用.本页介绍了如何启动新的 Android 应用项目或导入现有项目 ...

  4. 另辟蹊径创建移动应用:iOS和Android代码共享

    2019独角兽企业重金招聘Python工程师标准>>> 过去几年,移动应用席卷了整个世界,在工作和生活的方方面面改变着我们使用互联网的方式.创建移动应用的各种技术也随之兴起,各种开发 ...

  5. Android 实现浏览器打开app

    我们经常看到当点击一个链接的时候,跳转到app,比如当我们在网页端浏览新闻的时候,要想查看更多评论等就会提示你跳转到app内打开查看,那是如何实现网页中打开app的呢? 怎么实现? 要想实现浏览器内打 ...

  6. Android 12 首个开发者预览版到来

    作者 / Dave Burke,工程副总裁 从手机和笔记本电脑,再到平板电脑.电视甚至汽车,Android 应用每天都在各种设备上支持着数十亿人的工作.娱乐.交流和创造.当越来越多的人开始依赖您所构建 ...

  7. android安装自动打开网页,Android调用系统自带浏览器打开网页的实现方法

    Android调用系统自带浏览器打开网页的实现方法 在Android中可以调用自带的浏览器,或者指定一个浏览器来打开一个链接.只需要传入一个uri,可以是链接地址. 启动android默认浏览器 在A ...

  8. android finish()传参数,48. (android开发) activity间传递数据(打开浏览器、拨号盘、传参)...

    在 android 中多个 activity 中互相访问是常见的一种操作,比如:打开系统浏览器.显示系统拨号盘等等. 做这些操作的时候,自然是需要传递一些数据过去,比如:要打开的网址.要拨打的电话号码 ...

  9. Chrome模拟手机浏览器(iOS/Android)的三种方法,亲测无误!

    各大网站都有推出自己的手机访问版本页面,不管是新闻类还是视频网站,我们在电脑是无法直接访问到手机网站的,比如我经常访问一个3g.qq.com这个手机站点,如果在电脑上直接打开它,则会跳转到其它页面,一 ...

最新文章

  1. matlab var std,Matlab var std cov 函数解析
  2. linux 串口命令
  3. php模板怎么导入服务器_php项目怎么放到服务器
  4. 操作系统实验_Chcore -- 上交IPADS操作系统银杏书配套Lab实验笔记 - Lab2内存管理(一)...
  5. SonarLint插件的安装与使用
  6. Atitit.软件硕士  博士课程 一览表 attilax 总结
  7. Mybatis中mysql blob类型乱码解决
  8. Go爬虫colly官方示例三【cryptocoinmarketcap】- 爬取加密数字货币最新市值
  9. wifi ap6212驱动移植及调试分析技术笔记
  10. 做好目标管理和任务管理,提高办公协同效率
  11. Proteus做C51最小系统的仿真
  12. ppt转图片LibreOffice解决方案--java
  13. android系统自带system/app下载,Android 把应用APK安装包放到system/app下的方法
  14. 容联完成1.25亿美元F轮融资
  15. 蓝桥杯国赛C++A组B组题解整理(第八、七、六、五、四届)
  16. 车载以太网 - SomeIP - 总纲
  17. 遥感学报(湖泊遥感专刊)投稿历程
  18. mariadb 卸载 Kali_流氓软件卸载不掉?赶紧试试这款免费专业的卸载神器!专治各类顽固派软件!...
  19. hp服务器pe系统安装win7系统安装系统安装失败,使用硬盘安装系统出现pGptRestore部署失败怎么解决?...
  20. 设计计算机系统,紫外可见分光光度计的计算机系统设计

热门文章

  1. 强化学习笔记: MDP - Policy iteration
  2. 模型独立学习:多任务学习与迁移学习
  3. Myeclipse10下搭建SSH框架(图解)Struts2.1+Spring3.0+Hibernate3.3
  4. python线程同步锁_Python实现的多线程同步与互斥锁功能示例
  5. matlab绘制横向柱状图
  6. 多种负载均算法及其 Java 代码实现 --转
  7. spring mvc DispatcherServlet详解之拾忆工具类utils
  8. Linux内核--网络栈实现分析(一)--网络栈初始化--转
  9. 【特征工程】(未完成)编码
  10. 【风控场景】互利网上数字金融典型场景: 网购运费险