参考资料:
- RecyclerView LayoutManager
public class CardStackLayoutManager extends RecyclerView.LayoutManager {
private static final String TAG = "CardStackLayoutManager";
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 在布局之前,将所有的子 View 先 Detach 掉,放入到 Scrap 缓存中
detachAndScrapAttachedViews(recycler);
int itemCount = getItemCount();
//Log.d(TAG, "onLayoutChildren: itemCount ==== " + itemCount);
// 在这里,我们默认配置 CardConfig.DEFAULT_SHOW_ITEM = 3。即在屏幕上显示的卡片数为3
// 当数据源个数大于最大显示数时
if (itemCount > CardConfig.DEFAULT_SHOW_ITEM) {
// 把数据源倒着循环,这样,第0个数据就在屏幕最上面了
for (int position = CardConfig.DEFAULT_SHOW_ITEM; position >= 0; position--) {
//Log.d(TAG, "onLayoutChildren: position itemCount > CardConfig.DEFAULT_SHOW_ITEM ==== " + position);
final View view = recycler.getViewForPosition(position);
// 将 Item View 加入到 RecyclerView 中
addView(view);
// 测量 Item View
measureChildWithMargins(view, 0, 0);
//Log.d(TAG, "onLayoutChildren: Width ==== " + getWidth());
//Log.d(TAG, "onLayoutChildren: Height ==== " + getHeight());
//Log.d(TAG, "onLayoutChildren: DecoratedMeasuredWidth ==== " + getDecoratedMeasuredWidth(view));
//Log.d(TAG, "onLayoutChildren: DecoratedMeasuredHeight ==== " + getDecoratedMeasuredHeight(view));
//Log.d(TAG, "onLayoutChildren: widthSpace ==== " + (getWidth() - getDecoratedMeasuredWidth(view)));
//Log.d(TAG, "onLayoutChildren: heightSpace ==== " + (getHeight() - getDecoratedMeasuredHeight(view)));
// getDecoratedMeasuredWidth(view) 可以得到 Item View 的宽度
// 所以 widthSpace 就是除了 Item View 剩余的值
int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
// 同理
int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);
// 将 Item View 放入 RecyclerView 中布局
// 在这里默认布局是放在 RecyclerView 中心
layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,
widthSpace / 2 + getDecoratedMeasuredWidth(view),
heightSpace / 2 + getDecoratedMeasuredHeight(view));
// 其实屏幕上有四张卡片,但是我们把第三张和第四张卡片重叠在一起,这样看上去就只有三张
// 第四张卡片主要是为了保持动画的连贯性
if (position == CardConfig.DEFAULT_SHOW_ITEM) {
// 按照一定的规则缩放,并且偏移Y轴。
// CardConfig.DEFAULT_SCALE 默认为0.1f,CardConfig.DEFAULT_TRANSLATE_Y 默认为14
float scale = 1 - (position - 1) * CardConfig.DEFAULT_SCALE;
//Log.d(TAG, "onLayoutChildren: position == CardConfig.DEFAULT_SHOW_ITEM scale ==== " + scale);
view.setScaleX(scale);
view.setScaleY(scale);
//view.setTranslationY((position - 1) * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
} else if (position > 0) {
float scale = 1 - position * CardConfig.DEFAULT_SCALE;
//Log.d(TAG, "onLayoutChildren: position > 0 scale ==== " + scale);
view.setScaleX(scale);
view.setScaleY(scale);
//view.setTranslationY(position * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
}else {
view.setScaleX(1f);
view.setScaleY(1f);
}
}
} else {
// 当数据源个数小于或等于最大显示数时,和上面的代码差不多
for (int position = itemCount - 1; position >= 0; position--) {
//Log.d(TAG, "onLayoutChildren: position itemCount <= CardConfig.DEFAULT_SHOW_ITEM ==== " + position);
final View view = recycler.getViewForPosition(position);
addView(view);
measureChildWithMargins(view, 0, 0);
int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);
layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,
widthSpace / 2 + getDecoratedMeasuredWidth(view),
heightSpace / 2 + getDecoratedMeasuredHeight(view));
if (position > 0) {
view.setScaleX(1 - position * CardConfig.DEFAULT_SCALE);
view.setScaleY(1 - position * CardConfig.DEFAULT_SCALE);
//view.setTranslationY(position * view.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
}else {
view.setScaleX(1f);
view.setScaleY(1f);
}
}
}
}
}
- ItemTouchHelper.Callback文件
public class CardStackItemTouchHelperCallback extends CardItemTouchHelper.CardCallback {
private static final String TAG = "CardStackItemTouchHelperCallback";
private final CardStackListAdapter mCardStackListAdapter;
private final OnSwipeListener<CardEntity> mOnSwipeListener;
public CardStackItemTouchHelperCallback(CardStackListAdapter adapter, OnSwipeListener<CardEntity> listener) {
this.mCardStackListAdapter = adapter;
this.mOnSwipeListener = listener;
}
@SuppressLint("LongLogTag")
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
int dragFlags = 0;
int swipeFlags = 0;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof CardStackLayoutManager2) {
swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
}
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
return false;
}
@SuppressLint({"LongLogTag", "NotifyDataSetChanged"})
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
//这块不重置下视图样式在快速滑动切换中可能会造成页面样式错乱
resetCardViewStatus(viewHolder);
int position = viewHolder.getLayoutPosition();
if (position < 0) {
position = 0;
}
CardEntity removeCardEntity = getCardStackListAdapter().getList().get(position);
getCardStackListAdapter().getList().remove(removeCardEntity);
getCardStackListAdapter().notifyItemRemoved(position);
getCardStackListAdapter().notifyItemRangeChanged(position, getCardStackListAdapter().getList().size() - position);
if (getOnSwipeListener() != null) {
getOnSwipeListener().onSwiped(viewHolder, removeCardEntity, direction == ItemTouchHelper.LEFT ? CardConfig.SWIPED_LEFT : CardConfig.SWIPED_RIGHT);
}
if (mCardStackListAdapter.getItemCount() == 0) {
if (getOnSwipeListener() != null) {
getOnSwipeListener().onSwipedClear();
}
}
}
@SuppressLint("LongLogTag")
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
onChildDrawSwipeLogic(recyclerView, viewHolder, dX);
}
}
@Override
public float getSwipeThreshold(RecyclerView.ViewHolder viewHolder) {
//滑动的比例达到多少之后, 视为滑动
return 0.3f;
}
private float getThreshold(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return recyclerView.getWidth() * /*getSwipeThreshold(viewHolder)*/ 0.5f;
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
resetCardViewStatus(viewHolder);
}
private void resetCardViewStatus(RecyclerView.ViewHolder viewHolder) {
viewHolder.itemView.setRotation(0f);
viewHolder.itemView.findViewById(R.id.like_flag).setAlpha(0f);
viewHolder.itemView.findViewById(R.id.dislike_flag).setAlpha(0f);
viewHolder.itemView.setScaleX(1f);
viewHolder.itemView.setScaleY(1f);
}
@Override
public void onChildDrawSwipeLogic(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX) {
View itemView = viewHolder.itemView;
// 得到滑动的阀值
float ratio = dX / getThreshold(recyclerView, viewHolder);
// ratio 最大为 1 或 -1
if (ratio > 1) {
ratio = 1;
} else if (ratio < -1) {
ratio = -1;
}
// 默认最大的旋转角度为 15 度
itemView.setRotation(ratio * CardConfig.DEFAULT_ROTATE_DEGREE);
int childCount = recyclerView.getChildCount();
// 当数据源个数大于最大显示数时
if (childCount > CardConfig.DEFAULT_SHOW_ITEM) {
for (int position = 1; position < childCount - 1; position++) {
int index = childCount - position - 1;
View view = recyclerView.getChildAt(position);
// 和之前 onLayoutChildren 是一个意思,不过是做相反的动画
view.setScaleX(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);
view.setScaleY(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);
//view.setTranslationY((index - Math.abs(ratio)) * itemView.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
}
} else {
// 当数据源个数小于或等于最大显示数时
for (int position = 0; position < childCount - 1; position++) {
int index = childCount - position - 1;
View view = recyclerView.getChildAt(position);
view.setScaleX(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);
view.setScaleY(1 - index * CardConfig.DEFAULT_SCALE + Math.abs(ratio) * CardConfig.DEFAULT_SCALE);
//view.setTranslationY((index - Math.abs(ratio)) * itemView.getMeasuredHeight() / CardConfig.DEFAULT_TRANSLATE_Y);
}
}
// 回调监听器
if (getOnSwipeListener() != null) {
if (ratio != 0) {
getOnSwipeListener().onSwiping(viewHolder, ratio, ratio < 0 ? CardConfig.SWIPING_LEFT : CardConfig.SWIPING_RIGHT);
} else {
getOnSwipeListener().onSwiping(viewHolder, ratio, CardConfig.SWIPING_NONE);
}
}
}
public CardStackAdapter2 getCardStackListAdapter() {
return mCardStackListAdapter;
}
public OnSwipeListener<HomeBean> getOnSwipeListener() {
return mOnSwipeListener;
}
}
- ItemTouchHelper文件
public class CardStackItemTouchHelper extends ItemTouchHelper {
RecyclerView mCardStackRecyclerView;
CardStackCallback mCardStackCallback;
protected ValueAnimator mValueAnimator;
public CardStackItemTouchHelper(@NonNull CardStackCallback callback) {
super(callback);
mCardStackCallback = callback;
}
@Override
public void attachToRecyclerView(@Nullable RecyclerView rv) {
mCardStackRecyclerView = rv;
super.attachToRecyclerView(rv);
}
//扩展实现:点击按钮实现左滑效果
public void toLeft(RecyclerView.ViewHolder holder) {
if (check()) {
animTo(holder, mCardStackRecyclerView, ItemTouchHelper.LEFT);
}
}
//扩展实现:点击按钮实现右滑效果
public void toRight(RecyclerView.ViewHolder holder) {
if (check()) {
animTo(holder, mCardStackRecyclerView, ItemTouchHelper.RIGHT);
}
}
@SuppressLint("LongLogTag")
protected void animTo(RecyclerView.ViewHolder holder, RecyclerView recyclerView, int direction) {
View view = holder.itemView;
float distance = (float) recyclerView.getWidth() + (float) recyclerView.getWidth() * 0.2f;
mValueAnimator = ValueAnimator.ofFloat(0f, direction == ItemTouchHelper.LEFT ? -distance : distance);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float dX = (float) valueAnimator.getAnimatedValue();
view.setTranslationX(dX);
if (mCardCallback != null){
mCardCallback.onChildDrawSwipeLogic(recyclerView, holder, dX);
}
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter() {
@SuppressLint("NotifyDataSetChanged")
@Override
public void onAnimationEnd(Animator animation) {
recyclerView.removeView(view);
if (mCardCallback != null){
mCardCallback.onSwiped(holder, direction);
}
}
});
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.setDuration(CardConfig.AUTO_SWIPED_ANIM_DURATION);
mValueAnimator.start();
}
public boolean isAutoSwiping() {
if (mValueAnimator != null) {
return mValueAnimator.isRunning();
}
return false;
}
@SuppressLint("LongLogTag")
public boolean check() {
if (isAutoSwiping()) {
return false;
}
if (mCardRecyclerView == null || mCardRecyclerView.getAdapter() == null) {
return false;
}
if (mCardRecyclerView.getAdapter().getItemCount() == 0) {
return false;
}
return true;
}
public abstract static class CardStackCallback extends Callback {
public abstract void onChildDrawSwipeLogic(RecyclerView recyclerView, RecyclerView.ViewHolder holder, float dX);
}
}
- OnSwipeListener文件
public interface OnSwipeListener<T> {
/**
* 卡片还在滑动时回调
*
* @param viewHolder 该滑动卡片的viewHolder
* @param ratio 滑动进度的比例
* @param direction 卡片滑动的方向,CardConfig.SWIPING_LEFT 为向左滑,CardConfig.SWIPING_RIGHT 为向右滑,
* <p>
* CardConfig.SWIPING_NONE 为不偏左也不偏右
*/
void onSwiping(RecyclerView.ViewHolder viewHolder, float ratio, int direction);
/**
* 卡片完全滑出时回调
*
* @param viewHolder 该滑出卡片的viewHolder
* @param t 该滑出卡片的数据
* @param direction 卡片滑出的方向,CardConfig.SWIPED_LEFT 为左边滑出;CardConfig.SWIPED_RIGHT 为右边滑出
*/
void onSwiped(RecyclerView.ViewHolder viewHolder, T t,int direction);
/**
* 所有的卡片全部滑出时回调
*/
void onSwipedClear();
}
- RecyclerView ItemView XML 布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF999999">
<androidx.viewpager.widget.ViewPager
android:id="@+id/iv_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="@+id/iv_like_icon"
android:layout_width="84dp"
android:layout_height="84dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:background="#ffff0000"
android:alpha="0" />
<View
android:id="@+id/iv_dislike_icon"
android:layout_width="84dp"
android:layout_height="84dp"
android:layout_alignParentEnd="true"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:background="#FF03DAC5"
android:alpha="0" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<View
android:id="@+id/iv_left_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
<View
android:id="@+id/iv_right_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</RelativeLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_alignParentBottom="true">
<View
android:id="@+id/iv_dislike_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#FFBB86FC"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"/>
<View
android:id="@+id/iv_like_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#FF6200EE"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
- CardStackConfig文件
public class CardConfig {
/**
* 显示可见的卡片数量
*/
public static final int DEFAULT_SHOW_ITEM = 3;
/**
* 默认缩放的比例
*/
public static final float DEFAULT_SCALE = 0.1f;
/**
* 卡片Y轴偏移量时按照14等分计算
*/
public static final int DEFAULT_TRANSLATE_Y = 14;
/**
* 卡片滑动时默认倾斜的角度
*/
public static final float DEFAULT_ROTATE_DEGREE = 15f;
/**
* 卡片滑动时不偏左也不偏右
*/
public static final int SWIPING_NONE = 1;
/**
* 卡片向左滑动时
*/
public static final int SWIPING_LEFT = 1 << 2;
/**
* 卡片向右滑动时
*/
public static final int SWIPING_RIGHT = 1 << 3;
/**
* 卡片从左边滑出
*/
public static final int SWIPED_LEFT = 1;
/**
* 卡片从右边滑出
*/
public static final int SWIPED_RIGHT = 1 << 2;
/**
* 滑动到最小位置
*/
public static final int SWIPED_MIN_POSITION = 20;
/**
* 自动滑动动画时间
*/
public static final int AUTO_SWIPED_ANIM_DURATION = 150;
}
- 如何调用
//禁用RecyclerView更新动画
if (ivCardStackList.getItemAnimator() != null) {
ivCardStackList.getItemAnimator().setAddDuration(0);
ivCardStackList.getItemAnimator().setChangeDuration(0);
ivCardStackList.getItemAnimator().setMoveDuration(0);
ivCardStackList.getItemAnimator().setRemoveDuration(0);
if (ivCardStackList.getItemAnimator() instanceof SimpleItemAnimator) {
((SimpleItemAnimator) ivCardStackList.getItemAnimator()).setSupportsChangeAnimations(false);
}
}
//设置LayoutManager
ivCardStackList.setLayoutManager(new CardStackLayoutManager());
//创建CardList适配器
mCardStackListAdapter = new CardStackListAdapter();
ivCardStackList.setAdapter(mCardStackListAdapter);
//创建ItemTouchHelperCallback
mCardStackItemTouchHelperCallback = new CardStackItemTouchHelperCallback(mCardStackListAdapter, new OnSwipeListener<CardEntity>() {
@Override
public void onSwiping(RecyclerView.ViewHolder viewHolder, float ratio, int direction) {
//根据滑动阀值对顶部喜欢与不喜欢指示图标做渐变显隐
if (viewHolder != null) {
View view = viewHolder.itemView;
float likeRatio;
float disLikeRatio;
if (ratio > 0F)
likeRatio = ratio + 0.25f;
else
likeRatio = 0F;
if (ratio < 0F)
disLikeRatio = -ratio + 0.25f;
else
disLikeRatio = 0F;
view.findViewById(R.id.iv_like_icon).setAlpha(likeRatio);
view.findViewById(R.id.iv_dislike_icon).setAlpha(disLikeRatio);
}
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, HomeBean homeBean, int position, int direction) {
if (direction == CardConfig.SWIPED_RIGHT) {
//右滑
}else if(direction == CardConfig.SWIPED_LEFT){
//左滑
}
}
@Override
public void onSwipedClear() {
//最后一页滑出可以做重新加载数据操作
}
});
//创建ItemTouchHelper
mCardStackItemTouchHelper = new CardStackItemTouchHelper(mCardStackItemTouchHelperCallback);
mCardStackItemTouchHelper.attachToRecyclerView(ivCardStackList);
//点击iv_dislike_button、iv_like_button按钮开始左滑右滑动画在动画运行中要禁用手动滑动
ivCardStackList.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
//自动滑动时禁止手动滑动
if (mCardStackItemTouchHelper != null) {
return mCardStackItemTouchHelper.isAutoSwiping();
}
return false;
}
});
- 能实现的效果
- 点击iv_left_container、iv_right_container 切换ViewPager页面
- 点击iv_dislike_button、iv_like_button 自动滑动切换动画
- 手动滑动