您的位置:首页 > 其它

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

2016-12-23 17:18 316 查看

布局分析

布局是由内容区域和删除区域两部分组成,所以这里自定义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


细节优化

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

滑出的宽度超过deleteview的宽度的一半的时候自动打开,否则关闭

永远只有一个能滑出来,也就是当有一个item处于打开状态的时候,我们只能让其先关闭再打开其他的。换句话说此时只有左滑出来的deleteview具有事件。

当有打开着的item的时候,liatview的事件要被屏蔽掉,要先关闭再释放开listview的事件

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已经出来了。还没回截取动态图,将就着看看,感兴趣了动手试试呗

总结

以上只是实现了左滑和点击事件,还有删除事件等等,感兴趣的自己试试呗。如果上述描述有什么不严谨的地方,或者是有更好的方案的话欢迎指正。

后期会进一步解决一下侧滑和左滑结合在一起的冲突问题,然后再来更新。需要代码的回头我会上传
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  左滑删除