Android 实现单指滑动、双指缩放照片

news2025/1/11 18:29:58

一、前景提示

最近接到一个查看大图的需求,现在图片展示还不够大,要求还要能缩小能放大还能保存照片。直接开始Google实现方式。

二、实现功能

根据查询到的结果分为两种,一个是使用手势监听来实现,第二种监听触摸事件来实现

  • 手势监听-- ScaleGestureDetector Google提供的手势监听类
  • 触摸事件–OnTouchListener 自己监听触摸事件自己实现放大缩小的逻辑

2.1 手势监听

先写布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <androidx.appcompat.widget.AppCompatImageView
    android:id="@+id/iv_example"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="Hello World!"
    android:scaleType="fitCenter"
    android:src="@drawable/muffin_7870491_1920"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

再去实现手势监听方法

class MainActivity : AppCompatActivity() {
    private lateinit var mScaleGestureDetector: ScaleGestureDetector
    private var mScaleFactor: Float = 1.0f
    private lateinit var mImageView: AppCompatImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mImageView = findViewById(R.id.iv_example)
        mScaleGestureDetector = ScaleGestureDetector(this, ScaleGestureListener())
        mImageView.setOnTouchListener { _, event ->
            mScaleGestureDetector.onTouchEvent(event)
            true
        }
    }


    private inner class ScaleGestureListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            mScaleFactor *= detector.scaleFactor
            // 限制缩放因子在0.1到10.0
            mScaleFactor = mScaleFactor.coerceIn(0.1f, 10.0f)
            mImageView.scaleX = mScaleFactor
            mImageView.scaleY = mScaleFactor
            return true
        }
    }
}

代码很简单直接使用ScaleGestureDetector去监听触摸事件,手势本质也是Google内部监听事件判断再回调给我们使用。当然我们这里不去查看源码,只看实现过程。
在使用过程中发现这种缩放并不平滑,而且响应有点慢,有延迟。猜想内部是由很多其他的判断吧。那我们只想简单一点怎么搞呢,那就是自己去判断缩放,还有实现单指滑动用手势也不太好实现的样子。所以我们试试第二种方式实现也就是触摸事件。

2.2 触摸事件

首先我们实现一下缩放,我们还是沿用上次使用onTouchListener来处理我们的触摸事件,布局文件中需要把imageView的缩放属性改为矩阵 android:scaleType=“matrix”

private var startMatrix = Matrix()
mImageView.setOnTouchListener { _, event ->
    when(event.action and MotionEvent.ACTION_MASK) {
        MotionEvent.ACTION_POINTER_DOWN -> {
            // 记录双指按下的位置和距离
            startDistance = getDistance(event)
            if (startDistance > 10f) {
                startMatrix.set(mImageView.imageMatrix)
                mode = 2
            }
            return@setOnTouchListener true
        }
    }
    true
}

没有自己处理过触摸事件的小伙伴可能会好奇MotionEvent.ACTION_MASK是什么,其实这个是为了处理多点触摸事件加的一个flag和action做and操作,我们就能处理ACTION_POINTER_DOWN和ACTION_POINTER_UP这两个多点触摸事件。
看下代码逻辑,我们先计算两个手指的距离,如果距离大于10就证明是缩放操作,设置成我们自己定义的模式,再把imageView的矩阵保存,后续对照片移动,缩放都是通过变换矩阵来实现的。
至于计算两个手指之间的距离用的勾股定理,来个示意图,大家就明白了。
在这里插入图片描述
计算如下。

 private fun getDistance(event: MotionEvent): Float {
        val dx = event.getX(0) - event.getX(1)
        val dy = event.getY(0) - event.getY(1)
        return sqrt(dx * dx + dy * dy)
    }

通过计算能得到直角边和邻边,对他们使用勾股定理就能得到斜边的值,也就是两个手指之间的距离。
有做过触摸事件监听的同学就应该知道,我们下一步要监听移动事件了也就是MotionEvent.ACTION_MOVE。

mImageView.setOnTouchListener { _, event ->
    when (event.action and MotionEvent.ACTION_MASK) {
        MotionEvent.ACTION_POINTER_DOWN -> {
            // 记录双指按下的位置和距离
            startDistance = getDistance(event)
            if (startDistance > 10f) {
                startMatrix.set(mImageView.imageMatrix)
                mode = 2
            }
            return@setOnTouchListener true
        }
        MotionEvent.ACTION_MOVE -> {
            if (mode == 2) {
                // 双指缩放
                val currentDistance = getDistance(event)
                if (currentDistance > 10f) {
                    val scale = currentDistance / startDistance
                    mImageView.imageMatrix = startMatrix.apply {
                        postScale(scale, scale, getMidX(event), getMidY(event))
                    }
                }
            }
            return@setOnTouchListener true
        }
        MotionEvent.ACTION_POINTER_UP -> {
            mode = 0
            return@setOnTouchListener true
        }

        else -> return@setOnTouchListener true

    }

}

