1. 程式人生 > >android實現控制元件的手勢縮放、移動以及雙擊還原

android實現控制元件的手勢縮放、移動以及雙擊還原

我分四部分介紹:

       1.Imageview利用Matrix和OnScaleGestureListener實現手勢縮放;

       2.在第一部分的基礎上實現圖片跟隨手指進行滑動;

       3.在一、二的基礎上利用GestureDetector的OnDoubleTap回撥實現圖片雙擊還原;

       4.在一、二、三的基礎上將ImageView擴充套件為FrameLayout,實現佈局內所有子控制元件都能手勢縮放、移動、雙擊還原。

一、Imageview利用Matrix和OnScaleGestureListener實現手勢縮放

1.基礎知識:

Matrix是一個3*3的矩陣

* MSCALE_X  MSKEW_X    MTRANS_X
* MKEW_Y    MSCALE_Y   MTRANS_Y
* MPERSP_0  MPERSP_1   MPERSP_2

Matrix.postScale(x, y, px, py)矩陣縮放,x是x方向上的縮放大小,y是y方向上的縮放大小,(px,py)是縮放中心

Matrix.postTranslate(dx, dy)矩陣平移

setImageMatrix(matrix)將自己的matrix賦值給ImageView的matrix從而實現ImageView中圖片的縮放、平移等操作

OnScaleGestureListener中的onScale回撥中的ScaleGestureDetector.getScaleFactor()可以獲取當前手勢縮放的大小

ImageView需要設定setScaleType(ScaleType.MATRIX);才能實現縮放

OnScaleGestureListener中的OnScaleBegin回撥必須return true才有縮放效果

2.實現思路:

實現OnScaleGestureListener監聽,在OnScale回撥方法中,根據ScaleGestureDetector.getScaleFactor()獲取當前手勢縮放的大小,利用Matrix.postScale(x, y, px, py)方法將縮放大小儲存在矩陣中,用setImageMatrix(matrix)方法,將矩陣賦值給ImageView的內部矩陣,從而實現圖片的手勢縮放,是不是很簡單。

3.實現程式碼:

/**
 * 只支援手勢縮放的ImageView
 */
public class ZoomOnlyImageView extends AppCompatImageView implements ScaleGestureDetector.OnScaleGestureListener {

    private ScaleGestureDetector scaleGestureDetector;//手勢縮放
    /**
     * MSCALE_X  MSKEW_X    MTRANS_X
     * MKEW_Y    MSCALE_Y   MTRANS_Y
     * MPERSP_0  MPERSP_1   MPERSP_2
     */
    private Matrix mMatrix;//縮放矩陣
    private float maxScale = 4.0f;//最大縮放到原圖的四倍
    private float minScale = 0.5f;//最小縮放到原圖的0.5倍

    public ZoomOnlyImageView(Context context) {
        super(context);
        init(context);
    }

