Android TV -2.1- Creating a Catalog Browser

本文指导您如何使用 Leanback 支持库的 v17 类来实现音乐或视频应用的媒体目录浏览界面,包括创建浏览布局、自定义视图布局、更新背景等关键步骤。

Creating a Catalog Browser

A media app that runs on a TV needs to allow users to browse its content offerings, make a selection, and start playing content. The content browsing experience for apps of this type should be simple and intuitive, as well as visually pleasing and engaging.

This lesson discusses how to use the classes provided by thev17 leanback support library to implement a user interface for browsing music or videos from your app's media catalog.

App main screen

Figure 1. The Leanback sample app browse fragment displays video catalog data.

Create a Media Browse Layout



The BrowseFragment class in the leanback library allows you to create a primary layout for browsing categories and rows of media items with a minimum of code. The following example shows how to create a layout that contains a BrowseFragment object:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:name="com.example.android.tvleanback.ui.MainFragment"
        android:id="@+id/main_browse_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

The application's main activity sets this view, as shown in the following example:

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
...

The BrowseFragment methods populate the view with the video data and UI elements and set the layout parameters such as the icon, title, and whether category headers are enabled.

The application's subclass that implements the BrowseFragment methods also sets up event listeners for user actions on the UI elements, and prepares the background manager, as shown in the following example:

public class MainFragment extends BrowseFragment implements
        LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {

...

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        loadVideoData();

        prepareBackgroundManager();
        setupUIElements();
        setupEventListeners();
    }
...

    private void prepareBackgroundManager() {
        mBackgroundManager = BackgroundManager.getInstance(getActivity());
        mBackgroundManager.attach(getActivity().getWindow());
        mDefaultBackground = getResources()
            .getDrawable(R.drawable.default_background);
        mMetrics = new DisplayMetrics();
        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
    }

    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(getResources().getColor(R.color.fastlane_background));
        // set search icon color
        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
    }

    private void loadVideoData() {
        VideoProvider.setContext(getActivity());
        mVideosUrl = getActivity().getResources().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());
    }
...

Set UI Elements

In the sample above, the private method setupUIElements() calls several of the BrowseFragment methods to style the media catalog browser:

  • setBadgeDrawable() places the specified drawable resource in the upper-right corner of the browse fragment, as shown in figures 1 and 2. This method replaces the title string with the drawable resource, ifsetTitle() is also called. The drawable resource should be 52dps tall.
  • setTitle() sets the title string in the upper-right corner of the browse fragment, unlesssetBadgeDrawable() is called.
  • setHeadersState() and setHeadersTransitionOnBackEnabled() hide or disable the headers. See Hide or Disable Headers for more information.
  • setBrandColor() sets the background color for UI elements in the browse fragment, specifically the header section background color, with the specified color value.
  • setSearchAffordanceColor() sets the color of the search icon with the specified color value. The search icon appears in the upper-left corner of the browse fragment, as shown in figures 1 and 2.


The browse fragment shown in figure 1 lists the video category names (the row headers) in the left pane. Text views display these category names from the video database. You can customize the header to include additional views in a more complex layout. The following sections show how to include an image view that displays an icon next to the category name, as shown in figure 2.

App main screen

Figure 2. The row headers in the browse fragment, with both an icon and a text label.

The layout for the row header is defined as follows:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/header_icon"
        android:layout_width="32dp"
        android:layout_height="32dp" />
    <TextView
        android:id="@+id/header_label"
        android:layout_marginTop="6dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Use a Presenter and implement the abstract methods to create, bind, and unbind the view holder. The following example shows how to bind the viewholder with two views, an ImageView and a TextView.

public class IconHeaderItemPresenter extends Presenter {
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
        LayoutInflater inflater = (LayoutInflater) viewGroup.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        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
    }
}

This example shows how to define the presenter for a complex layout with multiple views, and you could use this pattern to do something even more complex. However, an easier way to combine a TextView with a drawable resource is to use the TextView.drawableLeft attribute. Doing it this way, you don't need theImageView shown here.

In the BrowseFragment implementation that displays the catalog browser, use thesetHeaderPresenterSelector() method to set the presenter for the row header, as shown in the following example.