这里在move事件中我们也需要对手指之间的距离进行计算,如果距离超过10,就开始计算缩放倍数,通过postScale进行矩阵变换。
在MotionEvent.ACTION_POINTER_UP事件中对mode值进行复位操作,毕竟还有个单指拖动操作。
如果大家把上面的代码运行过就会发现怎么图片没有居中显示,这是因为我们的缩放属性被改为矩阵也就是android:scaleType=“matrix”,那么想要图片居中显示怎么操作呢,只需要在触摸时去改变缩放属性,其他的时候不变即可。
我们把imageView恢复成android:scaleType=“fitCenter”,在onTouchListener中加入(放在when前即可)

mImageView.scaleType = ImageView.ScaleType.MATRIX

这样一开始就可以保持图片在中央了。
这样缩放功能实现了,下面实现单指拖动功能,思路很简单记录第一次按下的位置,在移动过程中计算应该需要偏移的距离,再记录下当前的位置,以便于下次计算。

private var lastX = 0f
private var lastY = 0f
mImageView.setOnTouchListener { _, event ->
    mImageView.scaleType = ImageView.ScaleType.MATRIX
    when (event.action and MotionEvent.ACTION_MASK) {
        MotionEvent.ACTION_DOWN -> {
            // 记录单指按下的位置
            lastX = event.x
            lastY = event.y
            mode = 1
            startMatrix.set(mImageView.imageMatrix)
            return@setOnTouchListener true
        }
        MotionEvent.ACTION_POINTER_DOWN -> {
            // 记录双指按下的位置和距离
            startDistance = getDistance(event)
            if (startDistance > 10f) {
                startMatrix.set(mImageView.imageMatrix)
                mode = 2
            }
            return@setOnTouchListener true
        }
        MotionEvent.ACTION_MOVE -> {
            if (mode == 1) {
                // 单指拖动
                val dx = event.x - lastX
                val dy = event.y - lastY
                mImageView.imageMatrix = startMatrix.apply {
                    postTranslate(dx, dy)
                }
                lastX = event.x
                lastY = event.y
            } else if (mode == 2) {
                // 双指缩放
                val currentDistance = getDistance(event)
                if (currentDistance > 10f) {
                    val scale = currentDistance / startDistance
                    mImageView.imageMatrix = startMatrix.apply {
                        postScale(scale, scale, getMidX(event), getMidY(event))
                    }
                }
            }
            return@setOnTouchListener true
        }
        MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
            mode = 0
            return@setOnTouchListener true
        }

        else -> return@setOnTouchListener true

    }

}

代码实现和思路一样,我们还需要在MotionEvent.ACTION_UP中复位模式,调用postTranslate进行偏移。
这样基本上功能我们都简单实现了。下面我们就需要优化了代码,如果各位跟着实现了,就会发现缩放倍数太大了导致轻轻动一下就会放很大,还有别的都是需要我们优化的。

三、功能优化

3.1 优化缩放倍数太大问题

其实这个问题和我们处理move事件有关系,熟悉Android事件机制都知道一个完整的事件流程就是down->move…move->up。知道了这个之后,再仔细看我们的代码

val currentDistance = getDistance(event)
if (currentDistance > 10f) {
	val scale = currentDistance / startDistance
	mImageView.imageMatrix = startMatrix.apply {
		postScale(scale, scale, getMidX(event), getMidY(event))
	}
}

在move事件中我们这样处理的,计算缩放倍数然后缩放,大体一看是没有什么问题的。**但是,我们的move事件不止执行一次,这就导致我们的缩放不止执行一次,每次都是在原来的基础上放大或者缩小。**所以轻轻移动倍数就会很多。
最简单的办法就是我们记录一下move过程中累计的倍数,如果到达最大值或者最小值就不让放大或者缩小了。代码如下。

if (scale > 1.0f) {
	sumScale += scale
} else {
	sumScale -= scale
}
if (sumScale >= maxScale || sumScale <= minScale) {
	return@setOnTouchListener true
}

简单但是有效的方式。其中max和min,可以自己赋值。

