滚动后如何使列表元素获得DragEvents

简洁版本:

  • 有没有办法让一个新创build的视图接收一个已经运行的拖放操作的DragEvent

还有如何注册一个DragEvent,而已经在当前的DragEvent? ,但我真的很喜欢一个更清洁的解决scheme。

build议的GONE-> VISIBLE解决方法是相当复杂的,因为您只需要确保只在列表项变得可见时使用它,而不是无条件地在所有当前列表视图项上使用它。 在这个黑客稍微泄漏,没有更多的解决方法代码,以使其正确。

长版本:

我有一个ListViewListView的元素是包含可拖动符号(小方块)的自定义View,例如类似于:

示例播放

可以在ListView的项目之间拖动小框,如将元素sorting到框中。 列表项上的拖动处理程序或多或less是微不足道的:

 @Override public boolean onDragEvent(DragEvent event) { if ((event.getLocalState() instanceof DragableSymbolView)) { final DragableSymbolView draggedView = (DragableSymbolView) event.getLocalState(); if (draggedView.getTag() instanceof SymbolData) { final SymbolData symbol = (SymbolData) draggedView.getTag(); switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: return true; case DragEvent.ACTION_DRAG_ENTERED: setSelected(true); return true; case DragEvent.ACTION_DRAG_ENDED: case DragEvent.ACTION_DRAG_EXITED: setSelected(false); return true; case DragEvent.ACTION_DROP: setSelected(false); // [...] remove symbol from soruce box and add to current box requestFocus(); break; } } } return super.onDragEvent(event); } 

将指针hover在一个符号上并开始拖动(即将其移动超过一个小的阈值)时,开始拖动。

但是,现在,屏幕大小可能不足以包含所有框,因此ListView需要滚动。 我发现了困难的方式,我需要自己实现滚动,因为ListView在拖动时不会自动滚动。

ListViewScrollingDragListener

 public class ListViewScrollingDragListener implements View.OnDragListener { private final ListView _listView; public static final int DEFAULT_SCROLL_BUFFER_DIP = 96; public static final int DEFAULT_SCROLL_DELTA_UP_DIP = 48; public static final int DEFAULT_SCROLL_DELTA_DOWN_DIP = 48; private int _scrollDeltaUp; private int _scrollDeltaDown; private boolean _doScroll = false; private boolean _scrollActive = false; private int _scrollDelta = 0; private int _scrollDelay = 250; private int _scrollInterval = 100; private int _scrollBuffer; private final Rect _visibleRect = new Rect(); private final Runnable _scrollHandler = new Runnable() { @Override public void run() { if (_doScroll && (_scrollDelta != 0) && _listView.canScrollVertically(_scrollDelta)) { _scrollActive = true; _listView.smoothScrollBy(_scrollDelta, _scrollInterval); _listView.postDelayed(this, _scrollInterval); } else { _scrollActive = false; } } }; public ListViewScrollingDragListener(final ListView listView, final boolean attach) { _scrollBuffer = UnitUtil.dipToPixels(listView, DEFAULT_SCROLL_BUFFER_DIP); _scrollDeltaUp = -UnitUtil.dipToPixels(listView, DEFAULT_SCROLL_DELTA_UP_DIP); _scrollDeltaDown = UnitUtil.dipToPixels(listView, DEFAULT_SCROLL_DELTA_DOWN_DIP); _listView = listView; if (attach) { _listView.setOnDragListener(this); } } public ListViewScrollingDragListener(final ListView listView) { this(listView, true); } protected void handleDragLocation(final float x, final float y) { _listView.getGlobalVisibleRect(_visibleRect); if (_visibleRect.contains((int) x, (int) y)) { if (y < _visibleRect.top + _scrollBuffer) { _scrollDelta = _scrollDeltaUp; _doScroll = true; } else if (y > _visibleRect.bottom - _scrollBuffer) { _scrollDelta = _scrollDeltaDown; _doScroll = true; } else { _doScroll = false; _scrollDelta = 0; } if ((_doScroll) && (!_scrollActive)) { _scrollActive = true; _listView.postDelayed(_scrollHandler, _scrollDelay); } } } public ListView getListView() { return _listView; } @Override public boolean onDrag(View v, DragEvent event) { /* hide sequence controls during drag */ switch (event.getAction()) { case DragEvent.ACTION_DRAG_ENTERED: _doScroll = true; break; case DragEvent.ACTION_DRAG_EXITED: case DragEvent.ACTION_DRAG_ENDED: case DragEvent.ACTION_DROP: _doScroll = false; break; case DragEvent.ACTION_DRAG_LOCATION: handleDragLocation(event.getX(), event.getY()); break; } return true; } } 

当你在可见区域的上边界或下边界附近拖动时,这基本上会滚动ListView 。 这并不完美,但是够好的。

但是,有一个问题:

当列表滚动到以前不可见的元素时,该元素不会收到DragEvent 。 拖动符号时不会被选中(突出显示),也不会接受丢弃。

关于如何使“滚动”视图的任何想法从已经有效的拖放操作中接收到DragEvent

Solutions Collecting From Web of "滚动后如何使列表元素获得DragEvents"

所以基本的问题是, ViewGroup (即ListView扩展)caching一个子列表来通知DragEvent 。 而且,它只在收到ACTION_DRAG_STARTED时填充这个caching。 有关更多详细信息,请阅读此处的源代码。

解决scheme! 我们将在ListView本身上监听它们,而不是在ListView的各行上监听drop事件。 然后,根据事件的坐标,我们将计算拖动的视图拖到哪个行或hover。 当下降发生时,我们将执行从上一行移除并添加到新行的事务。

 private void init(Context context) { setAdapter(new RandomIconAdapter()); // Adapter that contains our data set setOnDragListener(new ListDragListener()); mListViewScrollingDragListener = new ListViewScrollingDragListener(this, false); } ListViewScrollingDragListener mListViewScrollingDragListener; private class ListDragListener implements OnDragListener { // The view that our dragged view would be dropped on private View mCurrentDropZoneView = null; private int mDropStartRowIndex = -1; @Override public boolean onDrag(View v, DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_LOCATION: // Update the active drop zone based on the position of the event updateCurrentDropZoneView(event); // Funnel drag events to separate listener to handle scrolling near edges mListViewScrollingDragListener.onDrag(v, event); if( mDropStartRowIndex == -1 ) // Only initialize once per drag->drop gesture { mDropStartRowIndex = indexOfChild(mCurrentDropZoneView) + getFirstVisiblePosition(); log("mDropStartRowIndex %d", mDropStartRowIndex); } break; case DragEvent.ACTION_DRAG_ENDED: case DragEvent.ACTION_DRAG_EXITED: mCurrentDropZoneView = null; mDropStartRowIndex = -1; break; case DragEvent.ACTION_DROP: // Update our data set based on the row that the dragged view was dropped in int finalDropRow = indexOfChild(mCurrentDropZoneView) + getFirstVisiblePosition(); updateDataSetWithDrop(mDropStartRowIndex, finalDropRow); // Let adapter update ui ((BaseAdapter)getAdapter()).notifyDataSetChanged(); break; } // The ListView handles ALL drag events all the time. Fine for now since we don't need to // drag -> drop outside of the ListView. return true; } private void updateDataSetWithDrop(int fromRow, int toRow) { log("updateDataSetWithDrop fromRow %d and toRow %d", fromRow, toRow); sIconsForListItems[fromRow]--; sIconsForListItems[toRow]++; } // NOTE: The DragEvent in local to DragDropListView, as are children coordinates private void updateCurrentDropZoneView(DragEvent event) { View previousDropZoneView = mCurrentDropZoneView; mCurrentDropZoneView = findFrontmostDroppableChildAt(event.getX(), event.getY()); log("mCurrentDropZoneView updated to %d for x/y : %f/%f with action %d", mCurrentDropZoneView == null ? -1 : indexOfChild(mCurrentDropZoneView) + getFirstVisiblePosition(), event.getX(), event.getY(), event.getAction()); if (mCurrentDropZoneView != previousDropZoneView) { if (previousDropZoneView != null) previousDropZoneView.setSelected(false); if (mCurrentDropZoneView != null) mCurrentDropZoneView.setSelected(true); } } } /** * The next four methods are utility methods taken from Android Source Code. Most are package-private on View * or ViewGroup so I'm forced to replicate them here. Original source can be found: * http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.0_r1/android/view/ViewGroup.java#ViewGroup.findFrontmostDroppableChildAt%28float%2Cfloat%2Candroid.graphics.PointF%29 */ private View findFrontmostDroppableChildAt(float x, float y) { int childCount = this.getChildCount(); for(int i=0; i<childCount; i++) { View child = getChildAt(i); if (isTransformedTouchPointInView(x, y, child)) { return child; } } return null; } static public boolean isTransformedTouchPointInView(float x, float y, View child) { PointF point = new PointF(x, y); transformPointToViewLocal(point, child); return pointInView(child, point.x, point.y); } static public void transformPointToViewLocal(PointF pointToModify, View child) { pointToModify.x -= child.getLeft(); pointToModify.y -= child.getTop(); } static public boolean pointInView(View v, float localX, float localY) { return localX >= 0 && localX < (v.getRight() - v.getLeft()) && localY >= 0 && localY < (v.getBottom() - v.getTop()); } static final int[] sIconsForListItems; static final int NUM_LIST_ITEMS = 50; static final int MAX_NUM_ICON_PER_ELEMENT = 8; static { sIconsForListItems = new int[NUM_LIST_ITEMS]; for (int i=0; i < NUM_LIST_ITEMS; i++) { sIconsForListItems[i] = (getRand(MAX_NUM_ICON_PER_ELEMENT)); } } private static final String TAG = DragDropListView.class.getSimpleName(); private static void log(String format, Object... args) { Log.d(TAG, String.format(format, args)); } 

很多评论希望代码是自我logging。 一些注意事项:

  • RandomIconAdapter只是扩展BaseAdapter的基本适配器,由sIconsForListItems支持。
  • ListViewScrollingDragListener与提示中的一样。
  • 在GS6 5.0.2上testing