ZoomImageView.b4xlib 里的双指缩放 + 单指拖动核心源码,看懂这个库是怎么工作的!
一句话总结功能
这段代码 = 安卓原生触摸事件封装实现:
- 单指:拖动图片
- 双指:缩放图片
- 点击记录
- 不拦截、不漏处理触摸事件
第一段:TouchListener_Event(所有触摸的入口)
Delphi
Private Sub TouchListener_Event (MethodName As String, Args() As Object) As Object
' 只处理onTouch事件
if MethodName <> "onTouch" Then Return Null
' 获取安卓原生触摸对象MotionEvent
Dim MotionEvent As JavaObject = Args(1)
' ==========================================
' 【单指触摸】才执行:拖动、点击计时
' ==========================================
If 1 = MotionEvent.RunMethod("getPointerCount", Null) Then
Dim action As Int = MotionEvent.RunMethod("getAction", Null)
' 手指按下 → 记录点击开始时间
If action = 0 Then ClickStart = DateTime.Now
' 把事件交给pnl_Touch处理(单指移动/抬起)
pnl_Touch(action, MotionEvent.RunMethod("getX", Null), MotionEvent.RunMethod("getY", Null))
End If
' ==========================================
' 【所有触摸】都传给缩放检测器(双指缩放用)
' ==========================================
ScaleDetector.RunMethod("onTouchEvent", Array(MotionEvent))
Return True ' 消费事件,不往下传递
End Sub
这段的作用(超直白)
- 拦截所有屏幕触摸
- 判断是不是单指
- 是 → 记录点击时间 + 交给拖动逻辑
- 不管单指双指,都传给缩放检测器
- 返回 True → 事件被消费,不穿透到下层
第二段:ScaleListener_Event(双指缩放逻辑)
Kotlin
Private Sub ScaleListener_Event (MethodName As String, Args() As Object) As Object
Dim ScaleGestureDetector As JavaObject = Args(0)
' 没有缩放动作 → 直接返回
If ScaleGestureDetector.RunMethod("isInProgress", Null) = False Then
Return True
End If
' 正在缩放 → 禁止单指拖动
TouchDown = False
' 获取双指的中心点(缩放围绕这个点)
Dim x As Float = ScaleGestureDetector.RunMethod("getFocusX", Null)
Dim y As Float = ScaleGestureDetector.RunMethod("getFocusY", Null)
' 获取双指当前距离
Dim currentspan As Float = ScaleGestureDetector.RunMethod("getCurrentSpan", Null)
' ==========================================
' 缩放开始 → 记录初始距离
' ==========================================
If MethodName = "onScaleBegin" Then
PrevSpan = currentspan ' 保存双指初始间距
pnl_Touch(pnl.TOUCH_ACTION_DOWN, x, y) ' 模拟按下
Return True
' ==========================================
' 缩放结束 / 无效间距 → 退出
' ==========================================
Else If MethodName = "onScaleEnd" Or currentspan = 0 Then
Return True
End If
' ==========================================
' 计算缩放倍数(核心公式)
' ==========================================
' 当前距离 / 上次距离 → 比例
' 再平方 → 让缩放更灵敏
Dim delta As Float = Power(currentspan / PrevSpan, 2)
' 更新上一次距离
PrevSpan = currentspan
' 触发真正的缩放方法
ZoomChanged(x, y, delta)
Return True
End Sub
缩放核心逻辑(最关键)
- 双指开始缩放
- 记录双指初始距离
PrevSpan
- 记录双指初始距离
- 双指移动
- 计算
当前距离 / 初始距离 - 平方 → 让缩放手感更灵敏(不是线性缩放)
- 计算
- 把中心点 (x,y) + 缩放比例 (delta) 传给 ZoomChanged
- ZoomChanged 才是真正放大 / 缩小图片的方法
两个方法的分工(最清晰)
| 方法 | 作用 | 处理对象 |
|---|---|---|
| TouchListener | 单指拖动、点击、分发事件 | 单指 |
| ScaleListener | 双指缩放、计算缩放比例、中心点 | 双指 |
你最关心的 3 个问题(直接给答案)
1. 为什么单指拖动、双指缩放不会冲突?
- 单指 → 走
TouchListener - 双指 → 走
ScaleListener,且会设置TouchDown = False关闭拖动 - 系统自动区分手指数量,完美隔离
2. 缩放公式为什么用 Power (...,2)?
delta = Power(currentspan / PrevSpan, 2)
- 让缩放更灵敏
- 手指稍微分开一点,图片放大得更明显
- 是图片缩放库的经典优化
3. 事件流程是什么?
触摸屏幕 → TouchListener 接收
- 单指 → pnl_Touch(拖动)
- 双指 → ScaleDetector → ScaleListener(缩放)→ 最终调用 ZoomChanged 改变图片大小
总结(极简版)
- TouchListener :处理单指拖动、点击
- ScaleListener :处理双指缩放 ,计算缩放中心点 和缩放倍数
- 两个事件完全不冲突
- 缩放用了平方公式让手感更顺滑
如果你需要,我还能帮你:✅ 把这个库改成支持双击放大 / 缩小 ✅ 改成最大 / 最小缩放限制 ✅ 改成惯性滑动✅ 解析完整的 ZoomChanged 图片缩放绘制逻辑
我继续给你逐行、超通俗、无死角 解析这两段核心代码:ZoomChanged(缩放) + pnl_Touch(拖动 / 点击) 这是整个 ZoomImageView 库的灵魂,看完你就完全懂这个控件怎么实现图片缩放拖动了。
第一段:Private Sub ZoomChanged (x As Int, y As Int, ZoomDelta As Float)
作用:根据双指缩放,计算并移动 / 放大图片面板 一句话:以双指中心为锚点,放大 / 缩小图片,且锚点不动
python
Private Sub ZoomChanged (x As Int, y As Int, ZoomDelta As Float)
' 1. 计算触摸点相对于图片面板左上角的偏移量
Dim ivx As Float = x - pnlBackground.Left
Dim ivy As Float = y - pnlBackground.Top
' 2. 限制最小缩放(不能缩得比容器还小)
ZoomDelta = Max(ZoomDelta, mBase.Width / pnlBackground.Width)
' 3. 核心:以触摸点为中心,缩放+移动图片面板
' 公式原理:保证触摸点(x,y)在缩放前后始终对准同一个位置
pnlBackground.SetLayoutAnimated(0,
x - ivx * ZoomDelta, ' 新 Left
y - ivy * ZoomDelta, ' 新 Top
pnlBackground.Width * ZoomDelta, ' 新宽度
pnlBackground.Height * ZoomDelta) ' 新高度
' 4. 刷新图片布局(让图片跟着面板走)
SetImageViewLayout
End Sub
超通俗解释(关键!)
- ivx, ivy 你双指捏的点,相对于图片左上角的距离。
- x - ivx * ZoomDelta 这行是缩放锚点算法 :缩放时,你手指按住的地方永远不会跑,这就是所有图片查看器的效果。
- ZoomDelta 限制防止图片缩到比屏幕还小。
- **SetLayoutAnimated(0,...)**瞬间改变位置大小,无动画,保证流畅。
第二段:Private Sub pnl_Touch (Action As Int, X1 As Float, Y1 As Float)
作用:单指拖动 + 点击事件判断 + 边界限制(图片不会被拖飞出屏幕) 这是拖动逻辑 + 点击判断的完整实现。
javascript
Private Sub pnl_Touch (Action As Int, X1 As Float, Y1 As Float)
' =====================
' 手指按下:记录起始位置
' =====================
If Action = pnl.TOUCH_ACTION_DOWN Or TouchDown = False Then
StartLeft = pnlBackground.Left ' 记录图片初始左坐标
StartTop = pnlBackground.Top ' 记录图片初始上坐标
StartX = X1 ' 记录手指初始X
StartY = Y1 ' 记录手指初始Y
TouchDown = True ' 标记正在触摸
If xui.IsB4A = False Then ClickStart = DateTime.Now
' =====================
' 手指移动:拖动图片
' =====================
Else If Action = pnl.TOUCH_ACTION_MOVE And TouchDown Then
' 计算新Left,并限制右边界(不能拖到右边空白太多)
pnlBackground.Left = Min(0.5 * mBase.Width, StartLeft + 1.2 * (X1 - StartX))
' 限制左边界(不能拖到左边空白太多)
pnlBackground.Left = Max(-(pnlBackground.Width - 0.5 * mBase.Width), pnlBackground.Left)
' 同上,限制Top上下边界
pnlBackground.Top = Min(0.5 * mBase.Height, StartTop + 1.2 * (Y1 - StartY))
pnlBackground.Top = Max(-(pnlBackground.Height - 0.5 * mBase.Height), pnlBackground.Top)
SetImageViewLayout ' 刷新
' =====================
' 手指抬起:判断点击
' =====================
Else if Action = pnl.TOUCH_ACTION_UP Then
TouchDown = False
' 如果按下+抬起时间很短 → 判定为点击
If DateTime.Now - ClickStart < ClickThreshold And DisableClickEvent = False Then
If xui.SubExists(mCallBack, mEventName & "_Click", 0) Then
CallSub(mCallBack, mEventName & "_Click") ' 触发点击事件
End If
End If
End If
End Sub
关键细节解析(你一看就懂)
1. 拖动边界限制(最核心)
pnlBackground.Left = Min(...)
pnlBackground.Left = Max(...)
作用:
- 图片不能拖到右边露出大片空白
- 图片不能拖到左边露出大片空白
- 上下同理这就是专业图片控件的边界回弹效果。
2. 1.2 倍灵敏度
1.2 * (X1 - StartX)
让拖动比手指移动快一点点,手感更顺滑。
3. 点击判断逻辑
- 按下:记录时间
- 抬起:计算时间差
- < 200ms(典型值) → 判定点击
- > 200ms → 判定拖动,不触发点击
完美解决拖动时误触发点击的问题。
整个库的完整流程(终极总结)
我给你串起来,你会彻底通透:
- 触摸屏幕 → 交给
TouchListener_Event - 单指 → 走
pnl_Touch→ 拖动 + 边界限制 + 点击判断 - 双指 → 走
ScaleListener_Event→ 计算缩放比例→ 调用ZoomChanged - ZoomChanged→ 以双指中心为锚点缩放图片→ 图片放大 / 缩小,且手指点哪就以哪为中心
- 最后都调用 →
SetImageViewLayout刷新图片显示
最关键的 3 个知识点(面试 / 改代码必用)
- ZoomChanged 里的锚点算法 让图片缩放时手指点的位置不偏移,是行业标准写法。
- pnl_Touch 里的 Min/Max 边界限制 保证图片不会被拖到看不见。
- 按下时间差判断点击 解决拖动误触点击的 BUG。
你要我继续帮你解析哪个?
我可以马上给你解析:
- SetImageViewLayout(最终刷新图片的方法)
- 如何限制最大缩放倍数
- 如何添加双击放大
- 如何调整缩放灵敏度
- 如何修复边界回弹更丝滑
你说一声,我直接给你完整可运行代码!