3.2 保持原图不缩小

实现起来也很简单,需要先定义一个变量记录当前缩放之后的倍数。大家测试就会发现,如果是放大操作那么倍数就会大于1如果是缩小倍数就会比1 小。我们就可以利用这点来处理我们的逻辑。

private var lastScaleFactor = 1f
if (scale * lastScaleFactor > 1.0f) {
	if (sumScale >= maxScale || sumScale <= minScale) {
		return@setOnTouchListener true
	}
	sumScale += scale
	mImageView.imageMatrix = startMatrix.apply {
		postScale(scale, scale, getMidX(event), getMidY(event))
		lastScaleFactor *= scale
	}
} else {
	sumScale -= scale
}

demo在这里点我点我
tips:demo好像不是放大不是很顺畅,但是在项目里用Gilde加载后很流畅,猜测是照片大小问题。但是思路是一样的问题不大。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444608.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

引入Tuning function design的自适应反步控制方法 下篇

引入Tuning function design的自适应反步控制方法 下篇 目录 引入Tuning function design的自适应反步控制方法 下篇利用Turning function解决高阶不匹配系统的控制器设计问题问题描述控制器设计小结总结上一篇文章写了如何通过推迟参数设计的方法来解决不匹配条件下的系统反步…

怎样把flv转换成mp4,4种方法轻松学

怎样把flv转换成mp4&#xff1f;当我们下载到视频格式是flv的时候&#xff0c;我们就要想着把flv转换成mp4&#xff1f;为什么这么说呢&#xff1f;因为FLV流媒体格式是一种全新的视频格式&#xff0c;全称为Flash Video。由于它形成的文件极小、加载速度极快&#xff0c;使得网…

这里有小白最关心的亚马逊防关联问题

账号的安全问题&#xff0c;很多时候和关联问题相关&#xff0c;一旦被检测到关联就会导致账号被永久封号。亚马逊更是官方出过规定&#xff0c;一个卖家只能开一个账号&#xff0c;如果同一个ip登录两个以上的账户&#xff0c;就很容易导致关联。这样讲可能会有点模糊&#xf…

微服务_微服务的架构演进之路

目录 一、前言 二、单体架构 三、分布式架构 四、微服务 五、SpringCloud 六、服务拆分 5.1服务拆分原则 5.2服务拆分示例 一、前言 微服务是一种软件开发架构风格&#xff0c;它将单个应用程序拆分成多个小型服务&#xff0c;每个服务都具有自己的特定功能。这些服务…

用TS写出20个数组方法的声明

前言 前段时间看直播看到狼叔直播驳斥”前端已死论“&#xff0c;前端死没死不知道&#xff0c;反正前端是拿不到以前那么多工资了&#xff1b;好&#xff0c;进入正题&#xff0c;狼叔在直播间提到要求前端写出20个数组上的方法&#xff0c;这确实不太简单&#xff0c;但是只…

(补)4.13每日一题

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 题目连接&#xff1a;https://leetcode.cn/problems/longest-substring-without-repeating-characters/ 解题 开始我把这个题目想简单了&#xff0c;我想的是输入一个字符串&#xff0c;从第一…

Day946.厂商定制的Android系统为什么也要解耦? -系统重构实战

厂商定制的Android系统为什么也要解耦&#xff1f; Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于厂商定制的Android系统为什么也要解耦&#xff1f;的内容。 一、Android 系统架构 AOSP&#xff0c;全称是 Android Open Source Project&#xff0c;中文译为“An…

永磁同步电机流频比(I/F)控制及Matlab/Simulink仿真分析

文章目录 前言一、流频比I/F控制原理二、永磁同步电机I/F控制系统Matlab/Simulink仿真分析2.1.仿真电路分析2.1.1 I/F控制算法2.1.2 电流环2.1.3 输出处理2.1.4 主电路 2.2 仿真结果分析 总结 前言 本章节采用流频比I/F控制方法驱动永磁同步电机的转动&#xff0c;首先分析流频…

传统机器学习(二)逻辑回归算法(一)

传统机器学习(二)逻辑回归算法(一) 1.1 算法概述 1.1.1 逻辑回归及其梯度推导 ​ 线性回归的任务&#xff0c;就是构造一个预测函数来映射输入的特征矩阵x和标签值y的线性关系&#xff0c;而构造预测函数的核心就是找出模型的参数,著名的最小二乘法就是用来求解线性回归中参…

J-Link不能连接目标MCU几点常见原因

