readaloud-player-morph.md 7.7 KB

朗读播放页与悬浮胶囊

本文记录当前项目实现的职责与验收路径,不包含外部项目的分析材料。

职责

  • MainActivity 组装同窗口播放浮层;主页启用悬浮底栏、底栏显示且不使用侧边导航时显示圆形入口,其余页面使用可拖拽胶囊。
  • core/ui/player/PlayerMorphHost 共用于朗读和有声书,管理布局锚点、飞行封面、返回与下拉手势; PlayerMorphCover 统一终点封面的请求与交接;ReadAloudMorphState 是唯一动画进度源。朗读宿主只保留朗读设置与经典控制动作。
  • ReadAloudCapsule 共用于应用内与应用外;ReadAloudOverlayWindow 负责 Android 窗口、权限检查和生命周期,附着现有朗读或有声书服务,复用已有播放状态。
  • PlaybackCapsuleGateway 统一胶囊所需的封面、进度与控制动作;Android 适配器监听既有播放事件,不初始化播放器或加载目录。播放中的会话优先,都暂停时保留当前会话,停止后回退到另一存活会话。
  • 主页、应用内和应用外胶囊共用相同选择规则、显示开关和保存坐标。应用外仅由当前会话所属服务挂载窗口,避免双窗口。
  • 两种胶囊均打开 Activity 同窗口形变播放页,不再用全屏底部弹层,且不改变原导航栈。书架、详情、通知和遗留 Intent 的有声书入口也汇入同一宿主;有声书的歌词、目录、倍速、播放模式、定时、换源和媒体回调保留。
  • 有声书封面保留用户选择的比例(包含圆形),目标矩形和圆角由实际布局上报。面板使用所在页面胶囊的真实锚点;只有同一引擎、同一本书的封面参与飞行,不同会话的封面淡出交接。
  • 浮层结束后释放有声书页面 ViewModel 和 Activity 回调,已加载的有声书即使尚未启动服务,也保留为可恢复的暂停会话;显式停止时清除准备会话。服务创建时记录书籍归属,销毁时只清除仍匹配该书的准备会话;服务存活期间切书或旧服务回调晚到时,新书准备会话保留。重新打开同步当前书籍。切换播放器先完成旧浮层收起,保证只有一个宿主驱动进度。
  • 应用外窗口仅在胶囊开关开启、应用无可见 Activity 且拥有系统悬浮窗权限时显示。回到应用、停止服务或撤销权限时移除。

  • 旧 MainRouteReadAloudPlayer 与 MainRouteAudioPlay 保留序列化类型和占位 entry,恢复旧栈时转入同窗口浮层并移除占位。播放浮层打开及退场期间清空下层导航与胶囊语义,收起结束后恢复。

主页长按控制

  • 长按主页封面入口,左侧悬浮导航收为 64dp 圆形菜单按钮,右侧播放胶囊展开;点击菜单或再次长按展开态封面同步恢复。
  • 两侧宽度由同一个临界阻尼弹簧进度驱动,胶囊高度保持 64dp,封面复用同一个节点,信息与按钮按展开进度显露。导航内容保持原宽度测量后渐隐,过渡期间禁用隐藏的导航项。背景采样副本只参与绘制和测量,不创建点击节点;菜单在根容器最上层接收触摸。
  • 展开内容包括书名、实际播放章节、章节进度、暂停/继续和退出。朗读章节由服务发布到会话状态,有声书章节复用播放模型,均经 Gateway 投影;UI 不读取播放服务或数据库。
  • 展开后的面板与封面继续上报真实布局端点,短按进入播放页保留原有形变。退出会话、隐藏胶囊或退出主页底栏宿主时恢复导航。

动画与位置

  • 展开、收起与手势结算使用临界阻尼弹簧:刚度 260,阈值 0.0001;预测性返回手势最多预览 45% 收起,确认后完成,取消后恢复。关闭预测性返回开关时,仅在确认返回后收起播放浮层;下层导航不处理这次返回。
  • 面板始终按宿主尺寸布局,仅在形变期间于最终坐标裁剪独立四角圆角,完全展开后背景和内容层均取消裁剪,开始收起时重新启用。API 31+ 使用系统公开圆角数据;未报告圆角时使用零半径。 -

