b4a用VB语言开发安卓APP-图片缩放库ZoomImageView讲解-双指缩放 + 单指拖动核心源码

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

这段的作用(超直白)

  1. 拦截所有屏幕触摸
  2. 判断是不是单指
    • 是 → 记录点击时间 + 交给拖动逻辑
  3. 不管单指双指,都传给缩放检测器
  4. 返回 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

缩放核心逻辑(最关键)

  1. 双指开始缩放
    • 记录双指初始距离 PrevSpan
  2. 双指移动
    • 计算 当前距离 / 初始距离
    • 平方 → 让缩放手感更灵敏(不是线性缩放)
  3. 把中心点 (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

超通俗解释(关键!)

  1. ivx, ivy 你双指捏的点,相对于图片左上角的距离
  2. x - ivx * ZoomDelta 这行是缩放锚点算法 :缩放时,你手指按住的地方永远不会跑,这就是所有图片查看器的效果。
  3. ZoomDelta 限制防止图片缩到比屏幕还小。
  4. **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 → 判定拖动,不触发点击

完美解决拖动时误触发点击的问题。


整个库的完整流程(终极总结)

我给你串起来,你会彻底通透:

  1. 触摸屏幕 → 交给 TouchListener_Event
  2. 单指 → 走 pnl_Touch → 拖动 + 边界限制 + 点击判断
  3. 双指 → 走 ScaleListener_Event → 计算缩放比例→ 调用 ZoomChanged
  4. ZoomChanged→ 以双指中心为锚点缩放图片→ 图片放大 / 缩小,且手指点哪就以哪为中心
  5. 最后都调用SetImageViewLayout 刷新图片显示

最关键的 3 个知识点(面试 / 改代码必用)

  1. ZoomChanged 里的锚点算法 让图片缩放时手指点的位置不偏移,是行业标准写法。
  2. pnl_Touch 里的 Min/Max 边界限制 保证图片不会被拖到看不见
  3. 按下时间差判断点击 解决拖动误触点击的 BUG。

你要我继续帮你解析哪个?

我可以马上给你解析:

  • SetImageViewLayout(最终刷新图片的方法)
  • 如何限制最大缩放倍数
  • 如何添加双击放大
  • 如何调整缩放灵敏度
  • 如何修复边界回弹更丝滑

你说一声,我直接给你完整可运行代码!

相关推荐
Mike_jia11 小时前
ShipShipShip:构建你的专属产品发布中心与社区互动平台
前端
a11177611 小时前
闪卡网页 第五人格 html 开源
前端·html
Sinsa_SI11 小时前
2026算法应用主题赛初赛-小学4-6组(c++)试卷(含答案+详细解析)
java·c++·算法
曦夜日长11 小时前
Linux系统篇,开发工具(六):文件的编译配置、调试的理解、cgdb和gdb的操作使用
java·linux·php
高级c11 小时前
10分钟上手昇腾 NPU 算子开发入门与实战
java·jvm·spring
路远_611 小时前
Java 后端开发者如何理解大模型应用架构
java·架构·大模型·agent
冴羽yayujs11 小时前
前端周报:Google I/O 发布 Agentic Web、TypeScript 6.0 正式版、npm 安全新策略
前端·javascript·前端开发·前端学习·前端周报
IT_陈寒11 小时前
Vite踩坑实录:静态资源加载把我搞懵了
前端·人工智能·后端
彦为君11 小时前
Spring定时任务开发指南(动态实现)
java·开发语言·后端·python·spring·wpf