效果图
实现分析
如上图,整体布局分为两部分,一部分是屏幕内的布局,一部分是屏幕外的布局,屏幕外的布局就是滑动时需要滑进屏幕内要显示的布局。这两部分布局可以是任意的View,最重要的是这个整体布局的选择,一开始为了简单,直接使用了LinearLayout作为了整体布局,但是发现效果不尽人意,又使用了RelativeLayout,效果也不是很好,当时没空去分析具体的原因,最终直接自定义View继承自ViewGroup,这才实现了效果。
具体实现
因为是继承自ViewGroup,自己需要处理子view的测量和布局,因此需要重写onMeasure和onLayout:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {//直接测量子View
measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
int totalWidth = 0;
for (int i = 0; i < childCount; i++) {//使所有的子View按照横向排列
View childView = getChildAt(i);
int width = childView.getMeasuredWidth();
int height = childView.getMeasuredHeight();
childView.layout(totalWidth, 0, totalWidth + width, height);
totalWidth += childView.getMeasuredWidth();
}
if (getChildCount() > 0) {//初始化边界值。
mLeftBorder = getChildAt(0).getLeft();
mRightBorder = getChildAt(getChildCount() - 1).getRight();
mExtendBorder = (mRightBorder - getWidth()) / 2;//当滑动距离大于或小于mExtendBorder,并且此时手指离开了屏幕,那么布局要自动滑动到合适的位置。
}
}
}
接下来,要处理滑动冲突。滑动冲突的解决方式有很多,我这选择的是请求父布局,先让ScrollDeleteLayout来直接处理滑动冲突。
@Override
public boolean onTouchEvent(MotionEvent event) {
requestDisallowInterceptTouchEvent(true);//请求处理滑动手势
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
return true;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
//边界处理
if (getScrollX() - Math.abs(deltaX) <= mLeftBorder && deltaX > 0) {
scrollTo(mLeftBorder, 0);
break;
}
//边界处理
if (getScrollX() + getWidth() + Math.abs(deltaX) >= mRightBorder && deltaX < 0) {
scrollTo(mRightBorder - getWidth(), 0);
break;
}
scrollBy(-deltaX, 0);
mLastX = x;
mLastY = y;
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
//调整布局
if (getScrollX() < mExtendBorder) {
shrink();
} else {
notifyOnExtendListener();
scrollTo(mRightBorder - getWidth(), 0);
}
default:
break;
}
return false;
}
在recyclerview中使用
如果想在Recyclerview滑动的时候使展开的view都恢复原样,那么只需要viewholder实现ScrollDeleteLayout.OnExtendListener的onExtend方法。示例代码如下:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mCurrentDeleteLayout != null) {
/***
* 当外面的布局滑动时,使ScrollDeleteLayout收缩布局。
*/
mCurrentDeleteLayout.shrink();
}
}
});
public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ItemViewHolder> {
private ScrollDeleteLayout layout;
@NonNull
@Override
public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
layout = (ScrollDeleteLayout) LayoutInflater.from(MainActivity.this)
.inflate(R.layout.item_layout, parent, false);
return new ItemViewHolder(layout);
}
其他代码...
class ItemViewHolder extends RecyclerView.ViewHolder implements ScrollDeleteLayout.OnExtendListener {
ScrollDeleteLayout layout;
ItemViewHolder(View itemView) {
super(itemView);
layout = (ScrollDeleteLayout) itemView;
/***
* 设置展开监听器.
*/
layout.setOnExtendListener(this);
其他代码...
}
其他代码...
@Override
public void onExtend(ScrollDeleteLayout layout) {
if (mCurrentDeleteLayout != null) {
mCurrentDeleteLayout .shrink();
}
mCurrentDeleteLayout = null;
mCurrentDeleteLayout = layout;
}
}
}