使用ViewDragHelper带你一步步实现仿照qq的左滑删除事件

原创 2016年12月23日 17:18:23

布局分析

布局是由内容区域和删除区域两部分组成,所以这里自定义view采用继承帧布局的形式,定义两个view为contentView和deleteVIew
重写onFinishInflate(),这个方法是在布局加载完成之后调用的,在这里按照顺序将其子view分别定义为contentView和deleteView。

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        contentView = getChildAt(0);
        deleteView = getChildAt(1);
    }

在onSizeChanged中计算出来两个view的宽高

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        contentWidth = contentView.getMeasuredWidth();
        contentHeight = contentView.getMeasuredHeight();
        deleteWidth = deleteView.getMeasuredWidth();
        deleteHeight = deleteView.getMeasuredHeight();
    }

ok,到这里的时候我们的布局中的内容区域和删除区域是重叠的,如下图所示:
我们需要的是删除区域位于内容区域的右边,那么下一个方法就要登场了。
我们需要的就是重新摆放的两个区域的位置,那么就需要重写onLayout方法。代码比较简单,不在细说

@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        contentView.layout(0,0,contentWidth,contentHeight);
        deleteView.layout(contentWidth,0,contentWidth+deleteWidth,deleteHeight);
    }

重新build一下我们的xml,然后就能看到他们各司其职,都回到了我们想要的位置上了。
能够看到屏幕外边有一块,那就是删除区域
到这里,我们的布局基本上是完成了,下边就是滑动了。

ViewDragHelper

初始化ViewDragHelper

ViewDraghelper通常定义在ViewGroup的内部,并通过静态工厂的方法进行初始化
<pre>
    //第一个参数是要监听的view,第二个参数是ViewDragHelper的一个回调
    ViewDragHelper viewDraghelper = ViewDragHelper.create(this,callback)
</pre>

