Android滑动消失卡片RecyclerView

<Excerpt in index | 首页摘要>

Android滑动消失卡片RecyclerView

<The rest of contents | 余下全文>

Demo

先看效果图

实现

思路

利用Recyclerview来实现

重写LayoutManager 实现item层叠显示

下面的缩放 跟随滑动距离放大。

顶部的正常显示,重写RecyclerView 托管滑动手势管理。

代码

LayoutManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

public class SwipeCardLayoutManager extends RecyclerView.LayoutManager {
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
for (int i = 0; i < getItemCount(); i++) {
View child = recycler.getViewForPosition(i);
measureChildWithMargins(child, 0, 0);
addView(child);
int width = getDecoratedMeasuredWidth(child);
int height = getDecoratedMeasuredHeight(child);
layoutDecorated(child, 0, 0, width, height);
//缩放
if (i < getItemCount() - 1) {
child.setScaleX(0.8f);
child.setScaleY(0.8f);
}
}
}
}

然后我们再写一个adapter,里面封装删除顶部的方法
ComRvHolder 是通用viewholder代码就不贴了,根据各自项目里面的方式去做就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import com.miaozan.xpro.common.ComRvHolder;

import java.util.List;

public abstract class SwipeCardAdapter extends RecyclerView.Adapter<ComRvHolder> {
protected List mList;

public SwipeCardAdapter(List list) {
mList = list;
}

/**
* 删除最顶部Item
*/
public void delTopItem() {
int position = getItemCount() - 1;
mList.remove(position);
notifyItemRemoved(position);
}

@Override
public int getItemCount() {
return mList == null ? 0 :mList.size();
}
}

重点来了。

RecyclerView

这里说一下,mDecorView 是用activity获取的,我的项目里面有activitymanager 可以拿到 如果你们项目没有 你需要想办法将activity传进view

mTopViewY 是顶部item距离屏幕顶部的距离,这个根据实际情况自己计算

PlayV2BaseInfo是要展示的数据集合,用来判断是否可以继续拖拽消失的,自己根据情况判断好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
import android.animation.Animator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;

import com.miaozan.xpro.bean.playv2.PlayV2BaseInfo;
import com.miaozan.xpro.manager.ActivityManager;
import com.miaozan.xpro.utils.DensityUtil;

import java.util.List;

public class SwipeCardRecyclerView extends RecyclerView {
private final static String TAG = "SwipeCardRecyclerView";

private float mTopViewY;

private float mTouchDownY;

private float mBorder;

private ItemRemovedListener mRemovedListener;

private FrameLayout mDecorView;
private int[] mDecorViewLocation = new int[2];
private List<PlayV2BaseInfo> mDatas;

public void setData(List<PlayV2BaseInfo> mDatas) {
this.mDatas = mDatas;
}


public interface ItemRemovedListener {
void onRemove();
}


private boolean isRunAnim;


public SwipeCardRecyclerView(Context context) {
super(context);
initView(context);
}

public SwipeCardRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initView(context);
}

public SwipeCardRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView(context);
}

private void initView(Context context) {
mBorder = DensityUtil.dip2px(context, 120);
try {
mDecorView = (FrameLayout) ActivityManager.getInstance().getTopActivity().getWindow().getDecorView();
mDecorView.getLocationOnScreen(mDecorViewLocation);
} catch (Exception e) {
e.printStackTrace();
}
mTopViewY = DensityUtil.dip2px(10) + DensityUtil.getSystemStatusHeight() + DensityUtil.getActionBarHeight();
}

public void setRemovedListener(ItemRemovedListener listener) {
mRemovedListener = listener;
}


@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (e.getY() <= (DensityUtil.getActionBarHeight() + DensityUtil.getSystemStatusHeight()))
return false;
return super.onInterceptTouchEvent(e);
}

@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent e) {
if (e.getY() <= (DensityUtil.getActionBarHeight() + DensityUtil.getSystemStatusHeight()))
return false;
View topView = getChildAt(getChildCount() - 1);
if (mDatas == null || mDatas.size() <= 1 || isRunAnim) {
return super.onTouchEvent(e);
}
float touchY = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mTouchDownY = touchY;
break;
case MotionEvent.ACTION_MOVE:
float dy = touchY - mTouchDownY;
float y = mTopViewY + dy;
if (y <= mTopViewY)
topView.setY(y);
updateNextItem(Math.abs(topView.getY() - mTopViewY) * 0.2 / mBorder + 0.8);
break;
case MotionEvent.ACTION_UP:
mTouchDownY = 0;
touchUp(topView);
break;
}
return super.onTouchEvent(e);
}

/**
* 更新下一个View的宽高
*
* @param factor
*/
private void updateNextItem(double factor) {
if (getChildCount() < 2) {
return;
}
if (factor > 1) {
factor = 1;
}
View nextView = getChildAt(getChildCount() - 2);
nextView.setScaleX((float) factor);
nextView.setScaleY((float) factor);
}

/**
* 手指抬起时触发动画
*
* @param view
*/
private void touchUp(final View view) {
isRunAnim = true;
float targetY;
boolean del;
if (Math.abs(view.getY() - mTopViewY) < mBorder) {
//不满足条件 恢复
targetY = mTopViewY;
del = false;
} else {
//满足条件 删除
del = true;
targetY = -view.getHeight();
if (mRemovedListener != null) {
mRemovedListener.onRemove();
}
}
View animView;
TimeInterpolator interpolator;
if (del) {
animView = getMirrorView(view);
interpolator = new LinearInterpolator();
} else {
animView = view;
interpolator = new OvershootInterpolator();
}
ValueAnimator va = ValueAnimator.ofFloat(animView.getTop(), targetY)
.setDuration(300);
va.setInterpolator(interpolator);
va.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
animView.setY(value);
if (!del) {
updateNextItem(Math.abs(view.getY() - mTopViewY) * 0.2 / mBorder + 0.8);
}
});
va.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}

@Override
public void onAnimationEnd(Animator animation) {
if (del) {
try {
mDecorView.removeView(animView);
} catch (Exception e) {
e.printStackTrace();
}
}
isRunAnim = false;
}

@Override
public void onAnimationCancel(Animator animation) {
}

@Override
public void onAnimationRepeat(Animator animation) {
}
});
va.start();
}

/**
* 创建镜像View替代原有顶部View展示删除动画
*
* @param view
* @return
*/
private ImageView getMirrorView(View view) {
view.destroyDrawingCache();
view.setDrawingCacheEnabled(true);
final ImageView mirrorView = new ImageView(getContext());
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
mirrorView.setImageBitmap(bitmap);
view.setDrawingCacheEnabled(false);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(bitmap.getWidth(), bitmap.getHeight());
int[] locations = new int[2];
view.getLocationOnScreen(locations);

mirrorView.setAlpha(view.getAlpha());
view.setVisibility(GONE);
((SwipeCardAdapter) getAdapter()).delTopItem();
mirrorView.setX(locations[0] - mDecorViewLocation[0]);
mirrorView.setY(locations[1] - mDecorViewLocation[1]);
mDecorView.addView(mirrorView, params);
return mirrorView;
}
}

重点使用说明

这样叠加出来的view是相反的,即List最后面的在最上面,业务层处理逻辑要相当注意!新卡片如果是在最底下显示,应该使用insert index应该是0!

还有需要注意的是,这样写layoutmanager,所有的item都会加载出来!不会复用!所以不要让list中包含太多数据!

如果确实需要很大的数据量!那么你需要去对LayoutManager做进一步优化。复用item!