    public ZoomOnlyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ZoomOnlyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    //初始化引數
    private void init(Context context) {
        setScaleType(ScaleType.MATRIX);//允許imageview縮放
        scaleGestureDetector = new ScaleGestureDetector(new WeakReference<Context>(context).get(),
                new WeakReference<ZoomOnlyImageView>(this).get());
        mMatrix = new Matrix();
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {//OnScaleGestureListener裡的方法
        if (getDrawable() == null) {
            return true;
        }
        //獲取本次的縮放值
        float scale = detector.getScaleFactor();
        Log.i("zhangdi", "scaleFactor = "+scale);
        float preScale = getPreScale();
        Log.i("zhangdi", "preScale = "+preScale);
        if (preScale * scale < maxScale &&
                preScale * scale > minScale) {//preScale * scale可以計算出此次縮放執行的話,縮放值是多少

            //detector.getFocusX()縮放手勢中心的x座標,detector.getFocusY()y座標
//            mMatrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY());
            mMatrix.postScale(scale, scale, getWidth()/2, getHeight()/2);
            setImageMatrix(mMatrix);
            makeDrawableCenter();
        }
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {//OnScaleGestureListener裡的方法,縮放開始
        return true;//必須返回true才有效果
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {//OnScaleGestureListener裡的方法,縮放結束

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return scaleGestureDetector.onTouchEvent(event);
    }

    //獲取目前一共縮放了多少
    private float getPreScale() {
        float[] matrix = new float[9];
        mMatrix.getValues(matrix);
        return matrix[Matrix.MSCALE_X];
    }

    //縮小的時候讓圖片居中
    private void makeDrawableCenter() {

        RectF rect = new RectF();
        Drawable d = getDrawable();
        if (d != null) {
            rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());//設定rect的初始四個角值是圖片的四個頂點值
            Log.i("zhangdi", "bitmapWidth = "+d.getIntrinsicWidth()+", bitmapHeight = "+d.getIntrinsicHeight());
            mMatrix.mapRect(rect);//獲取通過當前矩陣變換後的四個角值
            Log.i("zhangdi", "matrixWidth = "+rect.width()+", matrixHeight = "+rect.height());
            Log.i("zhangdi", "bmLeft: "+rect.left+" bmRight: "+rect.right+" bmTop: "+rect.top+" bmBottom: "+rect.bottom);
        }

        int width = getWidth();
        int height = getHeight();

        float dx=0, dy=0;

        // 如果寬或高大於螢幕,則控制範圍
        if (rect.width() >= width)
        {
            if (rect.left > 0)
            {
                dx = -rect.left;
            }
            if (rect.right < width)
            {
                dx = width - rect.right;
            }
        }
        if (rect.height() >= height)
        {
            if (rect.top > 0)
            {
                dy = -rect.top;
            }
            if (rect.bottom < height)
            {
                dy = height - rect.bottom;
            }
        }

        if (rect.width() < width) {
            dx = width/2 - (rect.right - rect.width()/2);//控制元件中心點橫座標減去圖片中心點橫座標為X方向應移動距離
        }
        if (rect.height() < height) {
            dy = height/2 - (rect.bottom - rect.height()/2);
        }
        Log.i("zhangdi", "dx = "+dx+", dy = "+dy);

        if (dx != 0 || dy != 0) {
            mMatrix.postTranslate(dx, dy);
            setImageMatrix(mMatrix);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        setImageDrawable(null);
        scaleGestureDetector = null;
    }
}

二、在第一部分的基礎上實現圖片跟隨手指進行滑動

1.實現思路:把每次的手指移動距離賦值給矩陣,將矩陣賦值給ImageView實現圖片的平移,注意控制移動範圍不能超過圖片範圍。

2.程式碼實現:修改第一部分的onTouchEvent方法

@Override
public boolean onTouchEvent(MotionEvent event) {
    scaleGestureDetector.onTouchEvent(event);

    float x=0, y=0;
    final int pointerCount = event.getPointerCount();//獲取手指個數
    for (int i=0; i<pointerCount; i++) {
        x += event.getX(i);
        y += event.getY(i);
    }
    x = x/pointerCount;//獲取x平均值
    y = y/pointerCount;//獲取y平均值
    if (pointerCount != lastPointerCount)
    {
        lastX = x;
        lastY = y;
    }
    Log.i("zhangdi", "pointCount: "+pointerCount+", lastPointCount: "+lastPointerCount);
    lastPointerCount = pointerCount;
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            lastX = x;
            lastY = y;
            break;
        case MotionEvent.ACTION_MOVE:
            float delX = x - lastX;//x方向計算移動距離
            float delY = y - lastY;//計算y方向移動距離

            RectF rectF = getMatrixRectF();
            //控制移動邊界不能超出圖片範圍
            if ((rectF.left >= 0 && delX > 0) || (rectF.right <= getWidth() && delX < 0)) {
                delX = 0;
            }
            if ((rectF.top >= 0 && delY > 0) || (rectF.bottom <= getHeight() && delY < 0)) {
                delY = 0;
            }

            mMatrix.postTranslate(delX, delY);
            setImageMatrix(mMatrix);
            lastX = x;
            lastY = y;
            break;
        case MotionEvent.ACTION_UP:
            lastPointerCount = 0;
            break;
    }
    return true;
}

三、在一、二的基礎上利用GestureDetector的OnDoubleTap回撥實現圖片雙擊還原

1.實現思路:利用GestureDetector的OnDoubleTap監聽回撥,判斷當前矩陣的縮放比例是不是1,不是的話則matrix.reset(),將matrix賦值給ImageView實現圖片雙擊還原。

2.實現程式碼:

   1.修改init()方法,建立GestureDetector物件:

//初始化引數
private void init(Context context) {
    setScaleType(ScaleType.MATRIX);//允許imageview縮放
    scaleGestureDetector = new ScaleGestureDetector(new WeakReference<Context>(context).get(),
            new WeakReference<ZoomTranslateDoubleTapImageView>(this).get());
    mMatrix = new Matrix();
    gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener(){
        @Override
        public boolean onDoubleTap(MotionEvent e) {//雙擊圖片還原
            if (getPreScale() != 1.0f) {
                ZoomTranslateDoubleTapImageView.this.postDelayed(new Runnable() {
                    @Override
                    public void run() {

                        mMatrix.reset();
                        setImageMatrix(mMatrix);
                        makeDrawableCenter();
                    }
                },16);
            }
            return true;
        }
    });
}

2.在onTouchEvent方法中增加雙擊事件關聯:

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

四、在一、二、三的基礎上將ImageView擴充套件為FrameLayout,實現佈局內所有子控制元件都能手勢縮放、移動、雙擊還原:

1.實現思路:與上面的方法基本沒什麼區別,只需要把繼承ImageView改為繼承FrameLayout,但是FrameLayout沒有setImageMatrix方法,我們進入ImageView的原始碼中可以看出setImageMatrix方法主要是將matrix賦值給了mDrawMatrix,而mDrawMatrix在onDraw方法中作用在了canvas上canvas.concat(mDrawMatrix),從而使圖片實現矩陣中的相關操作,所以我們的FrameLayout雖然沒有setImageMatrix這個方法,但是我們只需要在Matrix相關操作賦值後,呼叫invalidate()方法重繪介面,並且在它的onDraw方法中使canvas與矩陣關聯就行了,需要注意的是ViewGroup出於效率的考慮有些是預設繞過onDraw方法的,所以我們需要重寫dispatchDraw方法而不是onDraw方法。

2.實現程式碼:

1.將所有的setImageMatrix方法替換成invalidate方法;

2.重寫dispatchDraw方法:

@Override
protected void dispatchDraw(Canvas canvas) {
    View view = getChildAt(0);
    canvas.save();
    canvas.concat(mMatrix);
    view.draw(canvas);
    canvas.restore();
}

3.使用注意事項:

在自定義佈局中只能包含一個ViewGroup控制元件,然後所有的子view都放到這個ViewGroup中才能實現預期效果,類似ScrollerView的使用。

五、程式碼下載,我上傳的是android studio裡的一個module,請自行建立project匯入使用。