项目中用到RecycleView左滑删除ui及功能效果。自己写了一个,感觉很简单易懂的,现分享一下。
效果图
ViewDragHelper是一个方便移动子控件的辅助类,特别方便。
初始化ViewDragHelper
public DragLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
}
要想达到子View滑动效果,记得将触摸事件交给ViewDragHelper处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mDragHelper.processTouchEvent(ev);
return true;
}
要想实现滑动删除效果,考虑RecylceView适配器里面的item是个FrameLayout,滑动之前的View显示在最上面,下面是删除按钮,被上面view遮住了,所以你看不到。当上面view向左移动时就会将删除按钮显露出来了,于是实现了滑动删除效果。可以限定移动范围刚好是下面删除按钮的宽度,这样上面的view就不会移动出边界。
指定谁可以滑动
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == dragView;//出入你想要谁可移动
}
最重要的逻辑就是限定移动左边界的范围。
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
mLeft = 0;
if (left < 0 && Math.abs(left) > maxLeftPointReverse) { //限定向左最大到达位置
mLeft = -maxLeftPointReverse;
return mLeft;
}
if (left > mInitX) { //设置向右最大的left置
mLeft = mInitX;
return mLeft;
}
mLeft = left;
return mLeft;
}
该方法返回的是移动view的左边位置。
注意2个边界值,注释都写清楚了。
学习两个方法:
viewDragHelper.settleCapturedViewAt(mInitX, mInitTop);
作用像方法名字一样,该方法只能在onViewReleased方法里面调用,否则会报错。该方法会将view平滑滑动到指定位置。
我们的效果是点击删除按钮,如果该项不可删除,则回到初始状态,删除按钮隐藏。这时用
viewDragHelper.smoothSlideViewTo(dragView, mInitX, mInitTop);
将view滑动回去。
另外,在RecycleView上下滑动时,已处于打开状态的item会自动回到关闭状态,删除按钮被遮住了。要想解决这个问题,我是在adapter里面给item设置tag的方式,当打开时tag为数据源,关闭时tag为空,当判断为打开过的item时再将其smoothSlideViewTo滑动到打开状态,否则滑动到初始状态。后来发现这样做依然不行,因为recycleView的回收机制,导致滑动时onBindViewHolder走不到,所以即使这样设置了也没有用,后来找到解决方案,需要设置recycleView.setItemViewCacheSize(0)才能保证onBindViewHolder走得到,这里要注意一下
if (orderTravelViewHolder.swipeLayout.getTag()!=null&&((OrderList_Bean)orderTravelViewHolder.swipeLayout.getTag()).equals(orderInfo)){
orderTravelViewHolder.swipeLayout.toOpen();
}else{
orderTravelViewHolder.swipeLayout.toNormal();
}
另外item的水平滑动事件可能会引起RecycleView的滑动事件,所以也要处理一下。
如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getRawX();
downY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float disX = ev.getRawX() - downX;
float disY = ev.getRawY() - downY;
ViewParent viewGroup = getParent();
if (Math.abs(disX) > Math.abs(disY)) {
viewGroup.requestDisallowInterceptTouchEvent(true);//水平滑动时请求reycleView不要拦截事件
} else {
viewGroup.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
viewDragHelper.cancel();
break;
}
if (canSwipe) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
} else {
return super.onInterceptTouchEvent(ev);
}
}
完整代码如此
public class SwipeLayout extends FrameLayout {
ViewDragHelper viewDragHelper;
ViewDragHelper.Callback callback;
View dragView;
TextView hideView; //隐藏在下面的view
int maxLeftPointReverse; //左边所能到达的最左边的距离的绝对值
boolean canSwipe = true; //是否可以滑动删除
int mHorizontalDragRange; //水平可以拖动的距离最大范围
Function<Void, Void> animOpenFunction;//打开后的回调
Function<Void, Void> animCloseFunction;//关闭后的回调
float downX = 0, downY = 0;
private int mInitX;
private int mLeft;
private int mInitTop;
public SwipeLayout(@NonNull Context context) {
super(context);
init(context);
}
public SwipeLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SwipeLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
public void setCanSwipe(boolean canSwipe) {
this.canSwipe = canSwipe;
}
public void setAnimCloseFunction(Function<Void, Void> animCloseFunction) {
this.animCloseFunction = animCloseFunction;
}
public void setAnimOpenFunction(Function<Void, Void> animOpenFunction) {
this.animOpenFunction = animOpenFunction;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (getChildCount() == 2) {
hideView = (TextView) getChildAt(0);
dragView = getChildAt(1);
hideView.post(new Runnable() {
@Override
public void run() {
mHorizontalDragRange = hideView.getWidth();
}
});
dragView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
mInitX = (int) dragView.getX();
mInitTop = dragView.getTop();
maxLeftPointReverse = hideView.getWidth() - mInitX;
dragView.removeOnLayoutChangeListener(this);
}
});
}
}
public void swipeToNormal() {
dragView.post(new Runnable() {
@Override
public void run() {
if (dragView.getX() != mInitX) {
boolean result = viewDragHelper.smoothSlideViewTo(dragView, mInitX, mInitTop);
LogUtil.d("-->result is " + result);
ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
}
}
});
}
public void toOpen() {
dragView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (dragView.getX() != -maxLeftPointReverse) {
viewDragHelper.smoothSlideViewTo(dragView, -maxLeftPointReverse, mInitTop);
}
dragView.removeOnLayoutChangeListener(this);
}
});
}
public void toNormal() {
dragView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (dragView.getX() != mInitX) {
viewDragHelper.smoothSlideViewTo(dragView, mInitX, mInitTop);
}
dragView.removeOnLayoutChangeListener(this);
}
});
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (canSwipe) {
viewDragHelper.processTouchEvent(ev);
return true;
} else {
return super.onTouchEvent(ev);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = ev.getRawX();
downY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float disX = ev.getRawX() - downX;
float disY = ev.getRawY() - downY;
ViewParent viewGroup = getParent();
if (Math.abs(disX) > Math.abs(disY)) {
viewGroup.requestDisallowInterceptTouchEvent(true);//水平滑动时请求reycleView不要拦截事件
} else {
viewGroup.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
viewDragHelper.cancel();
break;
}
if (canSwipe) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
} else {
return super.onInterceptTouchEvent(ev);
}
}
private void init(Context context) {
callback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return child == dragView;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
mLeft = 0;
if (left < 0 && Math.abs(left) > maxLeftPointReverse) { //限定向左最大到达位置
mLeft = -maxLeftPointReverse;
return mLeft;
}
if (left > mInitX) { //设置向右最大位置
mLeft = mInitX;
return mLeft;
}
mLeft = left;
return mLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
LogUtil.d("-->top is " + top);
return mInitTop;
}
@Override
public void onEdgeTouched(int edgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags, pointerId);
}
@Override
public int getViewHorizontalDragRange(View child) {
return mHorizontalDragRange;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (Math.abs(mInitX - mLeft) >= mHorizontalDragRange / 3) {//open
if (viewDragHelper.settleCapturedViewAt(-maxLeftPointReverse, mInitTop)) {
ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
}
if (animOpenFunction != null) {
try {
animOpenFunction.apply(null);
} catch (Exception e) {
e.printStackTrace();
}
}
} else {//close
if (viewDragHelper.settleCapturedViewAt(mInitX, mInitTop)) {
ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
}
if (animCloseFunction != null) {
try {
animCloseFunction.apply(null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
viewDragHelper = ViewDragHelper.create(this, 1.0f, callback);
}
@Override
public void computeScroll() {
if (viewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}
特别鸣谢:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0911/1680.html
https://www.cnblogs.com/epilogue/p/7723482.html
如果你觉得对你有一丁点帮助,欢迎给点个爱心,谢谢!