Android TV Leanback BrowseFragment如何为Header行添加自定义图片
问题描述
代码如下:
public class PageAndListRowFragment extends BrowseFragment { private static final long HEADER_ID_1 = 1; private static final String HEADER_NAME_1 = "Page Fragment"; private static final long HEADER_ID_2 = 2; private static final String HEADER_NAME_2 = "Rows Fragment"; private static final long HEADER_ID_3 = 3; private static final String HEADER_NAME_3 = "Settings Fragment"; private static final long HEADER_ID_4 = 4; private static final String HEADER_NAME_4 = "User agreement Fragment"; private BackgroundManager mBackgroundManager; private ArrayObjectAdapter mRowsAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupUi(); loadData(); mBackgroundManager = BackgroundManager.getInstance(getActivity()); mBackgroundManager.attach(getActivity().getWindow()); getMainFragmentRegistry().registerFragment(PageRow.class, new PageRowFragmentFactory(mBackgroundManager)); } private void setupUi() { setHeadersState(HEADERS_ENABLED); setHeadersTransitionOnBackEnabled(true); setBrandColor(getResources().getColor(R.color.fastlane_background)); setTitle("Title goes here"); setOnSearchClickedListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText( getActivity(), getString(R.string.implement_search), Toast.LENGTH_SHORT) .show(); } }); prepareEntranceTransition(); } private void loadData() { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); setAdapter(mRowsAdapter); new Handler().postDelayed(new Runnable() { @Override public void run() { createRows(); startEntranceTransition(); } }, 2000); } private void createRows() { HeaderItem headerItem1 = new HeaderItem(HEADER_ID_1, HEADER_NAME_1); PageRow pageRow1 = new PageRow(headerItem1); mRowsAdapter.add(pageRow1); HeaderItem headerItem2 = new HeaderItem(HEADER_ID_2, HEADER_NAME_2); PageRow pageRow2 = new PageRow(headerItem2); mRowsAdapter.add(pageRow2); HeaderItem headerItem3 = new HeaderItem(HEADER_ID_3, HEADER_NAME_3); PageRow pageRow3 = new PageRow(headerItem3); mRowsAdapter.add(pageRow3); HeaderItem headerItem4 = new HeaderItem(HEADER_ID_4, HEADER_NAME_4); PageRow pageRow4 = new PageRow(headerItem4); mRowsAdapter.add(pageRow4); } private static class PageRowFragmentFactory extends BrowseFragment.FragmentFactory { private final BackgroundManager mBackgroundManager; PageRowFragmentFactory(BackgroundManager backgroundManager) { this.mBackgroundManager = backgroundManager; } @Override public Fragment createFragment(Object rowObj) { Row row = (Row)rowObj; mBackgroundManager.setDrawable(null); if (row.getHeaderItem().getId() == HEADER_ID_1) { return new SampleFragmentA(); } else if (row.getHeaderItem().getId() == HEADER_ID_2) { return new SampleFragmentB(); } else if (row.getHeaderItem().getId() == HEADER_ID_3) { return new SettingsFragment(); } else if (row.getHeaderItem().getId() == HEADER_ID_4) { return new WebViewFragment(); } throw new IllegalArgumentException(String.format("Invalid row %s", rowObj)); } } public static class PageFragmentAdapterImpl extends MainFragmentAdapter<SampleFragmentA> { public PageFragmentAdapterImpl(SampleFragmentA fragment) { super(fragment); } } /** * Simple page fragment implementation. */ public static class SampleFragmentA extends GridFragment { private static final int COLUMNS = 4; private final int ZOOM_FACTOR = FocusHighlight.ZOOM_FACTOR_SMALL; private ArrayObjectAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupAdapter(); loadData(); getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter()); } private void setupAdapter() { VerticalGridPresenter presenter = new VerticalGridPresenter(ZOOM_FACTOR); presenter.setNumberOfColumns(COLUMNS); setGridPresenter(presenter); CardPresenterSelector cardPresenter = new CardPresenterSelector(getActivity()); mAdapter = new ArrayObjectAdapter(cardPresenter); setAdapter(mAdapter); setOnItemViewClickedListener(new OnItemViewClickedListener() { @Override public void onItemClicked( Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { Card card = (Card)item; Toast.makeText(getActivity(), "Clicked on "+card.getTitle(), Toast.LENGTH_SHORT).show(); } }); } private void loadData() { String json = Utils.inputStreamToString(getResources().openRawResource( R.raw.grid_example)); CardRow cardRow = new Gson().fromJson(json, CardRow.class); mAdapter.addAll(0, cardRow.getCards()); } } /** * Page fragment embeds a rows fragment. */ public static class SampleFragmentB extends RowsFragment { private final ArrayObjectAdapter mRowsAdapter; public SampleFragmentB() { mRowsAdapter = new ArrayObjectAdapter(new ShadowRowPresenterSelector()); setAdapter(mRowsAdapter); setOnItemViewClickedListener(new OnItemViewClickedListener() { @Override public void onItemClicked( Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { Toast.makeText(getActivity(), "Implement click handler", Toast.LENGTH_SHORT) .show(); } }); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); createRows(); getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter()); } private void createRows() { String json = Utils.inputStreamToString(getResources().openRawResource( R.raw.page_row_example)); CardRow[] rows = new Gson().fromJson(json, CardRow[].class); for (CardRow row : rows) { if (row.getType() == CardRow.TYPE_DEFAULT) { mRowsAdapter.add(createCardRow(row)); } } } private Row createCardRow(CardRow cardRow) { PresenterSelector presenterSelector = new CardPresenterSelector(getActivity()); ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector); for (Card card : cardRow.getCards()) { adapter.add(card); } HeaderItem headerItem = new HeaderItem(cardRow.getTitle()); return new CardListRow(headerItem, adapter, cardRow); } } public static class SettingsFragment extends RowsFragment { private final ArrayObjectAdapter mRowsAdapter; public SettingsFragment() { ListRowPresenter selector = new ListRowPresenter(); selector.setNumRows(2); mRowsAdapter = new ArrayObjectAdapter(selector); setAdapter(mRowsAdapter); } @Override public void onAttach(Activity activity) { super.onAttach(activity); new Handler().postDelayed(new Runnable() { @Override public void run() { loadData(); } }, 200); } private void loadData() { if (isAdded()) { String json = Utils.inputStreamToString(getResources().openRawResource( R.raw.icon_example)); CardRow cardRow = new Gson().fromJson(json, CardRow.class); mRowsAdapter.add(createCardRow(cardRow)); getMainFragmentAdapter().getFragmentHost().notifyDataReady( getMainFragmentAdapter()); } } private ListRow createCardRow(CardRow cardRow) { SettingsIconPresenter iconCardPresenter = new SettingsIconPresenter(getActivity()); ArrayObjectAdapter adapter = new ArrayObjectAdapter(iconCardPresenter); for(Card card : cardRow.getCards()) { adapter.add(card); } HeaderItem headerItem = new HeaderItem(cardRow.getTitle()); return new CardListRow(headerItem, adapter, cardRow); } } public static class WebViewFragment extends Fragment implements MainFragmentAdapterProvider { private MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this); private WebView mWebview; @Override public MainFragmentAdapter getMainFragmentAdapter() { return mMainFragmentAdapter; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getMainFragmentAdapter().getFragmentHost().showTitleView(false); } @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { FrameLayout root = new FrameLayout(getActivity()); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); lp.setMarginStart(32); mWebview = new WebView(getActivity()); mWebview.setWebViewClient(new WebViewClient()); mWebview.getSettings().setJavaScriptEnabled(true); root.addView(mWebview, lp); return root; } @Override public void onResume() { super.onResume(); mWebview.loadUrl("https://www.google.com/policies/terms"); getMainFragmentAdapter().getFragmentHost().notifyDataReady(getMainFragmentAdapter()); } } }
使用上述代码,已成功实现带行的Header Item展示,当前UI效果如下图所示:
开发中使用了leanback库的BrowseFragment组件。
目前需要对每个Header行的标题进行自定义,在标题区域添加图片,最终实现如下图所示的展示效果:
需要提供在Header行项中添加图片的具体实现方法。
实现方案
Leanback默认的Header渲染使用纯文本的HeaderPresenter,要实现带图标的Header效果,核心是自定义Header的渲染逻辑,替换默认实现,具体步骤如下:
- 自定义Header布局,同时容纳图标和标题
- 扩展原生
HeaderItem,增加图标资源字段 - 实现自定义
Presenter处理Header的视图绑定 - 给
BrowseFragment设置自定义的Header渲染器 - 替换原有Header创建逻辑,传入图标资源
1. 创建自定义Header布局
新建布局文件res/layout/custom_header_view.xml,横向排列图标和标题:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal" android:paddingHorizontal="24dp" android:paddingVertical="12dp"> <ImageView android:id="@+id/iv_header_icon" android:layout_width="24dp" android:layout_height="24dp" android:layout_marginEnd="12dp" android:contentDescription="@null" /> <TextView android:id="@+id/tv_header_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="15sp" android:textStyle="bold" /> </LinearLayout>
2. 扩展HeaderItem支持图标
原生HeaderItem没有存储图标的字段,自定义子类扩展:
public class IconHeaderItem extends HeaderItem { private final int iconRes; public IconHeaderItem(long id, String name, int iconRes) { super(id, name); this.iconRes = iconRes; } public int getIconRes() { return iconRes; } }
3. 实现自定义Header Presenter
继承Presenter实现自定义渲染逻辑,完成图标和标题的绑定:
public class IconHeaderPresenter extends Presenter { @Override public ViewHolder onCreateViewHolder(ViewGroup parent) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.custom_header_view, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder viewHolder, Object item) { HeaderItem header = (HeaderItem) item; View itemView = viewHolder.view; TextView titleView = itemView.findViewById(R.id.tv_header_title); ImageView iconView = itemView.findViewById(R.id.iv_header_icon); titleView.setText(header.getName()); if (header instanceof IconHeaderItem) { iconView.setVisibility(View.VISIBLE); iconView.setImageResource(((IconHeaderItem) header).getIconRes()); } else { iconView.setVisibility(View.GONE); } } @Override public void onUnbindViewHolder(ViewHolder viewHolder) { ImageView iconView = viewHolder.view.findViewById(R.id.iv_header_icon); iconView.setImageDrawable(null); } }
4. 替换BrowseFragment默认Header渲染器
在原有setupUi()方法中,添加Header Presenter的设置:
private void setupUi() { setHeadersState(HEADERS_ENABLED); setHeadersTransitionOnBackEnabled(true); setBrandColor(getResources().getColor(R.color.fastlane_background)); // 新增:设置自定义带图标Header渲染器 setHeaderPresenterSelector(new PresenterSelector() { @Override public Presenter getPresenter(Object item) { return new IconHeaderPresenter(); } }); setTitle("Title goes here"); setOnSearchClickedListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText( getActivity(), getString(R.string.implement_search), Toast.LENGTH_SHORT) .show(); } }); prepareEntranceTransition(); }
5. 修改Header创建逻辑
把原有createRows()方法中创建普通HeaderItem的代码,替换为自定义的IconHeaderItem,传入对应图标资源即可:
private void createRows() { // 将R.drawable下的对应资源替换为你自己的Header图标 HeaderItem headerItem1 = new IconHeaderItem(HEADER_ID_1, HEADER_NAME_1, R.drawable.ic_page); PageRow pageRow1 = new PageRow(headerItem1); mRowsAdapter.add(pageRow1); HeaderItem headerItem2 = new IconHeaderItem(HEADER_ID_2, HEADER_NAME_2, R.drawable.ic_rows); PageRow pageRow2 = new PageRow(headerItem2); mRowsAdapter.add(pageRow2); HeaderItem headerItem3 = new IconHeaderItem(HEADER_ID_3, HEADER_NAME_3, R.drawable.ic_settings); PageRow pageRow3 = new PageRow(headerItem3); mRowsAdapter.add(pageRow3); HeaderItem headerItem4 = new IconHeaderItem(HEADER_ID_4, HEADER_NAME_4, R.drawable.ic_agreement); PageRow pageRow4 = new PageRow(headerItem4); mRowsAdapter.add(pageRow4); }
补充说明
- 如果需要调整Header选中态的样式(比如图标/文字变色、放大效果),可以给布局添加状态选择器,或者在
onBindViewHolder中根据选中状态动态调整样式。 - 如果内部列表行的Header也需要加图标,给对应
ListRowPresenter调用setHeaderPresenter()方法传入上述自定义的IconHeaderPresenter即可,逻辑完全一致。
内容的提问来源于stack exchange,提问作者MARSH
相关产品推荐
相关产品推荐

