自定义可以点击缩放的ImageView

在开发时会遇到双击放大,放大后可以拖动的ImageView,就跟腾讯朋友圈一样的效果。下面我们就手撸一个这样的自定义控件。

先介绍下要用到的知识点:双击的点击事件的获取,放大缩小的动画,在滑动Filing的惯性滑动处理。

双击的点击事件,我们交给GestureDetectorCompat去处理。先介绍下OnGestureListener

// 步骤1:创建手势检测器实例 & 传入OnGestureListener接口(需要复写对应方法)
// 构造函数有3个,常用的是第二个
// 1. GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener);
// 2. GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
// 3. GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
    GestureDetector mGestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {

        // 1. 用户轻触触摸屏
        public boolean onDown(MotionEvent e) {
            Log.i("MyGesture", "onDown");
            return false;
        }

        // 2. 用户轻触触摸屏,尚未松开或拖动
        // 与onDown()的区别:无松开 / 拖动
        // 即:当用户点击的时,onDown()就会执行,在按下的瞬间没有松开 / 拖动时onShowPress就会执行
        public void onShowPress(MotionEvent e) {
            Log.i("MyGesture", "onShowPress");
        }

        // 3. 用户长按触摸屏
        public void onLongPress(MotionEvent e) {
            Log.i("MyGesture", "onLongPress");
        }

        // 4. 用户轻击屏幕后抬起
        public boolean onSingleTapUp(MotionEvent e) {
            Log.i("MyGesture", "onSingleTapUp");
            return true;
        }

        // 5. 用户按下触摸屏 & 拖动
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                float distanceX, float distanceY) {
            Log.i("MyGesture", "onScroll:");
            return true;
        }

        // 6. 用户按下触摸屏、快速移动后松开
        // 参数:
        // e1:第1个ACTION_DOWN MotionEvent
        // e2:最后一个ACTION_MOVE MotionEvent
        // velocityX:X轴上的移动速度,像素/秒
        // velocityY:Y轴上的移动速度,像素/秒
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                               float velocityY) {
            Log.i("MyGesture", "onFling");
            return true;
        }

    });

// 步骤2-1:让某个View检测手势 - 重写View的onTouch函数,将View的触屏事件交给GestureDetector处理,从而对用户手势作出响应
    View.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            mGestureDetector.onTouchEvent(event);
            return true; // 注:返回true才能完整接收触摸事件
        }
    });

// 步骤2-2:让某个Activity检测手势:重写Activity的dispatchTouchEvent函数,将触屏事件交给GestureDetector处理,从而对用户手势作出响应
  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        mGestureDetector.onTouchEvent(ev); // 让GestureDetector响应触碰事件
        super.dispatchTouchEvent(ev); // 让Activity响应触碰事件
        return false;
    }

处理双击的OnDoubleTapListener

// 步骤1:创建手势检测器实例
    // 注:使用OnDoubleTapListener接口时,需要使用GestureDetector,而GestureDetector的创建则必须传入OnGestureListener接口
    // 所以在使用OnDoubleTapListener接口时,也必须实现OnGestureListener接口
    // 构造函数有3个,常用的是第二个
    // 1. GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener);
    // 2. GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
    // 3. GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
        GestureDetector mGestureDetector = new GestureDetector(this, new GestureDetector.OnGestureListener() {

            // 1. 用户轻触触摸屏
            public boolean onDown(MotionEvent e) {
                Log.i("MyGesture", "onDown");
                return false;
            }

            // 2. 用户轻触触摸屏,尚未松开或拖动
            // 与onDown()的区别:无松开 / 拖动
            // 即:当用户点击的时,onDown()就会执行,在按下的瞬间没有松开 / 拖动时onShowPress就会执行
            public void onShowPress(MotionEvent e) {
                Log.i("MyGesture", "onShowPress");
            }

            // 3. 用户长按触摸屏
            public void onLongPress(MotionEvent e) {
                Log.i("MyGesture", "onLongPress");
            }

            // 4. 用户轻击屏幕后抬起
            public boolean onSingleTapUp(MotionEvent e) {
                Log.i("MyGesture", "onSingleTapUp");
                return true;
            }

            // 5. 用户按下触摸屏 & 拖动
            public boolean onScroll(MotionEvent e1, MotionEvent e2,
                                    float distanceX, float distanceY) {
                Log.i("MyGesture", "onScroll:");
                return true;
            }

            // 6. 用户按下触摸屏、快速移动后松开
            // 参数:
            // e1:第1个ACTION_DOWN MotionEvent
            // e2:最后一个ACTION_MOVE MotionEvent
            // velocityX:X轴上的移动速度,像素/秒
            // velocityY:Y轴上的移动速度,像素/秒
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                                   float velocityY) {
                Log.i("MyGesture", "onFling");
                return true;
            }

        });

