基于recyclerview的itemview左滑删除置顶功能(二)
改善用户体验(滑动)
在使用过QQ或者类似左滑功能的时候,都不会说滑到一半就停住了。必定会写一个逻辑判断用户滑动是否到达一定的值,假若到达则自动滑过去,若没有,则还原。在上一篇文章中,我们仅仅实现了滑动功能,只要随时放手,滑动的view都会停住。因此我们定义一个onSlide函数进行自动滑动
/*
* flag = 0代表正在滑动时的调用,以便在滑到指定值时自动滑动,flag = 1代表收回滑块
* ObjectAnimator:ValueAnimator的子类,可以对一个object的属性进行值动画
* */
private boolean onSlide(int flag) {
if (itemView == null){
return false;
}
int scrollX = itemView.getScrollX();
int scrollWidth = mviewWidth; //自定义view的长度
if (manimator != null){
return false;
}
int to = 0; //需要滑到的位置;
int duration = 100; //滑动时间
if (flag == 0) {
if (scrollX >= scrollWidth/2){ //滑到大于一半,则自动滑完
to = scrollWidth;
}else { //否侧复原
to = 0;
}
}else{
to = 0;
}
manimator = ObjectAnimator.ofInt(itemView , "scrollX" , to); //一个ObjectAnimator的用法
manimator.setDuration(duration);
manimator.addListener(new Animator.AnimatorListener() { //动画监听
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
manimator = null;
if (!isExposed()){
itemView = null;
}
}
@Override
public void onAnimationCancel(Animator animation) {
manimator = null;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
manimator.start();
return true;
}
接下来我们只要在onTouchEvent的MotionEvent.ACTION_UP事件判断中添加Slide(0)即可。
判断是否点击了自定义的view
在我们自定义了view后,应该如何判断并触发他的点击事件呢?我们编写一个函数isHit(int x, int y)根据点击的地方判断是否点中。
/*
* 判断是否点中
* 根据itemView的属性建立一个矩形区域(Rect),并利用其的contains(int x , int y)函数判断
* 是否在目标区域
* */
private boolean isHit(int x, int y) {
if (itemView == null || !isExposed()){
return false;
}
//这里需要左上角和右下角的点
int top = itemView.getTop(); //左上角y坐标
int bottom = itemView.getBottom(); //右下角y坐标
int left = itemView.getWidth() - mviewWidth;
int right = left + mviewWidth;
Rect rect = new Rect(left , top , right , bottom);
boolean isHit = rect.contains(x , y);
return isHit;
}
这样就基本可以了。但随之而来有个问题。如果我要加第二个自定义的view呢,这里的矩形区域是写死的,也就是说难道我每次加的时候要算坐标改代码岂不是很麻烦。这个后面再说,就编写几个接口,然后通过接口的调用就可以解决这个麻烦。 这里用到了个isExposed()函数,用于判断当前是否view已经展开(滑出)
/*
* 判断是否滑出
* */
private boolean isExposed() {
return itemView!= null && itemView.getScrollX() == mviewWidth;
}
添加自定义view监听
在只有一个view的情况下很简单就可以添加了,这里不用addListener这样的函数,而是直接在 onInterceptTouchEvent函数中MotionEvent.ACTION_DOWN中写代码即可
if (isExposed() && isHit(firstX , firstY) && itemView != null){
Log.d("good", "选中了 ");
itemView = null;
}
这是不是很不面向对象。我也这么觉得。而且要是你又新添加了view,那又要大改特改,很麻烦。在第三篇中会对代码进行优化和重构。
事件拦截
在我们画出滑块之后,我们竟然发现还能在其他的itemView中继续滑多一个出来,太诡异了!而且,这时候如果对itemView添加了监听,也会触发。因此我们要使用OnInterceptTouchEvent进行指定拦截。
//自定义view展开时,却点到了其他的地方
if (isExposed() &&!isHit(x , y) && itemView != null){
onSlide(1); //自定义view复原,滑回去
itemView = null;
return true; //拦截,防止触发itemView的监听
}
接下来要拦截itemView的点击事件,什么时候是点击itemView,什么时候是滑动? 你要点击itemView,在OnInterceptTouchEvent函数中,就要返回false,不拦截。你要滑动,就要返回true进行拦截。因此我们要判断什么时候该拦截什么时候不该拦截。 方法就是利用一个isIntercept的布尔值判断。 因此比较完整的拦截函数:
boolean isIntercept = false;
switch (action){
case MotionEvent.ACTION_DOWN:
InitclickItem(x , y); //初始化调用mcallback后的值
firstX = x;
firstY = y;
if (isExposed() && isHit(firstX , firstY) && itemView != null){
onSlide(1);
Log.d("good", "选中了 ");
itemView = null;
isIntercept = true; //拦截,不让事件分发下去,以免点击到item
}
break;
case MotionEvent.ACTION_MOVE:
int deltaX = firstX - x;
int delteY= firstY - y;
isIntercept = deltaX >= mTouchSlop;
if (Math.abs(deltaX) < Math.abs(delteY)){ // 滑动recyclerview
return false;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return isIntercept;
}
总结
1.改善用户体验 2.判断是否点击到了目标区域 3.为点击添加事件 4.事件拦截