我们可以在 Android 中使用缩放手势检测器进行缩放吗?

Posted

技术标签:

【中文标题】我们可以在 Android 中使用缩放手势检测器进行缩放吗?【英文标题】:Can we use scale gesture detector for pinch zoom in Android? 【发布时间】:2011-08-13 00:39:33 【问题描述】:

我们可以在 android 中使用缩放手势检测器进行捏缩放吗?

【问题讨论】:

【参考方案1】:

你可以用这个

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

public class MyImageView extends View 

private static final int INVALID_POINTER_ID = -1;

    private Drawable mImage;
    private float mPosX;
    private float mPosY;

    private float mLastTouchX;
    private float mLastTouchY;
    private int mActivePointerId = INVALID_POINTER_ID;

    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;

    public MyImageView(Context context) 
        this(context, null, 0);
    mImage=act.getResources().getDrawable(context.getResources().getIdentifier("imag­ename", "drawable", "packagename"));

        mImage.setBounds(0, 0, mImage.getIntrinsicWidth(), mImage.getIntrinsicHeight());
    

    public MyImageView(Context context, AttributeSet attrs) 
        this(context, attrs, 0);
    

    public MyImageView(Context context, AttributeSet attrs, int defStyle) 
        super(context, attrs, defStyle);
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    

    @Override
    public boolean onTouchEvent(MotionEvent ev) 
        // Let the ScaleGestureDetector inspect all events.
        mScaleDetector.onTouchEvent(ev);

        final int action = ev.getAction();
        switch (action & MotionEvent.ACTION_MASK) 
        case MotionEvent.ACTION_DOWN: 
            final float x = ev.getX();
            final float y = ev.getY();

            mLastTouchX = x;
            mLastTouchY = y;
            mActivePointerId = ev.getPointerId(0);
            break;
        

        case MotionEvent.ACTION_MOVE: 
            final int pointerIndex = ev.findPointerIndex(mActivePointerId);
            final float x = ev.getX(pointerIndex);
            final float y = ev.getY(pointerIndex);

            // Only move if the ScaleGestureDetector isn't processing a gesture.
            if (!mScaleDetector.isInProgress()) 
                final float dx = x - mLastTouchX;
                final float dy = y - mLastTouchY;

                mPosX += dx;
                mPosY += dy;

                invalidate();
            

            mLastTouchX = x;
            mLastTouchY = y;

            break;
        

        case MotionEvent.ACTION_UP: 
            mActivePointerId = INVALID_POINTER_ID;
            break;
        

        case MotionEvent.ACTION_CANCEL: 
            mActivePointerId = INVALID_POINTER_ID;
            break;
        

        case MotionEvent.ACTION_POINTER_UP: 
            final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) 
                    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
            final int pointerId = ev.getPointerId(pointerIndex);
            if (pointerId == mActivePointerId) 
                // This was our active pointer going up. Choose a new
                // active pointer and adjust accordingly.
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                mLastTouchX = ev.getX(newPointerIndex);
                mLastTouchY = ev.getY(newPointerIndex);
                mActivePointerId = ev.getPointerId(newPointerIndex);
            
            break;
        
        

        return true;
    

    @Override
    public void onDraw(Canvas canvas) 
        super.onDraw(canvas);

        canvas.save();
        Log.d("DEBUG", "X: "+mPosX+" Y: "+mPosY);
        canvas.translate(mPosX, mPosY);
        canvas.scale(mScaleFactor, mScaleFactor);
        mImage.draw(canvas);
        canvas.restore();
    

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener 
        @Override
        public boolean onScale(ScaleGestureDetector detector) 
            mScaleFactor *= detector.getScaleFactor();

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));

            invalidate();
            return true;
        
    


在您的 activity.setContentView(new MyImageView(this)); 中调用它

【讨论】:

在缩小时会将您带到点 0,0。它不会放大手指的初始点。为什么会这样? sajjoo:我认为你可以使用ScaleGestureDetector.getFocusX,Y() 我试过了,但是缩放非常迟钝且不稳定(我正在以编程方式在画布上画线)。知道为什么吗? 如何添加 canvas.drawCircle 而不是 Image 感谢@Flexo 和 Jazz,你们为我节省了很多时间【参考方案2】:

您可以创建一个实现OnTouchListener 的可重用类来完成此操作。

