本博客原创,转载请标明 原文出处:http://blog.csdn.net/sql26/article/details/52252644
1.概述
在android项目开发中,需求对ListView中的商品item进行拖拽重新排序,网上看了一些帖子做的效果不错,就是代码不开源只写了思路,要么代码没注释,还不如自己写一个。。
2.效果图:
3.原理:
1.在touch事件里面通过ListView的pointToPosition(x, y)方法拿到当前点击的item的position;
2.根据当前点击的x,y判断是否点是的拖拽触发按钮;
3.获取点击的条目视图,itemView = ListView.getChildAt(dragEndPosition - getFirstVisiblePosition());
4.通过itemView.setDrawingCacheEnabled(true); Bitmap.createBitmap(itemView.getDrawingCache())拷贝当前view为bigtmap;
5.通过WindowManager来把这个bitmap展示出来,可以用windowParams = WindowManager.LayoutParams(); windowManager.addView(imageView, windowParams);来设置itemView的位置,大小;
6.在onTouchEvent中的滑动事件MotionEvent.ACTION_MOVE中实时更改windowParams的Y值,并且更新视图windowManager.updateViewLayout(dragImageView, windowParams);
7.实时切换移动中相邻2个item的数据;
8.监听touch事件中MotionEvent.ACTION_UP行为,为拖拽结束信号,调用WindowManager.removeView(View view)来销毁创建的悬浮View;由于第7步的原因,起始拖拽的item的数据已经移动到当前position,看起来的效果就像是拖过来的;
注意:并不是真的把item的视图进行切换,而是转移了他们的数据而已,悬浮view只是一种交互形式。过程中数据的交换,类似冒泡排序
4.代码
DragListView:
package com.lunge.dragablelistview; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.ListView; /** * Created by Lunger on 8/17 2016 11:21 */ public class DragListView extends ListView { private WindowManager windowManager;// windows窗口控制类 private WindowManager.LayoutParams windowParams;// 用于控制拖拽项的显示的参数 private ImageView dragImageView;// 被拖拽的项(item),其实就是一个ImageView private int dragbeginPosition;// 手指拖动项原始position private int dragEndPosition;// 手指点击准备拖动的时候,当前拖动项在列表中的位置. private int dragPoint;// 在当前数据项中的位置 private int dragYOffset;// 当前视图和屏幕的距离(这里只使用了y方向上) private int upScrollBounce;// 拖动的时候,开始向上滚动的边界 private int downScrollBounce;// 拖动的时候,开始向下滚动的边界 private final static int step = 1;// ListView 滑动步伐. private int current_Step;// 当前步伐. private int temChangId;// 临时交换id private boolean isLock;// 是否上锁. private MyDragListener mMyDragListener; /** * @param isLock 拖拽功能的开关,true为关闭 */ public void setLock(boolean isLock) { this.isLock = isLock; } public DragListView(Context context, AttributeSet attrs) { super(context, attrs); } /*** * touch事件拦截 在这里我进行相应拦截, */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // 按下 if (ev.getAction() == MotionEvent.ACTION_DOWN && !isLock) { int x = (int) ev.getX();// 获取相对与ListView的x坐标 int y = (int) ev.getY();// 获取相应与ListView的y坐标 temChangId = dragbeginPosition = dragEndPosition = pointToPosition(x, y); // 无效不进行处理 if (dragEndPosition == AdapterView.INVALID_POSITION) { return super.onInterceptTouchEvent(ev); } // 获取当前位置的item视图(可见状态) ViewGroup itemView = (ViewGroup) getChildAt(dragEndPosition - getFirstVisiblePosition()); // 获取到的dragPoint其实就是在你点击指定item项中的高度. dragPoint = y - itemView.getTop(); // 这个值是固定的:其实就是ListView这个控件与屏幕最顶部的距离(一般为标题栏+状态栏. dragYOffset = (int) (ev.getRawY() - y); // 获取可拖拽的图标 View dragger = itemView.findViewById(R.id.iv_move); //点击的x坐标大于移动按钮的x坐标,就当成是按到了iv_move触发了移动 if (dragger != null && x > dragger.getLeft()) { //如果想点击item的任意位置都能进行拖拽,把x > dragger.getLeft()限定去掉就行 upScrollBounce = getHeight() / 3;// 取得向上滚动的边际,大概为该控件的1/3 downScrollBounce = getHeight() * 2 / 3;// 取得向下滚动的边际,大概为该控件的2/3 itemView.setBackgroundColor(Color.parseColor("#a35151")); itemView.setDrawingCacheEnabled(true);// 开启cache. Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());// 根据cache创建一个新的bitmap对象,就是你拖着狂奔的对象 startDrag(bm, y);// 初始化影像 } return false; } return super.onInterceptTouchEvent(ev); } /** * 触摸事件处理 */ @Override public boolean onTouchEvent(MotionEvent ev) { // item的view不为空,且获取的dragPosition有效 if (dragImageView != null && dragEndPosition != INVALID_POSITION && !isLock) { 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; case MotionEvent.ACTION_DOWN: break; default: break; } return true;// 取消ListView滑动. } return super.onTouchEvent(ev); } /** * 准备拖动,初始化拖动项的图像 * * @param bm * @param y */ private void startDrag(Bitmap bm, int y) { /*** * 初始化window. */ windowParams = new WindowManager.LayoutParams(); windowParams.gravity = Gravity.TOP; windowParams.x = 0; windowParams.y = y - dragPoint + dragYOffset; 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; // 窗口占满整个屏幕,忽略周围的装饰边框(例如状态栏)。此窗口需考虑到装饰边框的内容。 ImageView imageView = new ImageView(getContext()); imageView.setImageBitmap(bm); windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); windowManager.addView(imageView, windowParams); dragImageView = imageView; } /** * 拖动执行,在Move方法中执行 * * @param y */ public void onDrag(int y) { int drag_top = y - dragPoint;// 拖拽view的top值不能<0,否则则出界. if (dragImageView != null && drag_top >= 0) { windowParams.alpha = 0.65f; windowParams.y = y - dragPoint + dragYOffset; windowManager.updateViewLayout(dragImageView, windowParams);// 时时移动(拖拽移动的核心) } // 为了避免滑动到分割线的时候,返回-1的问题 int tempPosition = pointToPosition(0, y); if (tempPosition != INVALID_POSITION) { dragEndPosition = tempPosition; } onChange(y);// 时时交换 doScroller(y);// listview移动. } /*** * ListView的移动. * 要明白移动原理:当我移动到下端的时候,ListView向上滑动,当我移动到上端的时候,ListView要向下滑动。 * 正好和实际的相反. */ public void doScroller(int y) { // ListView需要下滑 if (y < upScrollBounce) { current_Step = step + (upScrollBounce - y) / 10;// 时时步伐 }// ListView需要上滑 else if (y > downScrollBounce) { current_Step = -(step + (y - downScrollBounce)) / 10;// 时时步伐 } else { current_Step = 0; } // 获取你拖拽滑动到位置及显示item相应的view上(注:可显示部分)(position) View view = getChildAt(dragEndPosition - getFirstVisiblePosition()); // 真正滚动的方法setSelectionFromTop() if (view != null) setSelectionFromTop(dragEndPosition, view.getTop() + current_Step); } /** * 停止拖动,删除影像 */ public void stopDrag() { if (dragImageView != null) { windowManager.removeView(dragImageView); dragImageView = null; } } /*** * 拖动时时change */ private void onChange(int y) { // 数据交换 if (dragEndPosition < getAdapter().getCount()) { DragListAdapter adapter = (DragListAdapter) getAdapter(); if (dragEndPosition != temChangId) { adapter.update(temChangId, dragEndPosition); temChangId = dragEndPosition;// 将点击最初所在位置position付给临时的,用于判断是否换位. } } // 为了避免滑动到分割线的时候,返回-1的问题 int tempPosition = pointToPosition(0, y); if (tempPosition != INVALID_POSITION) { dragEndPosition = tempPosition; } // 超出边界处理(如果向上超过第二项Top的话,那么就放置在第一个位置) if (y < getChildAt(0).getTop()) { // 超出上边界 dragEndPosition = 0; // 如果拖动超过最后一项的最下边那么就防止在最下边 } else if (y > getChildAt(getChildCount() - 1).getBottom()) { // 超出下边界 dragEndPosition = getAdapter().getCount() - 1; } } /** * 拖动放下的时候 * * @param y */ public void onDrop(int y) { // 数据交换 if (dragEndPosition < getAdapter().getCount()) { DragListAdapter adapter = (DragListAdapter) getAdapter(); adapter.notifyDataSetChanged();// 刷新. Log.d("wbl", "dragEndPosition :" + dragEndPosition); Log.d("wbl", "dragbeginPosition :" + dragbeginPosition); //换位成功后的回调 if (mMyDragListener != null) { mMyDragListener.onDragFinish(dragbeginPosition, dragEndPosition); } } } //换位成功后的回调接口 interface MyDragListener { void onDragFinish(int srcPositon, int finalPosition); } //设置换位成功后的回调接口 public void setMyDragListener(MyDragListener listener) { mMyDragListener = listener; } }
DragListAdapter:
package com.lunge.dragablelistview; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.ArrayList; /** * Created by Lunger on 8/17 2016 11:21 */ class DragListAdapter extends BaseAdapter { private ArrayList<String> mDatas; private Context context; public DragListAdapter(Context context, ArrayList<String> arrayTitles) { this.context = context; this.mDatas = arrayTitles; } @Override public View getView(final int position, View convertView, ViewGroup parent) { View view; /*** * 在这里尽可能每次都进行实例化新的,这样在拖拽ListView的时候不会出现错乱. * 具体原因不明,不过这样经过测试,目前没有发现错乱。虽说效率不高,但是做拖拽LisView足够了。 */ view = LayoutInflater.from(context).inflate( R.layout.drag_list_item, null); TextView textView = (TextView) view .findViewById(R.id.tv_name); textView.setText(mDatas.get(position)); return view; } /*** * 动态修改ListVIiw的方位.(数据移位) * * @param start 点击移动的position * @param end 松开时候的position */ public void update(int start, int end) { String data = mDatas.get(start); mDatas.remove(start);// 删除该项 mDatas.add(end, data);// 添加删除项 notifyDataSetChanged();// 刷新ListView } @Override public int getCount() { return mDatas.size(); } @Override public Object getItem(int position) { return mDatas.get(position); } @Override public long getItemId(int position) { return position; } }
item布局:
<?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"> <TextView android:id="@+id/tv_name" android:layout_width="300px" android:layout_height="100px" android:layout_centerVertical="true" android:gravity="center" android:text="Your infomation" android:textSize="25px"/> <ImageView android:id="@+id/iv_move" android:layout_width="53px" android:layout_height="75px" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="20px" android:src="@mipmap/ic_launcher"/> </RelativeLayout>
MainActivity:
package com.lunge.dragablelistview; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.widget.Toast; import java.util.ArrayList; /** * Created by Lunger on 8/17 2016 11:21 */ public class MainActivity extends AppCompatActivity { private DragListView mLv; private ArrayList<String> mDatas; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLv = (DragListView) findViewById(R.id.lv); mDatas = new ArrayList<>(); for (int i = 0; i < 50; i++) { mDatas.add("Monster kill " + i); } mLv.setAdapter(new DragListAdapter(this, mDatas)); mLv.setMyDragListener(new DragListView.MyDragListener() { @Override public void onDragFinish(int srcPositon, int finalPosition) { Toast.makeText(MainActivity.this, "beginPoisiton : " + srcPositon + "...endPosition : " + finalPosition, Toast.LENGTH_LONG).show(); } }); } }
main布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.lunge.dragablelistview.MainActivity"> <com.lunge.dragablelistview.DragListView android:id="@+id/lv" android:layout_width="wrap_content" android:layout_height="match_parent" /> </RelativeLayout>