每周学一个小轮子之 可以缩放的ScalableView

它是手势监听类关于一些单击、双击的大管家类,一般用于onTouchEvent的拦截。

  • GestureDetector.SimpleOnGestureListener

它是上面那个大管家类的内部类,里面有每个手势对应的方法,我们只需重写,并将其构造在大管家上,就能实现我们想要的效果。SimpleOnGestureListener是同时实现了单击和双击的手势,比我们分别去实现GestureDetector.OnDoubleTapListenerGestureDetector.OnGestureListener要简单的多。

注意,我们需要在 onDown方法返回true,原理和onTouchEvent一样,如果不是true,就接收不到后面的事件了。

  • ScaleGestureDetector

双指缩放的精髓类,它是Android专门用于解决双指缩放下缩放系数变化的API,它是个大管家类,用于onTouchEvent的拦截。

这里注意一点,它也有个ScaleGestureDetectorCompat类,但是这个类和它已经是两码事了,所以说不上是兼容类。可能只是个扩充。

  • ScaleGestureDetector.OnScaleGestureListener

缩放监听类,可以监听到缩放开始、缩放时、缩放结束的状态,我们需要重写三个类,并且在 onScaleBegin返回true,原理和onTouchEvent一样。

  • OverScroller

回弹Scroller,它和Scroller类的区别就是它可以设置回弹边界,所以喜欢谁就用谁,因为它们的计算API都是一样的,用法上几乎没有区别。

  • postOnAnimation(Runnable action)

配合OverScroller食用更加美味,它的作用是展现动画的下一帧,也就是说,我们在滑动图片的时候,我们需要在滑动的过程中通过 OverScroller去计算每一帧的滑动速度、坐标,同时又要让其展现出来,所以我们需要在算完每一帧的时候,通过postOnAnimation去画出来~有没有点像异步处理,Handler什么的啊哈哈哈哈

1、缩放的依据是什么?

缩放的依据是什么?就是我们根据什么来缩放,先要理清这个东西特别特别的重要。因为我们所有方法的代码都要根据这个缩放依据来进行操作(比如做动画、做画布平移),而如果选择一个不好的缩放依据,会给我们留下很多的坑。

正常的缩放依据选择有两个:

  1. 缩放比例

缩放比例很简单,范围是 0-100%,以百分比形式呈现,0%就是原始大小,100%就是放大的最大倍数。

这样的依据比较直观,也可行。

  1. 缩放倍数(系数)

放大倍数,范围在 最初的放大倍数—最大的放大倍数。

我这里选择的是第二个。原因是ScaleGestureDetector.OnScaleGestureListener里的onScale是我们双指捏撑的重要方法,它的getScaleFactor()能够提供当前的缩放系数,比如说我在一个 1倍->3倍的放大过程中,这个方法能够返回能够直接和缩放倍数挂钩。

2、原始图片是怎么样的?放大后的图片又是怎么样的?

Bitmap是我们要缩放的图片,而view是这个bitmap的容器,那么Bitmap应该要在这个View居中显示更符合实际情况。

其次原始图片时怎样的?我们需要留空吗?比如一个图片可能才 300*200,你让他居中显示,它左右上下都会留白。

通过查看多个App的情况,缩小的状态其实就是 大的那一边填充,放大状态的就是小的那一边填充:

  1. 如果图片比例是 宽大于高的

那么缩小状态应该是左右填充屏幕,放大状态应该是上下边填充屏幕

这样的话,最小缩放系数就是 view.getWidth()/bitmap.getWidth(),最大缩放系数就是 getHeight() / mBitmap.getHeight()

  1. 如果图片比例是 高大于宽的

那么缩小状态应该是上下填充屏幕,放大状态应该是左右填充屏幕

缩小/放大系数反之。

其实这个解释不是很难,大家找幅图,然后拿着图片去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) {

//懒加载缩放动画,用currentScale缩小到放大,值域在smallScale -> bigScale之间

smallAnimator = ObjectAnimator.ofFloat(this, “currentScale”, currentScale, smallScale);

smallAnimator.addListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animation) {

}

@Override

public void onAnimationEnd(Animator animation) {

//每次动画结束的时候要将offsetX/Y置为初始值

offsetX = 0;

offsetY = 0;

}

@Override

public void onAnimationCancel(Animator animation) {

}

@Override

public void onAnimationRepeat(Animator animation) {

}

});

}

smallAnimator.setFloatValues(currentScale, smallScale);

return smallAnimator;

}

总结

现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

[外链图片转存中…(img-xskgI85C-1715333437827)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值