public class MyScaleGestures implements OnTouchListener, OnScaleGestureListener        
    private View view;
    private ScaleGestureDetector gestureScale;
    private float scaleFactor = 1;  
    private boolean inScale = false;

    public MyScaleGestures (Context c) gestureScale = new ScaleGestureDetector(c, this); 

    @Override
    public boolean onTouch(View view, MotionEvent event) 
        this.view = view; 
        gestureScale.onTouchEvent(event);
        return true;
       

    @Override
    public boolean onScale(ScaleGestureDetector detector) 
        scaleFactor *= detector.getScaleFactor();
        scaleFactor = (scaleFactor < 1 ? 1 : scaleFactor); // prevent our view from becoming too small //
        scaleFactor = ((float)((int)(scaleFactor * 100))) / 100; // Change precision to help with jitter when user just rests their fingers //
        view.setScaleX(scaleFactor);
        view.setScaleY(scaleFactor);
        return true;
    

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) 
        inScale = true;
        return true;
    

    @Override
    public void onScaleEnd(ScaleGestureDetector detector)  inScale = false; 

然后像这样将其分配为您的ViewOnTouchListener

myView.setOnTouchListener(new MyScaleGestures(context));

如果您想为View 添加滚动功能,您需要从OnGestureListener 接口实现onScroll。您可以将此覆盖添加到 MyScaleGestures 类来完成此操作。

@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float x, float y) 
    float newX = view.getX();
    float newY = view.getY();
    if(!inScale)
        newX -= x;
        newY -= y;
    
    WindowManager wm = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
    Display d = wm.getDefaultDisplay();
    Point p = new Point();
    d.getSize(p);

    if (newX > (view.getWidth() * scaleFactor - p.x) / 2)
        newX = (view.getWidth() * scaleFactor - p.x) / 2;
     else if (newX < -((view.getWidth() * scaleFactor - p.x) / 2))
        newX = -((view.getWidth() * scaleFactor - p.x) / 2);
    

    if (newY > (view.getHeight() * scaleFactor - p.y) / 2)
        newY = (view.getHeight() * scaleFactor - p.y) / 2;
     else if (newY < -((view.getHeight() * scaleFactor - p.y) / 2))
        newY = -((view.getHeight() * scaleFactor - p.y) / 2);
    

    view.setX(newX);
    view.setY(newY);

    return true;

以上所有操作的最终结果将给你这样的课程:

public class StandardGestures implements OnTouchListener, OnGestureListener, OnDoubleTapListener, OnScaleGestureListener 
    private View view;
    private GestureDetector gesture;
    private ScaleGestureDetector gestureScale;
    private float scaleFactor = 1;
    private boolean inScale;

    public StandardGestures(Context c)
        gesture = new GestureDetector(c, this);
        gestureScale = new ScaleGestureDetector(c, this);
    

    @Override
    public boolean onTouch(View view, MotionEvent event) 
        this.view = view;
        gesture.onTouchEvent(event);
        gestureScale.onTouchEvent(event);
        return true;
    

    @Override
    public boolean onDown(MotionEvent event) 
        return true;
    

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2, float x, float y) 
        return true;
    

    @Override
    public void onLongPress(MotionEvent event) 
    

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float x, float y) 
        float newX = view.getX();
        float newY = view.getY();
        if(!inScale)
            newX -= x;
            newY -= y;
        
        WindowManager wm = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
        Display d = wm.getDefaultDisplay();
        Point p = new Point();
        d.getSize(p);

        if (newX > (view.getWidth() * scaleFactor - p.x) / 2)
            newX = (view.getWidth() * scaleFactor - p.x) / 2;
         else if (newX < -((view.getWidth() * scaleFactor - p.x) / 2))
            newX = -((view.getWidth() * scaleFactor - p.x) / 2);
        

        if (newY > (view.getHeight() * scaleFactor - p.y) / 2)
            newY = (view.getHeight() * scaleFactor - p.y) / 2;
         else if (newY < -((view.getHeight() * scaleFactor - p.y) / 2))
            newY = -((view.getHeight() * scaleFactor - p.y) / 2);
        

        view.setX(newX);
        view.setY(newY);

        return true;
    

    @Override
    public void onShowPress(MotionEvent event) 
    

    @Override
    public boolean onSingleTapUp(MotionEvent event) 
        return true;
    

    @Override
    public boolean onDoubleTap(MotionEvent event) 
        view.setVisibility(View.GONE);
        return true;
    

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) 
        return true;
    

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) 
        return true;
    

    @Override
    public boolean onScale(ScaleGestureDetector detector) 

        scaleFactor *= detector.getScaleFactor();
        scaleFactor = scaleFactor < 1 ? 1 : scaleFactor; // prevent our image from becoming too small
        scaleFactor = (float) (int) (scaleFactor * 100) / 100; // Change precision to help with jitter when user just rests their fingers //
        view.setScaleX(scaleFactor);
        view.setScaleY(scaleFactor);
        onScroll(null, null, 0, 0); // call scroll to make sure our bounds are still ok //
        return true;
    

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) 
        inScale = true;
        return true;
    

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) 
        inScale = false;
        onScroll(null, null, 0, 0); // call scroll to make sure our bounds are still ok //
    