setHeaderPresenterSelector(new PresenterSelector() {
    @Override
    public Presenter getPresenter(Object o) {
        return new IconHeaderItemPresenter();
    }
});

Hide or Disable Headers

Sometimes you may not want the row headers to appear: when there aren't enough categories to require a scrollable list, for example. Call the BrowseFragment.setHeadersState() method during the fragment'sonActivityCreated() method to hide or disable the row headers. The setHeadersState() method sets the initial state of the headers in the browse fragment given one of the following constants as a parameter:

  • HEADERS_ENABLED - When the browse fragment activity is created, the headers are enabled and shown by default. The headers appear as shown in figures 1 and 2 on this page.
  • HEADERS_HIDDEN - When the browse fragment activity is created, headers are enabled and hidden by default. The header section of the screen is collapsed, as shown in figure 1 of Providing a Card View. The user can select the collapsed header section to expand it.
  • HEADERS_DISABLED - When the browse fragment activity is created, headers are disabled by default and are never displayed.

If either HEADERS_ENABLED or HEADERS_HIDDEN is set, you can call setHeadersTransitionOnBackEnabled() to support moving back to the row header from a selected content item in the row. This is enabled by default (if you don't call the method), but if you want to handle the back movement yourself, you should pass the value falseto setHeadersTransitionOnBackEnabled() and implement your own back stack handling.

Display Media Lists



The BrowseFragment class allows you to define and display browsable media content categories and media items from a media catalog using adapters and presenters. Adapters enable you to connect to local or online data sources that contain your media catalog information. Adapters use presenters to create views and bind data to those views for displaying an item on screen.

The following example code shows an implementation of a Presenter for displaying string data:

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.getContext().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
    }
}

Once you have constructed a presenter class for your media items, you can build an adapter and attach it to theBrowseFragment to display those items on screen for browsing by the user. The following example code demonstrates how to construct an adapter to display categories and items in those categories using theStringPresenter class shown in the previous code example:

private ArrayObjectAdapter mRowsAdapter;
private static final int NUM_ROWS = 4;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    buildRowsAdapter();
}

private void buildRowsAdapter() {
    mRowsAdapter = 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);
        mRowsAdapter.add(new ListRow(header, listRowAdapter));
    }

    mBrowseFragment.setAdapter(mRowsAdapter);
}

This example shows a static implementation of the adapters. A typical media browsing application uses data from an online database or web service. For an example of a browsing application that uses data retrieved from the web, see the Android Leanback sample app.

Update the Background



In order to add visual interest to a media-browsing app on TV, you can update the background image as users browse through content. This technique can make interaction with your app more cinematic and enjoyable.

The Leanback support library provides a BackgroundManager class for changing the background of your TV app activity. The following example shows how to create a simple method for updating the background within your TV app activity:

protected void updateBackground(Drawable drawable) {
    BackgroundManager.getInstance(this).setDrawable(drawable);
}

Many of the existing media-browse apps automatically update the background as the user navigates through media listings. In order to do this, you can set up a selection listener to automatically update the background based on the user's current selection. The following example shows you how to set up anOnItemViewSelectedListener class to catch selection events and update the background:

protected void clearBackground() {
    BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
}

protected OnItemViewSelectedListener getDefaultItemViewSelectedListener() {
    return new OnItemViewSelectedListener() {
        @Override
        public void onItemSelected(Object item, Row row) {
            if (item instanceof Movie ) {
                Drawable background = ((Movie)item).getBackdropDrawable();
                updateBackground(background);
            } else {
                clearBackground();
            }
        }
    };
}

Note: The implementation above is a simple example shown for purposes of illustration. When creating this function in your own app, you should consider running the background update action in a separate thread for better performance. In addition, if you are planning on updating the background in response to users scrolling through items, consider adding a time to delay a background image update until the user settles on an item. This technique avoids excessive background image updates.

