本文档记录 TV 版(leanback)详情页各模式的代码结构、文件对应关系和差异控制点,便于后续维护和功能对齐。
设置项位于:设置 → 增强 → 详情页模式(SettingEnhanceActivity)
字符串定义:app/src/main/res/values-zh-rCN/strings.xml
模式常量定义:app/src/main/java/com/fongmi/android/tv/setting/Setting.java(第37-42行)
| 设置项名称(中文) | 常量 | 值 | 承载 Activity | 说明 |
|---|---|---|---|---|
| 原生增强 | DETAIL_OPEN_ORIGINAL_ENHANCED |
5 | VideoActivity |
上游原生详情页,TMDB 内嵌增强 |
| 沉浸融合 | DETAIL_OPEN_FUSION |
0 | TmdbDetailActivity |
TMDB 独立详情页,融合布局 |
| 炫彩详情 | DETAIL_OPEN_ENHANCED |
1 | TmdbDetailActivity |
TMDB 独立详情页,富信息布局 |
| 详情直放 | DETAIL_OPEN_PLAYER |
4 | TmdbDetailActivity |
TMDB 独立详情页,播放器优先 |
| 影视原生 | DETAIL_OPEN_DIRECT |
2 | VideoActivity |
上游原生详情页,无 TMDB |
设置界面可选模式(
SettingEnhanceActivity.DETAIL_OPEN_MODES,第55行)共5项,顺序为:原生增强、沉浸融合、炫彩详情、详情直放、影视原生。另有
DETAIL_OPEN_CINEMA(光影剧幕,值3)和DETAIL_OPEN_ORIGINAL(原始详情,已废弃)为代码内保留模式,不在设置项中展示。
app/src/leanback/java/com/fongmi/android/tv/ui/activity/VideoActivity.java— 原生增强 / 影视原生app/src/main/java/com/fongmi/android/tv/ui/activity/TmdbDetailActivity.java— 沉浸融合 / 炫彩详情 / 详情直放
VideoActivity.start(...)(第372行附近)负责决定打开哪个 Activity:
// 简化逻辑
if (tmdbItem == null && shouldOpenLegacyTmdbDetail(key, cast)) {
// 满足 TMDB 独立详情页条件 → 跳转 TmdbDetailActivity
TmdbDetailActivity.start(activity, key, id, name, pic, mark, null, Setting.getDetailOpenMode());
return;
}
// 否则 → 打开 VideoActivity
Intent intent = new Intent(activity, VideoActivity.class);
...shouldOpenLegacyTmdbDetail(key, cast)(VideoActivity.java:311):
return canOpenLegacyTmdbDetail(key, cast)
&& Setting.isTmdbDetailPage() // TMDB 原生模式且配置就绪
&& Setting.isStandaloneTmdbDetailMode(mode); // mode ∈ {FUSION, ENHANCED, PLAYER}isStandaloneTmdbDetailMode(mode)(Setting.java:915):
return mode == DETAIL_OPEN_FUSION
|| mode == DETAIL_OPEN_ENHANCED
|| mode == DETAIL_OPEN_PLAYER;结论:沉浸融合 / 炫彩详情 / 详情直放 走
TmdbDetailActivity;原生增强 / 影视原生 走VideoActivity。
VideoActivity 同时承载"原生增强"和"影视原生"两个模式,通过以下判断区分:
// Setting.java:927
public static boolean isDirectDetailPage() {
return getDetailOpenMode() == DETAIL_OPEN_DIRECT;
}
// VideoActivity.java:544 —— 是否使用上游原生选集模块
private boolean shouldUseUpstreamNativeEpisodeModule() {
return Setting.isDirectDetailPage() && !isTmdbMode();
}影视原生模式下,选集列表使用上游原生分段网格布局(setUpstreamNativeEpisodeItems)。
// Setting.java:935
public static boolean isOriginalEnhancedDetailPage() {
return getDetailOpenMode() == DETAIL_OPEN_ORIGINAL_ENHANCED;
}
// VideoActivity.java:1094 —— 是否加载 TMDB 富集数据
setOriginalEnhancedActionVisibility(loadTmdbDetail && Setting.isOriginalEnhancedDetailPage());原生增强模式下,会在 VideoActivity 内部加载 TMDB 富集信息(演员、剧照、推荐等),但布局主体仍是 activity_video.xml。
// VideoActivity.java:1131
private boolean shouldLoadTmdbDetail() {
return mTmdbUIAdapter != null && mTmdbUIAdapter.isReady();
}mTmdbUIAdapter 由 initTmdbMode()(VideoActivity.java:4183)在 isTmdbSourceEnabled() 为真时初始化。
TmdbDetailActivity 承载三个模式,通过 Intent 的 detail_mode extra 区分:
private int getDetailMode() {
if (getIntent().hasExtra("detail_mode"))
return normalizeDetailMode(getIntent().getIntExtra("detail_mode", DETAIL_OPEN_ENHANCED));
return getIntent().getBooleanExtra("fusion", false) ? DETAIL_OPEN_FUSION : DETAIL_OPEN_ENHANCED;
}
private boolean isFusionMode() { return getDetailMode() == DETAIL_OPEN_FUSION || getIntent().getBooleanExtra("fusion", false); }
private boolean isPlayerMode() { return getDetailMode() == DETAIL_OPEN_PLAYER; }
private boolean isCinemaMode() { /* DETAIL_OPEN_CINEMA 或 isTmdbCinemaStyle() */ }binding.playerPanel.setVisibility(isFusionMode() ? VISIBLE : GONE); // 沉浸融合显示内嵌播放器面板
binding.heroSpacer.setVisibility(isFusionMode() ? GONE : VISIBLE); // 沉浸融合无顶部间距
binding.fusionActions.setVisibility(isFusionMode() ? VISIBLE : GONE); // 沉浸融合用 fusionActions 按钮组
binding.detailActions.setVisibility(isFusionMode() ? GONE : VISIBLE); // 其他模式用 detailActions 按钮组Setting.getDetailThemeMode():DETAIL_STYLE_PROFILE(0)/DETAIL_STYLE_CINEMA(1)/DETAIL_STYLE_NATIVE(2)- 主题影响配色和卡片样式,通过
setDetailActionButton、castAdapter.setCinema()等应用 - 原生增强模式默认
DETAIL_STYLE_NATIVE,其余默认DETAIL_STYLE_PROFILE(Setting.java:795)
app/src/leanback/res/layout/activity_video.xml— 详情页主布局- 顶部按钮行(HorizontalScrollView,id=row2):
content(简介) /shortDisplay(短显) /searchDetail(搜索) /keep(收藏) /change1(换源,已隐藏) /tmdbRematch(重匹配) - 滚动区(NestedScrollView,id=scroll):
flag(线路) /quality(清晰度) /array(分段) /episodeContainer(选集) / TMDB 富集区(演员/剧照/主创/推荐)
- 顶部按钮行(HorizontalScrollView,id=row2):
app/src/leanback/res/layout/view_control_vod.xml— 播放器控制栏容器(include 到 activity_video 的 id=control)app/src/leanback/res/layout/view_control_vod_action.xml— 控制栏动作按钮行- 按钮:
next/prev/episodes/reset/search/change2(已隐藏)/fullscreen/player/decode/speed/scale/actionQuality/lut/text/audio/video/opening/ending/danmaku/title/repeat
- 按钮:
app/src/main/res/layout/activity_tmdb_detail.xml— TMDB 独立详情页主布局(共用,通过fusionActions/detailActions等切换子区域可见性)- 按钮组:
changeSource/changeSourceDetail/playerChangeSource(换源,已改为搜索文本)/themeMode/themeModeTop/themeModeDetail(主题) /rematch/rematchTop/rematchFusion(重匹配)
- 按钮组:
原生增强、沉浸融合、炫彩详情和详情直放统一遵循以下规则:
-
当前线路的
Flag.getEpisodes()是可播放内容的唯一事实源; -
TMDB 完整季度只用于补充标题、剧照、日期和季集位置,不生成播放项;
-
季度导航只显示能够从当前线路可靠确认的季度;
-
当前线路只有一个可播放季度时,隐藏切换导航,但必须在顶部元信息和选集标题保留该季度上下文;
-
无法可靠分季时隐藏季度导航,并保留线路原始集数列表;
-
不显示可点击但没有播放 URL 的 TMDB 季度或集数。
-
原生增强必须在 TMDB 标准标题覆盖线路原名之前保存来源季度,并在“选集 · 第 X 季”及移动端融合元信息中展示;
-
明确来源季度时,TMDB 剧集标题只允许绑定该季,失败时不得回退套用第 1 季;
-
聚合续播与单集位置缓存都以季号隔离:不同季度的同集号不得共享进度,季号未知的旧记录只在同一来源 key 内兼容恢复。
详细解析优先级、状态切换、测试矩阵见:docs/tmdb-playable-episode-availability-design.md。
问题:activity_video.xml 和 view_control_vod_action.xml(通过 include 嵌套)中曾同时存在 android:id="@+id/search",导致 mBinding.search 绑定到错误视图,点击事件失效。
解决:将详情页顶部搜索按钮改名为 searchDetail,控制栏的保留为 search(通过 mBinding.control.action.search 访问)。
规则:使用 <include> 嵌套布局时,整个布局树内所有 android:id 必须唯一。ViewBinding 不会对重复 id 报错,但会绑定到不确定的视图,导致点击/可见性设置失效且无任何错误提示。新增按钮前务必全局搜索 id 是否已存在。
项目同时存在:
app/src/leanback/java/.../VideoActivity.java(TV 版)app/src/mobile/java/.../VideoActivity.java(手机版)
修改 TV 版详情页时,确认改的是 leanback source set 下的文件。同理布局文件区分 leanback/res/layout 与 mobile/res/layout(及 mobile/res/layout-sw600dp)。
"换源"按钮在多处存在,统一改为搜索时需逐个处理:
| 按钮 ID | 文件 | 位置 |
|---|---|---|
change1 |
activity_video.xml |
VideoActivity 顶部按钮行(已隐藏) |
change2 |
view_control_vod_action.xml |
VideoActivity 控制栏(已隐藏) |
changeSource |
activity_tmdb_detail.xml |
TmdbDetailActivity 顶部(文本改为搜索) |
changeSourceDetail |
activity_tmdb_detail.xml |
TmdbDetailActivity 详情区(文本改为搜索) |
playerChangeSource |
activity_tmdb_detail.xml |
TmdbDetailActivity 播放器(文本改为搜索) |
- VideoActivity 视频内搜索:
onSearch()→initSearch(keyword, false)→startSearch()→mViewModel.searchContent()→QuickSearchDialog(弹窗式搜索结果列表) - 全局搜索:
SearchActivity.start(context, keyword)(带输入框)或SearchActivity.direct(context, keyword)(直接进CollectActivity聚合结果页,无输入框) - TmdbDetailActivity 无 QuickSearchDialog:当前 TmdbDetailActivity 的搜索按钮(短按+长按)均调用
openGlobalSourceSearch()→SearchActivity.direct(),与 VideoActivity 的视频内搜索效果不同。若要对齐原生增强的弹窗式搜索,需在 TmdbDetailActivity 引入SiteViewModel+QuickAdapter+QuickSearchDialog,成本较高。
- 模拟器架构:项目只编译 ARM 架构(
leanbackArm64_v8a/leanbackArmeabi_v7a),x86_64 模拟器无法安装运行。需用 ARM 模拟器或真机测试。 - 点击事件无效排查:在
setOnClickListener的 lambda 内首行加android.util.Log.e("TAG", "=== clicked ==="),配合adb logcat | grep "==="验证回调是否触发。若无日志,优先怀疑 id 冲突或焦点被父容器拦截。 - adb 操作模拟器:TV 版要选宽屏设备,先用
adb devices和adb -s <device> shell wm size确认,例如emulator-5556是1920x1080。实际安装包名是com.silent.android.webhtv,源码 namespace 是com.fongmi.android.tv。启动当前 app 用adb -s emulator-5556 shell monkey -p com.silent.android.webhtv -c android.intent.category.LEANBACK_LAUNCHER 1,或显式组件adb -s emulator-5556 shell am start -n com.silent.android.webhtv/com.fongmi.android.tv.ui.activity.HomeActivity;adb logcat -c清空日志。
# 清理
./gradlew clean
# 编译 TV 版(ARM64)
./gradlew app:assembleLeanbackArm64_v8aDebug
# 安装到已连接设备
adb install -r app/build/outputs/apk/leanbackArm64_v8a/debug/app-leanback-arm64_v8a-debug.apk任务名规则:app:assembleLeanback{Arm64_v8a|Armeabi_v7a}Debug(注意 flavor 与 ABI 连写,没有单独的 assembleLeanbackDebug)。
| 方法 | 判断内容 |
|---|---|
getDetailOpenMode() |
当前详情页模式(0~5) |
isTmdbDetailPage() |
是否为 TMDB 原生模式且配置就绪 |
isStandaloneTmdbDetailMode(mode) |
mode ∈ {FUSION, ENHANCED, PLAYER} |
isFusionDetailPage() |
沉浸融合 |
isSearchDetailPage() |
炫彩详情(ENHANCED) |
isPlayerDetailPage() |
详情直放 |
isDirectDetailPage() |
影视原生 |
isOriginalEnhancedDetailPage() |
原生增强 |
isCinemaDetailPage() |
光影剧幕 |
getDetailThemeMode() |
详情页主题(PROFILE/CINEMA/NATIVE) |
getTmdbModel() |
TMDB 模型(默认 NATIVE=0) |
详情页模式相关字符串(values-zh-rCN/strings.xml):
setting_detail_open_mode 详情页模式
setting_detail_open_original_enhanced 原生增强
setting_detail_open_fusion 沉浸融合
setting_detail_open_enhanced 炫彩详情
setting_detail_open_player 详情直放
setting_detail_open_direct 影视原生
setting_detail_open_cinema 光影剧幕
setting_detail_open_native TMDB 内嵌增强
setting_detail_open_original 原始详情(已废弃)
setting_detail_theme_mode 详情页主题
按钮相关字符串:
play_search 搜索
play_change 换源
detail_desc 简介
keep 收藏