【讨论】:

这是一个很好的答案,因为代码不涉及创建派生自 ImageView 的新类。不幸的是,构造函数和类名不匹配,布尔值 inScale 丢失并且只有在存在 onScroll() 方法时才有意义。比上述更重要的是,onScroll()method 不会覆盖任何东西(该类是否应该从另一个侦听器派生?),因此不起作用。或者,也许我错过了一些大事。这个答案可以改进吗? @Baltasarq 感谢您指出这些。答案中的类使用的是我使用的更大类的代码。正如您发现的那样,我为了使其适用于答案而删减了太多内容。我已更新我的答案以更正您发现的错误。感谢您帮助我抓住它们并帮助我改进它。 感谢您的快速响应和修复。另一个问题,我认为可以用this.gestureScale.isInProgress() 代替inScale。这是真的还是使用维护自己的布尔值更好? 还有一个问题。通过实现GestureDector.OnGestureListener,我必须提供onLongPress()onDown()onFling()onShowPress()onSingleTapUp() @Baltasarq,我不确定这是否有帮助,但可能值得一试。在onDown()onFling()onSingleTapUp() 中,我正在返回true,并且我的代码中有一条注释说返回true 是为了不阻止这些事件中的输入。我已将完整课程附加到我的答案中,以防我遗漏了其他内容。【参考方案3】:

ScaleGestureDetector 从 Android 2.2(又名 Froyo,API 级别 8)开始可用。见:http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html

在 2.0/2.1 中,您没有 ScaleGestureDetector,但您可以使用 Ed Burnette 的 ZDNet 博客条目(Pieter888 链接到上面)提供捏合缩放:http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-6-implementing-the-pinch-zoom-gesture/1847

【讨论】:

【参考方案4】:

实际上有一个库使用这个类来缩放图像。

它叫做“TouchImageView

【讨论】:

【参考方案5】:

触摸图像视图

