android可拖拽item的ListView--DragListVie

本博客原创,转载请标明 原文出处: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>
时间: 2024-10-12 20:06:20

android可拖拽item的ListView--DragListVie的相关文章

【Android】可拖拽排序的ListView

[Android]可拖拽排序的ListView 实现Item的拖拽排序效果 下载地址:http://www.devstore.cn/code/info/746.html 运行截图:

ListView可拖拽item的原理

通过继承ListView实现可拖拽的ListView,先说说实现拖拽的原理吧,实现拖拽需要考虑三个问题:第一怎么确定你在拖拽listview里面的item的时候就是你手指当前选中的item:第二实现拖拽的效果,就是有一个浮动的层跟随你的手指在移动:第三你放开手指时怎么把你拖拽的这个item放到当前listView的位置(也就是说改变item的位置).明白了这三个问题就比较好实现了. 里面会涉及到一些比较重要的方法调用,首先是pointToPosition(int x, int y)这方方法And

自定义控件——可拖拽排序的ListView

前言 最经研究了一下拖拽排序的ListView,跟酷狗里的播放列表排序一样,但因为要添加自己特有的功能,所以研究了好长时间.一开始接触的是GitHub的开源项目--DragSortListView,实现的效果和流畅度都很棒.想根据他的代码自己写一个,但代码太多了,实现的好复杂,看别人的代码你懂的了,就去尝试寻找其他办法.最后还是找到了更简单的实现方法,虽然跟开源项目比要差一点,但对我来说可以了,最重要的是完全可以自定义. 实现的效果如下: 主要问题 如何根据触摸的位置确定是哪个条目? ListV

Android DragAndDrop API 拖拽效果 交换ListView的Item值

前言 Android系统自API Level11开始添加了关于控件拖拽的相关API,可以方便的实现控件的一些拖拽效果,而且比自己用Touch事件写的效果更好.下面就来看下DragAndDrop吧. 使用Android的DragAndDrop框架,我们可以方便的在当前布局中用拖拽的形式实现两个View之间数据的互换.DragAndDrop框架包括一个拖拽事件的类,拖拽监听器,以及一些帮助方法和类. 尽管DragAndDrop主要是为了数据移动而设计,但是我们也可用他做别的UI处理.举个例子,我们可

Android Launcher拖拽事件详解【android4.0--Launcher系列二】

AndroidICS4.0版本的launcher拖 拽的流程,基本和2.3的相似.就是比2.3写的封装的接口多了一些,比如删除类的写法就多了个类.等等.4.0的改变有一些,但是不是特别大.这个月一 直在改动Launcher的缩略图的效果,4.0的缩略图的功能没有实现,还得从2.3的Launcher中摘出来.通过做这个缩略图对Launcher 的模块有一点点了解,拿来分享一下Launcher拖拽的工作流程.有图有真相!   (1) 先来看看类之间的继承关系      图(1)  (2)再来看看La

android ListView和GridView拖拽移位实现代码

关于ListView拖拽移动位置,想必大家并不陌生,比较不错的软件都用到如此功能了.如:搜狐,网易,百度等,但是相比来说还是百度的用户体验较好,不偏心了,下面看几个示例:             首先说一下:拖拽ListView的item就不应该可以任意移动,只应该在ListView所在的范围内,而网易的你看看我都可以移动到状态栏了,虽然你做了处理,但是用户体验我个人感觉不好,在看看百度的,不仅控制了移动范围,更不错的百度的移动起来会时时的换位,看起来相当的形象,所以我认为这样相当的棒.说明一点

android ListView和GridView拖拽移位具体实现及拓展

关于ListView拖拽移动位置,想必大家并不陌生,比较不错的软件都用到如此功能了.如:搜狐,网易,百度等,但是相比来说还是百度的用户体验较好,不偏心了,下面看几个示例:              首先说一下:拖拽ListView的item就不应该可以任意移动,只应该在 ListView所在的范围内,而网易的你看看我都可以移动到状态栏了,虽然你做了处理,但是用户体验我个人感觉不好,在看看百度的,不仅控制了移动范 围,更不错的百度的移动起来会时时的换位,看起来相当的形象,所以我认为这样相当的棒.

Android 仿今日头条频道管理(下)(GridView之间Item的移动和拖拽)

前言 上篇博客我们说到了今日头条频道管理的操作交互体验,我也介绍了2个GridView之间Item的相互移动.详情请參考:Android 仿今日头条频道管理(上)(GridView之间Item的移动和拖拽) 今天把相对照较复杂的gridView的拖拽也记录下.在開始之前我们事先要了解下Android的事件分发机制.网上这方面的资料也比較多.由于自己定义控件大部分要用到事件分发机制的知识. 实现思路 要实现Item的拖拽.事实上并非真正要去拖拽GridView的Item.而是使用WindowMan

Android中GridView拖拽的效果

最 近看到联想,摩托罗拉等,手机launcher中有个效果,进入mainmenu后,里面的应用程序的图标可以拖来拖去,所以我也参照网上给的代码,写了 一个例子.还是很有趣的,实现的流畅度没有人家的那么好,我只是模仿这种效果,我写的这个拖拽是两个图标之间进行交换,所以,当从一行的某个位置,换到下 一行的另一列的时候,发现有好几个图标都改变位置了,因为是相邻两个交换位置,所以每经过相邻的图标的时候都改变位置.先弄个雏形,以后再更新优化. 转载请标明出处:http://blog.csdn.net/wd