内容概要:本文详细介绍了一个基于C++的养老院管理系统的设计与实现,旨在应对人口老龄化带来的管理挑战。系统通过整合住户档案、健康监测、护理计划、任务调度等核心功能,构建了从数据采集、清洗、AI风险预测到服务调度与可视化的完整技术架构。采用C++高性能服务端结合消息队列、规则引擎和机器学习模型,实现了健康状态实时监控、智能任务分配、异常告警推送等功能,并解决了多源数据整合、权限安全、老旧硬件兼容等实际问题。系统支持模块化扩展与流程自定义,提升了养老服务效率、医护协同水平和住户安全保障,同时为运营决策提供数据支持。文中还提供了关键模块的代码示例,如健康指数算法、任务调度器和日志记录组件。; 适合人群:具备C++编程基础,从事软件开发或系统设计工作1-3年的研发人员,尤其是关注智慧养老、医疗信息系统开发的技术人员。; 使用场景及目标:①学习如何在真实项目中应用C++构建高性能、可扩展的管理系统;②掌握多源数据整合、实时健康监控、任务调度与权限控制等复杂业务的技术实现方案;③了解AI模型在养老场景中的落地方式及系统架构设计思路。; 阅读建议:此资源不仅包含系统架构与模型描述,还附有核心代码片段,建议结合整体设计逻辑深入理解各模块之间的协同机制,并可通过重构或扩展代码来加深对系统工程实践的掌握。
内容概要:本文详细介绍了一个基于C++的城市交通流量数据可视化分析系统的设计与实现。系统涵盖数据采集与预处理、存储与管理、分析建模、可视化展示、系统集成扩展以及数据安全与隐私保护六大核心模块。通过多源异构数据融合、高效存储检索、实时处理分析、高交互性可视化界面及模块化架构设计,实现了对城市交通流量的实时监控、历史趋势分析与智能决策支持。文中还提供了关键模块的C++代码示例,如数据采集、清洗、CSV读写、流量统计、异常检测及基于SFML的柱状图绘制,增强了系统的可实现性与实用性。; 适合人群:具备C++编程基础,熟悉数据结构与算法,有一定项目开发经验的高校学生、研究人员及从事智能交通系统开发的工程师;适合对大数据处理、可视化技术和智慧城市应用感兴趣的技术人员。; 使用场景及目标:①应用于城市交通管理部门,实现交通流量实时监测与拥堵预警;②为市民出行提供路径优化建议;③支持交通政策制定与信号灯配时优化;④作为智慧城市建设中的智能交通子系统,实现与其他城市系统的数据协同。; 阅读建议:建议结合文中代码示例搭建开发环境进行实践,重点关注多线程数据采集、异常检测算法与可视化实现细节;可进一步扩展机器学习模型用于流量预测,并集成真实交通数据源进行系统验证。
### 解决 Kubernetes 中 Calico-system Pod 处于 ContainerCreating 状态的方法 当遇到 `calico-system` Pod 始终处于 `ContainerCreating` 状态时,这通常意味着存在网络配置错误或资源不足等问题。以下是详细的排查和解决方案: #### 1. 检查节点状态 确认所有节点的状态是否正常运行。可以使用命令来查看集群中的节点状况。 ```bash kubectl get nodes ``` 如果发现某些节点不健康,则需进一步调查这些节点上的日志和其他指标[^1]。 #### 2. 查看Pod事件详情 通过获取特定命名空间下Calico组件的具体情况以及其最近发生的活动记录来进行诊断。 ```bash kubectl describe pod -n calico-system <pod-name> ``` 此操作有助于识别任何潜在的问题,比如镜像拉取失败、存储卷挂载问题等[^2]。 #### 3. 验证CNI插件安装 确保Calico CNI 插件已正确部署并加载到每个工作节点上。可以通过SSH登录任意一个worker node执行如下指令验证: ```bash ls /opt/cni/bin | grep calico ``` 如果没有找到相应的文件,说明可能需要重新初始化或者修复CNI设置[^3]。 #### 4. 审核iptables规则 有时防火墙策略会阻止必要的通信流量,在这种情况下应该仔细审查iptables链表内的条目是否存在异常项影响到了容器间的交流过程。 ```bash sudo iptables-save | less ``` 对于复杂的环境来说,建议借助专门工具如`conntrack`, `tcpdump`辅助分析数据包流动路径找出瓶颈所在之处[^4]。 #### 5. 调整资源请求与限制 适当调整CPU/Memory Request/Limits参数以适应实际负载需求,防止因过度分配而导致调度器无法为新创建的pods安排合适的宿主机位置。 ```yaml resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m" ``` 以上措施能够有效缓解由于资源配置不当所引起的启动延迟现象[^5]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值