当前位置: 首页 > news >正文

聊聊如何实现Android 放大镜效果

一、前言

很久没有更新Android 原生技术内容了,前些年一直在做跨端方向开发,最近换工作用重新回到原生技术,又回到了熟悉但有些生疏的环境,真是感慨万分。
近期也是因为准备做地图交互相关的需求,功能非常复杂,尤其是交互部分,不过再复杂的交互,只要一点点将它拆解,分而治之,问题还是可以解决,就比如接下来要做的放大镜功能。

二、功能设计

该功能的场景是在操作地图时,对于边缘的精细化操作(像素级别的)需要在放大镜里显示正在操作的地图区域。比如
在这里插入图片描述

如上图,我在操作地图里的内容时,可以在左上角看到我手指操作的内容。
这里需要支持如下几点:

  • 支持设置放大镜的放大倍数
  • 支持实时更新放大镜里的内容,手指操作地图时,放大镜要里的内容要一直显示(刷新),手指松开时,放大镜里的内容要清空。
  • 放大镜是个圆形。

三、功能实现

分下下来,放大镜的功能实现,拆解下来可以分成两部分来实现。

  • 绘制圆形容器。
  • 绘制手指操作的区域内容。
    接下来我们逐个实现

3.1 绘制圆形容器

关于绘制圆形容器,这里会涉及到Path 路径知识,因为正常的图形都是方形,因此需要图形裁切才行,这里会涉及到canvas.clipPath()API。
具体代码如下:

	private Paint paint;// 用于裁剪成圆形private Path clipPath;private void init() {paint = new Paint(Paint.ANTI_ALIAS_FLAG);clipPath = new Path();}@Overrideprotected void onDraw(@NonNull Canvas canvas) {super.onDraw(canvas);// 获取 MagnifierView 的宽高int viewWidth = getWidth();int viewHeight = getHeight();// 计算圆形半径float radius = Math.min(viewWidth, viewHeight) / 2f;clipPath.reset();// 绘制圆形路径clipPath.addCircle(viewHeight / 2f, (float) viewHeight /2,radius,Path.Direction.CW);// 裁切圆形画布canvas.clipPath(clipPath);}

3.2 绘制手指操作区域

放大镜本质就是放大图片,而图片在Android 里面就是Bitmap。这里有个问题。

3.2.1 如何获取当前手指操作View 的Bitmap呢?

答案是用getDrawingCache()。
具体实现如下:

setDrawingCacheEnabled(true);
Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
setDrawingCacheEnabled(false);