J-Link是嵌入式软件最常用的工具之一&#xff0c;但是&#xff0c;在使用这个工具时&#xff0c;也会遇到各种各样的问题。 J-Link的连接 使用J-Link&#xff0c;首先第一步硬件连接&#xff0c;确认J-Link和PC机之间是否连接正常&#xff0c;并确认上位机能和 J-Link 建立正…

ZedGraph 绘制动态曲线

文章目录 前言&#xff1a;开发环境&#xff1a;1 下载ZedGraph 控件并设置图形界面2 功能实现3 需求升级4 小结 话不多数&#xff0c;先上一个效果图&#xff1a; 前言&#xff1a; 需要采集一些设备的数据以图表的形式展示出来&#xff0c;研究数据的走向是否平稳&#xff0…

mac Homebrew方式安装 activemq

两种方式安装 activemq 一、通过Homebrew管理安装 1. 确保homebrew可用 查看brew版本 brew -v 如果报错&#xff0c;则可能是未启用brew&#xff0c;需要安装或更新 更新并重新查看是否安装成功 brew update brew -v 2. 安装 activemq&#xff1a;下载activemq前 会先下载相…

Direct3D 12——纹理——寻址模式

可将经过常数插值或线性插值的纹理定义为一个返回向量值的函数T&#xff08;u, v&#xff09; &#xff08;r,g,b,a&#xff09;&#xff0c;即给 定纹理坐标&#xff08;u,v&#xff09;∈[0,1]^2,则上述纹理函数T将返回颜色&#xff08;r,g, b, a&#xff09;。 Direct3D允许…

MLCC周期性分析:当前时点处于周期反转前夜

MLCC是电子工业大米&#xff0c;供需波动导致行业成周期性波动 MLCC是最常用的被动元器件之一&#xff0c;终端下游涵盖消费电子、家电、汽车、通信等。在5g、汽车电子、智能硬件的推动下&#xff0c;MLCC行业需求稳步增长。供给端来看&#xff0c;中国大陆厂商合计市场份额不…

MFC加载动态gif图片文件C++语言,基于MFC的动画播放控件

MFC加载动态gif图片&#xff0c;使用VS2015环境 一、将下载的PictureEx.h和PictureEx.cpp放在工程文件的目录下&#xff0c;动态gif图片放在工程文件的res文件夹下&#xff1b;&#xff08;GIF动图下载 https://icons8.com/preloaders/en/search/move&#xff09; &#xff08…

企业级VUE前端项目各目录文件的作用

概述 本文项目是基于Vue CLI3构建工具&#xff08;基于 webpack)生成的脚手架项目。Vue CLI 现已处于维护模式&#xff0c;VUE官方推荐使用 create-vue&#xff08;基于 Vite&#xff09;构建工具。 vue-cli2.0与3.0在目录结构方面&#xff0c;有明显的不同,vue-cli3.0移除了…

Linux性能优化实战

1. TCP/IP报文详解 TCP/IP 定义了电子设备如何连入因特网&#xff0c;以及数据如何在它们之间传输的标准。协议采用了4层的层级结构&#xff0c;每一层都呼叫它的下一层所提供的协议来完成自己的需求。TCP负责发现传输的问题&#xff0c;一有问题就发出信号&#xff0c;要求重…

根据cadence设计图学习硬件知识day04了解一些芯片

1.PI3PCIE3212 &#xff08;双向信道多路复用器/多路分解器开关&#xff09; PI3PCIE3212是PCIe Gen3.0、8Gbps、4对2差分&#xff0c;PCI ExpressR 3.0性能&#xff0c;8.0Gbps 双向信道多路复用器/多路分解器开关。由于其低的位对位偏斜&#xff0c;高的通道对通道噪声隔离…

邂逅Node.js开发

目录&#xff1a; 1 Node.js是什么&#xff1f; 2 Node的应用场景 3 Node安装和管理 4 JavaScript代码执行 5 Node的输入和输出 6 Node的全局对象 node命令是可以直接运行js脚本的,在某文件夹底下只要有js文件&#xff0c;就可以通过命令提示符运行该js文件。格式是 &…

简单聊聊煤炭行业的数字化和可持续发展

煤在普通人的心目中是一种能引起复杂感情的东西。我们喜欢它在冬天给我们带来温暖&#xff0c;我们不喜欢它因为它黢黑黢黑的&#xff0c;沾在身上特别黑&#xff0c;看起来脏兮兮的。在笔者的记忆中&#xff0c;小时候煤可是生活的必需品。 小时候在冬天的河北必须要生炉子&a…