【问题标题】:Zoom and Panning ImageView Android缩放和平移 ImageView Android
【发布时间】:2012-08-23 14:08:43
【问题描述】:

我为 ImageView 创建了一个缩放和平移类。

我正在尝试创建的功能。 - 它平移单指触摸和移动 - 它可以缩放和平移两根手指的触摸和移动

在大多数情况下,这非常有效。 当我执行以下操作时,它有一个小错误: - 我用一根手指平移(状态:没问题) - 我放下第二根手指,缩放和平移(状态:没问题) - 我松开我的第二根手指(状态:图像跳跃一点)

希望有人能帮我解决这个问题。

我认为它必须与“case MotionEvent.ACTION_POINTER_UP”中的 mLastTouchX 和 mLastTouchY 重置有关

任何帮助将不胜感激!

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

public class MyImageView extends ImageView {

    private static final int INVALID_POINTER_ID = -1;

    private float mPosX;
    private float mPosY;

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

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

    public MyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    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: {
                if (!mScaleDetector.isInProgress()) {
                    final float x = ev.getX();
                    final float y = ev.getY();

                    mLastTouchX = x;
                    mLastTouchY = y;
                    mActivePointerId = ev.getPointerId(0);
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_1_DOWN: {
                if (mScaleDetector.isInProgress()) {
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();
                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {

                // Only move if the ScaleGestureDetector isn't processing a gesture.
                if (!mScaleDetector.isInProgress()) {
                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(pointerIndex);
                    final float y = ev.getY(pointerIndex);

                    final float dx = x - mLastTouchX;
                    final float dy = y - mLastTouchY;

                    mPosX += dx;
                    mPosY += dy;

                    invalidate();

                    mLastTouchX = x;
                    mLastTouchY = y;
                }
                else{
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();

                    final float gdx = gx - mLastGestureX;
                    final float gdy = gy - mLastGestureY;

                    mPosX += gdx;
                    mPosY += gdy;

                    invalidate();

                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }

                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) {
                    Log.d("DEBUG", "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) {

        canvas.save();

        canvas.translate(mPosX, mPosY);

        if (mScaleDetector.isInProgress()) {
            canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
        }
        else{
            canvas.scale(mScaleFactor, mScaleFactor);
        }
        super.onDraw(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;
        }
    }
}

【问题讨论】:

  • 嗯。尝试了您的代码并用小图像观察,然后我用第二根手指将图像“跳转”到它。是你提到的错误吗?
  • 是的,跳跃就是我要解决的问题。
  • 看起来是因为使用了焦点坐标。今天稍后将尝试调试代码。
  • 顺便说一句,然后你放第二根手指的预期行为是什么?似乎缩放图像应该放在手指之间。
  • 当两根手指放下时,它会缩放图像,但它也会同时使用两根手指的中点进行平移。

标签: android imageview zooming pinch pan


【解决方案1】:

似乎“onDraw”方法的“else”语句中的 canvas.scale() 需要 mLastGestureX 和 mLastGestureY 来停止跳跃。 在“MotionEvent.ACTION_POINTER_UP”案例中返回单指平移时,我还会刷新 mLastTouchX 和 mLastTouchY

这是最终的,可能不适合每个人,因为它不限制平移过去的图像边界,但这应该很容易完成,关于这个主题有很多讨论。

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;

public class MyImageView extends ImageView {

    private static final int INVALID_POINTER_ID = -1;

    private float mPosX;
    private float mPosY;

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

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

    public MyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    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: {
                if (!mScaleDetector.isInProgress()) {
                    final float x = ev.getX();
                    final float y = ev.getY();

                    mLastTouchX = x;
                    mLastTouchY = y;
                    mActivePointerId = ev.getPointerId(0);
                }
                break;
            }
            case MotionEvent.ACTION_POINTER_1_DOWN: {
                if (mScaleDetector.isInProgress()) {
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();
                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }
                break;
            }
            case MotionEvent.ACTION_MOVE: {

                // Only move if the ScaleGestureDetector isn't processing a gesture.
                if (!mScaleDetector.isInProgress()) {
                    final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                    final float x = ev.getX(pointerIndex);
                    final float y = ev.getY(pointerIndex);

                    final float dx = x - mLastTouchX;
                    final float dy = y - mLastTouchY;

                    mPosX += dx;
                    mPosY += dy;

                    invalidate();

                    mLastTouchX = x;
                    mLastTouchY = y;
                }
                else{
                    final float gx = mScaleDetector.getFocusX();
                    final float gy = mScaleDetector.getFocusY();

                    final float gdx = gx - mLastGestureX;
                    final float gdy = gy - mLastGestureY;

                    mPosX += gdx;
                    mPosY += gdy;

                    invalidate();

                    mLastGestureX = gx;
                    mLastGestureY = gy;
                }

                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);
                }
                else{
                    final int tempPointerIndex = ev.findPointerIndex(mActivePointerId);
                    mLastTouchX = ev.getX(tempPointerIndex);
                    mLastTouchY = ev.getY(tempPointerIndex);
                }

                break;
            }
        }

        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {

        canvas.save();

        canvas.translate(mPosX, mPosY);

        if (mScaleDetector.isInProgress()) {
            canvas.scale(mScaleFactor, mScaleFactor, mScaleDetector.getFocusX(), mScaleDetector.getFocusY());
        }
        else{
            canvas.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
        }
        super.onDraw(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;
        }
    }
}

【讨论】:

  • 如何限制它移出ImageView?
  • 将所有内容都移到了 OpenGL 中,因为它是一个更强大的图形映射器,但我认为如果你在任何缩放 kevel 处找出图像的边界,你可能会检查并防止平移跨度>
  • 当第二根手指放在屏幕上时,它仍然会跳转。
  • 嗨@Hank,我正在尝试使用它,但上述问题仍然存在(图像跳跃)。你有解决办法吗??
  • @Hank - 为什么有 2 个 MyImageView 构造函数?
【解决方案2】:

一个多星期以来,我一直在想办法解决这个问题,但它给我带来了很多问题。不过,我已经大大缩小了这个问题的范围。你上面的解决方案对我不起作用,但我下面的解决方案很接近。问题是,只要第二根手指被按下或抬起,它就会跳跃。我发现发生这种情况是因为 mPosX 和 mPosY 并不总是真正应该代表的变量。这就是我的意思:

当调用 ACTION_MOVE 代码进入“else”语句(处理缩放事件)时,mPosX 和 mPosY 只是根据焦点变化而变化,不 em> 缩放变化。这意味着用两根手指平移有效,用两根手指缩放有效,但 mPosX 和 mPosY 并没有随着缩放的变化而适当改变。

我一直在尝试使用缩放中的差异变化 (mScaleDetector.getScaleFactor()) 和焦点的差异变化来找出解决此问题的方法,但我似乎无法很好地完成逻辑以找到有用的东西。

另一种解决方案是将所有缩放操作移到 OnTouchListener 中,并完全摆脱 ScaleListener。这意味着更多的数学运算,但这绝对是一个解决方案。

这里是onDraw:

    @Override
    public void onDraw(Canvas c) {
        c.save();

        if (mScaleDetector.isInProgress()) {
            c.scale(mScaleFactor, mScaleFactor, mLastGestureX - mPosX,
                    mLastGestureY - mPosY);
        } else {
            c.scale(mScaleFactor, mScaleFactor, mLastGestureX, mLastGestureY);
        }

        c.translate(mPosX / mScaleFactor, mPosY / mScaleFactor);

        // drawing instruction here

        c.restore();
    }

以下是代码对手指按压的反应:

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mScaleDetector.onTouchEvent(ev);

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

                mLastTouchX = x;
                mLastTouchY = y;

                mActivePointerId = ev.getPointerId(0);
            }
            break;
        }

        case MotionEvent.ACTION_POINTER_DOWN: {
            if (!mScaleDetector.isInProgress()) {
                final float gx = mScaleDetector.getFocusX();
                final float gy = mScaleDetector.getFocusY();

                mLastGestureX = gx;
                mLastGestureY = gy; 
            }
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            if (!mScaleDetector.isInProgress()) {
                Log.i("hi", "SD not in progress");
                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
                final float x = ev.getX(pointerIndex);
                final float y = ev.getY(pointerIndex);

                final float dx = x - mLastTouchX;
                final float dy = y - mLastTouchY;

                mPosX += dx;
                mPosY += dy;

                invalidate();

                mLastTouchX = x;
                mLastTouchY = y;
            } else {
                Log.i("hi", "SD in progress");
                final float gx = mScaleDetector.getFocusX();
                final float gy = mScaleDetector.getFocusY();

                final float gdx = gx - mLastGestureX;
                final float gdy = gy - mLastGestureY;

                mPosX += gdx;
                mPosY += gdy;

                // SOMETHING NEEDS TO HAPPEN RIGHT HERE.

                invalidate();

                mLastGestureX = gx;
                mLastGestureY = gy;
            }

            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);
            } else {
                final int tempPointerIndex = ev.findPointerIndex(mActivePointerId);

                mLastTouchX = ev.getX(tempPointerIndex);
                mLastTouchY = ev.getY(tempPointerIndex);
            }

            break;
        }
        }

        return true;
    }

虽然它大多不相关,但这里是 ScaleListener:

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

            invalidate();
            return true;
        }
    }

同样,这段代码运行不完美,但非常接近。我已经解释了上面的确切问题,但仍然无法正常工作。我不知道这是否会在你的通知中弹出,汉克,但希望有人能看到它并帮助我。

【讨论】:

  • 谢谢,这是对以前代码的改进,但是当第二根手指在屏幕上触摸时仍然会跳转。
【解决方案3】:

汉克的解决方案对我有用。我添加了一个重置​​功能,以便后续图像可以正常显示。

public void ResetView() {
        mScaleFactor = 1.f;
        mPosX = 0.f;
        mPosY = 0.f;
    }

【讨论】:

    【解决方案4】:

    如果没有查看代码,我会假设您在有 2 个手指时基于 2 个手指进行位置计算。在这种情况下,你总会得到一个跳跃。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-30
      • 2012-07-08
      • 2014-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多