快速拖动后 OnDragListener 未收到回调
OnDragListener not receiving callbacks after dragged fastly
首先,这个问题可能看起来像 this question, and looks like recorded as issue here 的重复,但在查看细节后,您会发现它是完全不同的。
设置
我正在尝试实现一个简单的拖放功能:当我将 ImageView A 拖到 ImageView B 时,它们将交换它们的背景颜色。
您可以下载这个小测试项目here。下面描述的问题可以通过克隆它并在您的 phone.
上构建来重现
如果你只想看代码,请看MainActivity
问题
如果拖放速度较慢(例如每次拖放 0.5 秒),效果很好。
但是,如果你尝试以你最快的速度拖放(比如一秒4到5次,反正你最快的速度),很快你会发现拖放动作不能再进行了。通过查看日志,我发现 none 的拖动回调被触发 .
03-12 10:41:53.782 (...) I/dragEvent: view.startDragAndDrop has been called
03-12 10:41:53.787 (...) I/dragEvent: ACTION_DRAG_STARTED received
03-12 10:41:53.787 (...) I/dragEvent: ACTION_DRAG_STARTED received
03-12 10:41:53.789 (...) I/dragEvent: ACTION_DRAG_ENTERED received
03-12 10:41:53.789 (...) I/dragEvent: ACTION_DRAG_LOCATION received
03-12 10:41:53.796 (...) I/dragEvent: ACTION_DRAG_LOCATION received
03-12 10:41:53.802 (...) I/dragEvent: ACTION_DROP received
03-12 10:41:53.815 (...) I/dragEvent: ACTION_DRAG_ENDED received
03-12 10:41:53.815 (...) I/dragEvent: ACTION_DRAG_ENDED received
03-12 10:41:54.014 (...) I/dragEvent: view.startDragAndDrop has been called //Since this call, no callbacks were received
此时,应用程序不会冻结。 OnTouchListener 仍在接收回调。
拖放冻结是因为我有一个标志来指示拖放是否正在进行,并且由于未触发 ACTION_DRAG_ENDED,因此该标志停留在 true
处,因此不再有拖放操作可以执行。
我真的不知道为什么,因为我已经记录了所有内容,每一行代码都是 运行 正确的,我希望收到来自 Android 框架的回调,但它不是......
为什么我认为它应该是可以解决的
实际上,这个小测试项目是我试图克隆 this 应用程序中实现的拖放图像交换。而且我无法在他们的应用程序中重现此问题...所以我认为我的代码中一定有问题;或其他解决方法。
查看下面的代码片段
private void setupTouchListeners () {
View.OnTouchListener listener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
boolean beyondLeft = event.getX() <= 0;
boolean beyondRight = event.getX() >= v.getWidth();
boolean beyondTop = event.getY() <= 0;
boolean beyondBottom = event.getY() >= v.getHeight();
if (beyondLeft || beyondRight || beyondTop || beyondBottom) {
startDragAndDrop(v);
return true;
} else {
// done changes here, simply changed flag to false and return false
isDragging=false;
return false;
}
}
return false;
}
};
ivGrid1.setOnTouchListener(listener);
ivGrid2.setOnTouchListener(listener);
ivGrid3.setOnTouchListener(listener);
ivGrid4.setOnTouchListener(listener);
}
它有颜色的逻辑问题,这不是这个问题的一部分,通过进行上述更改,您将能够以您想要的任何速度拖动。
编码愉快:)
您的快速拖动导致了您未处理的快速移动,并且拖放工具没有通知您它没有通过正式结束拖动来继续进行。以下更新 MainActivity.java
有以下变化:
- 许多处理与拖动无关的代码已被删除。我这样做只是为了简化此处的发布,没有其他原因。
- 引入
GestureDetectorCompat
以检测何时发生了投掷行为。我对此没有做任何事情,只是记录它发生了。您可能需要考虑在代码中使用手势检测工具进行触摸。
- 检查是否存在
ACTION_UP
事件以在拖动未完成时重置布局 (isDragging == true
)。
开始拖动后,flings 似乎有一个混乱的结局。下面的代码可以帮助您解决问题。
MainActivity.java
public class MainActivity extends AppCompatActivity {
ImageView ivGrid1, ivGrid2, ivGrid3, ivGrid4;
private GestureDetectorCompat mDetector;
//Drag and Drop
View dragOriginView;
Drawable whiteDrawable;
Drawable tempDrawableStorage;
boolean isDragging = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDetector = new GestureDetectorCompat(this, new MyGestureListener());
ivGrid1 = findViewById(R.id.grid_1);
ivGrid2 = findViewById(R.id.grid_2);
ivGrid3 = findViewById(R.id.grid_3);
ivGrid4 = findViewById(R.id.grid_4);
setupTouchListeners();
setupDragListeners();
whiteDrawable = new ColorDrawable(ContextCompat.getColor(this, android.R.color.white));
}
private void setupTouchListeners() {
View.OnTouchListener listener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
boolean beyondLeft = event.getX() <= 0;
boolean beyondRight = event.getX() >= v.getWidth();
boolean beyondTop = event.getY() <= 0;
boolean beyondBottom = event.getY() >= v.getHeight();
if (beyondLeft || beyondRight || beyondTop || beyondBottom) {
startDragAndDrop(v);
return true;
}
isDragging = false;
break;
case MotionEvent.ACTION_UP:
if (isDragging) {
dragOriginView.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
v.setAlpha(1.0f);
isDragging = false;
}
}
return false;
}
};
ivGrid1.setOnTouchListener(listener);
ivGrid2.setOnTouchListener(listener);
ivGrid3.setOnTouchListener(listener);
ivGrid4.setOnTouchListener(listener);
}
private void startDragAndDrop(View view) {
if (isDragging) return;
isDragging = true;
dragOriginView = view;
view.setAlpha(0.5f);
ClipData.Item item = new ClipData.Item((String) view.getTag());
ClipData dragData = new ClipData((String) view.getTag(), new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN},
item);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
view.startDragAndDrop(dragData, new View.DragShadowBuilder(view), null, 0);
} else {
view.startDrag(dragData, new View.DragShadowBuilder(view), null, 0);
}
Log.i("dragEvent", "view.startDragAndDrop has been called");
tempDrawableStorage = view.getBackground();
view.setBackground(whiteDrawable);
}
private void setupDragListeners() {
View.OnDragListener listener = new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
Log.i("dragEvent", "<<<<<<<<<<<<<<called");
switch (event.getAction()) {
case ACTION_DRAG_STARTED:
Log.i("dragEvent", "ACTION_DRAG_STARTED received");
return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
case ACTION_DRAG_ENTERED:
Log.i("dragEvent", "ACTION_DRAG_ENTERED received");
v.setAlpha(0.5f);
return true;
case ACTION_DRAG_LOCATION:
Log.i("dragEvent", "ACTION_DRAG_LOCATION received");
return true;
case ACTION_DRAG_EXITED:
Log.i("dragEvent", "ACTION_DRAG_EXITED received");
v.setAlpha(1f);
return true;
case ACTION_DROP:
Log.i("dragEvent", "ACTION_DROP received");
v.setAlpha(1f);
swapColor(v);
return true;
case ACTION_DRAG_ENDED:
Log.i("dragEvent", "ACTION_DRAG_ENDED received");
if (!event.getResult() && v == dragOriginView) {
dragOriginView.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
}
v.setAlpha(1f);
isDragging = false;
default:
//Do nothing
}
return false;
}
};
ivGrid1.setOnDragListener(listener);
ivGrid2.setOnDragListener(listener);
ivGrid3.setOnDragListener(listener);
ivGrid4.setOnDragListener(listener);
}
private void swapColor(View dragTarget) {
if (dragOriginView == dragTarget) {
//Restore color
dragOriginView.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
return;
}
Drawable targetBackground = dragTarget.getBackground();
dragOriginView.setBackground(targetBackground);
dragTarget.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
this.mDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
private static final String DEBUG_TAG = "Gestures";
@Override
public boolean onDown(MotionEvent event) {
Log.d(DEBUG_TAG, "onDown: " + event.toString());
return true;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(DEBUG_TAG, "onFling: " + ((event1 != null) ? event1.toString() : "" + event2.toString()));
return true;
}
}
}
首先,这个问题可能看起来像 this question, and looks like recorded as issue here 的重复,但在查看细节后,您会发现它是完全不同的。
设置
我正在尝试实现一个简单的拖放功能:当我将 ImageView A 拖到 ImageView B 时,它们将交换它们的背景颜色。
您可以下载这个小测试项目here。下面描述的问题可以通过克隆它并在您的 phone.
上构建来重现如果你只想看代码,请看MainActivity
问题
如果拖放速度较慢(例如每次拖放 0.5 秒),效果很好。
但是,如果你尝试以你最快的速度拖放(比如一秒4到5次,反正你最快的速度),很快你会发现拖放动作不能再进行了。通过查看日志,我发现 none 的拖动回调被触发 .
03-12 10:41:53.782 (...) I/dragEvent: view.startDragAndDrop has been called
03-12 10:41:53.787 (...) I/dragEvent: ACTION_DRAG_STARTED received
03-12 10:41:53.787 (...) I/dragEvent: ACTION_DRAG_STARTED received
03-12 10:41:53.789 (...) I/dragEvent: ACTION_DRAG_ENTERED received
03-12 10:41:53.789 (...) I/dragEvent: ACTION_DRAG_LOCATION received
03-12 10:41:53.796 (...) I/dragEvent: ACTION_DRAG_LOCATION received
03-12 10:41:53.802 (...) I/dragEvent: ACTION_DROP received
03-12 10:41:53.815 (...) I/dragEvent: ACTION_DRAG_ENDED received
03-12 10:41:53.815 (...) I/dragEvent: ACTION_DRAG_ENDED received
03-12 10:41:54.014 (...) I/dragEvent: view.startDragAndDrop has been called //Since this call, no callbacks were received
此时,应用程序不会冻结。 OnTouchListener 仍在接收回调。
拖放冻结是因为我有一个标志来指示拖放是否正在进行,并且由于未触发 ACTION_DRAG_ENDED,因此该标志停留在 true
处,因此不再有拖放操作可以执行。
我真的不知道为什么,因为我已经记录了所有内容,每一行代码都是 运行 正确的,我希望收到来自 Android 框架的回调,但它不是......
为什么我认为它应该是可以解决的
实际上,这个小测试项目是我试图克隆 this 应用程序中实现的拖放图像交换。而且我无法在他们的应用程序中重现此问题...所以我认为我的代码中一定有问题;或其他解决方法。
查看下面的代码片段
private void setupTouchListeners () {
View.OnTouchListener listener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
boolean beyondLeft = event.getX() <= 0;
boolean beyondRight = event.getX() >= v.getWidth();
boolean beyondTop = event.getY() <= 0;
boolean beyondBottom = event.getY() >= v.getHeight();
if (beyondLeft || beyondRight || beyondTop || beyondBottom) {
startDragAndDrop(v);
return true;
} else {
// done changes here, simply changed flag to false and return false
isDragging=false;
return false;
}
}
return false;
}
};
ivGrid1.setOnTouchListener(listener);
ivGrid2.setOnTouchListener(listener);
ivGrid3.setOnTouchListener(listener);
ivGrid4.setOnTouchListener(listener);
}
它有颜色的逻辑问题,这不是这个问题的一部分,通过进行上述更改,您将能够以您想要的任何速度拖动。
编码愉快:)
您的快速拖动导致了您未处理的快速移动,并且拖放工具没有通知您它没有通过正式结束拖动来继续进行。以下更新 MainActivity.java
有以下变化:
- 许多处理与拖动无关的代码已被删除。我这样做只是为了简化此处的发布,没有其他原因。
- 引入
GestureDetectorCompat
以检测何时发生了投掷行为。我对此没有做任何事情,只是记录它发生了。您可能需要考虑在代码中使用手势检测工具进行触摸。 - 检查是否存在
ACTION_UP
事件以在拖动未完成时重置布局 (isDragging == true
)。
开始拖动后,flings 似乎有一个混乱的结局。下面的代码可以帮助您解决问题。
MainActivity.java
public class MainActivity extends AppCompatActivity {
ImageView ivGrid1, ivGrid2, ivGrid3, ivGrid4;
private GestureDetectorCompat mDetector;
//Drag and Drop
View dragOriginView;
Drawable whiteDrawable;
Drawable tempDrawableStorage;
boolean isDragging = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDetector = new GestureDetectorCompat(this, new MyGestureListener());
ivGrid1 = findViewById(R.id.grid_1);
ivGrid2 = findViewById(R.id.grid_2);
ivGrid3 = findViewById(R.id.grid_3);
ivGrid4 = findViewById(R.id.grid_4);
setupTouchListeners();
setupDragListeners();
whiteDrawable = new ColorDrawable(ContextCompat.getColor(this, android.R.color.white));
}
private void setupTouchListeners() {
View.OnTouchListener listener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
boolean beyondLeft = event.getX() <= 0;
boolean beyondRight = event.getX() >= v.getWidth();
boolean beyondTop = event.getY() <= 0;
boolean beyondBottom = event.getY() >= v.getHeight();
if (beyondLeft || beyondRight || beyondTop || beyondBottom) {
startDragAndDrop(v);
return true;
}
isDragging = false;
break;
case MotionEvent.ACTION_UP:
if (isDragging) {
dragOriginView.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
v.setAlpha(1.0f);
isDragging = false;
}
}
return false;
}
};
ivGrid1.setOnTouchListener(listener);
ivGrid2.setOnTouchListener(listener);
ivGrid3.setOnTouchListener(listener);
ivGrid4.setOnTouchListener(listener);
}
private void startDragAndDrop(View view) {
if (isDragging) return;
isDragging = true;
dragOriginView = view;
view.setAlpha(0.5f);
ClipData.Item item = new ClipData.Item((String) view.getTag());
ClipData dragData = new ClipData((String) view.getTag(), new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN},
item);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
view.startDragAndDrop(dragData, new View.DragShadowBuilder(view), null, 0);
} else {
view.startDrag(dragData, new View.DragShadowBuilder(view), null, 0);
}
Log.i("dragEvent", "view.startDragAndDrop has been called");
tempDrawableStorage = view.getBackground();
view.setBackground(whiteDrawable);
}
private void setupDragListeners() {
View.OnDragListener listener = new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
Log.i("dragEvent", "<<<<<<<<<<<<<<called");
switch (event.getAction()) {
case ACTION_DRAG_STARTED:
Log.i("dragEvent", "ACTION_DRAG_STARTED received");
return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
case ACTION_DRAG_ENTERED:
Log.i("dragEvent", "ACTION_DRAG_ENTERED received");
v.setAlpha(0.5f);
return true;
case ACTION_DRAG_LOCATION:
Log.i("dragEvent", "ACTION_DRAG_LOCATION received");
return true;
case ACTION_DRAG_EXITED:
Log.i("dragEvent", "ACTION_DRAG_EXITED received");
v.setAlpha(1f);
return true;
case ACTION_DROP:
Log.i("dragEvent", "ACTION_DROP received");
v.setAlpha(1f);
swapColor(v);
return true;
case ACTION_DRAG_ENDED:
Log.i("dragEvent", "ACTION_DRAG_ENDED received");
if (!event.getResult() && v == dragOriginView) {
dragOriginView.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
}
v.setAlpha(1f);
isDragging = false;
default:
//Do nothing
}
return false;
}
};
ivGrid1.setOnDragListener(listener);
ivGrid2.setOnDragListener(listener);
ivGrid3.setOnDragListener(listener);
ivGrid4.setOnDragListener(listener);
}
private void swapColor(View dragTarget) {
if (dragOriginView == dragTarget) {
//Restore color
dragOriginView.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
return;
}
Drawable targetBackground = dragTarget.getBackground();
dragOriginView.setBackground(targetBackground);
dragTarget.setBackground(tempDrawableStorage);
tempDrawableStorage = null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
this.mDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
private static final String DEBUG_TAG = "Gestures";
@Override
public boolean onDown(MotionEvent event) {
Log.d(DEBUG_TAG, "onDown: " + event.toString());
return true;
}
@Override
public boolean onFling(MotionEvent event1, MotionEvent event2,
float velocityX, float velocityY) {
Log.d(DEBUG_TAG, "onFling: " + ((event1 != null) ? event1.toString() : "" + event2.toString()));
return true;
}
}
}