FrameLayout中的Android拖拽和缩放

时间:2016-07-12 11:23:43

标签: android android-framelayout

最近,我一直在尝试在我放置在FrameLayout中的图片上实现拖动和缩放。我想要实现的很简单:能够拖动图片并进行缩放。我去了Android开发者网站并关注guide there

然后按照该网站上的代码示例编写 MyCustomView

public class MyCustomView extends ImageView {
private static final int INVALID_POINTER_ID = 0xDEADBEEF;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float mLastTouchX, mLastTouchY;
private int mActivePointerId = INVALID_POINTER_ID;
private LayoutParams mLayoutParams;
private int mPosX, mPosY;

public MyCustomView(Context context) {
    super(context);

    mScaleDetector = new ScaleGestureDetector(context, new CustomScaleListener());
    mLayoutParams = (LayoutParams) super.getLayoutParams();
    if (mLayoutParams != null) {
        mPosX = mLayoutParams.leftMargin;
        mPosY = mLayoutParams.topMargin;
    } else {
        mLayoutParams = new LayoutParams(300, 300);
        mLayoutParams.leftMargin = 0;
        mLayoutParams.topMargin = 0;
    }
}

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

    canvas.save();
    canvas.scale(mScaleFactor, mScaleFactor);

    canvas.restore();
}

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

    final int action = MotionEventCompat.getActionMasked(ev);

    switch (action) {
        case MotionEvent.ACTION_DOWN: {
            final int pointerIndex = MotionEventCompat.getActionIndex(ev);

            //final float x = MotionEventCompat.getX(ev, pointerIndex);
            //final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float x = ev.getRawX();
            final float y = ev.getRawY();

            // Remember where we started (for dragging)
            mLastTouchX = x;
            mLastTouchY = y;
            // Save the ID of this pointer (for dragging)
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            // Find the index of the active pointer and fetch its position
            final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);

            //final float x = MotionEventCompat.getX(ev, pointerIndex);
            //final float y = MotionEventCompat.getY(ev, pointerIndex);
            final float x = ev.getRawX();
            final float y = ev.getRawY();

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

            //TODO: Update the location of this view
            mPosX += dx;
            mPosY += dy;

            mLayoutParams.leftMargin += dx;
            mLayoutParams.topMargin += dy;
            super.setLayoutParams(mLayoutParams);

            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 = MotionEventCompat.getActionIndex(ev);
            final int pointerID = MotionEventCompat.getPointerId(ev, 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 = MotionEventCompat.getX(ev, newPointerIndex);
                //mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex);
                mLastTouchX = ev.getRawX();
                mLastTouchY = ev.getRawY();
                mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
            }

            break;
        }
    }
    return true;
}




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

        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

        invalidate();

        return true;
    }
}

MainActivity 中,我只是实例化一个 MyCustomView 对象,并将其附加到后台的ViewGroup,这是一个FrameLayout。 xml文件只有一个FrameLayout。

    public class MainActivity extends AppCompatActivity {
    private ViewGroup layoutRoot;

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

        layoutRoot = (ViewGroup) findViewById(R.id.view_root);

        final MyCustomView ivAndroid = new MyCustomView(this);
        ivAndroid.setImageResource(R.mipmap.ic_launcher);
        ivAndroid.setLayoutParams(new FrameLayout.LayoutParams(300, 300));
        layoutRoot.addView(ivAndroid);
    }
}

这就是困扰我的问题:     Android Developer网站使用它来获取触摸图片的手指坐标:

final float x = MotionEventCompat.getX(ev, pointerIndex);
final float y = MotionEventCompat.getY(ev, pointerIndex);

但它的工作非常糟糕!图片会移动,但它并不完全跟着我的手指,它总是比我的手指移动得少,最重要的是,它会闪烁。

这就是为什么你可以在 MyCustomView 中看到我已经注释掉了这一行,而是使用了这段代码:

final float x = ev.getRawX();
final float y = ev.getRawY();

虽然这次图片按照我的手指顺利移动,但这种变化引入了一个新问题。在Android Developer website上进行拖动和缩放,有一个设计原则:

  

在拖动(或滚动)操作中,应用程序必须跟踪原始指针(手指),即使其他手指放在屏幕上也是如此。例如,假设在拖动图像时,用户将第二个手指放在触摸屏上并抬起第一个手指。如果您的应用只是跟踪单个指针,它会将第二个指针视为默认值,并将图像移动到该位置。

在我开始使用 ev.getRawX() ev.getRawY()之后,在屏幕上添加第二根手指可以完全解决上述问题。但 MotionEventCompat。 getX (ev,pointerIndex) MotionEventCompat。 getY (ev,pointerIndex)不会。< / p>

有人可以帮我解释为什么会这样吗?我知道MotionEventCompat.getX(ev,pointerIndex)在经过某种调整后返回坐标,并且ev.getRawX()返回绝对坐标。但我不明白调整的确切程度(是否有公式或图形解释?)。我还想知道为什么使用MotionEventCompat.getX(...)会阻止图片跳到屏幕上的第二个手指(第一个手指被抬起后)。

最后但并非最不重要的是,缩放代码根本不起作用。如果有人教我,那也将非常感激!

1 个答案:

答案 0 :(得分:0)

这个问题很长,所以我会把它分成小块。此外,英语不是我的母语,所以我在写答案时遇到了一些困难。评论部件是否不清楚。

  

有人可以帮我解释为什么会这样吗?

getRawX()ev.getRawY()都会为您提供事件的绝对像素值。这些也是(为了向后兼容,当大多数屏幕一次只能跟踪1个“区域”时)将始终将手指视为与设备交互的第一个(也是唯一的)手指。

然后,出现了可以跟踪手指ID的改进。MotionEventCompat.getX(ev, pointerIndex)MotionEventCompat.getY(ev, pointerIndex)功能在创建我们的onTouch()听众时可以进一步细化。

  

是否有公式或图形说明?

基本上,您需要考虑该设备的“屏幕密度”。如:

float SCREEN_DENSITY = getResources().getDisplayMetrics().density;

protected void updateFrame(FrameLayout frameLayout, int h, int w, int x, int y) {
    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            (int) ((w * SCREEN_DENSITY) + 0.5),
            (int) ((h * SCREEN_DENSITY) + 0.5)
    );
    params.leftMargin = (int) ((x * SCREEN_DENSITY) + 0.5);
    params.topMargin = (int) ((y * SCREEN_DENSITY) + 0.5);
    frameLayout.setLayoutParams(params);
}
  

我也想知道为什么使用MotionEventCompat.getX(...)会阻止图片跳到屏幕上的第二根手指(第一根手指被抬起后)

如果考虑到第一个“手指”被抬起,那么新手指就会有一个不同的“初始点”和不同的“历史”,因此,它可以发送与运动有关的事件制作,而不是屏幕上的最终位置。这样它就不会“跳到手指所在的位置”,但会根据“x单位”和“y单位”的数量而移动。

  

最后但并非最不重要的是,缩放代码根本不起作用。如果有人教我,那也将非常感激!

您正在使用该事件(通过在onTouch侦听器上返回true),因此,没有其他侦听器可以继续读取事件,从而可以触发更多侦听器。

如果需要,可以在onTouch内移动两个功能(移动和调整大小)。我的onTouch监听器有超过1700行代码(因为它做了很多东西,包括以编程方式创建视图并添加监听器),所以我不能在这里发布,但基本上是:

1 Finger = move the frame. Get raw values, and use the "updateFrame"
2 Fingers = resize the frame. Get raw values, and use the "updateFrame"
3+ Fingers = Drop first finger, suppose 2 Fingers.