前言
ListView在Android App中占有重要的地位,很多界面的展示都要借助于这个控件,虽然RecyclerView已经逐步取代它的地位,掌握它的一些基本使用技巧还是很有必要的,现在就来探究一下如何实现拖动ListView的条目和侧滑删除ListView中的数据。
Android DND实现拖动
Android从3.0引入的Drag&Drop框架,实现在界面中的拖拽效果,用户为需要拖拽的子视图设置传递的数据、拖拽产生的阴影对象和本地数据等参数,调用View.startDrag方法就可以拖动子视图。同时还要对需要接收拖动事件的子视图设置拖动回调函数,拖动回调方法里处理多种拖动事件,根据拖动事件做出反应。
拖动事件 | 意义 |
---|---|
DragEvent.ACTION_DRAG_STARTED | 目标对象接收到拖动子视图开始,这时如果返回true代表对拖动的子视图感兴趣,系统会将后续的事件返送过来,否则就是不感兴趣,系统就忽略当前拖拽监视者,只会回调一次 |
DragEvent.ACTION_DRAG_ENTERED | DRAG_STARTED返回true之后,拖动子视图进入当前监控子视图范围,只会回调一次 |
DragEvent.ACTION_DRAG_LOCATION | 拖动子视图进入后开始准备向监控子视图drop,目前还在不断移动定位中,可以回调多次 |
DragEvent.ACTION_DRAG_EXITED | 拖动子视图离开了当前监控子视图的范围,只会回调一次 |
DragEvent.ACTION_DROP | 用户将拖动的子视图drop到当前监控子视图,只会回调一次 |
DragEvent.ACTION_DRAG_ENDED | 用户松手后所有的其他事件都已发送处理完成,最后发送DRAG_ENDED事件 |
在ListView的拖动事件中,被拖动的子视图就是用户点击的子视图,监听的子视图是当前屏幕中所有的ListView展示的子视图,当然被拖动的子视图自己除外。通常的拖动操作都是经过LongClick触发,只需要在ListView的adapter中getView生成的View设置LongClick时间监听。
final View dragView = convertView;
convertView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// DND框架要求传递的数据
ClipData.Item item = new ClipData.Item(String.valueOf(position));
ClipData clipData = new ClipData("", new String[] {
ClipDescription.MIMETYPE_TEXT_PLAIN
}, item);
// 开始当前View的拖动操作,将当前拖动对象的position当作localState传递到拖动事件中
dragView.startDrag(clipData, new View.DragShadowBuilder(dragView), position, 0);
return true;
}
});
View.DragShadowBuilder对象会在当前的界面上绘制子视图的外形称作DragShadow,也就是拖动阴影对象,这个对象会被透明化,这个应该是底层操作的,无法在应用层修改。设置了拖动监听之后开始设置其他的可能和它交换的Drag监听事件。
convertView.setOnDragListener(new View.OnDragListener() {
@Override
public boolean onDrag(View v, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_STARTED:
Log.d(TAG, "target: action = ACTION_DRAG_STARTED, clipData = " + event.getClipData());
// 如果传递的数据类型正确,而且监听对象不是当前拖动的对象
// event.getLocalState()可以获取前面拖动对方放进去的localState的position
if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) &&
Integer.parseInt(String.valueOf(event.getLocalState())) != position) {
return true;
}
break;
case DragEvent.ACTION_DRAG_ENTERED:
Log.d(TAG, "target: action = ACTION_DRAG_ENTERED, clipData = " + event.getClipData());
// 当进入当前监听对象,设置当前监听对象背景变色 dragView.setBackgroundColor(context.getResources().getColor(R.color.colorAccent));
return true;
case DragEvent.ACTION_DRAG_LOCATION:
// 正在当前监听对象内拖动,不必关心
Log.d(TAG, "target: action = ACTION_DRAG_LOCATION, clipData = " + event.getClipData());
return true;
case DragEvent.ACTION_DRAG_EXITED:
// 如果离开了当前监听对象那么恢复当前监听对象的背景色
Log.d(TAG, "target: action = ACTION_DRAG_EXITED, clipData = " + event.getClipData());
dragView.setBackgroundColor(Color.WHITE);
return true;
case DragEvent.ACTION_DROP:
Log.d(TAG, "target: action = ACTION_DROP, clipData = " + event.getClipData());
final int srcPosition = Integer.parseInt((String) event.getClipData().getItemAt(0).getText());
// 如果用户将拖动对象drop到了当前监听对象,
// 交换拖动对象和当前监听对象数据位置并且刷新
if (srcPosition == position) {
return true;
}
Collections.swap(users, srcPosition, position);
notifyDataSetChanged();
return <