首页 > 代码库 > Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现

Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现

Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现


我们做一些好友列表或者商品列表的时候,居多的需求可能就是需要列表拖拽了,而我们选择了ListView,也是因为使用ListView太久远了,导致对他已经有浓厚的感情了,我们之前也是写过几篇关于ListView的博文

  • Android实训案例(三)——实现时间轴效果的ListView,加入本地存储,实现恋爱日记的效果!

  • Android高级控件(一)——ListView绑定CheckBox实现全选,增加和删除等功能

  • Android高级控件(五)——如何打造一个企业级应用对话列表,以QQ,微信为例

而今天我们就来实现以下课拖拽的方案,大家可以借鉴以下

我们大致的思路,其实是这样子的,也是我的设想,我们可以先去实现一个简单的ListView的数据,但是他的Adapter,我们可以用系统封装好的,然后传递进去一个实体类,最后自定义一个listview去操作,所以我们先把准备的工作做好,比如?

list_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:id="@+id/iv_logo"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentLeft="true"
        android:layout_centerInParent="true"
        android:layout_marginLeft="10dp"/>

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@id/iv_logo"/>

</RelativeLayout>

这就只有一个头像和一句话了,然后我们把实体类也给写完了

DragBean

package com.liuguilin.draglistviewsample.entity;

/*
 *  项目名:  DragListViewSample 
 *  包名:    com.liuguilin.draglistviewsample.entity
 *  文件名:   DragBean
 *  创建者:   LGL
 *  创建时间:  2016/8/29 22:49
 *  描述:    实体类
 */

public class DragBean {

    private int ivId;
    private String text;

    public DragBean() {

    }

    public DragBean(int ivId, String text) {
        this.ivId = ivId;
        this.text = text;
    }

    public int getIvId() {
        return ivId;
    }


    public String getText() {
        return text;
    }

}

ok,其实很简单,id是图片,然后是文本,这样我们就可以来实现一个Adapter了,这里我用的是ArrayAdapter这样能让我们插入和删除很轻松

DragAdapter

package com.liuguilin.draglistviewsample.adapter;

/*
 *  项目名:  DragListViewSample 
 *  包名:    com.liuguilin.draglistviewsample.adapter
 *  文件名:   DragAdapter
 *  创建者:   LGL
 *  创建时间:  2016/8/29 22:41
 *  描述:    拖拽列表的数据源
 */

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.liuguilin.draglistviewsample.R;
import com.liuguilin.draglistviewsample.entity.DragBean;

import java.util.List;

public class DragAdapter extends ArrayAdapter<DragBean> {

    /**
     * 构造方法
     *
     * @param context
     * @param mList
     */
    public DragAdapter(Context context, List<DragBean> mList) {
        super(context, 0, mList);
    }

    /**
     * 实现View
     *
     * @param position
     * @param convertView
     * @param parent
     * @return
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = View.inflate(getContext(), R.layout.list_item, null);
            viewHolder = new ViewHolder();
            viewHolder.imageView = (ImageView) view
                    .findViewById(R.id.iv_logo);
            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;
    }

}

好的,其实到这里,他就是一个最普通的ListView了,我们给他填充点数据

MainActivity

package com.liuguilin.draglistviewsample;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.liuguilin.draglistviewsample.adapter.DragAdapter;
import com.liuguilin.draglistviewsample.entity.DragBean;
import com.liuguilin.draglistviewsample.view.DragListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    //列表
    private DragListView mListView;
    //数据
    private List<DragBean> mList = new ArrayList<>();
    //数据源
    private DragAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    /**
     * 初始化View
     */
    private void initView() {
        mListView = (DragListView) findViewById(R.id.mListView);

        //新增數據
        for (int i = 0; i < 30; i++) {
            DragBean bean = new DragBean(R.mipmap.ic_launcher, "刘某人程序员" + i);
            mList.add(bean);
        }
        //初始化数据源
        adapter = new DragAdapter(this,mList);
        mListView.setAdapter(adapter);
    }
}

现在可以看看实际的效果了

技术分享

现在我们可以重写我们的ListView了

