Android拖拽功能首次使用定位异常问题排查求助
问题:Android拖拽View首次定位异常,后续正常
开发Android应用时实现了View的拖拽放置功能,期望将View放置在用户拖拽释放的位置,但首次执行拖拽操作时View会被放置在无关位置甚至屏幕外,后续拖拽则可正常定位。
相关代码
setOnDragListener()
contentTableLayout.setOnDragListener(new View.OnDragListener() { @Override public boolean onDrag(View view, DragEvent event) { Log.i(TAG, " event.getAction(): " + event.getAction() + " getView: ID: " + view.getId() + " getContext: " + view.getContext()); String getImageFileNameSelectedRoom = getImageFileNameToRoom(); switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { View draggedView = (View) event.getLocalState(); initialX = draggedView.getX(); initialY = draggedView.getY(); Log.i(TAG, "ACTION_DRAG_STARTED -> initialX: " + initialX + " initialY: " + initialY); changeBackgroundColorOnDrag(view, "#664CAF50", true); return true; } return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); case DragEvent.ACTION_DRAG_ENTERED: changeBackgroundColorOnDrag(view, "#664CAF50", true); return true; case DragEvent.ACTION_DRAG_LOCATION: return true; case DragEvent.ACTION_DRAG_EXITED: changeBackgroundColorOnDrag(view, "#66D32121", true); return true; case DragEvent.ACTION_DROP: Log.i(TAG, "ACTION_DROP -> "); View draggedView = (View) event.getLocalState(); ViewGroup owner = (ViewGroup) draggedView.getParent(); owner.removeView(draggedView); FrameLayout container = (FrameLayout) view; container.addView(draggedView); View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(draggedView); Point shadowSize = new Point(); shadowBuilder.onProvideShadowMetrics(shadowSize, new Point()); int x = (int) event.getX() - draggedView.getMeasuredWidth() / 2; int y = (int) event.getY() - draggedView.getMeasuredHeight() / 2; draggedView.setLayoutParams(new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT)); Log.i(TAG, "ACTION_DROP -> " + x + " y: " + y); draggedView.setX(x); draggedView.setY(y); draggedView.setVisibility(View.VISIBLE); return true; case DragEvent.ACTION_DRAG_ENDED: if (!event.getResult()) { if (getImageFileNameSelectedRoom != null) { File targetFile = findImageFile(getImageFileNameSelectedRoom); if (targetFile != null) { Bitmap bitmap = BitmapFactory.decodeFile(targetFile.getAbsolutePath()); BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap); ((FrameLayout) view).setBackground(bitmapDrawable); view.invalidate(); } else { Log.e(TAG, "Resim dosyası bulunamadı."); } } View droppedView = (View) event.getLocalState(); droppedView.setVisibility(View.VISIBLE); } return true; default: break; } return false; } });
setOnLongClickListener()
mainView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View view) { ClipData.Item item = new ClipData.Item((CharSequence) view.getTag()); ClipData dragData = new ClipData((CharSequence) view.getTag(), new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}, item); View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view); Log.i(TAG, " getXFromView: " + view.getX() + "getYFromView: " + view.getY()); view.startDrag(dragData, shadowBuilder, view, 0); return true; } });
尝试过的修改(首次正常但后续失效)
case DragEvent.ACTION_DROP: // ... int x = (int) (initialX + event.getX() - shadowSize.x / 2); int y = (int) (initialY + event.getY() - shadowSize.y / 2); //initialX and initialY values come from the ACTION_DRAG_STARTED case. // ...
回答
首次拖拽定位错误的根源
View测量值未初始化
原代码在ACTION_DROP阶段用draggedView.getMeasuredWidth()/getMeasuredHeight()计算坐标偏移,但首次拖拽时,View刚从原父容器移除并添加到新容器,还没完成测量布局流程,此时获取到的宽高大概率是0或者默认无效值,导致计算出的x、y完全偏离预期位置。而后续拖拽时,View已经在新容器中完成过测量,宽高值正确,所以坐标计算正常。修改后的坐标逻辑混淆了坐标体系
你尝试的修改方案中,initialX是View在原父容器中的坐标(或相对于屏幕的绝对坐标),而event.getX()是相对于接收拖拽的目标容器的局部坐标,两者属于不同的坐标空间,直接叠加计算完全不合理。首次拖拽看似正常只是巧合,后续拖拽时initialX已经是上次拖拽后的位置,叠加后坐标会持续偏移,导致定位彻底失效。
修复方向
- 强制触发View测量:在
container.addView(draggedView)后调用draggedView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)),确保能获取到正确的宽高值。 - 严格使用目标容器的局部坐标:直接用
event.getX() - draggedView.getMeasuredWidth()/2、event.getY() - draggedView.getMeasuredHeight()/2计算坐标,不要混用不同坐标体系的数值。
内容的提问来源于stack exchange,提问作者Ozgur Baykal
相关产品推荐
相关产品推荐

