最近,我一直在尝试在我放置在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(...)会阻止图片跳到屏幕上的第二个手指(第一个手指被抬起后)。
最后但并非最不重要的是,缩放代码根本不起作用。如果有人教我,那也将非常感激!
答案 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.