我们首先拦截他的事件

 /**
     * 获取触点所在条目的位置
     * 获取选中条目的图片
     * 事件的拦截机制
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //识别动作
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //获取触点的坐标
                int x = (int) ev.getX();
                int y = (int) ev.getY();
                //这样就可以计算我按到哪个条目上了
                mStartPosition = mEndPosition = pointToPosition(x, y);
                //判断触点是否在logo的区域
                ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition());
                //记录手指在条目中的相对Y坐标
                dragPoint = y - itemView.getTop();
                //ListView在屏幕中的Y坐标
                dragOffset = (int) (ev.getRawY() - y);
                //拖动的图标
                View dragger = itemView.findViewById(R.id.iv_logo);
                //判断触点是否在logo区域
                if (dragger != null && x < dragger.getRight() + 10) {
                    //定义ListView的滚动条目
                    //上
                    upScroll = getHeight() / 3;
                    //下
                    downScroll = getHeight() * 2 / 3;
                    //获取选中的图片/截图
                    itemView.setDrawingCacheEnabled(true);
                    //获取截图
                    Bitmap bitMap = itemView.getDrawingCache();
                    //图片滚动
                    startDrag(bitMap, y);
                }
                break;
        }
        //还会传递事件到子View
        return false;
    }

获取到他的position之后我们直接截图,并且显示我们的window,这里做的事情就比较多了我们还要判断是否点击的是头像才去显示window

 /**
     * 图片在Y轴,也就是上下可滚动
     *
     * @param bitMap
     * @param y
     */
    private void startDrag(Bitmap bitMap, int y) {
        //窗体仿照
        wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        //设置窗体参数
        lParams = new WindowManager.LayoutParams();
        //设置宽高
        lParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        //属性
        lParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
        //设置半透明
        lParams.alpha = 0.8f;
        //设置居中
        lParams.gravity = Gravity.TOP;
        //设置xy
        lParams.x = 0;
        lParams.y = y-dragPoint + dragOffset;
        //属性
        lParams.format = PixelFormat.TRANSLUCENT;
        //设置动画
        lParams.windowAnimations = 0;
        //图片
        dragImageView = new ImageView(getContext());
        //设置截图
        dragImageView.setImageBitmap(bitMap);
        //添加显示窗体
        wm.addView(dragImageView, lParams);
    }

好的,我们初始化一下window,光显示还不行呢,我们还要可以滑动,怎么监听?onTouchEvent的ACTION_MOVE事件是可以做到的


    /**
     * 触摸事件
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //错误的位置
        if (dragImageView != null && mEndPosition != INVALID_POSITION) {
            //在滑动事件中控制上下滑动
            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    //直接获取到Y坐标进行移动
                    int moveY = (int) ev.getY();
                    doDrag(moveY);
                    break;
                //停止拖动成像
                case MotionEvent.ACTION_UP:
                    int upY = (int) ev.getY();
                    stopDrag();
                    onDrag(upY);
                    break;
            }
        }
        //拦截到事件
        return true;
    }

我们在移动的时候不断的去更新他的位置

    /**
     * 控制窗体移动
     *
     * @param moveY
     */
    private void doDrag(int moveY) {
        if (dragImageView != null) {
            lParams.y = moveY - dragPoint + dragOffset;
            wm.updateViewLayout(dragImageView, lParams);
        }
        //判断移动到分割线 返回-1
        int tempLine = pointToPosition(0, moveY);
        //我们处理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移动到分割线 我才处理
            mEndPosition = tempLine;
        }

        //拖拽时滚动、滚动速度
        int scrollSpeed = 0;
        //上滚
        if (moveY < upScroll) {
            scrollSpeed = 10;
            //下滚
        } else if (moveY > downScroll) {
            scrollSpeed = -10;
        }
        //开始滚动
        if (scrollSpeed != 0) {
            //计算条目的Y坐标
            int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop();
            //当前速度
            int dy = dragItemY + scrollSpeed;
            //设置
            setSelectionFromTop(mEndPosition, dy);
        }
    }