public class TouchImageView extends ImageView 
    Matrix matrix;
    // We can be in one of these 3 states
    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;

    int mode = NONE;

    // Remember some things for zooming
    PointF last = new PointF();
    PointF start = new PointF();
    float minScale = 1f;
    float maxScale = 3f;
    float[] m;
    int viewWidth, viewHeight;

    static final int CLICK = 3;

    float saveScale = 1f;

    protected float origWidth, origHeight;

    int oldMeasuredWidth, oldMeasuredHeight;

    ScaleGestureDetector mScaleDetector;

    Context context;

    public TouchImageView(Context context) 
        super(context);
        sharedConstructing(context);
    

    public TouchImageView(Context context, AttributeSet attrs) 
        super(context, attrs);
        sharedConstructing(context);
    

    private void sharedConstructing(Context context) 

        super.setClickable(true);

        this.context = context;

        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());

        matrix = new Matrix();

        m = new float[9];

        setImageMatrix(matrix);

        setScaleType(ScaleType.MATRIX);

        setOnTouchListener(new OnTouchListener() 

            @Override
            public boolean onTouch(View v, MotionEvent event) 

                mScaleDetector.onTouchEvent(event);

                PointF curr = new PointF(event.getX(), event.getY());

                switch (event.getAction()) 

                    case MotionEvent.ACTION_DOWN:

                        last.set(curr);

                        start.set(last);

                        mode = DRAG;

                        break;

                    case MotionEvent.ACTION_MOVE:

                        if (mode == DRAG) 

                            float deltaX = curr.x - last.x;

                            float deltaY = curr.y - last.y;

                            float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);

                            float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);

                            matrix.postTranslate(fixTransX, fixTransY);

                            fixTrans();

                            last.set(curr.x, curr.y);

                        

                        break;

                    case MotionEvent.ACTION_UP:

                        mode = NONE;

                        int xDiff = (int) Math.abs(curr.x - start.x);

                        int yDiff = (int) Math.abs(curr.y - start.y);

                        if (xDiff < CLICK && yDiff < CLICK)

                            performClick();

                        break;

                    case MotionEvent.ACTION_POINTER_UP:

                        mode = NONE;

                        break;

                

                setImageMatrix(matrix);

                invalidate();

                return true; // indicate event was handled
            
        );
    

    public void setMaxZoom(float x) 

        maxScale = x;
    

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener 

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) 

            mode = ZOOM;

            return true;
        

        @Override
        public boolean onScale(ScaleGestureDetector detector) 

            float mScaleFactor = detector.getScaleFactor();

            float origScale = saveScale;

            saveScale *= mScaleFactor;

            if (saveScale > maxScale) 

                saveScale = maxScale;

                mScaleFactor = maxScale / origScale;

             else if (saveScale < minScale) 

                saveScale = minScale;

                mScaleFactor = minScale / origScale;

            

            if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)

                matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);

            else

                matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());

            fixTrans();

            return true;
        
    

    void fixTrans() 

        matrix.getValues(m);

        float transX = m[Matrix.MTRANS_X];

        float transY = m[Matrix.MTRANS_Y];

        float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);

        float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);

        if (fixTransX != 0 || fixTransY != 0)

            matrix.postTranslate(fixTransX, fixTransY);
    

    float getFixTrans(float trans, float viewSize, float contentSize) 

        float minTrans, maxTrans;

        if (contentSize <= viewSize) 

            minTrans = 0;

            maxTrans = viewSize - contentSize;

         else 

            minTrans = viewSize - contentSize;

            maxTrans = 0;

        

        if (trans < minTrans)

            return -trans + minTrans;

        if (trans > maxTrans)

            return -trans + maxTrans;

        return 0;
    

    float getFixDragTrans(float delta, float viewSize, float contentSize) 

        if (contentSize <= viewSize) 

            return 0;

        

        return delta;
    

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        viewWidth = MeasureSpec.getSize(widthMeasureSpec);

        viewHeight = MeasureSpec.getSize(heightMeasureSpec);

        //
        // Rescales image on rotation
        //
        if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight

                || viewWidth == 0 || viewHeight == 0)

            return;

        oldMeasuredHeight = viewHeight;

        oldMeasuredWidth = viewWidth;

        if (saveScale == 1) 

            //Fit to screen.

            float scale;

            Drawable drawable = getDrawable();

            if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)

                return;

            int bmWidth = drawable.getIntrinsicWidth();

            int bmHeight = drawable.getIntrinsicHeight();

            Log.e("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);

            float scaleX = (float) viewWidth / (float) bmWidth;

            float scaleY = (float) viewHeight / (float) bmHeight;

            scale = Math.min(scaleX, scaleY);

            matrix.setScale(scale, scale);

            // Center the image

            float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);

            float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);

            redundantYSpace /= (float) 2;

            redundantXSpace /= (float) 2;

            matrix.postTranslate(redundantXSpace, redundantYSpace);

            origWidth = viewWidth - 2 * redundantXSpace;

            origHeight = viewHeight - 2 * redundantYSpace;

            setImageMatrix(matrix);

        

        fixTrans();
    

MainActivity.java

public class SecondActivity extends AppCompatActivity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);


        TouchImageView img = new TouchImageView(this);
        img.setImageResource(R.drawable.ic_launcher);
        img.setMaxZoom(4f);
        setContentView(img);
    

【讨论】:

【参考方案6】:

是的,我们可以在这里是示例代码,其中 onPinch() 和 onZoom() 是您自己实现的操作

public class simpleOnScaleGestureListener extends
        SimpleOnScaleGestureListener 

    @Override
    public boolean onScale(ScaleGestureDetector detector) 
        startScale = detector.getScaleFactor();
        return true;
    

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) 
        return true;
    

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) 
        endScale = detector.getScaleFactor();



        if (startScale > endScale) 
            Log.i("onScaleEnd", "Pinch Dection");
            onPinch();
         else if (startScale < endScale) 
            Log.i("onScaleEnd", "Zoom Dection");
            onZoom();
        


    


【讨论】:

我认为您遗漏了太多内容,以至于这个答案没有用。这只是你的 ScaleGestureListener,剩下的呢? 我已经调用了 onpinch() 和 onzoom(),它们在我的 customeview 上实现了 pinchzoom,你可以自己实现 这就是我要找的

以上是关于我们可以在 Android 中使用缩放手势检测器进行缩放吗?的主要内容,如果未能解决你的问题,请参考以下文章

Android使用GestureDetector进行手势检测

Android使用GestureDetector进行手势检测

在移动浏览器上缩放或滚动

由于辅助功能设置未检测到 iOS 手势

Android 手势识别—缩放

android控件随手势旋转