ListView列表拖拽排序
ListView列表拖拽排序能够參考Android源代码下的Music播放列表,他是能够拖拽的,源代码在[packages/apps/Music下的TouchInterceptor.java
下]。
首先是搭建框架,此处的ListView列表相似于QQ消息列表,当然数据不过模拟,为了简单起见,没有把ListView的条目的所有的属性所有写上。首先是消息的实体类Msg.java:
package me.chenfuduo.mymsgdrag;
public class Msg {
private int ivId;
private String text;
public Msg() {
}
public Msg(int ivId, String text) {
this.ivId = ivId;
this.text = text;
}
public int getIvId() {
return ivId;
}
public String getText() {
return text;
}
}
然后是数据列表的每一个Item的布局item.xml:
<?
xml version="1.0" encoding="utf-8"?
>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_marginLeft="10dp"
android:layout_centerInParent="true"
android:id="@+id/imageView"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:layout_toRightOf="@id/imageView"
android:layout_centerInParent="true"
android:layout_marginLeft="10dp"
/>
</RelativeLayout>
如今能够新建一个MsgAdapter适配器类,让其继承自ArrayAdapter,并实现其构造方法和重写getView()
方法。
package me.chenfuduo.mymsgdrag;
import java.util.List;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class MsgAdapter extends ArrayAdapter<Msg> {
public MsgAdapter(Context context, List<Msg> msgList) {
super(context, 0, msgList);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = View.inflate(getContext(), R.layout.item, null);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) view
.findViewById(R.id.imageView);
viewHolder.textView = (TextView) view.findViewById(R.id.textView);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.imageView.setImageResource(getItem(position).getIvId());
viewHolder.textView.setText(getItem(position).getText());
return view;
}
static class ViewHolder {
ImageView imageView;
TextView textView;
}
}
主界面MainActivity设置适配器:
package me.chenfuduo.mymsgdrag;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
private MyDragListView list;
private List<Msg> msgList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
list = (MyDragListView) findViewById(R.id.list);
msgList = new ArrayList<Msg>();
initData();
list.setAdapter(new MsgAdapter(this, msgList));
}
private void initData() {
for (int i = 0; i < 30; i++) {
msgList.add(new Msg(R.drawable.ic_launcher, "new item" + i));
}
}
}
这里提到的MyDragListView
就是以下我们要着重介绍的自己定义的ListView,先无论。
OK,如今运行,数据所有展示在ListView上了。以下開始新建一个类MyDragListView
,并让其继承自ListView。提供三个构造方法。
public MyDragListView(Context context) {
this(context, null);
}
public MyDragListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyDragListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
接下来。须要重写onInterceptTouchEvent()
拦截事件的方法,为了能在子控件响应触摸事件的情况下此ListView也能监听到触摸事件,须要重写此方法,做一些初始化的工作,在这里捕获ACTION_DOWN
事件。在ACTION_DOWN
事件中,做一些拖动的准备工作。
- 获取PV数据项,初始化一些变量(pointToPosition)
- 推断是否是拖动还是不过点击
- 假设是拖动,建立拖动影像()
以上都是后面拖动的基础。
那首先定义我们须要的一些变量:
// 原始条目位置
private int dragSrcPosition;
// 目标条目位置
private int dragDestPosition;
//在当前数据项中的位置
private int dragPoint;
//拖动的时候,開始向上滚动的边界
private int upScrollBounce;
//拖动的时候,開始向下滚动的边界
private int downScrollBounce;
//窗口控制类
private WindowManager windowManager;
//用于控制拖拽项显示的參数
private WindowManager.LayoutParams windowParams;
//当前视图和屏幕的距离(这里只使用了y轴上的)
private int dragOffset;
// 推断滑动的一个距离,scroll的时候会用到
private int scaledTouchSlop;
// 被拖拽项的影像。事实上就是一个ImageView。在我们这里是"用户头像"
private ImageView dragImageView;
我们在构造器中获取滑动的距离:
public MyDragListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
注意获取这个系统所能识别出的被觉得是滑动的最小距离
的方式。
getScaledTouchSlop()
是一个距离,表示滑动的时候,手的移动要大于这个距离才開始移动控件。
假设小于这个距离就不触发移动控件。
接下来,在onInterceptTouchEvent()
捕获的ACTION_DOWN
事件中,做处理。
还是依照上面的来,第一部须要得到选中的数据项的位置,这里使用pointToPosition(x,y)
就可以。假设想要測试这个api。也非常easy,以下是实例代码:
mListView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int item=mListView.pointToPosition((int) event.getX(), (int) event.getY());
System.out.println("---> 如今点击了ListView中第"+(item+1)+"个Item");
return true;
}
});
ok,在我们这里是这样:
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 触点所在的条目的位置
int x = (int) ev.getX();
int y = (int) ev.getY();
dragSrcPosition = dragDestPosition = pointToPosition(x, y);
//假设是无效位置(超出边界,切割线等位置)。返回
if(dragDestPosition==AdapterView.INVALID_POSITION){
return super.onInterceptTouchEvent(ev);
}
}
如今我们要获取ListView的单个Item,由于获取了这个单个的Item,才干获取Item里面的”头像”(姑且这么叫),ok,代码例如以下:
//这里假设不减去,会报空指针异常
ViewGroup itemView = (ViewGroup) getChildAt(dragSrcPosition
- getFirstVisiblePosition());
在这里我当时遇到一个NPE
的问题。就是当ListView滚动到以下的时候,我选择以下的Item。报错了。归根究竟,还是没有理解好getChildAt(i)
这种方法。
这里。我參考了以下的资料去理解的。
ListView中getChildAt(index)的使用注意事项
通过getChildAt方法取得AdapterView中第n个Item
stackover:ListView getChildAt returning null for visible children
说究竟,getChildAt(i)
是获取可见视图的。
接下来,就能够获取”用户头像”了:
// 图标
View dragger = itemView.findViewById(R.id.imageView);
以下须要推断手指的触点是不是在logo(”用户头像”)范围内:
if (dragger != null && x < dragger.getRight() + 10) {
upScrollBounce = Math.min(y - scaledTouchSlop, getHeight() / 3);
downScrollBounce = Math.max(y + scaledTouchSlop,
getHeight() * 2 / 3);
}
这个非常好理解。
接着便能够获取选中条目的图片了。
itemView.setDrawingCacheEnabled(true);
Bitmap bitmap = itemView.getDrawingCache();
startDrag(bitmap, y);
itemView.setDrawingCacheEnabled(false);
这里。又学到一招,将View转化为Bitmap,相关的api:
- setDrawingCacheEnabled(boolean)注意最后须要设置其为false
- getDrawingCache()
那么如今就能够拖动了。
startDrag(bitmap, y);
最后,我们返回false,让事件能够传递到子控件。
总体的代码入下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 触点所在的条目的位置
int x = (int) ev.getX();
int y = (int) ev.getY();
dragSrcPosition = dragDestPosition = pointToPosition(x, y);
// 假设是无效位置(超出边界,切割线等位置),返回
if (dragDestPosition == AdapterView.INVALID_POSITION) {
return super.onInterceptTouchEvent(ev);
}
//这里假设不减去
ViewGroup itemView = (ViewGroup) getChildAt(dragSrcPosition
- getFirstVisiblePosition());
// 手指在条目中的相对y坐标
dragPoint = y - itemView.getTop();
/*Log.e("Test", "dragPoint:" + dragPoint + "\n" + "y:"+ y
+ "\n" + "itemView.getTop():" + itemView.getTop());*/
dragOffset = (int) (ev.getRawY() - y);
/*Log.e("Test", "dragOffset:" + dragPoint + "\n" + "y:"+ y
+ "\n" + "ev.getRawY():" + ev.getRawY());*/
// 图标
View dragger = itemView.findViewById(R.id.imageView);
// 推断触点是否在logo的区域
if (dragger != null && x < dragger.getRight() + 10) {
upScrollBounce = Math.min(y - scaledTouchSlop, getHeight() / 3);
downScrollBounce = Math.max(y + scaledTouchSlop,
getHeight() * 2 / 3);
// 获取选中条目的图片
itemView.setDrawingCacheEnabled(true);
Bitmap bitmap = itemView.getDrawingCache();
itemView.setDrawingCacheEnabled(false);
startDrag(bitmap, y);
}
// 能够传递到子控件
return false;
}
return super.onInterceptTouchEvent(ev);
}
在上面有获取坐标的getRawY()等等,假设不清楚。能够看下这个文章。
android MotionEvent中getX()和getRawX()的差别
接下来是拖拽的方法startDrag(bitmap, y);
:
private void startDrag(Bitmap bitmap, int y) {
// 释放影像,在准备影像的时候。防止影像没释放,每次都运行一下
stopDrag();
windowManager = (WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE);
// 窗口參数配置
windowParams = new WindowManager.LayoutParams();
windowParams.gravity = Gravity.TOP;
windowParams.x = 0;
// 图片在屏幕上的绝对坐标
windowParams.y = y - dragPoint + dragOffset;
/*Log.e("Test", "windowParams.y:" + windowParams.y + "\n" + "y:"+ y
+ "\n" + "dragPoint:" + dragPoint +
"\n" + "dragOffset" + dragOffset);*/
// 加入显示窗口
windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 以下这些參数能够帮助准确定位到选中项点击位置,照抄就可以
windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
windowParams.format = PixelFormat.TRANSLUCENT;
windowParams.windowAnimations = 0;
// 把影像ImagView加入到当前视图中
ImageView imageView = new ImageView(getContext());
imageView.setImageBitmap(bitmap);
windowManager = (WindowManager) getContext().getSystemService("window");
windowManager.addView(imageView, windowParams);
// 把影像ImageView引用到变量drawImageView,用于兴许操作(拖动,释放等等)
dragImageView = imageView;
}
假设做过自己定义Toast,对上面的代码不会陌生。
不做解释,接着重写boolean onTouchEvent(MotionEvent ev)
:
@Override
public boolean onTouchEvent(MotionEvent ev) {
// 假设dragImageView为空。说明拦截事件中已经判定不过点击,不是拖动,返回
// 假设点击的是无效位置,返回,须要又一次推断
if (dragImageView != null && dragDestPosition != INVALID_POSITION) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
int upY = (int) ev.getY();
// 释放拖动影像
stopDrag();
// 放下后。推断位置,实现对应的位置删除和插入
onDrop(upY);
break;
case MotionEvent.ACTION_MOVE:
int moveY = (int) ev.getY();
// 拖动影像
onDrag(moveY);
break;
default:
break;
}
return true;
}
// 这个返回值能够实现selected的选中效果,假设返回true则无选中效果
return super.onTouchEvent(ev);
}
首先得推断是点击还是拖动,直接通过dragImageView
就可以推断。假设dragImageView为空,说明拦截事件中已经判定不过点击,不是拖动。返回。接着分析拖动的方法onDrag(moveY);
:
拖动的时候,当前拖动的条目的透明度让其有所变化,然后是位置在不断更新,其次须要推断位置是否合法。最后是滚动。
private void onDrag(int y) {
if (dragImageView != null) {
windowParams.alpha = 0.8f;
windowParams.y = y - dragPoint + dragOffset;
windowManager.updateViewLayout(dragImageView, windowParams);
}
// 为了避免滑动到切割线的时候,返回-1的问题
int tempPosition = pointToPosition(0, y);
if (tempPosition != INVALID_POSITION) {
dragDestPosition = tempPosition;
}
// 滚动
int scrollHeight = 0;
if (y < upScrollBounce) {
scrollHeight = 8;// 定义向上滚动8个像素,假设能够向上滚动的话
} else if (y > downScrollBounce) {
scrollHeight = -8;// 定义向下滚动8个像素,。假设能够向上滚动的话
}
if (scrollHeight != 0) {
// 真正滚动的方法setSelectionFromTop()
setSelectionFromTop(dragDestPosition,
getChildAt(dragDestPosition - getFirstVisiblePosition())
.getTop() + scrollHeight);
}
}
这里的ViewManager.updateViewLayout(View arg0, LayoutParams arg1)
会使得view所引用的实例使用params又一次绘制自己。
接下来介绍下ListView的setSelectionFromTop(...)
和setSelection(...)
方法。
看一下setSelectionFromTop()的详细实现,代码例如以下:
/**
* Sets the selected item and positions the selection y pixels from the top edge
* of the ListView. (If in touch mode, the item will not be selected but it will
* still be positioned appropriately.)
*
* @param position Index (starting at 0) of the data item to be selected.
* @param y The distance from the top edge of the ListView (plus padding) that the
* item will be positioned.
*/
public void setSelectionFromTop(int position, int y) {
if (mAdapter == null) {
return;
}
if (!isInTouchMode()) {
position = lookForSelectablePosition(position, true);
if (position >= 0) {
setNextSelectedPositionInt(position);
}
} else {
mResurrectToPosition = position;
}
if (position >= 0) {
mLayoutMode = LAYOUT_SPECIFIC;
mSpecificTop = mListPadding.top + y;
if (mNeedSync) {
mSyncPosition = position;
mSyncRowId = mAdapter.getItemId(position);
}
requestLayout();
}
}
从上面的代码能够得知,setSelectionFromTop()
的作用是设置ListView选中的位置。同一时候在Y轴设置一个偏移量(padding值)。
ListView另一个方法叫setSelection()
,传入一个index整型数值,就能够让ListView定位到指定Item的位置。
这两个方法有什么差别呢?看一下setSelection()的详细实现。代码例如以下:
/**
* Sets the currently selected item. If in touch mode, the item will not be selected
* but it will still be positioned appropriately. If the specified selection position
* is less than 0, then the item at position 0 will be selected.
*
* @param position Index (starting at 0) of the data item to be selected.
*/
@Override
public void setSelection(int position) {
setSelectionFromTop(position, 0);
}
原来。setSelection()
内部就是调用了setSelectionFromTop()
,只不过是Y轴的偏移量是0而已。
Ok,当手指抬起来的时候。须要停止拖动:
private void stopDrag() {
if (dragImageView != null) {
windowManager.removeView(dragImageView);
dragImageView = null;
}
}
最后得将Item放到正确的位置:
private void onDrop(int y) {
// 获取放下位置在数据集合中position
// 定义暂时位置变量为了避免滑动到切割线的时候,返回-1的问题,假设为-1,则不改动dragPosition的值,急需运行。达到跳过无效位置的效果
int tempPosition = pointToPosition(0, y);
if (tempPosition != INVALID_POSITION) {
dragDestPosition = tempPosition;
}
// 超出边界处理
if (y < getChildAt(1).getTop()) {
// 超出上边界,设为最小值位置0
dragDestPosition = 0;
} else if (y > getChildAt(getChildCount() - 1).getTop()) {
// 超出下边界。设为最大值位置,注意哦,假设大于可视界面中最大的View的底部则是越下界,所以推断中用getChildCount()方法
// 可是最后一项在数据集合中的position是getAdapter().getCount()-1。这点要区分清除
dragDestPosition = getAdapter().getCount() - 1;
}
// 数据更新
if (dragDestPosition >= 0 && dragDestPosition < getAdapter().getCount()) {
MsgAdapter adapter = (MsgAdapter) getAdapter();
Msg dragItem = adapter.getItem(dragSrcPosition);
// 删除原位置数据项
adapter.remove(dragItem);
// 在新位置插入拖动项
adapter.insert(dragItem, dragDestPosition);
}
}
ListView列表拖拽排序的更多相关文章
- vue列表拖拽排序功能实现
1.实现目标:目标是输入一个数组,生成一个列表:通过拖拽排序,拖拽结束后输出一个经过排序的数组. 2.实现思路: 2.1是使用HTML5的drag功能来实现,每次拖拽时直接操作Dom节点排序,拖拽结束 ...
- jQuery图片列表拖拽排序布局
在线演示 本地下载
- php接口实现拖拽排序功能
列表拖拽排序是一个很常见的功能,但是后端接口如何处理却是一个令人纠结的问题 如何实现才能达到效率最高呢 先分析一个场景,假如有一个页面有十条数据,所谓的拖拽就是在这十条数据来来回回的拖,但是每次拖动都 ...
- vue实现拖拽排序
基于vue实现列表拖拽排序的效果 在日常开发中,特别是管理端,经常会遇到要实现拖拽排序的效果:这里提供一种简单的实现方案. 此例子基于vuecli3 首先,我们先了解一下js原生拖动事件: 在拖动目标 ...
- jQuery可拖拽排序列表jquery-sortable-lists
jquery-sortable-lists可以通过鼠标进行拖动排列树型菜单,可以定义某个列表元素是否拖动,拖动后回调,点击可以折叠树型结点,可以用来在后台模仿wordpress后台拖动菜单,实现多级菜 ...
- odoo开发笔记-tree列表视图拖拽排序
odoo列表tree视图 拖拽排序 实现效果: 实现方式: 模型中定义字段: class CusYourModel(models.Model): """ 你的模型 &qu ...
- jquery拖拽排序,针对后台列表table进行拖拽排序(超实用!)
现在很多后台列表为了方便均使用拖拽排序的功能,对列表进行随意的排序. 话不多说 ,我在网上找了一些demo,经过对比,现在把方便实用的一个demo列出来,基于jqueryUI.js 先上html代码, ...
- 基于html5拖拽api实现列表的拖拽排序
基于html5拖拽api实现列表的拖拽排序 html代码: <ul ondrop="drop_handler(event);" ondragover="dragov ...
- RecyclerViewItemTouchHelperDemo【使用ItemTouchHelper进行拖拽排序功能】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 记录使用ItemTouchHelper对Recyclerview进行拖拽排序功能的实现. 效果图 代码分析 ItemTouchHel ...
随机推荐
- php 在web端读出pdf 与各种文件下载
单纯的下载功能实现 <?php // 表示调用文本类型为pdf的应用 header('Content-type: application/pdf'); // 这句可以输出下载页面进行下载 hea ...
- Wpf 数据绑定简介、实例1
简介:1.WPF绑定使用的源属性必须是依赖项属性,这是因为依赖项属性具有内置的更改通知支持,元素绑定表达式使用了Xaml扩展标记, WPF绑定一个控件是使用Binding.ElementName, 绑 ...
- OC语法简写
NSNumber [NSNumber numberWithInt:666] 等价于 @666 [NSNumber numberWithLongLong:666ll] 等价于 @666ll [NSNum ...
- 利用iframe实现提交表单是页面部分刷新
直接上代码: <%@ page language="java" import="java.util.*" pageEncoding="utf-8 ...
- mybatis 一对一关联
首先建表: CREATE TABLE teacher( t_id INT PRIMARY KEY AUTO_INCREMENT, t_name VARCHAR(20) ); CREATE TABLE ...
- JavaScript中instanceof与typeof运算符的用法及区别详细解析
JavaScript中的instanceof和typeof常被用来判断一个变量是什么类型的(实例),但它们的使用还是有区别的: typeof 运算符 返回一个用来表示表达式的数据类型的字符串. typ ...
- javascript-Cookie的应用
在我平时开发网页的过程中,可能涉及到浏览器本地的存储,现在主流的浏览器存储方式有:cookie,直接读取xml,userData,H5 的LocalStorage等,Cookie存储数据有限,但对于数 ...
- Ecshop 数据库操作方法getRow、getAll、getOne区别
ECShop没有使用一些开源的数据库操作类,比如adodb或者PEAR,而是封装了自己的实现.这样做的好处是实现非常轻量,大大减小了分发包的文件大小.另外,当网站需要做memcached缓存时,也可以 ...
- 【行为型】TemplateMethod模式
模板方法意图是为算法定义好骨架结构,并且其中的某些步骤延迟到子类实现.该模式算是较为简单的一种设计模式.在实际中,应用也较为频繁.模式的类关系图参考如下: 模式的编码结构参考如下: namespace ...
- Python自动化运维之16、线程、进程、协程、queue队列
一.线程 1.什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行 ...