当你移动完成之后,我就可以停止你的window体了

     /**
     * 停止的位置
     */
    private void stopDrag() {
        //直接移除窗体
        if (dragImageView != null) {
            wm.removeView(dragImageView);
            dragImageView = null;
        }

    }

这样,我就直接拼接你的position达到一个拖拽的效果


    /**
     * 最终开始成像
     *
     * @param upY
     */
    private void onDrag(int upY) {
        //分割线的处理
        //判断移动到分割线 返回-1
        int tempLine = pointToPosition(0, upY);
        //我们处理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移动到分割线 我才处理
            mEndPosition = tempLine;
        }

        /**
         * 你在最上方就直接落在第一个最下方就直接最下面一个
         */

        //上边界处理
        if (upY < getChildAt(1).getTop()) {
            mEndPosition = 1;
            //下边界处理
        } else if (upY > getChildAt(getChildCount() - 1).getTop()) {
            mEndPosition = getAdapter().getCount() - 1;
        }

        //开始更新item顺序
        if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) {
            DragAdapter adapter = (DragAdapter) getAdapter();
            //删除原来的条目
            adapter.remove(adapter.getItem(mStartPosition));
            //更新
            adapter.insert(adapter.getItem(mStartPosition), mEndPosition);
        }
    }

全部代码贴上

DragListView

package com.liuguilin.draglistviewsample.view;


/*
 *  项目名:  DragListViewSample 
 *  包名:    com.liuguilin.draglistviewsample.view
 *  文件名:   DragListView
 *  创建者:   LGL
 *  创建时间:  2016/8/29 20:50
 *  描述:    自定义高仿QQ列表可拖拽的ListView
 */

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.ListView;

import com.liuguilin.draglistviewsample.R;
import com.liuguilin.draglistviewsample.adapter.DragAdapter;

public class DragListView extends ListView {

    //按下选中的position
    private int mStartPosition;
    //需要达到的position
    private int mEndPosition;
    //手指在条目中的相对Y坐标
    private int dragPoint;
    //ListView在屏幕中的Y坐标
    private int dragOffset;
    //上
    private int upScroll;
    //下
    private int downScroll;
    //窗体
    private WindowManager wm;
    //显示的截图
    private ImageView dragImageView;
    //窗体参数
    private WindowManager.LayoutParams lParams;

