テレビで動作するメディアアプリでは、ユーザーが提供しているコンテンツをブラウジングして、 コンテンツの再生を開始します。コンテンツ ブラウジング エクスペリエンス シンプルで直感的で、視覚的に楽しく訴求力のあるものでなければなりません。
このガイドでは、androidx.leanback ライブラリが提供するクラスの使用方法について説明します。 を使用して、アプリのメディア カタログから音楽や動画を閲覧するためのユーザー インターフェースを実装します。
注: ここに示した実装例では、
BrowseSupportFragment
(非推奨の BrowseFragment
ではなく)
クラスです。BrowseSupportFragment
は AndroidX を拡張します。
Fragment
クラス、
デバイスと Android バージョン間で一貫した動作を確保できます。
![アプリのメイン画面](https://developer.android.com/static/images/tv/app-browse.png?authuser=4&hl=ja)
図 1. Leanback サンプルアプリのブラウズ フラグメントでは、動画カタログのデータが表示されます。
メディア ブラウズ レイアウトを作成する
BrowseSupportFragment
クラス(Leanback UI ツールキット)
を使用すると、閲覧カテゴリ用のプライマリ レイアウトと、メディア アイテムの行を
最小限に抑える必要があります。次の例は、次の要素を含むレイアウトを作成する方法を示しています。
BrowseSupportFragment
オブジェクト:
<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>
次の例に示すように、このビューはアプリケーションのメイン アクティビティによって設定されます。
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); } ...
BrowseSupportFragment
メソッドは、ビューに
動画データと UI 要素をサポートし、レイアウト パラメータ(アイコン、タイトル、
カテゴリ ヘッダーが有効かどうか
BrowseSupportFragment
を実装するアプリのサブクラス
メソッドは、UI 要素に対するユーザー操作のイベント リスナーを設定し、
バックグラウンド マネージャーで利用できます。
Kotlin
class MainFragment : BrowseSupportFragment(), LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> { 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 header 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<HashMap<String, List<Movie>>> { } ... @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 header 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()); } ...
UI 要素を設定する
前のサンプルでは、プライベート メソッド setupUIElements()
が複数の
BrowseSupportFragment
メソッド: メディア カタログ ブラウザのスタイルを設定します。
setBadgeDrawable()
指定されたドローアブル リソースをブラウズ フラグメントの右上隅に配置します。 図 1 と図 2 をご覧ください。このメソッドは、タイトルの文字列を ドローアブル リソース(setTitle()
も呼び出された場合)。ドローアブル リソースは 52 dp であること あります。setTitle()
ブラウズ フラグメントの右上隅にタイトル文字列を設定します。ただし、setBadgeDrawable()
が呼び出されます。setHeadersState()
とsetHeadersTransitionOnBackEnabled()
は、ヘッダーを非表示または無効にします。 詳細については、ヘッダーを非表示または無効にするをご覧ください。setBrandColor()
ブラウズ フラグメントの UI 要素(特にヘッダー)の背景色を設定します。 指定した色の値で、セクションの背景色。setSearchAffordanceColor()
検索アイコンの色を指定された色の値に設定します。検索アイコン 図 1 と図 2 に示すように、ブラウズ フラグメントの左上に表示されます。
ヘッダービューをカスタマイズする
図 1 に示すブラウズ フラグメントでは、動画のカテゴリ名が表示されています。 これは、テキストビューの動画データベースの行ヘッダーです。また、 より複雑なレイアウトで追加のビューを含めることができます。以降のセクションでは、 図 2 に示すように、カテゴリ名の横にアイコンを表示する画像ビューを含める。
![アプリのメイン画面](https://developer.android.com/static/images/tv/custom-head.png?authuser=4&hl=ja)
図 2. 両方のアイコンがあるブラウズ フラグメントの行ヘッダー テキストラベルです。
行ヘッダーのレイアウトは次のように定義されます。
<?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>
Presenter
を使用して以下を実装します。
ビューホルダーを作成、バインド、バインド解除するための抽象メソッド。次の
例は、ビューホルダーを 2 つのビューにバインドする方法を示しています。
ImageView
と TextView
。
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<ImageView>(R.id.header_icon).apply { rootView.resources.getDrawable(R.drawable.ic_action_video, null).also { icon -> setImageDrawable(icon) } } rootView.findViewById<TextView>(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 } }
ヘッダーはフォーカス可能にして、D-pad を使用して以下の操作を行えるようにする必要があります。 スクロールしてください。これを管理する方法は 2 つあります。
onBindViewHolder()
でビューをフォーカス可能に設定します。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 // ... }
- レイアウトをフォーカス可能に設定します。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" ... android:focusable="true">
最後に、BrowseSupportFragment
カタログ ブラウザでは、setHeaderPresenterSelector()
を使用します。
メソッドを使用して、行ヘッダーのプレゼンターを設定します。次の例をご覧ください。
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(); } });
詳細な例については、 <ph type="x-smartling-placeholder"></ph> Leanback サンプルアプリ をタップします。
ヘッダーを非表示または無効にする
行ヘッダーを表示したくない場合は、
スクロール可能なリストが必要です。BrowseSupportFragment.setHeadersState()
を呼び出す
メソッド(フラグメントの onActivityCreated()
内で実行される)
メソッドを使用して、行ヘッダーを非表示または無効にできます。setHeadersState()
メソッドは、次のいずれかの条件を考慮して、ブラウズ フラグメントのヘッダーの初期状態を設定します。
次のように指定します。
HEADERS_ENABLED
: ブラウズ フラグメント アクティビティが作成されると、ヘッダーが有効になり、 あります。ヘッダーはこのページの図 1 と図 2 のように表示されます。HEADERS_HIDDEN
: ブラウズ フラグメント アクティビティが作成されると、ヘッダーはデフォルトで有効になり、非表示になります。 に示すように、画面のヘッダー セクションが閉じられています。 カードビューを提供するの図。「 折りたたまれたヘッダー セクションを選択して展開できます。HEADERS_DISABLED
: ブラウズ フラグメント アクティビティが作成されると、ヘッダーはデフォルトで無効になり、 表示されます。
HEADERS_ENABLED
または HEADERS_HIDDEN
が設定されている場合は、
setHeadersTransitionOnBackEnabled()
行で選択したコンテンツ アイテムから行ヘッダーに戻る操作をサポートします。これは以下によって実現されます。
メソッドを呼び出さなかった場合のデフォルトです。背中の動きを自分で処理するには、
false
を setHeadersTransitionOnBackEnabled()
に渡す
独自のバックスタック処理を実装できます
メディアリストを表示する
BrowseSupportFragment
クラスを使用すると、
ブラウズ可能なメディア コンテンツ カテゴリとメディア アイテムを、
アダプターとプレゼンターを使って
メディアカタログを作成しますアダプターで接続
ローカルまたはオンラインのデータソースにエクスポートできます。
アダプタはプレゼンターを使用してビューを作成し、ビューにデータをバインドします。
画面上にアイテムを表示すること
次のサンプルコードは、文字列データを表示するための Presenter
の実装を示しています。
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 } }
メディア アイテムのプレゼンター クラスを作成したら、
アダプターを作成し、BrowseSupportFragment
に接続する。
それらのアイテムを画面に表示し、ユーザーが閲覧できます。次の例をご覧ください。
カテゴリとアイテムを表示するアダプタを作成する方法を示すコード
カテゴリに 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); }
この例では、アダプターの静的実装を示しています。典型的なメディア閲覧アプリ オンライン データベースやウェブサービスのデータを使用する。たとえば、Google Chat で ウェブから取得したデータを使用する場合は、 <ph type="x-smartling-placeholder"></ph> Leanback サンプルアプリ をタップします。
背景を切り替える
テレビのメディア ブラウジング アプリを目立たせるために、背景を更新します 画像が表示されます。この手法により、アプリとのインタラクションが増えます。 映画のような楽しいものです
Leanback UI ツールキットには BackgroundManager
が用意されています。
クラスを使用して TV アプリのアクティビティの背景を変更できます。次の例で、
TV アプリのアクティビティ内の背景を更新するための簡単なメソッドを作成します。
Kotlin
protected fun updateBackground(drawable: Drawable) { BackgroundManager.getInstance(this).drawable = drawable }
Java
protected void updateBackground(Drawable drawable) { BackgroundManager.getInstance(this).setDrawable(drawable); }
多くのメディア ブラウジング アプリでは、ユーザーの操作に応じて背景が自動的に更新される
メディア リスティング。そのためには、選択リスナーを設定して、
ユーザーの現在の選択に基づいて背景を更新します。次の例は
OnItemViewSelectedListener
クラスをセットアップして、
次のようにして、選択イベントを捕捉して背景を更新します。
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(); } } }; }
注: 上記の実装は、 説明します。独自のアプリでこの関数を作成する場合は、 バックグラウンド更新アクションを別のスレッドで実行すると、パフォーマンスが向上します。また ユーザーが項目をスクロールしたときに背景を更新することを計画しており、 ユーザーがアイテムに定着するまで背景画像の更新を遅らせる時間。この方法により、 過度な背景画像の更新