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

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

相关推荐
无限的鲜花13 分钟前
反射(原创推荐)
java·开发语言
IT二叔28 分钟前
Java项目部署-03-teamcity-cicd-docker镜像流水线方式部署
java·ci/cd·持续部署
前端之虎陈随易42 分钟前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·vue.js·人工智能·typescript·node.js
一路向北he44 分钟前
字节钢铁军团--“提供情境,而非控制”
java·开发语言·前端
kyriewen1 小时前
豆包和千问同时关了智能体,我用它们搭的 3 个自动化全废了——迁移方案整理
前端·javascript·ai编程
超级数据查看器1 小时前
超级数据查看器 v10.0 发布
java·大数据·数据库·sqlite·安卓
前端一小卒1 小时前
我用 TypeScript 从零手写了一个 Claude Code,然后发现它的核心只有 30 行
前端·agent
GitLqr2 小时前
Flutter 3.44 插件内置 Kotlin (KGP) 双向兼容适配指南
android·flutter·dart
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第50篇:阻塞队列与并发容器(2026版)
java·面试题·java进阶·blockingqueue·并发容器·集合源码·java100天进阶
ai_coder_ai3 小时前
编写自动化脚本,在自己后端服务中使用Open Api进行设备相关操作
java·运维·自动化