欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android自定义ImageView实现图片缩放滑动,双击放大缩小,多点触控旋转,圆角矩形,圆形和仿刮刮卡效果

程序员文章站 2022-07-12 21:19:24
...

概述

这是一个可以设置成圆角或者圆角矩形的ImageView,并且可以设置是否支持多点触控放大,缩小,旋转图片,双击放大缩小的自定义的控件。还有一个仿刮刮卡效果的自定义View。

效果展示

效果展示
录制的视频5.4M,可能打不开得下下来看。

相关知识点

关键代码和注意事项

初始化

因为我们是使用matirx来做图形的变化,所以要设置 setScaleType(ScaleType.MATRIX);图形变换的方法setImageMatrix(Matrix matrix);才会生效。

接着我们在xml中配置ImageView

<com.example.administrator.imagetest.MyImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@mipmap/test" />

问题来了,图片太大显示不全,而且位于控件的左上角。我想让他像微信一样,自动调整到控件中心点,并且等比例缩放到一个屏幕放的下。这时我们就要在控件图像被绘制出来的时候,调整图片大小和位置

实现implements ScaleGestureDetector.OnScaleGestureListener接口

    /**
     * 控件被加载到窗口时,监听View变化
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            getViewTreeObserver().addOnGlobalLayoutListener(this);
        }
    }

    /**
     * 控件被销毁时,关闭监听
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    }

初始化图片大小和位置:

    /**
     * 当View发生改变的时候,会调用这个监听,可能多次调用,所以要加判断
     */
    @Override
    public void onGlobalLayout() {
        if (once) {
            initView();
            once = false;
        }
    }

    /**
     * 初始化图片大小,位置
     */
    private void initView() {
        Drawable d = getDrawable();
        if (d == null)
            return;
        //获取imageview宽高
        int width = getWidth();
        int height = getHeight();

        //获取图片宽高
        startWidth = startWidth > 0 ? startWidth : d.getIntrinsicWidth();
        startHeight = startHeight > 0 ? startHeight : d.getIntrinsicHeight();

        float scale = 1.0f;

        //如果图片的宽或高大于屏幕,缩放至屏幕的宽或者高
        if (startWidth > width && startHeight <= height) {
            scale = (float) width / startWidth;
        }
        if (startHeight > height && startWidth <= width) {
            scale = (float) height / startHeight;
        }
        //如果图片宽高都大于屏幕,按比例缩小
        if (startWidth > width && startHeight > height) {
            scale = Math.min((float) startWidth / width, (float) startHeight / height);
        }
        //如果图片宽高都小于屏幕,选取差比较小的那个比例,放大
        if (startWidth < width && startHeight < height) {
            scale = Math.min((float) width / startWidth, (float) height / startHeight);
        }
        //将图片移动至屏幕中心
        matrix.postTranslate((width - startWidth) / 2, (height - startHeight) / 2);
        //然后再放大
        matrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
        firstScale = firstScale > 0 ? firstScale : scale;
        setImageMatrix(matrix);
        getMatrixRectF();
    }

这样图片就可以在整个控件中居中,并且完全显示了。

放大缩小

实现ScaleGestureDetector.OnScaleGestureListener接口,
在onScale(ScaleGestureDetector detector)中做放大缩小的逻辑处理。

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        //这个放大缩小是每次进行细微的变化,通过频繁变化,来改变图片大小
        //通过与imageView本身的宽高进行限制,最大不过4倍,最小不过四分之一
        //放大
        if (detector.getScaleFactor() >= 1) {
            if (Math.min(getMatrixRectF().bottom - getMatrixRectF().top, 
                    getMatrixRectF().right - getMatrixRectF().left) / getWidth() <= MAX_SCALE) {
                matrix.postScale(detector.getScaleFactor(),
                        detector.getScaleFactor(), getWidth() / 2, getHeight() / 2);
                setImageMatrix(matrix);
                getMatrixRectF();
            }
        } else {
            //缩小
            if (getWidth() / Math.min(getMatrixRectF().bottom - getMatrixRectF().top,
                    getMatrixRectF().right - getMatrixRectF().left) <= MIN_SCALE) {
                matrix.postScale(detector.getScaleFactor(), detector.getScaleFactor(), 
                        getWidth() / 2, getHeight() / 2);
                setImageMatrix(matrix);
                getMatrixRectF();
            }
        }
        return true;
    }

这时,我们发现放大的时候,图片超过了屏幕范围,却不能放大,这个体验超级差的。这个时候,就要实现GestureDetector.OnGestureListener,在onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)方法中处理滑动事件了:

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        //判断宽度或者高度有一个大于控件宽高的
        if (((getMatrixRectF().right - getMatrixRectF().left) > getWidth()
                || (getMatrixRectF().bottom - getMatrixRectF().top) > getHeight())) {
            //滑动到横坐标边界,将事件交给viewpager处理
            if(amendment(-distanceX, -distanceY)[0]==0){
                //可以滑动,拦截Viewpager事件
                getParent().requestDisallowInterceptTouchEvent(false);
            }else{
                //可以滑动,拦截Viewpager事件
                getParent().requestDisallowInterceptTouchEvent(true);
            }
            matrix.postTranslate(amendment(-distanceX, -distanceY)[0],
                    amendment(-distanceX, -distanceY)[1]);
        } else {
            getParent().requestDisallowInterceptTouchEvent(false);//不能滑动,viewpager处理事件
        }
        setImageMatrix(matrix);
        return false;
    }
    /**
     * 滑动前判断滑动是否会造成越界,并对最终滑动距离进行修正
     */
    private float[] amendment(float distanceX, float distanceY) {
        float[] dis = new float[]{distanceX, distanceY};
        //先判断图片的宽高是否大于屏幕,大于屏幕才能滑动
        if ((getMatrixRectF().bottom - getMatrixRectF().top) > getHeight()) {
            //判断Y轴上图片的顶部加上滑动的距离是否大于屏幕的顶部
            if (getMatrixRectF().top + dis[1] > getTop()) {
                //如果超过了,重新设置滑动距离,使之不超过边界
                dis[1] = getTop() - getMatrixRectF().top;
            }
            if (getMatrixRectF().bottom + dis[1] < getBottom()) {
                dis[1] = getBottom() - getMatrixRectF().bottom;
            }
        } else {
            dis[1] = 0;
        }
        if ((getMatrixRectF().right - getMatrixRectF().left) > getWidth()) {
            if (getMatrixRectF().left + dis[0] > getLeft()) {
                dis[0] = getLeft() - getMatrixRectF().left;
            }
            if (getMatrixRectF().right + dis[0] < getRight()) {
                dis[0] = getRight() - getMatrixRectF().right;
            }
        } else {
            dis[0] = 0;
        }
        return dis;
    }
    /**
     * 根据当前图片的Matrix获得图片的范围
     * @return
     */
    private RectF getMatrixRectF() {
        Drawable d = getDrawable();
        if (null != d) {
            rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            matrix.mapRect(rect);
        }
        return rect;
    }

这里的代码比较多,可实际上滑动图片也就是matrix.postTranslate();这个方法而已。只要是还要做一个图片是否可以滑动的判断,是否滑动越界,还有处理嵌套ViewPager时的事件分发冲突处理。

双击放大缩小

这里我们不能设置成第一次双击放大,第二次双击缩小,这样子的。正常我们是会设置最大倍数和最小倍数的,如果用户先用触控把图片拉到最大,然后双击图片。这个时候,图片就没有反应了。所以要根据当前的一个放大倍率来决定是要放大还是缩小图片。

    /**
     * 双击放大缩小
     */
    private void doubleClick() {
        gestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                return false;
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                scaleType();
                return false;
            }

            @Override
            public boolean onDoubleTapEvent(MotionEvent e) {
                return false;
            }
        });
    }
    /**
     * 双击放大缩小的动画
     */
    private void scaleType() {
        final float scale = getScale() / firstScale;
        //想要放大缩小的中心是在屏幕的中心,那么图片的中心也要在屏幕中心,
        // 这是就得通过计算偏移量,把图片先移动到屏幕中心来
        if (scale >= 1 && scale <= 2) {
            matrix.postTranslate(getWidth() / 2 - getMatrixRectF().centerX(), 
                    getHeight() / 2 - getMatrixRectF().centerY());
            matrix.postScale(MAX_SCALE / scale, MAX_SCALE / scale,
                    getWidth() / 2, getHeight() / 2);
            setImageMatrix(matrix);
            getMatrixRectF();
        } else {
            matrix.postTranslate(getWidth() / 2 - getMatrixRectF().centerX(),
                    getHeight() / 2 - getMatrixRectF().centerY());
            matrix.postScale(1 / scale, 1 / scale, getWidth() / 2, getHeight() / 2);
            //将图片移动至屏幕中心
            setImageMatrix(matrix);
            getMatrixRectF();
        }
    }

注意啦,敲黑板!

这段处理双击逻辑的代码,在缩放之前添加了一个平移的方法,这是因为如果我们在放大以后,移动图片再双击缩小的话,图片的位置会改变,不再是屏幕的中心(原来的位置是在屏幕的中心)而我们在缩放的时候是以屏幕的中心位置为缩放中心进行缩放的,所以图片的位置会发生偏移。但是我们通过触控放大缩小图片的时候不会出现这种情况,是因为move事件的频率快,每次变化只有一点点,这个时候平移和缩放是在交替执行的,所以能够自动修正到正确的位置

快十二点了,剩下的周五再写吧。下方有这个demo的源码地址(效率好低)

项目地址链接

github项目地址

个人公众号

Android自定义ImageView实现图片缩放滑动,双击放大缩小,多点触控旋转,圆角矩形,圆形和仿刮刮卡效果
不只是技术文章哦,快关注我吧。
搜索公众号:kedasay