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

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

【问题讨论】:

    标签: android pinchzoom android-gesture android-touch-event


    【解决方案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,你可以自己实现
          • 这就是我要找的
          猜你喜欢
          • 1970-01-01
          • 2013-01-25
          • 2013-08-13
          • 1970-01-01
          • 2016-08-17
          • 1970-01-01
          • 2018-12-02
          • 1970-01-01
          • 2011-11-29
          相关资源
          最近更新 更多