关闭

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

标签: 左滑删除
186人阅读 评论(0) 收藏 举报
分类:

布局分析

布局是由内容区域和删除区域两部分组成,所以这里自定义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已经出来了。还没回截取动态图,将就着看看,感兴趣了动手试试呗

总结

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

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:13064次
    • 积分:372
    • 等级:
    • 排名:千里之外
    • 原创:19篇
    • 转载:5篇
    • 译文:0篇
    • 评论:9条
    文章分类
    最新评论