自定義圖片控件,實現(xiàn)雙擊放大,多點觸碰放大縮小

xml:

 <com.meiJia.operator.view.widget.MyPhotoView
       android:id="@+id/iv_big_img"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

MyPhotoView:

/**
 * 自定義圖片展示  支持雙擊放大,多點觸碰
 *
 * create by meiJia on 2019/7/26
 */
public class MyPhotoView extends android.support.v7.widget.AppCompatImageView implements ViewTreeObserver.OnGlobalLayoutListener{
    private boolean mIsOneLoad = true;

    //初始化的比例,也就是最小比例
    private float mInitScale;
    //圖片最大比例
    private float mMaxScale;
    //雙擊能達(dá)到的最大比例
    private float mMidScale;

    private Matrix mScaleMatrix;
    //捕獲用戶多點觸控
    private ScaleGestureDetector mScaleGestureDetector;

    //移動
    private GestureDetector gestureDetector;

    //雙擊
    private boolean isEnlarge = false;//是否放大
    private ValueAnimator mAnimator; //雙擊縮放動畫

    //滾動
    private OverScroller scroller;
    private int mCurrentX, mCurrentY;
    private ValueAnimator translationAnimation; //慣性移動動畫

    //單擊
    private OnClickListener onClickListener;//單擊監(jiān)聽

    public MyPhotoView(Context context) {
        this(context, null);
    }

    public MyPhotoView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyPhotoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //記住,一定要把ScaleType設(shè)置成ScaleType.MATRIX,否則無法縮放
        setScaleType(ScaleType.MATRIX);