当然,这里里的Bitmap是一整个View的,我们仅想放大手指操作(比如点击、滑动)区域的Bitmap,这就需要手指所在的坐标区域。
我们可以自定义一个View,然后重写它的onTouchEvent方法,通过event.getX(), event.getY()获取手指实时操作的坐标,然后传给放大镜View(比如我们这次自定义的放大镜MagnifierView)里面,连同前面的Bitmap一起传过去。这里可以搞一个回调接口。

	public interface MagnifierListener {// 传递放大镜内容void onMagnify(Bitmap bitmap, float x, float y);// 隐藏放大镜void onHideMagnifier();}

3.2.2 绘制操作区域

得到了要绘制的内容Bitmap,接下来就是绘制出来。
绘制Bitmap很简单,就是调用下面的API:

canvas.drawBitmap(bitmap, srcRect, dstRect, paint);

复杂的是计算放大内容的源区域,也就是srcRect。
这里先解释下dstRect,它是定义目标矩形(也就是放大镜自身大小)实现如下:

RectF dstRect = new RectF(0, 0, viewWidth, viewHeight);

接下来我们开始设置srcRect。写到这里我们好像漏了一个关键参数“放大倍数”。

// 放大倍数
private static final float SCALE_FACTOR = 2.0f;

因为我们要显示的区域和放大倍数有直接关联。srcRect 是一个Rect 对象,它里面有四个参数(左上右下),相当于当前显示区域范围,计算公式如下:

// 计算放大内容的源区域
float srcLeft = focusX - (viewWidth / SCALE_FACTOR) / 2;
float srcTop = focusY - (viewHeight / SCALE_FACTOR) / 2;
float srcRight = focusX + (viewWidth / SCALE_FACTOR) / 2;
float srcBottom = focusY + (viewHeight / SCALE_FACTOR) / 2;

先来解释下上面的公式意义,focusX对应的就是手指操作的x坐标,即event.getX(),focusY 同理则是event.getY()。
SCALE_FACTOR 是放大倍数。
因为我们想要显示的是“手指为中心,显示区域大小是当前放大镜的面积”,因此需要(viewWidth / SCALE_FACTOR) / 2,这里用focusX是确定左边界的位置。后面参数计算和前面差不多,这里我不重复解释。不过写到这里还不算完成。为了防止越界,这里还需要做一次矫正防护:

// 防止越界
srcLeft = Math.max(0, srcLeft);
srcTop = Math.max(0, srcTop);
srcRight = Math.min(bitmap.getWidth(), srcRight);
srcBottom = Math.min(bitmap.getHeight(), srcBottom);

最后的放大区域代码如下:

		// 计算放大内容的源区域float srcLeft = focusX - (viewWidth / SCALE_FACTOR) / 2;float srcTop = focusY - (viewHeight / SCALE_FACTOR) / 2;float srcRight = focusX + (viewWidth / SCALE_FACTOR) / 2;float srcBottom = focusY + (viewHeight / SCALE_FACTOR) / 2;// 防止越界srcLeft = Math.max(0, srcLeft);srcTop = Math.max(0, srcTop);srcRight = Math.min(bitmap.getWidth(), srcRight);srcBottom = Math.min(bitmap.getHeight(), srcBottom);// 定义源矩形(放大的内容区域)Rect srcRect = new Rect((int) srcLeft,(int) srcTop,(int) srcRight,(int) srcBottom);

3.3 小结

这里附上这个功能的完整代码:

/*** author      : coffer* date        : 2025/1/11* description : 放大镜视图*/
public class MagnifierView extends View {private float focusX = 0f; // 放大内容的中心点Xprivate float focusY = 0f; // 放大内容的中心点Y// 要放大的内容private Bitmap bitmap;private Paint paint;// 用于裁剪成圆形private Path clipPath;// 放大倍数private static final float SCALE_FACTOR = 2.0f;// 是否可以显示private boolean isVisible = false;public MagnifierView(Context context) {super(context);init();}public MagnifierView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);init();}public MagnifierView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init();}private void init() {paint = new Paint(Paint.ANTI_ALIAS_FLAG);clipPath = new Path();}/*** 设置放大镜的内容和位置*/public void setFocus(Bitmap bitmap, float x, float y) {this.bitmap = bitmap;this.focusX = x;this.focusY = y;this.isVisible = true;invalidate(); // 触发重绘}/*** 隐藏放大镜*/public void hide() {this.isVisible = false;invalidate();}@Overrideprotected void onDraw(@NonNull Canvas canvas) {super.onDraw(canvas);if (!isVisible || bitmap == null) {return;}// 获取 MagnifierView 的宽高int viewWidth = getWidth();int viewHeight = getHeight();// 计算圆形半径float radius = Math.min(viewWidth, viewHeight) / 2f;clipPath.reset();clipPath.addCircle(viewHeight / 2f, (float) viewHeight /2,radius,Path.Direction.CCW);canvas.clipPath(clipPath);// 计算放大内容的源区域float srcLeft = focusX - (viewWidth / SCALE_FACTOR) / 2;float srcTop = focusY - (viewHeight / SCALE_FACTOR) / 2;float srcRight = focusX + (viewWidth / SCALE_FACTOR) / 2;float srcBottom = focusY + (viewHeight / SCALE_FACTOR) / 2;// 防止越界srcLeft = Math.max(0, srcLeft);srcTop = Math.max(0, srcTop);srcRight = Math.min(bitmap.getWidth(), srcRight);srcBottom = Math.min(bitmap.getHeight(), srcBottom);// 定义源矩形(放大的内容区域)Rect srcRect = new Rect((int) srcLeft,(int) srcTop,(int) srcRight,(int) srcBottom);// 定义目标矩形(放大镜自身大小)RectF dstRect = new RectF(0, 0, viewWidth, viewHeight);// 绘制放大内容canvas.drawBitmap(bitmap, srcRect, dstRect, paint);}
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"android:shape="oval"><stroke android:color="#abf4db"android:width="1dp"/></shape>

上面的这个drawable 可有可无,这里这是方便测试用。

四、总结

一开始接到这个需求的时候我是真的有些懵,因为以前从来没有写过。不过后来在仔细拆分问题后,觉的还是可以实现的。这里我要着重感谢ChatGPT,它给了我很大的帮助,就比如这个功能的实现,它就给了我思路,就像老师一样,能从它身上学到很多技能。
但是,请注意!不能完全依赖它,即使AI 帮你做了,你也必须要深入搞懂背后的原理,要把知识吸收到自己大脑思维中,否则未来你将会被AI取代!!!

相关文章:

聊聊如何实现Android 放大镜效果

一、前言 很久没有更新Android 原生技术内容了&#xff0c;前些年一直在做跨端方向开发&#xff0c;最近换工作用重新回到原生技术&#xff0c;又回到了熟悉但有些生疏的环境&#xff0c;真是感慨万分。 近期也是因为准备做地图交互相关的需求&#xff0c;功能非常复杂&#x…...

linux 安装mysql5.6

下载mysql安装包 https://dev.mysql.com/downloads/mysql/5.6.html卸载系统自带的mariadb [rootgpap-prod-3 ~]# rpm -qa| grep mariadb mariadb-libs-5.5.68-1.el7.x86_64 [rootgpap-prod-3 ~]# rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64 warning: /etc/my.cnf sav…...

【Vue3 入门到实战】3. ref 和 reactive区别和适用场景

目录 ​编辑 1. ref 部分 1.1 ref定义基本数据类型 1.2 ref 定义引用数据类型 2. reactive 函数 3. ref 和 reactive 对比 3.1 原理 3.2 区别 3.3 使用原则 在 Vue 3 中 ref 和 reactive 是用于创建响应式数据的两个核心函数。它们都属于 Composition API 的一部分&…...

edge浏览器恢复旧版滚动条

1、地址栏输入edge://flags 2、搜索Fluent scrollbars.&#xff0c;选择disabled&#xff0c;重启即可...

Flink(十):DataStream API (七) 状态

1. 状态的定义 在 Apache Flink 中&#xff0c;状态&#xff08;State&#xff09; 是指在数据流处理过程中需要持久化和追踪的中间数据&#xff0c;它允许 Flink 在处理事件时保持上下文信息&#xff0c;从而支持复杂的流式计算任务&#xff0c;如聚合、窗口计算、联接等。状…...

AWTK fscript 中的 输入/出流 扩展函数

fscript 是 AWTK 内置的脚本引擎&#xff0c;开发者可以在 UI XML 文件中直接嵌入 fscript 脚本&#xff0c;提高开发效率。本文介绍一下 fscript 中的 iostream 扩展函数 1.iostream_get_istream 获取输入流对象。 原型 iostream_get_istream(iostream) > object示例 va…...

C# OpenCvSharp 部署3D人脸重建3DDFA-V3

目录 说明 效果 模型信息 landmark.onnx net_recon.onnx net_recon_mbnet.onnx retinaface_resnet50.onnx 项目 代码 下载 参考 C# OpenCvSharp 部署3D人脸重建3DDFA-V3 说明 地址&#xff1a;https://github.com/wang-zidu/3DDFA-V3 3DDFA_V3 uses the geometri…...

【人工智能】:搭建本地AI服务——Ollama、LobeChat和Go语言的全方位实践指南

前言 随着自然语言处理&#xff08;NLP&#xff09;技术的快速发展&#xff0c;越来越多的企业和个人开发者寻求在本地环境中运行大型语言模型&#xff08;LLM&#xff09;&#xff0c;以确保数据隐私和提高响应速度。Ollama 作为一个强大的本地运行框架&#xff0c;支持多种先…...

数据结构——堆(介绍,堆的基本操作、堆排序)

我是一个计算机专业研0的学生卡蒙Camel&#x1f42b;&#x1f42b;&#x1f42b;&#xff08;刚保研&#xff09; 记录每天学习过程&#xff08;主要学习Java、python、人工智能&#xff09;&#xff0c;总结知识点&#xff08;内容来自&#xff1a;自我总结网上借鉴&#xff0…...

Excel中函数ABS( )的用法

Excel中函数ABS的用法 1. 函数详细讲解1.1 函数解释1.2 使用格式1.3 参数定义1.4 要点 2. 实用演示示例3. 注意事项4. 文档下载5. 其他文章6. 获取全部Excel练习素材快来试试吧&#x1f970; 函数练习素材&#x1f448;点击即可进行下载操作操作注意只能下载不能在线操作 1. 函…...

【数据分析】02- A/B 测试:玩转假设检验、t 检验与卡方检验

一、背景&#xff1a;当“审判”成为科学 1.1 虚拟场景——法庭审判 想象这样一个场景&#xff1a;有一天&#xff0c;你在王国里担任“首席审判官”。你面前站着一位嫌疑人&#xff0c;有人指控他说“偷了国王珍贵的金冠”。但究竟是他干的&#xff0c;还是他是被冤枉的&…...

Windows下的C++内存泄漏检测工具Visual Leak Detector (VLD)介绍及使用

在软件开发过程中&#xff0c;内存管理是一个至关重要的环节。内存泄漏不仅会导致程序占用越来越多的内存资源&#xff0c;还可能引发系统性能下降甚至程序崩溃。对于Linux平台来说&#xff0c;内存检测工具非常丰富&#xff0c;GCC自带的AddressSanitizer (asan) 就是一个功能…...

[苍穹外卖] 1-项目介绍及环境搭建

项目介绍 定位&#xff1a;专门为餐饮企业&#xff08;餐厅、饭店&#xff09;定制的一款软件产品 功能架构&#xff1a; 管理端 - 外卖商家使用 用户端 - 点餐用户使用 技术栈&#xff1a; 开发环境的搭建 整体结构&#xff1a; 前端环境 前端工程基于 nginx 运行 - Ngi…...

人物一致性训练测评数据集

1.Pulid 训练:由1.5M张从互联网收集的高质量人类图像组成,图像标题由blip2自动生成。 测试:从互联网上收集了一个多样化的肖像测试集,该数据集涵盖了多种肤色、年龄和性别,共计120张图像,我们称之为DivID-120,作为补充资源,还使用了最近开源的测试集Unsplash-50,包含…...

AI的出现,是否能替代IT从业者?

AI的出现&#xff0c;是否能替代IT从业者&#xff1f; AI在IT领域中的应用已成趋势&#xff0c;IT 从业者们站在这风暴之眼&#xff0c;面临着一个尖锐问题&#xff1a;AI 是否会成为 “职业终结者”&#xff1f;有人担忧 AI 将取代 IT 行业的大部分工作&#xff0c;也有人坚信…...

乘联会:1月汽车零售预计175万辆 环比暴跌33.6%

快科技1月18日消息&#xff0c;据乘联会的初步推算&#xff0c;2025年1月狭义乘用车零售总市场规模预计将达到约175万辆左右。与去年同期相比&#xff0c;这一数据呈现了-14.6%的同比下降态势&#xff1b;而相较于上个月&#xff0c;则出现了-33.6%的环比暴跌情况。 为了更清晰…...

LLM - 大模型 ScallingLaws 的 CLM 和 MLM 中不同系数(PLM) 教程(2)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/145188660 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 Scalin…...

开发神器之cursor

文章目录 cursor简介主要特点 下载cursor页面的简单介绍切换大模型指定ai学习的文件指定特定的代码喂给ai创建项目框架文件 cursor简介 Cursor 是一款专为开发者设计的智能代码编辑器&#xff0c;集成了先进的 AI 技术&#xff0c;旨在提升编程效率。以下是其主要特点和功能&a…...

使用 Ansys Motor-CAD 的自适应模板加速创新

应对现代电机设计挑战 电机设计不断发展&#xff0c;Ansys 正在通过创新解决方案引领潮流&#xff0c;不断突破可能的界限。随着电动汽车、工业自动化和可再生能源系统的快速增长&#xff0c;对优化电机的需求从未如此之高。工程师面临着越来越大的压力&#xff0c;他们需要开发…...

RabbitMQ前置概念

文章目录 1.AMQP协议是什么&#xff1f;2.rabbitmq端口介绍3.消息队列的作用和使用场景4.rabbitmq工作原理5.整体架构核心概念6.使用7.消费者消息推送限制&#xff08;work模型&#xff09;8.fanout交换机9.Direct交换机10.Topic交换机&#xff08;推荐&#xff09;11.声明队列…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

三维GIS开发cesium智慧地铁教程(5)Cesium相机控制

一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点&#xff1a; 路径验证&#xff1a;确保相对路径.…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...