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

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

相关推荐
阿维的博客日记6 分钟前
Windows自由切换jdk版本
java·windows
摇滚侠8 分钟前
MyBatis 入门到项目实战 MyBatis 逆向工程 62
java·开发语言·mybatis
Hooray8 分钟前
前端暗黑模式的适配艺术
前端·vue.js·视觉设计
恋猫de小郭9 分钟前
解析华为 DevEco Code 和小米 MiMo Code,都基于 OpenCode ,有什么区别?
android·前端·ios
IT_陈寒11 分钟前
Vue的响应式让我原地裂开,你们也有这情况吗
前端·人工智能·后端
ch.ju13 分钟前
Java Programming Chapter 4——Multi-level inheritance
java·开发语言
yuezhilangniao16 分钟前
2026删除K8s命名空间 卡 Terminating 的 ns
java·容器·kubernetes
2501_9327502621 分钟前
Android 控件与布局全面解析
android
GZ_TOGOGO25 分钟前
Spring AI Alibaba 格式化输出
java·人工智能·spring
问心无愧051328 分钟前
ctfshow web入门114
android·前端·笔记