创建callback

    1. tryCaptureView方法的介绍
    //当前触摸的子控件,返回值代表当前控件是否可以滑动。
    //这里只有两个子控件,并且两个都可以被拖动,所以返回如下
        @Override
        public boolean tryCaptureView(View child, int pointerId) 
               //我们这里只有内容区域和删除区域,两个都是可滑动的,所以可以直接返回true。
                   return child == contentView || child == deleteView;
         }
    
    2. 上边的方法能够让子view跟着手势滑动,但是是随意滑动的,而我们还需要给他们定义滑动规则和范围 首先上边实现的滑动仅仅是单独的某个view的滑动,那么我们需要解决的第一个问题就是让两个一起滑动 那么就需要重写位置改变时候的监听,当某一个子view的位置改变的时候手动的改变另一个view的位置
         @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
                super.onViewPositionChanged(changedView, left, top, dx, dy);
                if(contentView == changedView){       deleteView.layout(deleteView.getLeft()+dx,deleteView.getTop(),deleteView.getRight()+dx,deleteView.getBottom());
      }else if(deleteView == changedView){
                    contentView.layout(contentView.getLeft()+dx,contentView.getTop(),contentView.getRight()+dx,contentView.getBottom());
                }
            }
      
    3. 到这里,两个view绑定在了一起可以一起滑动了,但是滑动的范围却没有限制,那么我们就来限制一下范围
        //这个是当view的位置发生变化时候的回调,对应的还有一个垂直方向上的回调,这里左滑是水平方向的,不需要另一个
        @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                //限制两个区域能够移动的范围
                if(contentView == child){
                    if(left > 0)left = 0;
                    if(left 

细节优化

用户体验的优化从下面几个方面进行

  1. 滑出的宽度超过deleteview的宽度的一半的时候自动打开,否则关闭
  2. 永远只有一个能滑出来,也就是当有一个item处于打开状态的时候,我们只能让其先关闭再打开其他的。换句话说此时只有左滑出来的deleteview具有事件。
  3. 当有打开着的item的时候,liatview的事件要被屏蔽掉,要先关闭再释放开listview的事件
  4. item的点击事件

第一个问题,自动打开和关闭的情况

当我们滑动出来的宽度是deleteview的宽度的一半的时候那就自动打开,否则就自动关闭
那么我们需要再介绍一个方法:onViewReleased()

    //这个方法是手指抬起,或者是事件拦截释放掉的回调
    @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
        //我们事先的是左滑,所以contentview的getLeft()是个负数
            if(contentView.getLeft() 

第二个问题:保证打开的单一性

实现思路:我在这里采用的是,定义一个集合来存放打开状态的item,理论上这个集合永远只有一条数据或者没有,所以在open()中将这一项add进集合,在close()中将这一项remove当有item处于打开状态的时候,触摸滑动是要被拦截的,只能先关闭处于打开状态的,才能再次进行触摸滑动,那么相应的就要在tryCaptureView()中来判断,具体看代码

    1. 定义一个集合用来存放当前不是关闭状态的那一项 public static List unClosedItemList = new ArrayList

第三个问题:当有打开项的时候,屏蔽掉listview的滑动事件

要解决这个问题我们首先需要清除知道是左右滑动还是上下滑动,我们需要拦截出来上下滑动的事件,如果没有打开项就把这个事件交给item的父布局listview来执行,反之就让item消费。那么相应的就是在item的onTouchEvent(MotionEvent ev)中判断了

    //上下滑动的距离大于左右滑动的距离
    else if(Math.abs(x-lastx) 

第四个问题:item的点击事件

说到点击事件,我们都会想,listview的item的click事件就可以的,是的,listview有这样的事件,我们来看看。在对应的activity中,我们再onItemclick()中打出toast,点击点击点击!没反应没反应没反应!傻眼了吧,你重写了item的onTouchEvent,事件在这里执行了,那里肯定没反应了。来解决一下吧!
想想点击事件,那么就是按下和抬起的时候是一样的,那么久作为点击事件,我们在item的onTouchEvent()中判断出来点击行为,代码如下:

    case MotionEvent.ACTION_UP:
                int upx = (int) event.getX();
                int upy = (int) event.getY();
                //按下和抬起时候是一样的,那么就是点击事件
                if(upx == clickx && upy == clicky){
                    AdapterView.OnItemClickListener itemClickListener = listView.getOnItemClickListener();
                    if(itemClickListener != null){
                        int position = (int) getTag(getId());
                        itemClickListener.onItemClick(listView,this,position,position);
                    }
        }

判断出点击行为之后,我们要做的就是点击事件,既然listview有onItemClickListener,那么我们就能相应的get到,如上代码,get到这个点击事件,然后再得到当前点击的position,然后执行onItemClick即可。
有了上边的分析,下面就是具体的操作了,我们要get到这个点击事件,那么就需要拿到listview,可以通过set方法传递到这个类,需要position,我们也需要传递过来。这样的操作肯定要在adapter中了。我们通过setTag的方式将position传递过来即可。
代码实现:

    @Override
    public View getView(int i, View convertView, ViewGroup viewGroup) {
        ViewHolder holder;
        if(convertView == null){
            holder = new ViewHolder();
            convertView = LayoutInflater.from(context).inflate(R.layout.custom_item,null);
            holder.tv = (TextView) convertView.findViewById(R.id.tv_content);
            holder.delete_tv = (TextView) convertView.findViewById(R.id.tv_delete);
            holder.customItem = (CustomItem) convertView;
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder) convertView.getTag();
        }
        
    //listview传递到item中,避免空指针,在item中可以做一下判空,上边我没有做哈
    holder.customItem.setListView(listView);
    //setTag的形式将pisition存起来,在item中取出来
        holder.customItem.setTag(holder.customItem.getId(),i);//settag,在customItem中gettag拿出position
        holder.tv.setText(list.get(i));
        holder.delete_tv.setOnClickListener(this);
        holder.delete_tv.setTag(i);
        return convertView;
    }

主要代码就是上述代码块中的粗斜体。再来试试,你的toast已经出来了。还没回截取动态图,将就着看看,感兴趣了动手试试呗

总结

以上只是实现了左滑和点击事件,还有删除事件等等,感兴趣的自己试试呗。如果上述描述有什么不严谨的地方,或者是有更好的方案的话欢迎指正。
后期会进一步解决一下侧滑和左滑结合在一起的冲突问题,然后再来更新。需要代码的回头我会上传

版权声明:本文为博主原创文章,未经博主允许不得转载。

Android使用ViewDragHelper实现仿QQ6.0侧滑界面(一)

http://www.jb51.net/article/79178.htm QQ是大家离不开的聊天工具,方便既实用,自从qq更新至6.0之后,侧滑由原来的划出后主面板缩小变成了左右平滑,在外...
  • Rodulf
  • Rodulf
  • 2016年04月13日 19:18
  • 1032

自定义View(仿QQ侧滑删除实现,ViewDragHelper)

版权声明:本文为博主原创文章,未经博主允许不得转载。 今天我们准备做侧滑删除的自定义视图,我采用了v4包里面ViewDragHelper。2013年谷歌i/o大会上介绍了两个新的la...

Android仿QQ侧滑删除(ViewDragHelper)

ViewDragHelper 1. 用来分析touch事件,来处理拖拽 2. 使用步骤 1. 创建 java mDragHelper = ViewDragHelpe...

ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突

QQ5.0的侧滑效果有多种实现方式, 如http://blog.csdn.net/lmj623565791/article/details/39257409 就是利用HorizontalScrollV...

仿QQ5.0侧滑(基于ViewDragHelper实现)

QQ5.0侧滑效果实现方案有很多方式,今天我们使用ViewDragHelper来实现一下。先上效果图: ①自定义控件SlidingMenu继承FrameLayout,放在FrameLayout上面的...

Android ViewDragHelper实现QQ侧滑边栏

Android ViewDragHelper实现QQ侧滑边栏 移动手机版的QQ的左边侧栏,有一个特殊的交互设计效果:当用户手指向右或向左滑动时,QQ的左边会弹出或收缩一个侧滑的边栏。这种效果简单的做...

仿QQ6.0侧滑之ViewDragHelper的使用(一)

1、高仿QQ6.0侧滑 2、ViewDragHelper简单介绍

【FastDev4Android框架开发】神器ViewDragHelper完全解析之详解实现QQ5.X侧滑酷炫效果(三十四)

(一).前言:            这几天正在更新录制实战项目,整体框架是采用仿照QQ5.X侧滑效果的。那么我们一般的做法就是自定义ViewGroup或者采用开源项目MenuDrawer或者Goog...

ViewDragHelper实现仿qq列表滑动删除

实现过程概述实现qq列表侧滑删除,核心还是ViewDragHelper的实现。 关于ViewDragHelper的基础用法,我在另外一篇博客ViewDragHelper简介中已经介绍过了。如果不是特...

ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突

http://www.cnblogs.com/liujingg/p/4694903.html QQ5.0的侧滑效果有多种实现方式, 如http://blog.csdn.net/lmj6...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用ViewDragHelper带你一步步实现仿照qq的左滑删除事件
举报原因:
原因补充:

(最多只允许输入30个字)