// 步骤2:创建 & 设置OnDoubleTapListener接口实现类
    mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {

        // 1. 单击事件
        // 关于OnDoubleTapListener.onSingleTapConfirmed()和 OnGestureListener.onSingleTapUp()的区别
        // onSingleTapConfirmed:再次点击(即双击),则不会执行
        // onSingleTapUp:手抬起就会执行
        public boolean onSingleTapConfirmed(MotionEvent e) {
            Log.i("MyGesture", "onSingleTapConfirmed");
            return false;
        }

        // 2. 双击事件
        public boolean onDoubleTap(MotionEvent e) {
            Log.i("MyGesture", "onDoubleTap");
            return false;
        }
        // 3. 双击间隔中发生的动作
        // 指触发onDoubleTap后,在双击之间发生的其它动作,包含down、up和move事件;
        public boolean onDoubleTapEvent(MotionEvent e) {
            Log.i("MyGesture", "onDoubleTapEvent");
            return false;
        }
    });

// 步骤3-1:让某个View检测手势 - 重写View的onTouch函数,将View的触屏事件交给GestureDetector处理,从而对用户手势作出响应
    View.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            mGestureDetector.onTouchEvent(event);
            return true; // 注:返回true才能完整接收触摸事件
        }
    });

// 步骤3-2:让某个Activity检测手势:重写Activity的dispatchTouchEvent函数,将触屏事件交给GestureDetector处理,从而对用户手势作出响应
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        mGestureDetector.onTouchEvent(ev); // 让GestureDetector响应触碰事件
        super.dispatchTouchEvent(ev); // 让Activity响应触碰事件
        return false;
    }

在处理缩小和放大的时候,添加了动画就是属性动画

ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);  

// ofFloat()作用有两个
// 1. 创建动画实例
// 2. 参数设置:参数说明如下
// Object object:需要操作的对象
// String property:需要操作的对象的属性
// float ....values:动画初始值 & 结束值(不固定长度)
// 若是两个参数a,b,则动画效果则是从属性的a值到b值
// 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值
// 以此类推
// 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator,同ValueAnimator讲解

anim.setDuration(500);
        // 设置动画运行的时长

        anim.setStartDelay(500);
        // 设置动画延迟播放时间

        anim.setRepeatCount(0);
        // 设置动画重复播放次数 = 重放次数+1
        // 动画播放次数 = infinite时,动画无限重复

        anim.setRepeatMode(ValueAnimator.RESTART);
        // 设置重复播放动画模式
        // ValueAnimator.RESTART(默认):正序重放
        // ValueAnimator.REVERSE:倒序回放

animator.start();  
// 启动动画

OverScroller的介绍:

Android里OverScroller类是为了实现View平滑滚动的一个Helper类。通常在自定义的View时使用,在View中定义一个私有成员mScroller = new OverScroller(context)。设置mScroller滚动的位置时,并不会导致View的滚动,通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动。使用方法和属性可以参考OverScroller的一些重要方法和属性

下面贴一下这个类的完整代码:

class ScaleImageView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int=0) : AppCompatImageView(context,attrs,defStyleAttr),
    GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, Runnable {
    private val scaleFactor  = 1.5f
    private val picture:Bitmap = BitmapFactory.decodeResource(resources, R.drawable.banner)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private var bigScale = 1f
    private var smallScale = 1f
    private var offsetX = 0f
    private var offsetY = 0f
    private var currentOffsetY = 0f
    private var currentOffsetX = 0f
    private var isBig = false
    private val gesture:GestureDetectorCompat = GestureDetectorCompat(context,this)
    private val overScroller = OverScroller(context)
    var currentScale:Float = 1f
        set(value) {
            field = value
            invalidate()
        }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if(w/width.toFloat() > h/height.toFloat()){
            bigScale = width / picture.width.toFloat() * scaleFactor
            smallScale = height / picture.height.toFloat()
        }else{
            smallScale = width / picture.width.toFloat()
            bigScale = height / picture.height.toFloat() * scaleFactor
        }
        offsetX = (width - picture.width) / 2f
        offsetY = (height - picture.height) / 2f
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        return gesture.onTouchEvent(event)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.let {
            var scaleFaction = (currentScale - smallScale) / (bigScale - smallScale)
            it.translate(currentOffsetX * scaleFaction, currentOffsetY * scaleFaction)
            it.scale(currentScale, currentScale, width / 2f, height / 2f)
            it.drawBitmap(picture,offsetX,offsetY,paint)
        }
    }

    override fun onShowPress(e: MotionEvent?) {

    }

    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        return false
    }

    override fun onDown(e: MotionEvent?): Boolean {
        return true
    }

    override fun onFling(
        down: MotionEvent?,
        event: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        if(isBig){
            overScroller.fling(currentOffsetX.toInt(),currentOffsetY.toInt(),
                velocityX.toInt(),velocityY.toInt(),
                -(bigScale * picture.width - width).toInt()/2,
                (bigScale * picture.width - width).toInt()/2,
                -(bigScale * picture.height - height).toInt()/2,
                (bigScale * picture.height - height).toInt()/2,
                TypedValue.complexToDimensionPixelSize(50,context.resources.displayMetrics),
                TypedValue.complexToDimensionPixelSize(50,context.resources.displayMetrics)
            )
            postOnAnimation(this)
        }
        return false
    }


    override fun run() {
        if(overScroller.computeScrollOffset()){
            currentOffsetY = overScroller.currY.toFloat()
            currentOffsetX = overScroller.currX.toFloat()
            invalidate()
            postOnAnimation(this)
        }
    }

    override fun onScroll(
        down: MotionEvent?,
        event: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        if(isBig) {
            currentOffsetX -= distanceX
            if(currentOffsetX > (bigScale*picture.width - width)/2){
                currentOffsetX = (bigScale*picture.width - width)/2
            }
            if(currentOffsetX <  -(bigScale*picture.width - width)/2){
                currentOffsetX = -(bigScale*picture.width - width)/2
            }
            currentOffsetY -= distanceY
            if(currentOffsetY > (bigScale*picture.height - height)/2){
                currentOffsetY = (bigScale*picture.height - height)/2
            }
            if(currentOffsetY <  -((bigScale*picture.height - height))/2){
                currentOffsetY = -((bigScale*picture.height - height))/2
            }
            invalidate()
        }
        return false

    }

    override fun onLongPress(e: MotionEvent?) {

    }

    override fun onDoubleTap(e: MotionEvent?): Boolean {
        isBig = !isBig
        var animator = ObjectAnimator.ofFloat(this,"currentScale",smallScale,bigScale)
        if(isBig){
            animator.start()
        }else{
            animator.reverse()
        }
        return false
    }

    override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
        return false

    }

    override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
        return false
    }
}

参考博客:

  1. OverScroller :https://blog.csdn.net/chaoyangsun/article/details/94398225
  2. GestureDetector:https://www.jianshu.com/p/2cb7ec3d3d5a
  3. 属性动画:https://www.jianshu.com/p/bce3f1d4e1f2

关于手势的缩放,会在下一篇博客中更新

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值