形变期间冻结胶囊端点;完全展开后重新接收真实位置,覆盖先开播放页、后启动服务才出现胶囊的情况。首次打开时,胶囊宿主提前按保存位置布局同一个不可见组件,等待真实端点连续两帧稳定后开始。主页显隐独立于封面关联资格,最多等待 60 个布局帧;仍缺失锚点或目标布局时固定本次展开为无起点淡入,不虚构坐标,也不在途中接入迟到的锚点。关闭胶囊开关时立即使用淡入淡出,不等待布局;封面未关联当前播放会话时,只等待胶囊与面板布局,不等待封面飞行终点。

  • 两端封面让位给单一飞行封面。旋转角独立同步,封面统一解码尺寸,封面页保持无交叉淡入的飞行交接。
  • 目录或歌词页没有可见封面时,返回使用实际胶囊封面位置,末段(进度 0.15..0)按材质交接曲线平滑渐显,不从屏幕外的 Pager 封面位置飞回。页面可见性在手势/动画中冻结,返回取消后重新同步。
  • 收起末段(展开进度 0..0.15)交还胶囊底色与进度圆环;内容淡入区间为 0.85..1。形变期间关闭胶囊阴影,结束后用 180ms 恢复。
  • 迷你与展开态共用单一内容节点和 320ms 展开进度;按钮保持完整测量尺寸,连续揭示宽度。迷你封面为 48dp,播放时每圈 20 秒,暂停时停转。
  • 保存坐标为相对屏幕底部中央的中心偏移,单位 dp,默认中心距底部 72dp。松手时距左右边缘不超过 72dp 则吸附,迷你封面中心位于边线,外侧半张位于屏幕外;展开时临时向内让位。
  • 系统窗口使用 WRAP_CONTENT、非焦点与非触摸模态标志,主题不绘制全屏应用背景;窗口允许部分移出屏幕。

验证

自动回归覆盖形变端点、四角插值、背景交接、返回进度映射、旋转同步、窗口高度变化、位置往返、权限与显示条件、左右吸附和连续位置采样。

.\gradlew.bat :app:assembleAppDebug :app:testAppDebugUnitTest --tests "io.legado.app.service.readaloud.ReadAloudOverlayWindowTest" --tests "io.legado.app.service.readaloud.PlaybackCapsuleSelectionTest" --tests "io.legado.app.service.readaloud.PlaybackCapsuleGatewayAdapterTest" --tests "io.legado.app.ui.book.readaloud.morph.PlayerMorphFramesTest" --tests "io.legado.app.ui.main.PlaybackPlayerAnchorsTest" --tests "io.legado.app.ui.book.audio.AudioPlayViewModelInitializationTest" --no-configuration-cache
git diff --check

设备验收应覆盖:有声书书架/详情/通知/胶囊入口、各封面比例、歌词/目录页下拉、定时/换源弹层返回、主页与阅读页开合、首次启动朗读、动画反向、系统返回完成与取消、按钮展开、暂停旋转、外部应用触摸透传、权限撤销以及最后几帧封面与背景交接。编译和几何测试不替代动画观感与设备帧时间验证。

审查边界

  • 当前吸附位置保存为数值横向偏移,不单独保存左右边。屏幕宽度增大后原贴边位置可能成为自由位置;旋转或分屏保持吸附边尚未实现。
  • 拖拽取消仅结束本地拖动,没有提交当前位置;若窗口或宿主随后销毁,重新挂载会恢复上次成功松手的位置。
  • 应用内定位以物理屏幕尺寸为基准,多窗口或小于屏幕的宿主可能无法完整显示该位置,仍需设备验证。
  • 此前 lintAppDebug 在 ModifierParameterDetector 分析 MainScreen.kt 时发生检查器异常,尚无通过的 lint 结果;没有关闭检查或放宽基线。