    //构造方法
    public DragListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 获取触点所在条目的位置
     * 获取选中条目的图片
     * 事件的拦截机制
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //识别动作
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //获取触点的坐标
                int x = (int) ev.getX();
                int y = (int) ev.getY();
                //这样就可以计算我按到哪个条目上了
                mStartPosition = mEndPosition = pointToPosition(x, y);
                //判断触点是否在logo的区域
                ViewGroup itemView = (ViewGroup) getChildAt(mStartPosition - getFirstVisiblePosition());
                //记录手指在条目中的相对Y坐标
                dragPoint = y - itemView.getTop();
                //ListView在屏幕中的Y坐标
                dragOffset = (int) (ev.getRawY() - y);
                //拖动的图标
                View dragger = itemView.findViewById(R.id.iv_logo);
                //判断触点是否在logo区域
                if (dragger != null && x < dragger.getRight() + 10) {
                    //定义ListView的滚动条目
                    //上
                    upScroll = getHeight() / 3;
                    //下
                    downScroll = getHeight() * 2 / 3;
                    //获取选中的图片/截图
                    itemView.setDrawingCacheEnabled(true);
                    //获取截图
                    Bitmap bitMap = itemView.getDrawingCache();
                    //图片滚动
                    startDrag(bitMap, y);
                }
                break;
        }
        //还会传递事件到子View
        return false;
    }

    /**
     * 图片在Y轴,也就是上下可滚动
     *
     * @param bitMap
     * @param y
     */
    private void startDrag(Bitmap bitMap, int y) {
        //窗体仿照
        wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        //设置窗体参数
        lParams = new WindowManager.LayoutParams();
        //设置宽高
        lParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        //属性
        lParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
        //设置半透明
        lParams.alpha = 0.8f;
        //设置居中
        lParams.gravity = Gravity.TOP;
        //设置xy
        lParams.x = 0;
        lParams.y = y-dragPoint + dragOffset;
        //属性
        lParams.format = PixelFormat.TRANSLUCENT;
        //设置动画
        lParams.windowAnimations = 0;
        //图片
        dragImageView = new ImageView(getContext());
        //设置截图
        dragImageView.setImageBitmap(bitMap);
        //添加显示窗体
        wm.addView(dragImageView, lParams);
    }

    /**
     * 触摸事件
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //错误的位置
        if (dragImageView != null && mEndPosition != INVALID_POSITION) {
            //在滑动事件中控制上下滑动
            switch (ev.getAction()) {
                case MotionEvent.ACTION_MOVE:
                    //直接获取到Y坐标进行移动
                    int moveY = (int) ev.getY();
                    doDrag(moveY);
                    break;
                //停止拖动成像
                case MotionEvent.ACTION_UP:
                    int upY = (int) ev.getY();
                    stopDrag();
                    onDrag(upY);
                    break;
            }
        }
        //拦截到事件
        return true;
    }

    /**
     * 最终开始成像
     *
     * @param upY
     */
    private void onDrag(int upY) {
        //分割线的处理
        //判断移动到分割线 返回-1
        int tempLine = pointToPosition(0, upY);
        //我们处理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移动到分割线 我才处理
            mEndPosition = tempLine;
        }

        /**
         * 你在最上方就直接落在第一个最下方就直接最下面一个
         */

        //上边界处理
        if (upY < getChildAt(1).getTop()) {
            mEndPosition = 1;
            //下边界处理
        } else if (upY > getChildAt(getChildCount() - 1).getTop()) {
            mEndPosition = getAdapter().getCount() - 1;
        }

        //开始更新item顺序
        if (mEndPosition > 0 && mEndPosition < getAdapter().getCount()) {
            DragAdapter adapter = (DragAdapter) getAdapter();
            //删除原来的条目
            adapter.remove(adapter.getItem(mStartPosition));
            //更新
            adapter.insert(adapter.getItem(mStartPosition), mEndPosition);
        }
    }

    /**
     * 停止的位置
     */
    private void stopDrag() {
        //直接移除窗体
        if (dragImageView != null) {
            wm.removeView(dragImageView);
            dragImageView = null;
        }

    }

    /**
     * 控制窗体移动
     *
     * @param moveY
     */
    private void doDrag(int moveY) {
        if (dragImageView != null) {
            lParams.y = moveY - dragPoint + dragOffset;
            wm.updateViewLayout(dragImageView, lParams);
        }
        //判断移动到分割线 返回-1
        int tempLine = pointToPosition(0, moveY);
        //我们处理他
        if (tempLine != INVALID_POSITION) {
            //只要你不移动到分割线 我才处理
            mEndPosition = tempLine;
        }

        //拖拽时滚动、滚动速度
        int scrollSpeed = 0;
        //上滚
        if (moveY < upScroll) {
            scrollSpeed = 10;
            //下滚
        } else if (moveY > downScroll) {
            scrollSpeed = -10;
        }
        //开始滚动
        if (scrollSpeed != 0) {
            //计算条目的Y坐标
            int dragItemY = getChildAt(mEndPosition - getFirstVisiblePosition()).getTop();
            //当前速度
            int dy = dragItemY + scrollSpeed;
            //设置
            setSelectionFromTop(mEndPosition, dy);
        }
    }
}

然后我们引用

layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="match_parent">

    <com.liuguilin.draglistviewsample.view.DragListView
        android:id="@+id/mListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

对了,别忘记了添加窗体的权限哦

 <!--窗口权限-->
 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

做完这一系列事情之后,就觉得这个设想其实是对的,然后我们可以尝试性的运行一下

技术分享

运行时成功的,说明我们的设想是没有问题的,但是随着而来的,也是诸多的bug,所以呢,这也只是思想的参考,不过这些bug我有时间野就修复一下,应该是可以的,好的,本篇博文就先到这里!

有兴趣的加群:555974449

DragListViewSample:http://download.csdn.net/detail/qq_26787115/9616355

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现