美文网首页Android自定义View
Android 自定义控件基础-图片预览和多点触控

Android 自定义控件基础-图片预览和多点触控

作者: 琼珶和予 | 来源:发表于2017-11-30 11:14 被阅读52次

      今天我记录一下怎么通过自定义控件来实现图片的预览的功能 -- 根据慕课网上学习的内容所写的。
      包含的内容主要有:

      1.图片的多指放大。
      2.图片的双击放大和缩小
      3.图片的滑动。
      4.与ViewPager的冲突解决办法

    1.概述

      图片的多指放大,主要用到一个类叫做ScaleGestureDetector,这个类用来一些多指的操作;图片的双击,主要用到一个类叫做GestureDetector,这个类用来处理双击的操作;图片的缩放用到了Matrix类来处理。

    2.自定义ImageView,并且完美的加载一张图片

      当我们在使用ImageView时,有时候要处理图片的大小很大或者很小的情况,这时候需要我们手动将图片放大或者缩小。在处理图片的放大和缩小,我们会使用一个类叫做Matrix,这个类来帮我处理图片的缩放和移动。
      有一个问题,就是在处理图片的时候,一定在ImageView加载完了的时候,因为在加载完成之前,控件的大小,我们是不知道的,所以怎么知道ImageView加载完成呢?之后需要我们监听整个视图树,视图树一旦有更新,我们便知道ImageView加载完成了。
      设置监听事件和取消监听事件分别在两个方法里面:onAttachedToWindow和onDetachedFromWindow。这两个方法分别表示该控件加载到窗口和该控件从窗口上移除时。

    @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            getViewTreeObserver().addOnGlobalLayoutListener(this);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
    
        @Override
        public void onGlobalLayout() {
            if (!mOnce) {
                //获得控件的宽和高
                int width = getWidth();
                int height = getHeight();
                Log.i("main", "width = " + width + " height = " + height);
                //获得图片的宽和高
                Drawable d = getDrawable();
                if (d == null) {
                    return;
                }
                int dh = d.getIntrinsicHeight();
                int dw = d.getIntrinsicWidth();
                Log.i("main", "dw = " + dw + " dh = " + dh);
                float scale = 1;
                if (dw > width && dh < height) {
                    scale = width * 1.0f / dw;
                }
                if (dw < width && dh > height) {
                    scale = height * 1.0f / dh;
                }
                if ((dh > height && dw > width) || (dh < height && dw < width)) {
                    scale = Math.min(height * 1.0f / dh, width * 1.0f / dw);
                }
                int dx = (int) (width / 2 - dw/ 2);
                int dy = (int) (height / 2 - dh / 2);
                mInitScale = scale;
                mMaxScale = 4 * scale;
                mMinScale = 0.7f * scale;
                mMidScale = 2 * scale;
                mMatrix.postTranslate(dx, dy);
                mMatrix.postScale(scale, scale, width / 2, height / 2);
                setImageMatrix(mMatrix);
                RectF rectF = getRectF();
                Log.i("main", "width = " + rectF.width() + " height = " + rectF.height());
                mOnce = true;
            }
        }
    

    3.多指放大和缩小

      多指的操作用到了一个类--ScaleGestureDetector。我们得定义ScaleGestureDetector类型的变量,并且在构造方法里面初始化它。初始化的时候需要我们实现OnScaleGestureListener,并且实现三个方法

    public boolean onScale(ScaleGestureDetector detector); // 多指缩放时调用
    public boolean onScaleBegin(ScaleGestureDetector detector); //开始缩放时调用
    public void onScaleEnd(ScaleGestureDetector detector);  //缩放结束时调用
    

      要想这三个方法接受到我们的手指事件,我们还必须在onTouch方法这样写:mScaleGestureDetector.onTouchEvent(event);将事件传到ScaleGestureDetector里面去。

    @Override
        public boolean onScale(ScaleGestureDetector detector) {
            if (getDrawable() == null) {
                return true;
            }
            float scaleFactor = detector.getScaleFactor();
            mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
            setImageMatrix(mMatrix);
            checkBorderAndCenterWhenScale();
            return true;
        }
    
        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            return true;
        }
    
        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
            float scale = getScale();
            if (scale > mMaxScale) {
                mMatrix.postScale(mMaxScale / scale, mMaxScale / scale, detector.getFocusX(), detector.getFocusY());
            }
            if (scale < mMinScale) {
                mMatrix.postScale(mMinScale / scale, mMinScale / scale, detector.getFocusX(), detector.getFocusY());
            }
            setImageMatrix(mMatrix);
            checkBorderAndCenterWhenScale();
        }
    
    private float getScale() {
             float[] values = new float[9];
             mMatrix.getValues(values);
             return values[Matrix.MSCALE_X];
    }
    

      在我们根据手指聚集的焦点缩放,会发现图片会到处乱跑。这时候还需要加入一个边界限定的函数。

    private void checkBorderAndCenterWhenScale() {
            //获得控件的宽和高
            int width = getWidth();
            int height = getHeight();
            //获得图片的宽和高
            Drawable d = getDrawable();
            if (d == null) {
                return;
            }
            int dh = d.getIntrinsicHeight();
            int dw = d.getIntrinsicWidth();
            float dx = 0;
            float dy = 0;
            RectF rectF = getRectF();
            //放大的调整
            //左右
            if (rectF.width() >= width) {
                //图片向右偏移了,需要向左调整
                if (rectF.left > 0) {
                    dx = -rectF.left;
                }
                //图片向左偏移了,需要向右调整了
                if (rectF.right < width) {
                    dx = width - rectF.right;
                }
            }
            if (rectF.height() >= height) {
                //图片向下偏移了,需要向上调整
                if (rectF.top > 0) {
                    dy = -rectF.top;
                }
                //图片向上偏移了,需要向下调整
                if (rectF.bottom < height) {
                    dy = height - rectF.bottom;
                }
            }
            //缩小的调整
            //左右
            if (rectF.width() <= width) {
                /**
                 *
                 *dx = width / 2 - (rectF.left + rectF.right) / 2
                 *
                 */
                dx = width / 2 - (rectF.left + rectF.right) / 2;
            }
            if (rectF.height() <= height) {
                /**
                 * dy = height / 2 - (rectF.top + rectF.bottom) / 2
                 */
                dy = height / 2 - (rectF.top + rectF.bottom) / 2;
            }
            mMatrix.postTranslate(dx, dy);
            setImageMatrix(mMatrix);
        }
    

    4.双击放大和缩小

      双击放大和缩小需要一个类:GestureDetector。这个类里面含有一些双击事件处理的方法。
      我们在初始化GestureDetector变量的时候,需要我们传入一个OnGestureListener接口类型的参数,我们传入SimpleOnGestureListener就行,因为这个类对很多方法进行了空实现,不能我们去写很多的代码。

    mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener(){
                @Override
                public boolean onDoubleTap(MotionEvent e) {
                    int x = (int) e.getX();
                    int y = (int) e.getY();
                    float scale = getScale();
                    AutoScaleRunnable autoScaleRunnable = null;
                    if(scale < mInitScale)
                    {
                        //mMatrix.postScale(mInitScale / scale, mInitScale / scale, x, y);
                        autoScaleRunnable = new AutoScaleRunnable(x, y, mInitScale);
                    }
                    if(scale >= mInitScale && scale < mMidScale)
                    {
                        //mMatrix.postScale(mMidScale / scale, mMidScale / scale ,x, y);
                        autoScaleRunnable = new AutoScaleRunnable(x, y, mMidScale);
                    }
                    if(scale >= mMidScale && scale < mMaxScale)
                    {
                        //mMatrix.postScale(mMaxScale / scale, mMaxScale / scale, x, y);
                        autoScaleRunnable = new AutoScaleRunnable(x, y, mMaxScale);
                    }
                    if(scale == mMaxScale)
                    {
                       // mMatrix.postScale(mInitScale / scale, mInitScale /  scale, x, y);
                        autoScaleRunnable = new AutoScaleRunnable(x, y, mInitScale);
                    }
                    //setImageMatrix(mMatrix);
                    postDelayed(autoScaleRunnable, 16);
                    return true;
                }
            });
    

      同时,我们还是在onTouch里面将事件传过来。

    if(mGestureDetector.onTouchEvent(event))
    {
        return true;
    }
    

      双击放大或者缩小,我们会发现,放大和缩小都是瞬间完成,所以我们需要实现缓慢的完成,这个需要我们调用postDelayed方法。postDelayed需要我们传入一个Runnabl,所以我们得自定义一个Runnable。

    rivate class AutoScaleRunnable implements Runnable
        {
            private int x = 0;
            private int y = 0;
            private float mTargetScale = 0;
            private float mTempScale = 0;
            private static final float BIGGER = 1.13f;
            private static final float SMALLER = 0.87f;
            public AutoScaleRunnable(int x, int y, float targetScale)
            {
                this.x = x;
                this.y = y;
                this.mTargetScale = targetScale;
                float scale = getScale();
                if(scale > targetScale)
                {
                    mTempScale = SMALLER;
                }
                if(scale < targetScale)
                {
                    mTempScale = BIGGER;
                }
            }
            @Override
            public void run() {
                mMatrix.postScale(mTempScale, mTempScale, x, y);
                setImageMatrix(mMatrix);
    
                float currentScale = getScale();
                if((mTempScale > 1.0f && currentScale < mTargetScale)||(mTempScale < 1.0f && currentScale > mTargetScale))
                {
                    postDelayed(this, 16);
                }
                else
                {
                    float scale = mTargetScale / getScale();
                    mMatrix.postScale(scale, scale, x, y);
                    setImageMatrix(mMatrix);
                    checkBorderAndCenterWhenScale();
                }
    
            }
        }
    

    5.图片的移动

    float x = 0;
            float y = 0;
            int pointCount = event.getPointerCount();
            for (int i = 0; i < pointCount; i++) {
                x += event.getX(i);
                y += event.getY(i);
            }
            //Log.i("main", "pointCount = " + pointCount);
            x /= pointCount * 1.0f;
            y /= pointCount * 1.0f;
            if (pointCount != mLastPointCount) {
                mIsCanDrag = false;
                mLastX = x;
                mLastY = y;
            }
            mLastPointCount = pointCount;
            RectF rectF = getRectF();
            Log.i("main", "rectFHeight = " + rectF.height() + " rectFWidth = " + rectF.width() + "\n width = " + getWidth() + " height = " + getHeight());
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    if(rectF.width() > getWidth() || rectF.height() > getHeight())
                    {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    if(rectF.width() > getWidth() || rectF.height() >getHeight())
                    {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    mIsCheckTopAndBottom = mIsCheckLeftAndRight = true;
                    float dx = x - mLastX;
                    float dy = y - mLastY;
                    if (!mIsCanDrag) {
                        mIsCanDrag = isMoveAction(dx, dy);
                    }
                    if (mIsCanDrag) {
                        if (rectF.height() <= getHeight()) {
                            mIsCheckTopAndBottom = false;
                            dy = 0;
                        }
                        if (rectF.width() <= getWidth()) {
                            mIsCheckLeftAndRight = false;
                            dx = 0;
                        }
                        mMatrix.postTranslate(dx * 1.2f, dy * 1.2f);
                        setImageMatrix(mMatrix);
                        checkBorderWhenTranslate();
                    }
                    mLastX = x;
                    mLastY = y;
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    mLastPointCount = 0;
                    break;
                }
            }
    

      在移动的时候,也需要我们进行边界的限定

    private void checkBorderWhenTranslate()
        {
            RectF rectF = getRectF();
            int deltaX = 0;
            int deltaY = 0;
            int height = getHeight();
            int width = getWidth();
            if(rectF.left > 0 && mIsCheckLeftAndRight) {
                deltaX = (int) -rectF.left;
            }
            if(rectF.right < width && mIsCheckLeftAndRight)
            {
                deltaX = (int) (width - rectF.right);
            }
            if(rectF.top > 0 && mIsCheckTopAndBottom)
            {
                deltaY = (int) - rectF.top;
            }
            if(rectF.bottom < height && mIsCheckTopAndBottom)
            {
                deltaY = (int) (height - rectF.bottom);
            }
            mMatrix.postTranslate(deltaX, deltaY);
            setImageMatrix(mMatrix);
        }
    

    5.冲突事件的处理

      当我们在移动图片的时候,我们会发现ViewPager会截获子View的事件。这时候我们只需要在我们想要不被截获的时候调用此方法就行:getParent().requestDisallowInterceptTouchEvent(true);

    相关文章

      网友评论

        本文标题:Android 自定义控件基础-图片预览和多点触控

        本文链接:https://www.haomeiwen.com/subject/bdfvbxtx.html