每周学一个小轮子之 可以缩放的ScalableView(1),android开发者指南
注意,我们需要在 onDown方法返回true,原理和onTouchEvent一样,如果不是true,就接收不到后面的事件了。
ScaleGestureDetector
双指缩放的精髓类,它是Android专门用于解决双指缩放下缩放系数变化的API,它是个大管家类,用于onTouchEvent的拦截。
这里注意一点,它也有个ScaleGestureDetectorCompat
类,但是这个类和它已经是两码事了,所以说不上是兼容类。可能只是个扩充。
ScaleGestureDetector.OnScaleGestureListener
缩放监听类,可以监听到缩放开始、缩放时、缩放结束的状态,我们需要重写三个类,并且在 onScaleBegin返回true,原理和onTouchEvent一样。
OverScroller
回弹Scroller,它和Scroller
类的区别就是它可以设置回弹边界,所以喜欢谁就用谁,因为它们的计算API都是一样的,用法上几乎没有区别。
postOnAnimation(Runnable action![](https://www.hualigs.cn/image/61dba891ed8ee.jpg) )
配合OverScroller食用更加美味,它的作用是展现动画的下一帧,也就是说,我们在滑动图片的时候,我们需要在滑动的过程中通过 OverScroller去计算每一帧的滑动速度、坐标,同时又要让其展现出来,所以我们需要在算完每一帧的时候,通过postOnAnimation去画出来~有没有点像异步处理,Handler什么的啊哈哈哈哈
1、缩放的依据是什么?
缩放的依据是什么?就是我们根据什么来缩放,先要理清这个东西特别特别的重要。因为我们所有方法的代码都要根据这个缩放依据来进行操作(比如做动画、做画布平移),而如果选择一个不好的缩放依据,会给我们留下很多的坑。
正常的缩放依据选择有两个:
- 缩放比例
缩放比例很简单,范围是 0-100%,以百分比形式呈现,0%就是原始大小,100%就是放大的最大倍数。
这样的依据比较直观,也可行。
- 缩放倍数(系数)
放大倍数,范围在 最初的放大倍数—最大的放大倍数。
我这里选择的是第二个。原因是ScaleGestureDetector.OnScaleGestureListener
里的onScale
是我们双指捏撑的重要方法,它的getScaleFactor()
能够提供当前的缩放系数,比如说我在一个 1倍->3倍的放大过程中,这个方法能够返回能够直接和缩放倍数挂钩。
2、原始图片是怎么样的?放大后的图片又是怎么样的?
Bitmap是我们要缩放的图片,而view是这个bitmap的容器,那么Bitmap应该要在这个View居中显示更符合实际情况。
其次原始图片时怎样的?我们需要留空吗?比如一个图片可能才 300*200,你让他居中显示,它左右上下都会留白。
通过查看多个App的情况,缩小的状态其实就是 大的那一边填充,放大状态的就是小的那一边填充:
- 如果图片比例是 宽大于高的
那么缩小状态应该是左右填充屏幕,放大状态应该是上下边填充屏幕
这样的话,最小缩放系数就是 view.getWidth()/bitmap.getWidth(),最大缩放系数就是 getHeight() / mBitmap.getHeight()
- 如果图片比例是 高大于宽的
那么缩小状态应该是上下填充屏幕,放大状态应该是左右填充屏幕
缩小/放大系数反之。
其实这个解释不是很难,大家找幅图,然后拿着图片去Activity里面试一下,再和别的App的比较一下就Ok了。
自定义流程
=====================================================================
OK,有了这些我们可以去开始做我们的ScalableView了。
第一步、继承自View,初始化所有变量
为了去除干扰因数,我选择继承自View而不是 ImageView。
下面是用到的所有变量,变量是边做边产生的:
public class RikkaScalableView extends View {
//初始化Bimap的宽度
private float imageWidth = Utils.dpToPixel(300);
//Bitmap图片
private Bitmap mBitmap;
//画笔
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//初始化bitmap偏移量X、Y值,让bitmap居中的偏移量
float originalOffsetX, originalOffsetY;
//偏移量 X、Y值
float offsetX, offsetY;
//旧的放大系数,和currentScale对应,用于放大前保存当前放大系数
float oldScale;
//放大倍数、缩小倍数
float bigScale, smallScale;
//放大的倍数还要再大个1.5倍
private float bigScaleMore = 1.5f;
//用currentScale表示当前放大倍数,而且之后用到的缩放、动画的差值都是这个以这个值为标准
private float currentScale;
//当前是否是放大状态
private boolean isBig = false;
//放大和缩小的动画,因为缩小动画就是放大动画的镜像,所以可以直接用放大动画的reverse来做
private ObjectAnimator bigAnimator, smallAnimator;
//手势缩放对象
private GestureDetectorCompat gestureDetector;
//自定义手势监听器
private RikkaGestureListener rikkaGestureListener = new RikkaGestureListener();
//手势缩放对象
private ScaleGestureDetector scaleDetector;
//自定义手势缩放监听器
private RikkaScaleListener rikkaScaleListener = new RikkaScaleListener();
//用OverScroller去计算滑动值,可以设置回弹动画
private OverScroller scroller;
//postOnAnimation里的Runnable
private RikkaRunnable rikkaRunnable;
}
在上面我们自定义了监听器。
第二步:将图片居中显示,求出最小缩放和最大缩放系数
图片就是我们的bitmap,居中指的是在View里面居中,我们需要在View测量好自己的大小之后,获取宽高来得到正中间的位置。我们可以在 layout()或者onSzieChanged()方法中获取,这个时候view的大小已经测量好了。
同时,我们也可以在这个时候算出:最小缩放系数和最大缩放系数。
算出来后,我们通过 canva.scale()来进行缩放。
//onSizeChanged表示view已经初始化完了,我们在这里初始化一些比例
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//计算初始偏移量,让图片居中显示,这也表明了,我们将缩放的坐标原点,放在了View的正中心
//偏移是以bitmap的左上角为点
originalOffsetX = (getWidth() - mBitmap.getWidth()) / 2;
originalOffsetY = (getHeight() - mBitmap.getHeight()) / 2;
//计算bigScale,smallScale
//smallScale是初始状态,要求图片:(1)如果图片比例宽大于高的话就是左右贴屏幕的边,(2)如果图片比例高大于宽的话就要上下贴屏幕的边
//bigScale是放大后的状态,要求图片:(1)如果图片比例宽大于高的话就是上下贴屏幕的边,(2)如果图片比例高大于宽的话就要左右贴屏幕的边
if ((float) mBitmap.getWidth() / mBitmap.getHeight() > (float) getWidth() / getHeight()) {
//表示当前图片比例宽大于高
smallScale = (float) getWidth() / mBitmap.getWidth();
bigScale = (float) getHeight() / mBitmap.getHeight() * bigScaleMore;
} else {
//表示当前图片比例高大于宽
smallScale = (float) getHeight() / mBitmap.getHeight();
bigScale = (float) getWidth() / mBitmap.getWidth() * bigScaleMore;
}
//初始放大倍数等于smallScale(最小)
currentScale = smallScale;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//这里用scale()方法来控制画布放大还是缩小
canvas.scale(currentScale, currentScale, getWidth() / 2f, getHeight() / 2f);
canvas.drawBitmap(mBitmap, originalOffsetX, originalOffsetY, mPaint);
}
第三步:实现双击缩放,并且可以进行单指移动画布
我们首先要去实现自定义的监听器,
…
gestureDetector = new GestureDetectorCompat(context, rikkaGestureListener);
…
//继承自SimpleOnGestureListener,这里面做了所有的单手势监听
class RikkaGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
//双击放大、缩小
@Override
public boolean onDoubleTap(MotionEvent e) {
isBig = !isBig;
if (isBig && currentScale < bigScale) {
//记录双击的位置,在这个点上放大,要把原点放在View的正中心,因为我们的图片时居中显示的。
offsetX = (e.getX() - getWidth() / 2) - ((e.getX() - getWidth() / 2) * bigScale / smallScale);
offsetY = (e.getY() - getHeight() / 2) - ((e.getY() - getHeight() / 2) * bigScale / smallScale);
notOutBound();
//如果当前是放大状态,就做放大动画
getBigAnimator().start();
} else {
//如果是缩小状态,就做缩小动画
getSmallAnimator().start();
}
return false;
}
}
//offsetX、offsetY不能超出边界
private void notOutBound() {
offsetX = Math.max(offsetX, -((mBitmap.getWidth() * bigScale - getWidth()) / 2f));
offsetX = Math.min(offsetX, (mBitmap.getWidth() * bigScale - getWidth()) / 2f);
offsetY = Math.max(offsetY, -((mBitmap.getHeight() * bigScale - getHeight()) / 2f));
offsetY = Math.min(offsetY, (mBitmap.getHeight() * bigScale - getHeight()) / 2f);
}
然后设置放大缩小动画,因为我们是根据currentScale来缩放的,所以我们的属性动画也是根据这个属性来做,动画每次设置一次currentScale,我们就需要invalidate()一次,并且将画布平移到 offsetX,offsetY的位置。
在每次做缩小动画的时候,需要重置一下offsetX/Y,否则下次双击,会导致放大到上一次的偏移位置上了。
@Override
protected void onDraw(Canvas canvas) {
…
//scalingFraction为放大倍数的百分比
float scalingFraction = (currentScale - smallScale) / (bigScale - smallScale);
canvas.translate(offsetX * scalingFraction, offsetY * scalingFraction);
…
}
public float getCurrentScale() {
return currentScale;
}
public void setCurrentScale(float currentScale) {
this.currentScale = currentScale;
//变化的时候要重绘
invalidate();
}
//放大动画
public ObjectAnimator getBigAnimator() {
if (bigAnimator == null) {
//懒加载缩放动画,用currentScale缩小到放大,值域在smallScale -> bigScale之间
bigAnimator = ObjectAnimator.ofFloat(this, “currentScale”, currentScale, bigScale);
}
//因为每次的currentScale都会变化,每次拿都要重新设置一遍currentScale
bigAnimator.setFloatValues(currentScale, bigScale);
return bigAnimator;
}
//缩小动画
public ObjectAnimator getSmallAnimator() {
if (smallAnimator == null) {
每周学一个小轮子之 可以缩放的ScalableView(1),android开发者指南相关推荐
- 实现一个小轮子:用AOP实现异步上传
文章来源:https://c1n.cn/2jnRk 目录 背景 代码与实现 结语 背景 相信很多系统里都有这一种场景:用户上传 Excel,后端解析 Excel 生成相应的数据,校验数据并落库. ...
- 每周一个小轮子之 贴纸效果
上周有点事情,所以拖到今天发. 这篇blog是学习 传送门的贴纸效果. Github在这里: Demo地址 先上个效果图: 是一个可以做平移.缩放.旋转.删除的一个简单的贴纸效果. 上周因为学习了关于 ...
- Android 每周一个小轮子之 学习仿网易云广场歌单的效果
这一篇Blog是学习自:Android自定义ViewGroup第十三式之移花接木 小缘老哥太顶了,写的东西都巨棒,关注Ta很久了,我决定向他学习,学着去像他那样思考问题. 建议各位老哥都去关注他! 这 ...
- Android 每周一个小轮子之 学习仿网易云广场歌单的效果
/** 这里要自己写一个ViewGroup的LayoutParams来记录 scale.alpha.from.to */ class RikkaLayoutParams extends MarginL ...
- 微信小程序趋势及前景,详细的Android学习指南
**新技术层出不穷,去年kotlin到如今Flutter,技术迭代,你是否会变得固步自封?**那么看本篇文章帮你解决问题,让你知道怎么样学习,学习那些技术点才能不被时代的迭代快速淘汰! 首先,先说一下 ...
- 教你50行代码实现前端路由小轮子
在SPA应用这么流行的当下,我们看到每个MV**框架都会有自己的路由插件用于实现单页应用的路由设置与监控,并且提供了一系列的生命周期来方便用户,那么到底它们都是怎么做到的呢,今天我会放上自己写的一个小 ...
- Java黑皮书课后题第8章:**8.4(计算每个雇员每周工作的小时数)假定所有雇员每周工作的小时数存储在一个二维数组中。1行包含7列,记录了一个雇员7天的工作小时数。编写一个程序,按照总工时降序显示
**8.4(计算每个雇员每周工作的小时数)假定所有雇员每周工作的小时数存储在一个二维数组中.1行包含7列,记录了一个雇员7天的工作小时数.编写一个程序,按照总工时降序显示 题目 题目描述 破题 代码 ...
- 学完javaee基础,编的一个小游戏—太阳系的八大行星
项目完整代码已放到码云上 自行下载: https://gitee.com/tutu_57893_7590/planets 学完javaee基础,编的一个小游戏-太阳系的八大行星 要使太阳系的八大行星, ...
- 适合刚学js小白熟悉运算符的一个小页面
** 适合刚学js小白熟悉运算符的一个小页 ** 刚学习js的小白,对于运算符上手的没那么快的,可以着手尝试一下这种简单,对逻辑提升比较快的小页面 因为我也是一个萌新,只是感觉这个页面帮助很大,所以分 ...
最新文章
- 机器学习与数据科学 基于R的统计学习方法(基础部分)
- Win10 无需安装虚拟机/双系统使用 linux
- 用WSDL定义Web服务
- [share]PHP购物车类的源码
- poj2175费用流消圈算法
- 工作随笔0828:领导离职前对我的忠告
- Backbone源码分析-Backbone架构+流程图
- mysql添加新的实例_MySQL中添加新用户权限的实例详解
- ios 中NSString的一些调用
- 实验五 存储管理实验
- Spring Boot入门(6)-使用AOP统一处理Web请求日志
- 如何在Eclipse 中查看(如Objects)的源代码
- 使用python的netCDF4库读取.nc文件 和 创建.nc文件
- Vue动态加载并注册组件
- python文档字符串和注释的区别,加载模块时,Python文档字符串和注释是否存储在内存中?...
- 第五章 国内著名网络数据库系统
- 4个方法判断食物的热量高低
- JAVA 九大排序算法
- 编写函数求x的n次方
- 马云的顿悟:阿里巴巴在维基经济学中成长