        scroller = new OverScroller(context);
        mScaleMatrix = new Matrix();
        //手勢縮放
        mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                scale(detector);
                return true;
            }

            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
                scaleEnd(detector);
            }
        });

        //滑動和雙擊監(jiān)聽
        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, final float distanceX, final float distanceY) {
                //滑動監(jiān)聽
                onTranslationImage(-distanceX, -distanceY);
                return true;
            }

            @Override
            public boolean onDoubleTap(MotionEvent e) {
                //雙擊監(jiān)聽
                onDoubleDrowScale(e.getX(), e.getY());
                return true;
            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

                //滑動慣性處理
                mCurrentX = (int) e2.getX();
                mCurrentY = (int) e2.getY();

                RectF rectF = getMatrixRectF();
                if (rectF == null) {
                    return false;
                }
                //startX為當(dāng)前圖片左邊界的x坐標(biāo)
                int startX = mCurrentX;
                int startY = mCurrentY;
                int minX = 0, maxX = 0, minY = 0, maxY = 0;
                int vX = Math.round(velocityX);
                int vY = Math.round(velocityY);

                maxX = Math.round(rectF.width());
                maxY = Math.round(rectF.height());

                if (startX != maxX || startY != maxY) {
                    //調(diào)用fling方法,然后我們可以通過調(diào)用getCurX和getCurY來獲得當(dāng)前的x和y坐標(biāo)
                    //這個坐標(biāo)的計算是模擬一個慣性滑動來計算出來的,我們根據(jù)這個x和y的變化可以模擬
                    //出圖片的慣性滑動
                    scroller.fling(startX, startY, vX, vY, 0, maxX, 0, maxY, maxX, maxY);
                }

                if (translationAnimation != null && translationAnimation.isStarted())
                    translationAnimation.end();

                translationAnimation = ObjectAnimator.ofFloat(0, 1);
                translationAnimation.setDuration(500);
                translationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        if (scroller.computeScrollOffset()) {
                            //獲得當(dāng)前的x坐標(biāo)
                            int newX = scroller.getCurrX();
                            int dx = newX - mCurrentX;
                            mCurrentX = newX;
                            //獲得當(dāng)前的y坐標(biāo)
                            int newY = scroller.getCurrY();
                            int dy = newY - mCurrentY;
                            mCurrentY = newY;
                            //進(jìn)行平移操作
                            if (dx != 0 && dy != 0)
                                onTranslationImage(dx, dy);
                        }
                    }
                });
                translationAnimation.start();
                return super.onFling(e1, e2, velocityX, velocityY);
            }

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                //單擊事件
                if(onClickListener != null)
                    onClickListener.onClick(MyPhotoView.this);
                return true;
            }
        });

    }

    @Override
    public void setOnClickListener(OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }

    /**
     * imageView加載完成后調(diào)用,獲取imageView加載完成后的圖片大小
     */
    @Override
    public void onGlobalLayout() {
        if (mIsOneLoad) {

            //得到控件的寬和高
            int width = getWidth();
            int height = getHeight();

            //獲取圖片,如果沒有圖片則直接退出
            Drawable d = getDrawable();
            if (d == null)
                return;
            //獲取圖片的寬和高
            int dw = d.getIntrinsicWidth();
            int dh = d.getIntrinsicHeight();

            float scale = 1.0f;
            if (dw > width && dh <= height) {
                scale = width * 1.0f / dw;
            }
            if (dw <= width && dh > height) {
                scale = height * 1.0f / dh;
            }
            if ((dw <= width && dh <= height) || (dw >= width && dh >= height)) {
                scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
            }

            //圖片原始比例,圖片回復(fù)原始大小時使用
            mInitScale = scale;
            //圖片雙擊后放大的比例
            mMidScale = mInitScale * 2;
            //手勢放大時最大比例
            mMaxScale = mInitScale * 4;

            //設(shè)置移動數(shù)據(jù),把改變比例后的圖片移到中心點
            float translationX = width * 1.0f / 2 - dw / 2;
            float translationY = height * 1.0f / 2 - dh / 2;

            mScaleMatrix.postTranslate(translationX, translationY);
            mScaleMatrix.postScale(mInitScale, mInitScale, width * 1.0f / 2, height * 1.0f / 2);
            setImageMatrix(mScaleMatrix);
            mIsOneLoad = false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mScaleGestureDetector.onTouchEvent(event)|
                gestureDetector.onTouchEvent(event);
    }

    //手勢操作(縮放)
    public void scale(ScaleGestureDetector detector) {

        Drawable drawable = getDrawable();
        if (drawable == null)
            return;

        float scale = getScale();
        //獲取手勢操作的值,scaleFactor>1說明放大,<1則說明縮小
        float scaleFactor = detector.getScaleFactor();
        //獲取手勢操作后的比例,當(dāng)放操作后比例在[mInitScale,mMaxScale]區(qū)間時允許放大
        mScaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
        setImageMatrix(mScaleMatrix);
        removeBorderAndTranslationCenter();

    }

    //手勢操作結(jié)束
    public void scaleEnd(ScaleGestureDetector detector) {
        float scale = getScale();
        scale = detector.getScaleFactor() * scale;
        if (scale < mInitScale) {
            scaleAnimation(mInitScale, getWidth() / 2, getHeight() / 2);
        } else if (scale > mMaxScale) {
            scaleAnimation(mMaxScale, getWidth() / 2, getHeight() / 2);
        }
    }

    //手勢操作(移動)
    private void onTranslationImage(float dx, float dy) {

        if (getDrawable() == null)
            return;

        RectF rect = getMatrixRectF();

        //圖片寬度小于控件寬度時不允許左右移動
        if (rect.width() <= getWidth())
            dx = 0.0f;
        //圖片高度小于控件寬度時,不允許上下移動
        if (rect.height() <= getHeight())
            dy = 0.0f;

        //移動距離等于0,那就不需要移動了
        if (dx == 0.0f && dy == 0.0f)
            return;

        mScaleMatrix.postTranslate(dx, dy);
        setImageMatrix(mScaleMatrix);
        //去除移動邊界
        removeBorderAndTranslationCenter();
    }

    //消除控件邊界和把圖片移動到中間
    private void removeBorderAndTranslationCenter() {
        RectF rectF = getMatrixRectF();
        if (rectF == null)
            return;

        int width = getWidth();
        int height = getHeight();
        float widthF = rectF.width();
        float heightF = rectF.height();
        float left = rectF.left;
        float right = rectF.right;
        float top = rectF.top;
        float bottom = rectF.bottom;
        float translationX = 0.0f, translationY = 0.0f;

        if (left > 0) {
            //左邊有邊界
            if (widthF > width) {
                //圖片寬度大于控件寬度,移動到左邊貼邊
                translationX = -left;
            } else {
                //圖片寬度小于控件寬度,移動到中間
                translationX = width * 1.0f / 2f - (widthF * 1.0f / 2f + left);
            }
        } else if (right < width) {
            //右邊有邊界
            if (widthF > width) {
                //圖片寬度大于控件寬度,移動到右邊貼邊
                translationX = width - right;
            } else {
                //圖片寬度小于控件寬度,移動到中間
                translationX = width * 1.0f / 2f - (widthF * 1.0f / 2f + left);
            }
        }

        if (top > 0) {
            //頂部有邊界
            if (heightF > height) {
                //圖片高度大于控件高度,去除頂部邊界
                translationY = -top;
            } else {
                //圖片高度小于控件寬度,移動到中間
                translationY = height * 1.0f / 2f - (top + heightF * 1.0f / 2f);
            }
        } else if (bottom < height) {
            //底部有邊界
            if (heightF > height) {
                //圖片高度大于控件高度,去除頂部邊界
                translationY = height - bottom;
            } else {
                //圖片高度小于控件寬度,移動到中間
                translationY = height * 1.0f / 2f - (top + heightF * 1.0f / 2f);
            }
        }

        mScaleMatrix.postTranslate(translationX, translationY);
        setImageMatrix(mScaleMatrix);
    }

    /**
     * 雙擊改變大小
     *
     * @param x 點擊的中心點
     * @param y 點擊的中心點
     */
    private void onDoubleDrowScale(float x, float y) {
        //如果縮放動畫已經(jīng)在執(zhí)行,那就不執(zhí)行任何事件
        if (mAnimator != null && mAnimator.isRunning())
            return;

        float drowScale = getDoubleDrowScale();
        //執(zhí)行動畫縮放,不然太難看了
        scaleAnimation(drowScale, x, y);
    }

    /**
     * 縮放動畫
     *
     * @param drowScale 縮放的比例
     * @param x         中心點
     * @param y         中心點
     */
    private void scaleAnimation(final float drowScale, final float x, final float y) {
        if (mAnimator != null && mAnimator.isRunning())
            return;
        mAnimator = ObjectAnimator.ofFloat(getScale(), drowScale);
        mAnimator.setDuration(300);
        mAnimator.setInterpolator(new AccelerateInterpolator());
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = ((float) animation.getAnimatedValue()) / getScale();
                mScaleMatrix.postScale(value, value, x, y);
                setImageMatrix(mScaleMatrix);
                removeBorderAndTranslationCenter();
            }
        });

        mAnimator.start();
    }


    //返回雙擊后改變的大小比例(我們希望縮放誤差在deviation范圍內(nèi))
    private float getDoubleDrowScale() {
        float deviation = 0.05f;
        float drowScale = 1.0f;
        float scale = getScale();

        if (Math.abs(mInitScale - scale) < deviation)
            scale = mInitScale;
        if (Math.abs(mMidScale - scale) < deviation)
            scale = mMidScale;
        if (Math.abs(mMaxScale - scale) < deviation)
            scale = mMaxScale;

        if (scale != mMidScale) {
            //當(dāng)前大小不等于mMidScale,則調(diào)整到mMidScale
            drowScale = mMidScale;
            isEnlarge = scale < mMidScale;
        } else {
            //如果等于mMidScale,則判斷放大或者縮小
            //判斷是放大或者縮小,如果上次是放大,則繼續(xù)放大,縮小則繼續(xù)縮小
            if (isEnlarge) {
                //放大
                drowScale = mMaxScale;
            } else {
                //縮小
                drowScale = mInitScale;
            }
        }
        return drowScale;
    }


    //獲取圖片寬高以及左右上下邊界
    private RectF getMatrixRectF() {

        Drawable drawable = getDrawable();
        if (drawable == null) {
            return null;
        }
        RectF rectF = new RectF(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());
        Matrix matrix = getImageMatrix();
        matrix.mapRect(rectF);

        return rectF;
    }


    /**
     * 獲取當(dāng)前圖片的縮放值
     *
     * @return
     */
    private float getScale() {
        float[] values = new float[9];
        mScaleMatrix.getValues(values);
        return values[Matrix.MSCALE_X];
    }


    /**
     * 解決和父控件滑動沖突 只要圖片邊界超過控件邊界,返回true
     *
     * @param direction
     * @return true 禁止父控件滑動
     */
    @Override
    public boolean canScrollHorizontally(int direction) {
        RectF rect = getMatrixRectF();
        if (rect == null || rect.isEmpty())
            return false;

        if (direction > 0) {
            return rect.right >= getWidth() + 1;
        } else {
            return rect.left <= 0 - 1;
        }

    }

    /**
     * 同樓上
     *
     * @param direction
     * @return
     */
    @Override
    public boolean canScrollVertically(int direction) {
        RectF rect = getMatrixRectF();
        if (rect == null || rect.isEmpty())
            return false;

        if (direction > 0) {
            return rect.bottom >= getHeight() + 1;
        } else {
            return rect.top <= 0 - 1;
        }
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容