V1.3.3: 移除点击时的纯色遮罩层 + App 音量默认值改为 85%
1) 纯色遮罩层: TvFocusStyler.install() 会把背景整体换成不透明的红→橙渐变作为
焦点/按下反馈, 而 prepareTvFocusableTree(window.decorView) 会遍历整棵树施加。
按钮/Tab/卡片需要该反馈, 但铺满屏幕或覆盖播放画面的「表面型」View 一旦被套上,
触摸一次(state_pressed)就会用纯色盖住整屏内容。
新增 TvFocusStyler.suppressFill(view, focusable) 显式排除:
- 播放触摸层 playerTouchOverlay(浮在播放画面上方, 主要元凶; 传 focusable=false
保持其 isFocusable=false, 不再抢遥控器焦点)
- 全屏播放容器 R.id.container、启动加载遮罩 databaseLoadingOverlay
- 手机点歌二维码浮层(首页右下 + 全屏右上)、全屏音量面板
- 原 preserveBackground 更名为 suppressFill(播放画面宿主/海报沿用)
同时 install() 对已标记的 View 直接跳过, 不再替调用方强制可聚焦。
2) App 音量默认 85%: 新增 KtvStore.DEFAULT_MUSIC_VOLUME 作为唯一来源,
KtvStore 字段默认值与 optInt 兜底、MainActivity.musicVolume 初值、
调音弹窗「默认」按钮(原 80)统一引用; 远程页面 assets/mobile 默认值同步为 85。
版本号 1.3.2 -> 1.3.3(versionCode 8 -> 9)。按约定本次不发布 Release, 仅本地打包测试。
This commit is contained in:
@@ -103,7 +103,7 @@ $BT/apksigner verify 目标名.apk
|
|||||||
|
|
||||||
### 版本号
|
### 版本号
|
||||||
|
|
||||||
- `app/build.gradle`:`versionName "1.3.2"` / `versionCode 8`
|
- `app/build.gradle`:`versionName "1.3.3"` / `versionCode 9`
|
||||||
- App 内显示统一用纯版本号(不加 `(versionCode)` 括号)
|
- App 内显示统一用纯版本号(不加 `(versionCode)` 括号)
|
||||||
|
|
||||||
## 三、关键实现约束(避免踩坑)
|
## 三、关键实现约束(避免踩坑)
|
||||||
@@ -134,6 +134,17 @@ $BT/apksigner verify 目标名.apk
|
|||||||
客户端为 `MiliLog`(有界队列 + 后台线程 + 失败重试),调用点 `MiliLog.info/notice/warn/error`
|
客户端为 `MiliLog`(有界队列 + 后台线程 + 失败重试),调用点 `MiliLog.info/notice/warn/error`
|
||||||
保持不变。启动上报(`reportDeviceBootInfo()`)只采集这 10 项:包名、版本号、安装时间、品牌、型号、
|
保持不变。启动上报(`reportDeviceBootInfo()`)只采集这 10 项:包名、版本号、安装时间、品牌、型号、
|
||||||
设备名、Android 版本、CPU 架构、内核版本、屏幕分辨率
|
设备名、Android 版本、CPU 架构、内核版本、屏幕分辨率
|
||||||
|
- **焦点填充与「纯色遮罩层」**:`TvFocusStyler.install()` 会把背景整体换成**不透明**红→橙渐变,
|
||||||
|
作为遥控器焦点/按下的反馈(`prepareTvFocusableTree(window.decorView)` 与对话框的
|
||||||
|
`installTree(decorView)` 会遍历整棵树施加)。按钮/Tab/卡片/list 行需要这个反馈;但
|
||||||
|
**大面积展示型表面绝不能加** —— 否则触摸一次(`state_pressed`)就用纯色盖住整屏内容。
|
||||||
|
已排除的表面:播放画面宿主(`home_player_container` / `player_container`)、视频层
|
||||||
|
(`persistentVideo`)、播放触摸层(`playerTouchOverlay`)、全屏播放容器(`R.id.container`)、
|
||||||
|
启动加载遮罩(`databaseLoadingOverlay`)、手机点歌二维码浮层、全屏音量面板。
|
||||||
|
**新增此类铺满/覆盖播放画面的 View 时,必须调用 `TvFocusStyler.suppressFill(view)`**
|
||||||
|
- **音量默认值**:`KtvStore.DEFAULT_MUSIC_VOLUME`(当前 85%)是唯一来源,
|
||||||
|
`KtvStore` 默认值与 `optInt` 兜底、`MainActivity.musicVolume` 初值、调音弹窗「默认」按钮
|
||||||
|
都引用它;远程页面 `assets/mobile/{index.html,app.js}` 的默认值需手工跟改
|
||||||
- **音量控制(只控 App 输出,不动系统音量)**:`musicVolume`(0-100) 是 **App 内部输出音量**,
|
- **音量控制(只控 App 输出,不动系统音量)**:`musicVolume`(0-100) 是 **App 内部输出音量**,
|
||||||
统一在 `applyPlaybackMode()` 内按线性系数施加到播放器(`KtvVideoView.setPlaybackVolume` /
|
统一在 `applyPlaybackMode()` 内按线性系数施加到播放器(`KtvVideoView.setPlaybackVolume` /
|
||||||
`selectAudioChannel`)与副音轨播放器(`vocalPlayer` / `embeddedVocalEngine`)。
|
`selectAudioChannel`)与副音轨播放器(`vocalPlayer` / `embeddedVocalEngine`)。
|
||||||
|
|||||||
+2
-2
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "com.local.ktv"
|
applicationId "com.local.ktv"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 25
|
targetSdk 25
|
||||||
versionCode 8
|
versionCode 9
|
||||||
versionName "1.3.2"
|
versionName "1.3.3"
|
||||||
ndk {
|
ndk {
|
||||||
// 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版
|
// 双 ABI 打包:兼容 64 位(Exynos 1580 等 arm64-v8a)与 32 位设备(极米 Play 超越版
|
||||||
// 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则
|
// 的 Amlogic 950X2/S905X2 采用 32 位 Android 用户态,必须包含 armeabi-v7a,否则
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ async function refresh() {
|
|||||||
$("#playButton span:last-child").textContent = data.playing ? "暂停" : "继续";
|
$("#playButton span:last-child").textContent = data.playing ? "暂停" : "继续";
|
||||||
$("#playButton").dataset.action = data.playing ? "pause" : "resume";
|
$("#playButton").dataset.action = data.playing ? "pause" : "resume";
|
||||||
$("#playButton .control-icon").textContent = data.playing ? "Ⅱ" : "▶";
|
$("#playButton .control-icon").textContent = data.playing ? "Ⅱ" : "▶";
|
||||||
$("#musicVolume").value = data.musicVolume ?? 70;
|
$("#musicVolume").value = data.musicVolume ?? 85;
|
||||||
$("#micVolume").value = data.micVolume ?? 70;
|
$("#micVolume").value = data.micVolume ?? 70;
|
||||||
$("#musicValue").textContent = data.musicVolume ?? 70;
|
$("#musicValue").textContent = data.musicVolume ?? 85;
|
||||||
// 麦克风音量由功放/混音器硬件调节(滑杆已禁用), 仅保留显示值。
|
// 麦克风音量由功放/混音器硬件调节(滑杆已禁用), 仅保留显示值。
|
||||||
const micHw = $("#micValue");
|
const micHw = $("#micValue");
|
||||||
if (micHw) micHw.textContent = data.micVolume ?? 70;
|
if (micHw) micHw.textContent = data.micVolume ?? 70;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<article class="glass-card audio-panel">
|
<article class="glass-card audio-panel">
|
||||||
<div class="panel-title"><div><span class="tiny-icon">♪</span><b>声音控制</b></div><small>即时生效</small></div>
|
<div class="panel-title"><div><span class="tiny-icon">♪</span><b>声音控制</b></div><small>即时生效</small></div>
|
||||||
<div class="seg"><button id="accompany" data-vocal="accompany"><span>伴</span>伴唱</button><button id="original" data-vocal="original"><span>原</span>原唱</button></div>
|
<div class="seg"><button id="accompany" data-vocal="accompany"><span>伴</span>伴唱</button><button id="original" data-vocal="original"><span>原</span>原唱</button></div>
|
||||||
<label><span><i class="volume-icon">♪</i>音乐音量</span><b><em id="musicValue">70</em>%</b><input id="musicVolume" type="range" min="0" max="100" value="70"></label>
|
<label><span><i class="volume-icon">♪</i>音乐音量</span><b><em id="musicValue">85</em>%</b><input id="musicVolume" type="range" min="0" max="100" value="85"></label>
|
||||||
<label class="mic-hw"><span><i class="volume-icon mic">●</i>麦克风音量</span><small>由功放/混音器硬件调节</small><input id="micVolume" type="range" min="0" max="100" value="70" disabled></label>
|
<label class="mic-hw"><span><i class="volume-icon mic">●</i>麦克风音量</span><small>由功放/混音器硬件调节</small><input id="micVolume" type="range" min="0" max="100" value="70" disabled></label>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class KtvStore {
|
|||||||
@JvmField var autoNext = true
|
@JvmField var autoNext = true
|
||||||
@JvmField var originalVocal = true
|
@JvmField var originalVocal = true
|
||||||
@JvmField var vocalChannelMode = "自动"
|
@JvmField var vocalChannelMode = "自动"
|
||||||
@JvmField var musicVolume = 70
|
@JvmField var musicVolume = DEFAULT_MUSIC_VOLUME
|
||||||
@JvmField var micVolume = 70
|
@JvmField var micVolume = 70
|
||||||
@JvmField var tone = 0
|
@JvmField var tone = 0
|
||||||
@JvmField var atmosphere = "标准"
|
@JvmField var atmosphere = "标准"
|
||||||
@@ -64,7 +64,7 @@ class KtvStore {
|
|||||||
autoNext = root.optBoolean("autoNext", true)
|
autoNext = root.optBoolean("autoNext", true)
|
||||||
originalVocal = root.optBoolean("originalVocal", true)
|
originalVocal = root.optBoolean("originalVocal", true)
|
||||||
vocalChannelMode = root.optString("vocalChannelMode", "自动")
|
vocalChannelMode = root.optString("vocalChannelMode", "自动")
|
||||||
musicVolume = root.optInt("musicVolume", 70)
|
musicVolume = root.optInt("musicVolume", DEFAULT_MUSIC_VOLUME)
|
||||||
micVolume = root.optInt("micVolume", 70)
|
micVolume = root.optInt("micVolume", 70)
|
||||||
tone = root.optInt("tone", 0)
|
tone = root.optInt("tone", 0)
|
||||||
atmosphere = root.optString("atmosphere", "标准")
|
atmosphere = root.optString("atmosphere", "标准")
|
||||||
@@ -203,6 +203,14 @@ class KtvStore {
|
|||||||
private fun toggleId(ids: MutableSet<String>, id: String): Boolean = if (ids.remove(id)) false else ids.add(id)
|
private fun toggleId(ids: MutableSet<String>, id: String): Boolean = if (ids.remove(id)) false else ids.add(id)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* App 内部输出音量默认值(%)。
|
||||||
|
*
|
||||||
|
* 三处音量入口(播放界面-调音、声音设置-歌曲最大音量、远程控制-音乐音量)共用它;
|
||||||
|
* 远程页面 `assets/mobile/{index.html,app.js}` 里的默认值需与之保持一致。
|
||||||
|
*/
|
||||||
|
const val DEFAULT_MUSIC_VOLUME = 85
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun stableId(song: Song): String = song.id?.takeIf(String::isNotEmpty)
|
fun stableId(song: Song): String = song.id?.takeIf(String::isNotEmpty)
|
||||||
?: song.path?.takeIf(String::isNotEmpty)
|
?: song.path?.takeIf(String::isNotEmpty)
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
private var originalVocal = true
|
private var originalVocal = true
|
||||||
private var vocalChannelMode = "自动"
|
private var vocalChannelMode = "自动"
|
||||||
private var musicVolume = 70
|
private var musicVolume = KtvStore.DEFAULT_MUSIC_VOLUME
|
||||||
private var micVolume = 70
|
private var micVolume = 70
|
||||||
private var tone = 0
|
private var tone = 0
|
||||||
private var atmosphere = "标准"
|
private var atmosphere = "标准"
|
||||||
@@ -2423,8 +2423,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val panel = LinearLayout(this).apply {
|
val panel = LinearLayout(this).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
gravity = Gravity.CENTER_HORIZONTAL
|
gravity = Gravity.CENTER_HORIZONTAL
|
||||||
|
// 仅用于吞掉触摸(点击穿透到全屏容器会误切控制层显示), 不是操作项;
|
||||||
|
// 因此禁止焦点/按下填充, 否则触摸时整块面板会变成纯色。
|
||||||
isClickable = true
|
isClickable = true
|
||||||
}
|
}
|
||||||
|
TvFocusStyler.suppressFill(panel)
|
||||||
val indicator = CircularVolumeView(this).apply {
|
val indicator = CircularVolumeView(this).apply {
|
||||||
setVolume(musicVolume)
|
setVolume(musicVolume)
|
||||||
visibility = View.GONE
|
visibility = View.GONE
|
||||||
@@ -2675,9 +2678,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
btnDefault.setOnClickListener(View.OnClickListener { v: View? ->
|
btnDefault.setOnClickListener(View.OnClickListener { v: View? ->
|
||||||
seekMicVol.setProgress(60)
|
seekMicVol.setProgress(60)
|
||||||
seekMicEffect.setProgress(50)
|
seekMicEffect.setProgress(50)
|
||||||
seekMusicVol.setProgress(80)
|
seekMusicVol.setProgress(KtvStore.DEFAULT_MUSIC_VOLUME)
|
||||||
micVolume = 60
|
micVolume = 60
|
||||||
musicVolume = 80
|
musicVolume = KtvStore.DEFAULT_MUSIC_VOLUME
|
||||||
applyPlaybackMode()
|
applyPlaybackMode()
|
||||||
saveState()
|
saveState()
|
||||||
tvMicVol.setText("60")
|
tvMicVol.setText("60")
|
||||||
@@ -3141,6 +3144,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
bottomMargin = dp(76)
|
bottomMargin = dp(76)
|
||||||
}
|
}
|
||||||
root.addView(box, params)
|
root.addView(box, params)
|
||||||
|
// 二维码浮层是展示型表面: 被焦点填充盖住会直接遮掉二维码。
|
||||||
|
TvFocusStyler.suppressFill(box)
|
||||||
mobileQrOverlay = box
|
mobileQrOverlay = box
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3179,6 +3184,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
container.addView(box, params)
|
container.addView(box, params)
|
||||||
box.bringToFront()
|
box.bringToFront()
|
||||||
|
// 同手机点歌二维码: 展示型表面不做焦点填充。
|
||||||
|
TvFocusStyler.suppressFill(box)
|
||||||
fullScreenQrOverlay = box
|
fullScreenQrOverlay = box
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6631,6 +6638,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
rankCategoryGrid = findViewById(R.id.rank_category_grid)
|
rankCategoryGrid = findViewById(R.id.rank_category_grid)
|
||||||
paginationBar = findViewById<View?>(R.id.pagination_bar)
|
paginationBar = findViewById<View?>(R.id.pagination_bar)
|
||||||
fullScreenContainer = findViewById<FrameLayout?>(R.id.container)
|
fullScreenContainer = findViewById<FrameLayout?>(R.id.container)
|
||||||
|
// 全屏播放容器: 点击用于显隐控制层, 本身是铺满屏幕的表面, 不能有焦点/按下填充。
|
||||||
|
fullScreenContainer?.let { TvFocusStyler.suppressFill(it) }
|
||||||
|
|
||||||
// 设置顶栏按钮事件
|
// 设置顶栏按钮事件
|
||||||
if (btnTopSearch != null) btnTopSearch!!.setOnClickListener(View.OnClickListener { v: View? -> showQuickSearchPage() })
|
if (btnTopSearch != null) btnTopSearch!!.setOnClickListener(View.OnClickListener { v: View? -> showQuickSearchPage() })
|
||||||
@@ -6670,6 +6679,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (isFullScreen) exitFullScreenPlayer() else showFullScreenPlayer()
|
if (isFullScreen) exitFullScreenPlayer() else showFullScreenPlayer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 视频层本身也是表面型 View, 只做点击进入/退出全屏, 不要焦点填充。
|
||||||
|
TvFocusStyler.suppressFill(persistentVideo)
|
||||||
val root = playerLayerRoot
|
val root = playerLayerRoot
|
||||||
if (root != null) {
|
if (root != null) {
|
||||||
val maskIndex = root.indexOfChild(findViewById<View>(R.id.mask))
|
val maskIndex = root.indexOfChild(findViewById<View>(R.id.mask))
|
||||||
@@ -6719,6 +6730,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
isFocusable = false
|
isFocusable = false
|
||||||
setOnClickListener { showFullScreenPlayer() }
|
setOnClickListener { showFullScreenPlayer() }
|
||||||
elevation = dp(10).toFloat()
|
elevation = dp(10).toFloat()
|
||||||
|
// 这层铺满播放画面且浮在最上层: 一旦被套上焦点/按下填充,
|
||||||
|
// 触摸就会把整块播放画面刷成纯色(遮罩层), 必须显式排除。
|
||||||
|
// 它只负责抓触摸, 保持 isFocusable=false, 不参与遥控器焦点。
|
||||||
|
TvFocusStyler.suppressFill(this, focusable = false)
|
||||||
}.also { root.addView(it, insertionIndex + 1, FrameLayout.LayoutParams(1, 1)) }
|
}.also { root.addView(it, insertionIndex + 1, FrameLayout.LayoutParams(1, 1)) }
|
||||||
}
|
}
|
||||||
homeVideo = persistentVideo
|
homeVideo = persistentVideo
|
||||||
@@ -6765,7 +6780,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
homeCardRank, homeCardSong, homeCardSinger, homeCardLocal, homeCardRegular,
|
homeCardRank, homeCardSong, homeCardSinger, homeCardLocal, homeCardRegular,
|
||||||
homeCardFavorite, homeCardCategory,
|
homeCardFavorite, homeCardCategory,
|
||||||
).forEach(::installPressFeedback)
|
).forEach(::installPressFeedback)
|
||||||
listOfNotNull(homePlayerHost, subPagePlayerHost, homePoster).forEach(TvFocusStyler::preserveBackground)
|
// 播放画面宿主/海报是「表面型」View: 不能套焦点/按下填充,
|
||||||
|
// 否则触摸一次就会用纯色层盖住整个播放画面或海报(见 TvFocusStyler.suppressFill)。
|
||||||
|
listOfNotNull(homePlayerHost, subPagePlayerHost, homePoster).forEach(TvFocusStyler::suppressFill)
|
||||||
(homePoster as? FrameLayout)?.foreground = getDrawable(R.drawable.bg_player_focus)
|
(homePoster as? FrameLayout)?.foreground = getDrawable(R.drawable.bg_player_focus)
|
||||||
configureTvFocusNavigation()
|
configureTvFocusNavigation()
|
||||||
prepareTvFocusableTree(window.decorView)
|
prepareTvFocusableTree(window.decorView)
|
||||||
@@ -8571,6 +8588,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
isFocusable = true
|
isFocusable = true
|
||||||
elevation = dp(30).toFloat()
|
elevation = dp(30).toFloat()
|
||||||
}
|
}
|
||||||
|
// 启动加载遮罩铺满整屏: 不允许被焦点/按下填充覆盖, 否则点击启动界面会整屏变纯色。
|
||||||
|
TvFocusStyler.suppressFill(overlay)
|
||||||
val center = LinearLayout(this).apply {
|
val center = LinearLayout(this).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
gravity = Gravity.CENTER
|
gravity = Gravity.CENTER
|
||||||
|
|||||||
@@ -13,13 +13,26 @@ import java.util.WeakHashMap
|
|||||||
/** Applies the original OTT red-to-orange focus fill to remote-control targets. */
|
/** Applies the original OTT red-to-orange focus fill to remote-control targets. */
|
||||||
object TvFocusStyler {
|
object TvFocusStyler {
|
||||||
private val installed = Collections.newSetFromMap(WeakHashMap<View, Boolean>())
|
private val installed = Collections.newSetFromMap(WeakHashMap<View, Boolean>())
|
||||||
private val preservedBackgrounds = Collections.newSetFromMap(WeakHashMap<View, Boolean>())
|
private val fillSuppressed = Collections.newSetFromMap(WeakHashMap<View, Boolean>())
|
||||||
|
|
||||||
/** Preserve image/video backgrounds while suppressing Android's grey focus halo. */
|
/**
|
||||||
fun preserveBackground(view: View) {
|
* 禁止对该 View 施加焦点/按下填充 —— 用于「大面积展示型表面」。
|
||||||
preservedBackgrounds.add(view)
|
*
|
||||||
|
* 背景:[install] 会把背景整体替换成不透明的红→橙渐变,作为遥控器焦点/按下的视觉反馈。
|
||||||
|
* 这对按钮、Tab、卡片是期望效果;但对铺满屏幕或覆盖播放画面的表面型 View
|
||||||
|
* (播放器宿主、视频层、播放触摸层、全屏播放容器、启动加载遮罩、二维码浮层)就是灾难:
|
||||||
|
* 只要触摸一次(`state_pressed`)就会用一个纯色层盖住整屏内容,即「纯色遮罩层」。
|
||||||
|
*
|
||||||
|
* 调用后该 View 保留自身背景且不再被 [install] 处理,平台默认灰色高亮也被关闭。
|
||||||
|
*
|
||||||
|
* @param focusable 是否顺带让该 View 可被遥控器聚焦(默认 true,与旧的 [install] 行为一致);
|
||||||
|
* 纯触摸捕获层(如 `playerTouchOverlay`)应传 false,否则会抢走遥控器焦点
|
||||||
|
* 且自身没有任何焦点视觉反馈。
|
||||||
|
*/
|
||||||
|
fun suppressFill(view: View, focusable: Boolean = true) {
|
||||||
|
fillSuppressed.add(view)
|
||||||
disablePlatformHighlight(view)
|
disablePlatformHighlight(view)
|
||||||
if (view.isClickable && view.isEnabled && view.visibility == View.VISIBLE) {
|
if (focusable && view.isClickable && view.isEnabled && view.visibility == View.VISIBLE) {
|
||||||
view.isFocusable = true
|
view.isFocusable = true
|
||||||
view.isFocusableInTouchMode = false
|
view.isFocusableInTouchMode = false
|
||||||
}
|
}
|
||||||
@@ -37,9 +50,12 @@ object TvFocusStyler {
|
|||||||
private fun install(view: View, force: Boolean) {
|
private fun install(view: View, force: Boolean) {
|
||||||
disablePlatformHighlight(view)
|
disablePlatformHighlight(view)
|
||||||
if (!view.isEnabled || view.visibility != View.VISIBLE || !view.isClickable) return
|
if (!view.isEnabled || view.visibility != View.VISIBLE || !view.isClickable) return
|
||||||
|
// 显式标记为「表面型」的 View: 既不填充背景, 也不替调用方强制可聚焦
|
||||||
|
// (例如 playerTouchOverlay 刻意要求 isFocusable=false, 否则会抢走遥控器焦点)。
|
||||||
|
if (!force && fillSuppressed.contains(view)) return
|
||||||
view.isFocusable = true
|
view.isFocusable = true
|
||||||
view.isFocusableInTouchMode = false
|
view.isFocusableInTouchMode = false
|
||||||
if (!force && (preservedBackgrounds.contains(view) || view.background?.isStateful == true)) return
|
if (!force && view.background?.isStateful == true) return
|
||||||
if (!installed.add(view) && !force) return
|
if (!installed.add(view) && !force) return
|
||||||
val normal = view.background ?: ColorDrawable(Color.TRANSPARENT)
|
val normal = view.background ?: ColorDrawable(Color.TRANSPARENT)
|
||||||
val normalShape = normal as? GradientDrawable
|
val normalShape = normal as? GradientDrawable
|
||||||
|
|||||||
Reference in New Issue
Block a user