Skip to content

安卓划线自动翻页问题#875

Open
addtion99 wants to merge 8 commits intoAnxcye:developfrom
addtion99:fix-android-selection-autopage-gating
Open

安卓划线自动翻页问题#875
addtion99 wants to merge 8 commits intoAnxcye:developfrom
addtion99:fix-android-selection-autopage-gating

Conversation

@addtion99
Copy link
Copy Markdown

@addtion99 addtion99 commented Apr 19, 2026

fix(reader): 修复 Android 选区跨页自动翻页的连续翻页与漏翻问题

问题背景

Android 真机上,阅读器在“长按选区并跨页拖动”的场景下,自动翻页行为不稳定,主要有两类异常:

  1. 连续翻页
    在第 1 页拖到页尾后,会正常翻到第 2 页;但进入第 2 页后,即使选区实际上还没有重新触达第 2 页页尾,也可能继续自动翻到第 3、4 页,导致中间页无法停留。

该问题主要出现在 Android 真机触摸手柄路径中,模拟器和鼠标路径不容易稳定复现。
我也是在自己手机触摸屏上使用过程中才发现了这个bug,在模拟器和鼠标路径不容易稳定复现。这可能也是导致这个bug没被发现的原因

修改问题之前:

f4a4915eddc93364fee22f1d1268ea92.mp4

修改问题之后:

97e11d29528325cd3596ebf46c06cace.mp4

原因分析

原有实现的问题不在“是否判断到了页尾”,而在于跨页时的状态管理不正确

1. 翻页触发没有严格绑定“当前页”

原逻辑在一次拖动过程中,会沿用上一个页面的部分触发状态。
这意味着:第 1 页触发翻页后,进入第 2 页时,没有完全基于“第 2 页当前边界 + 当前选区状态”重新开始判断,导致上一页的触发结果影响了下一页。

结果就是:

  • 可能在新页未真正触底时继续翻页

2. “本页已触发”标记写入时机过早

原逻辑在 next-page-scheduled 阶段就把当前页视为“已触发”。
但从“调度翻页”到“真正执行翻页”之间存在一个等待窗口(约 1 秒),这段时间里用户仍可能继续调整选区。

如果这 1 秒内发生了以下情况:

  • 选区短暂离开页尾
  • pending 定时器被取消

那么逻辑上这次翻页其实并没有真正发生,但当前页已经被提前标记为“触发过”。
后续即使用户重新把选区拖到当前页末尾,也会被错误地跳过,表现为“明明到页尾了却不翻页”。

3. 翻页后新页状态存在短暂滞后

view.next() 执行后,选区变化和 lastLocation 更新并不是完全同步的。
也就是说,页面视觉上已经翻到了下一页,但内部用于判定“当前页边界”的位置数据可能稍后才更新。

这会导致一种典型漏判:

  • 用户已经在新页末尾
  • 但判定仍然参考旧页边界
  • 因而错过本应成立的翻页条件

解决方案

这次修复的核心思路是:把自动翻页改成严格的逐页触发状态机

1. 改为逐页判定,不再复用跨页触发结果

为每一页生成独立的 pageKey,并在一次选区会话中按页维护状态。
自动翻页只能基于“当前页”的边界进行判断,翻到新页后,后续是否继续翻页必须重新按新页计算,不能继承上一页的触发结果。

这样做的目的,是把“第 N 页是否允许翻页”完全限定在“第 N 页自己的状态”上,避免连续失控翻页。

2. 统一翻页触发条件

当前页触发自动翻页必须同时满足两个条件:

  • 选区结束位置已经到达或超过当前页边界
    compareBoundaryPoints(Range.END_TO_END, lastLocation.range) >= 0
  • 选区底部已经接近屏幕底部阈值
    selectionBottom >= 0.9

也就是说,只有“内容边界触底”和“视觉位置触底”同时成立,才认为当前页真正进入可翻页状态。

这样做的目的,是避免仅凭某一个条件成立就过早触发翻页。

3. 调整“已触发”标记的写入时机

保留“每页只触发一次”的门控机制,但将 triggeredPages.add(pageKey) 的时机从“调度翻页时”改为“真正执行翻页时”。

也就是说:

  • next-page-scheduled 只表示“准备翻”
  • next-page-trigger 才表示“真的翻了”

这样做的目的,是确保:

  • 如果等待期间离开页尾导致取消,本页不会被错误标记为已翻
  • 用户重新回到底部后,仍然可以再次触发

这一步是修复“偶发不翻页”的关键。

4. 离开页尾时取消 pending,回到底部允许重新调度

如果当前页已经进入等待翻页状态,但用户在等待窗口内把选区移出了页尾条件,那么立即取消 pending 状态。

这样做的目的,是让系统真实反映“用户当前是否仍然在请求翻页”,而不是机械执行之前的定时动作。

5. 翻页后增加短时重检,处理新页定位滞后

在执行 view.next() 后,增加有限次数的短时重检,主动触发后续判定,直到:

  • 新页边界状态更新完成
  • 或达到最大重检次数

这样做的目的,是覆盖 lastLocation 更新滞后带来的窗口期,避免“新页其实已经满足条件,但判定还没跟上”的漏翻问题。


修改内容

本次改动集中在以下文件:

  • assets/foliate-js/src/book.js
  • assets/foliate-js/dist/bundle.js

主要代码调整包括:

  1. 新增按会话维护的逐页自动翻页状态
  2. 引入 pageKey,将触发判断绑定到当前页
  3. 重写 pending / triggered 的状态流转顺序
  4. 将“已触发”标记延后到真正执行翻页时写入
  5. 翻页后增加有限次短时重检
  6. 在功能稳定后删除调试日志与临时调试分支,保留最终可读实现

修复后的行为

修复后,自动翻页遵循以下规则:

  1. 只有当选区真正到达当前页末尾时,才允许翻到下一页
  2. 每翻到一个新页,都会基于该页重新判断,不会沿用上一页的触发结果
  3. 如果等待翻页期间用户离开页尾,则本次翻页取消;重新回到底部后可再次触发
  4. 翻页后如果选区继续停留在新页末尾,系统会在新页状态完成更新后继续按规则触发下一次翻页
  5. 不会再出现“新页未触底却连续翻页”的问题
  6. 不会再出现“本页已到末尾但因为提前标记已触发而无法翻页”的问题

验证结果

已通过以下验证:

构建验证

  • flutter build apk --debug 通过

真机手动回归

  • 第 1 页触底后可正常翻到第 2 页
  • 第 2 页未触底时不会继续翻页
  • 第 2 页触底并停留约 1 秒后,只翻 1 页
  • 等待期间若离开页尾,本次翻页会取消
  • 取消后重新拖到页尾,仍可再次触发
  • 翻页后若新页已处于页尾,后续翻页仍可按规则继续触发
  • 未再复现连续失控翻页与“已触底但不翻”的问题

影响范围与风险

  • 本次变更仅影响分页模式下 Android 选区跨页自动翻页逻辑
  • 未修改其他平台的选区处理流程
  • 改动范围集中,主要是自动翻页状态机的修正与整理

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant