Android coder进阶Android小坑程序猿阵线联盟-汇总各类技术干货

Android自定义ImageView(上)之多点触控放大缩小,

2017-11-02  本文已影响244人  badc59a1a8c4

[TOC]

概述

这是一个可以设置成圆角或者圆角矩形的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项目地址

个人公众号


不只是技术文章哦,快关注我吧。
搜索公众号:kedasay

上一篇下一篇

猜你喜欢

热点阅读