Android实现电子书放大镜效果
龙旋
共 5258字,需浏览 11分钟
· 2021-10-24
前言
最近看电子书发现了一个挺有意思的效果,类似于一个放大镜,因此就花了点时间实现了一个放大镜效果的自定义View,电子书里面的效果,如图所示:
效果展示
我写的效果如下:
实现原理
控件的代码如下,所有的步骤都加入了注释,可以直接参考注释
public class MagnifierLayout extends FrameLayout {
private Bitmap mBitmap;
private Paint mPaintShadow,mPaintBitmap;
private long mShowTime = 0;//用于判断显示放大镜的时间
private boolean mIsShowMagnifier = false;//是否显示放大镜
private Path mPath;//用于裁剪画布的路径
private float mShowMagnifierX = 0;//显示放大镜的X坐标
private float mShowMagnifierY = 0;//显示放大镜的Y坐标
private float mMagnifierRadius = 200;//放大镜的半径
private float mScaleRate = 3f;//放大比例
private Handler mHandler = new Handler();
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
mIsShowMagnifier = true;
postInvalidate();
}
};
public MagnifierLayout(@NonNull Context context) {
this(context,null);
}
public MagnifierLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MagnifierLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
//绘制放大镜边缘投影的画笔
mPaintShadow = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置放大镜边缘的投影
mPaintShadow.setShadowLayer(20,6,6, Color.BLACK);
//绘制Bitmap的画笔
mPaintBitmap = new Paint(Paint.ANTI_ALIAS_FLAG);
mPath = new Path();
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mShowTime = System.currentTimeMillis();
mShowMagnifierX = event.getX() + mMagnifierRadius;
mShowMagnifierY = event.getY() - mMagnifierRadius;
//如果持续按超过一秒就显示放大镜
startShowMagnifierDelay();
break;
case MotionEvent.ACTION_MOVE:
//触摸时间大于1秒才显示
if(System.currentTimeMillis() - mShowTime >= 1000){
stopShowMagnifier();
mIsShowMagnifier = true;
mShowMagnifierX = event.getX() + mMagnifierRadius;
mShowMagnifierY = event.getY() - mMagnifierRadius;
//重绘
postInvalidate();
}
break;
case MotionEvent.ACTION_UP:
stopShowMagnifier();
mIsShowMagnifier = false;
postInvalidate();
break;
}
return true;
}
private void startShowMagnifierDelay() {
stopShowMagnifier();
mHandler.postDelayed(mRunnable,1000);
}
private void stopShowMagnifier() {
mHandler.removeCallbacks(mRunnable);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if(mIsShowMagnifier){
//创建整个布局内容的Bitmap
mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas mCanvas = new Canvas(mBitmap);
//将当前布局的内容绘制在Bitmap上
super.dispatchDraw(mCanvas);
//将Bitmap绘制出来(否则页面是空白,因为上面是用我们自定义的Canvas进行绘制的,因此我们还需要用系统的这个进行绘制一遍)
canvas.drawBitmap(mBitmap,0,0,mPaintBitmap);
//绘制出放大镜边缘的投影
canvas.drawCircle(mShowMagnifierX,mShowMagnifierY,mMagnifierRadius,mPaintShadow);
canvas.save();
//计算出要裁剪画布的圆形路径
mPath.reset();
mPath.addCircle(mShowMagnifierX,mShowMagnifierY,mMagnifierRadius, Path.Direction.CW);
//将圆形路径的画布裁剪出来,这样绘制的Bitmap就是圆形的
canvas.clipPath(mPath);
//绘制当前布局的背景颜色,否则放大镜显示的背景会是黑色
getBackground().draw(canvas);
//根据缩放的比例计算出裁剪的Bitmap的最小宽高
float magnifierWidthAndHeight = mMagnifierRadius * 2 / mScaleRate;
//计算出该裁剪的区域(就是使手指所在的点在要裁剪的Bitmap的中央),并进行边界值处理(开始裁剪的X点不能小于0和大于Bitmap的宽度,并且X点的位置加上要裁剪的宽度不能大于Bitmap的宽度,Y点也是一样)
int cutX = Math.max((int) (mShowMagnifierX - mMagnifierRadius - magnifierWidthAndHeight / 2), 0);
int cutY = Math.min(Math.max((int) (mShowMagnifierY + mMagnifierRadius - magnifierWidthAndHeight / 2),0), mBitmap.getHeight());
//适配边界值
int cutWidth = magnifierWidthAndHeight + cutX > mBitmap.getWidth() ? mBitmap.getWidth() - cutX : (int)magnifierWidthAndHeight;
int cutHeight = magnifierWidthAndHeight + cutY > mBitmap.getHeight() ? mBitmap.getHeight() - cutY : (int)magnifierWidthAndHeight;
mBitmap = Bitmap.createBitmap(mBitmap,cutX,cutY,cutWidth,cutHeight);
//将裁剪出来的区域放大
mBitmap = Bitmap.createScaledBitmap(mBitmap,(int) (mBitmap.getWidth() * mScaleRate),(int)(mBitmap.getHeight() * mScaleRate),true);
//绘制放大后的Bitmap,由于Bitmap的缩放是从左上点开始的因此还要根据缩放的比例进行相应的偏移展示
canvas.drawBitmap(mBitmap,mShowMagnifierX - mMagnifierRadius ,mShowMagnifierY - mMagnifierRadius,mPaintBitmap);
canvas.restore();
mBitmap.recycle();
mBitmap = null;
}else {
super.dispatchDraw(canvas);
}
}
}
源码地址:
https://gitee.com/itfitness/magnifier-layout
到这里就结束啦。
评论
15种时间序列预测方法总结(包含多种方法代码实现)
向AI转型的程序员都关注了这个号👇👇👇在这篇文章中,我们将深入探讨时间序列预测的基本概念和方法。我们将首先介绍单元预测和多元预测的概念,然后详细介绍各种深度学习和传统机器学习方法如何应用于时间序列预测,包括循环神经网络(RNN)、一维卷积神经网络(1D-CNN)、Transformer、自回归模型(
机器学习AI算法工程
0
SpringBoot 实现图片防盗链功能
程序员的成长之路互联网/程序员/技术/资料共享 关注阅读本文大概需要 4 分钟。来自:blog.csdn.net/weixin_46157208/article/details/138051737前言出于安全考虑,我们需要后端返回的图片只允许在某个网站内展示,不想被爬虫拿到图片地
程序员的成长之路
0
一站式解决方案:基于 Arthas 实现服务发现和权限控制
来源:juejin.cn/post/7281849496983994383👉 欢迎加入小哈的星球 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 赠书福利全栈前后端分离博客项目 2.0 版本完结啦, 演示链接
小哈学Java
0
用 Shader 实现旗帜飘扬动画效果
我觉得对于刚入门 3D 编程的朋友来说,如果能够完成代码创建模型数据->创建材质->编写Shader动画这一系列,想必会有满满的成就感。今天就用 Cocos Creator 的 utils.MeshUtils.createMesh 接口,带大家感受一下这个流程。这个流程不仅可以用于新手学
COCOS
2
SpringBoot+Minio实现上传凭证、分片上传、秒传和断点续传
关注我们,设为星标,每天7:40不见不散,架构路上与您共享回复架构师获取资源大家好,我是你们的朋友架构君,一个会写代码吟诗的架构师。Spring Boot整合Minio后,前端的文件上传有两种方式:1、文件上传到后端,由后端保存到Minio这种方式好处是完全由后端集中管理,可以很好的做到、身份验证、
Java架构师社区
0
超越原生,散点图实现华夫饼图
之前我们介绍过了如何使用新卡片图实现华夫饼图。参考:超越原生,PowerBI 华夫饼图实现但是利用卡片图实现的华夫饼图有一些缺点,形状之间的大小跟间距不太好把握,而且有时形状大一点的话显示就会不正常,需要做出二次调整。今天给大家介绍一种原生视觉对象生成华夫饼图的更佳方案,既简单又美观。上图是利用散点
PowerBI战友联盟
2
全新 SOTA backbone | 2024年了,再见ViT系列Backbone,实数难得,不知道效果如何?
点击上方“小白学视觉”,选择加"星标"或“置顶”重磅干货,第一时间送达在构建用于精确匹配的深度固定长度表示时,确定指纹上的密集特征点,特别是在像素 Level 上,具有重大意义。为了探索指纹匹配的可解释性,作者提出了一种多阶段可解释的指纹匹配网络,名为通过视觉 Transformer 进行指纹匹配的
小白学视觉
10
Spring Boot + flowable 快速实现工作流
关注我们,设为星标,每天7:40不见不散,架构路上与您共享回复架构师获取资源大家好,我是你们的朋友架构君,一个会写代码吟诗的架构师。来源:blog.csdn.net/zhan107876/article/details/120815560总览一、flowable-ui部署运行二、绘制流程图绘图细节:
Java架构师社区
0