Android 滑动翻页失效
Android 滑动翻页失效排查记录
日期:2026-06-26
涉及文件:ReaderPage.xaml、ReaderPage.xaml.cs
平台:.NET MAUI 9.0.120 / net9.0-android(SDK 35,TargetSdk 34,Redmi K70 Pro)
一、现象
手机端阅读界面左右滑动无法翻页(”试了一天没效果”)。但底部工具栏的「◀ 上页 / 下页 ▶」按钮翻页正常。
关键信号:按钮和滑动绑的是同一个
NextPageCommand/PreviousPageCommand。按钮能用 → Command 逻辑没问题;滑动不行 → 问题在手势识别层。
二、原本的实现
翻页手势写在 ReaderPage.xaml,用的是 SwipeGestureRecognizer,挂在 canvas 区域的外层 Grid 上:
1 | |
而 NextPage / PreviousPage 的命令实现在 ReaderViewModel.cs 里(基于“前一页/当前页/后一页”三页缓存 + 偏移推进),逻辑健全、可被按钮正常触发。
三、排查过程
1. 第一层根因:SwipeGestureRecognizer 被 GraphicsView 吞掉
阅读界面是自定义绘制:Grid 里只有一个 GraphicsView(填满整个 Grid),由 ReaderDrawable 负责画字。
问题:GraphicsView 在 Android 上的平台控件会消费(return true)所有触摸事件,用来触发它自己那套交互回调(StartInteraction 等)。一旦触摸事件被它消费掉,挂在父容器上的 SwipeGestureRecognizer 就再也收不到手势 —— 于是滑动毫无反应。
这是 MAUI 的已知交互问题(dotnet/maui#18417:GraphicsView 的 interactions 与 gestures 不能同时工作,Android 上尤其明显)。
结论:SwipeGestureRecognizer 挂在父 Grid 上这条路在 Android 走不通。要换成 GraphicsView 自身的交互事件 —— 那是它消费触摸后保证会触发的回调,是 Android 上唯一可靠的触摸入口。
2. 第一次修复尝试(踩坑):用了 TouchEventArgs.Points
把手势改成交互事件,code-behind 订阅:
1 | |
然后在处理函数里按记忆取触摸点坐标:
1 | |
编译失败:
1 | |
3. Points 到底存不存在 —— 一连串环境踩坑
我的第一反应是 using 命名空间冲突(解析到了另一个同名 TouchEventArgs)。于是把签名改成全限定名再编译:
1 | |
仍然报 Points 不存在。全限定名排除了命名空间歧义 → 结论变成:MAUI 9.0.120 的 TouchEventArgs 真的没有 Points 属性,文档(net-maui-10.0)和实际安装版本对不上。
于是想去实际程序集里读真实成员。但这个环境一路不顺:
- 想用
MetadataReader精确读:写了 PowerShell 脚本,但本机是 Windows PowerShell 5.1,没有内置System.Reflection.Metadata(Add-Type报ASSEMBLY_NOT_FOUND)。 - 想换 PowerShell 7(
pwsh):没装,command -v pwsh→NO_PWSH。 - 想建个最小 net9.0 控制台项目用
MetadataReader:被否(不想为排查建一堆项目文件)。
最后用最朴素的 grep 直接抠 dll 元数据里的标识符(零依赖,git bash 自带):
1 | |
输出里出现了:
1 | |
Points 确实在 dll 里(属于别的类型),而 TouchEventArgs 自身的触摸点集合属性是 Touches。再交叉查 TouchEventArgs 官方文档 得到确认:
MAUI 9.0:
Touches返回PointF[];构造函数TouchEventArgs(PointF[] touches, bool isInsideBounds)。⚠️
Touches在 9.0 是PointF[](用.Length/[0]),10.0 起改为IReadOnlyList<PointF>(用.Count)。本项目锁 9.0.120,用数组写法。
4. 第二层隐患:GraphicsView 在 Android 的默认尺寸
在查 API 的同时,搜到一条关键经验(SO 79018287):
GraphicsView在 Android 上默认WidthRequest/HeightRequest为 -1,会导致它不渲染、也不接收触摸事件,StartInteraction不触发。
也就是说,即使把手势方式改对了,如果 GraphicsView 尺寸不对,Android 上照样收不到触摸。必须确保它占满 canvas 行、有有效尺寸。
四、最终修复
ReaderPage.xaml.cs —— 用交互事件判定方向
完整文件,核心:
1 | |
判定逻辑要点:
StartInteraction记下按下点,DragInteraction持续更新“最后位置”(比只看EndInteraction的e.Touches更稳,避免抬起时坐标丢失)。- 抬起时算总位移
dx/dy。 - 三个过滤:位移 < 50px(轻点)忽略;水平位移 < 垂直位移(主要是纵向拖动)忽略;最后才按
dx正负定方向。 - 复用已有的
NextPageCommand/PreviousPageCommand,按钮和滑动行为完全一致。
ReaderPage.xaml —— 移除 Swipe,确保 GraphicsView 填满
1 | |
HorizontalOptions="Fill" VerticalOptions="Fill" 解决第二层隐患,保证 GraphicsView 在 Android 上占满 canvas 行、能正常接收触摸。
五、构建与产物
构建命令(Release / Android):
1 | |
⚠️ 坑:Android 的
BuildApk任务把临时文件 rename 成最终 APK 时,若旧 APK 被 .NET build-server / VS 锁住,会报XABBA7000: Xamarin.Tools.Zip.ZipException: Renaming temporary file failed: Permission denied。
编译本身已成功(无error CS),只是打包阶段卡住。
解法:先dotnet build-server shutdown释放锁,删掉bin/Release/net9.0-android/*.apk,再重建。
产物:ReadingTool/bin/Release/net9.0-android/com.readingtool.app-Signed.apk(~55.9 MB,签名沿用默认 debug keystore,可覆盖安装)。
六、关键要点 / 经验总结
MAUI 触摸事件层级:
SwipeGestureRecognizer/TapGestureRecognizer挂在父容器上,子元素若是GraphicsView会被吞触摸。给GraphicsView自己用StartInteraction/DragInteraction/EndInteraction交互事件,是 Android 上唯一可靠的触摸来源。API 以实际安装的 dll 为准:MAUI 不同大版本(9.0 vs 10.0)的
TouchEventArgs属性名从Points变成了Touches(且类型从PointF[]变IReadOnlyList<PointF>)。文档可能滞后,以 NuGet 包里的 dll 为准。没好工具时,grep dll 元数据能救命:环境里
pwsh、ildasm、MetadataReader(5.1 内置版)都不可用时,1
grep -aoE '[A-Za-z][A-Za-z0-9_]+' "<package>/lib/<tfm>/<asm>.dll" | grep <keyword> | sort -u能快速抠出类型/方法/属性名,足够判断 API 是否存在。(局限:看不出成员归属哪个类型,必要时再用
MetadataReader精读。)GraphicsView 在 Android 的尺寸坑:默认
-1会导致不收触摸。给Fill或显式尺寸。Build-server 文件锁:Android Release 重新打包 APK 前若报
Permission denied,dotnet build-server shutdown+ 删旧 APK 再重建。