目录创建 android,创建目录浏览器 | Android 开发者 | Android Developers
在 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();
}
}
};
}
注意:以上實現是出于說明目的而展示的一個簡單示例。在您自己的應用中創建此功能時,您應考慮在一個單獨的線程內運行背景更新操作,以便獲得更好的性能。此外,如果您計劃在用戶滾動瀏覽內容時更新背景,不妨考慮添加一個延遲時間,使背景圖片更新延遲到用戶選擇某個內容后再執行。此方法可以避免背景圖片更新過于頻繁。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的目录创建 android,创建目录浏览器 | Android 开发者 | Android Developers的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html 地址坐标图标,浏览器地址栏中显
- 下一篇: android 条件筛选吸顶,自定义吸顶