今天跟大家分享一个卡片切换的效果:
一个说不上酷炫但很有意思的效果
首先大体思路是:效果有多个View和动画组成的集合,既然是多个View那么复用肯定是首先要想到的,我这边用到的是RecycleView ,控件的叠加效果可以通过自定义RecycleView的LayoutManager来实现,而RecycleView并没有提供相关Api来支持拖拽效果的监听,好在SDK提供的一个强大的工具类ItemTouchHelper 来让我们实现RecycleView条目的拖拽效果。
这里就不贴代码了就是写了几个带圆角的shape作为背景,然后就是RecycleView一些普通的代码。
然后就是自定义LayoutManager,需要实现generateDefaultLayoutParams()方法,这个方法主要为设置LayoutManager的RecycleView提供LayoutParams参数
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
}
实现叠加效果重点在于在onLayoutChildren()方法中对显示的item和位置重新排布@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
//将所有的itemView先全部detach,放到Scrap集合里面,进行重新排布
detachAndScrapAttachedViews(recycler);
//获取当前recycler中的条目书
int count = getItemCount();
//取条目中前最大显示数的条目下标
int mShowItemPos;
if(count<=mMaxShowCount){
mShowItemPos = 0;
}else{
mShowItemPos = count-mMaxShowCount;
}
//遍历排布
for(int i = mShowItemPos;i
设置给RecycleView之后发现只剩一个item,其他的item位置均一样所给给覆盖住
我们需要根据不同层级进行不同的位置和缩放即可实现叠加效果,代码如下:
//当前item的层级 0 1 2 3
int tier = count-i-1;
//根据不同层级进行偏移和缩放
item.setTranslationY(tier * mTransLB / 2); //向下偏移
item.setTranslationX(-tier * mTransLB); //向左偏移
//宽高缩放
item.setScaleX(1-mScaleRate*tier);
item.setScaleY(1-mScaleRate*tier);
层级效果已经完成然后就是左右切换的效果
切换效果需要我们自己定义ItemTouchHelper,并复写 onMove(),onSwiped()等方法
onMove()方法为移动条目时所需要处理的方法,这里直接返回默认false即可
onSwiped() 方法是滑动结束后所触发的方法
我们还需要复写两个重要的方法
getMovementFlags() 设置滑动的方向(也可以在构造器中设置方向)
onChildDraw() 设置item移动时的动画效果
直接贴上代码
public class SwipeCardCallBack extends ItemTouchHelper.SimpleCallback {
private RecyclerView mRecyclerView;
private List mDatas;
//当前item最大显示数
private int mMaxShowCount = 4;
//item左下偏移
private int mTransLB;
//item缩放比例
private float mScaleRate = 0.05f;
public SwipeCardCallBack(Context context) {
super(0, 0);
mTransLB = DensityUtils.dp2px(context,15);
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int swipeFlags = 0;
mRecyclerView = recyclerView;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof SwipeCardLayoutManager) {
//设置滑动的方向
swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT ;
}
return makeMovementFlags(0, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//移除并添加数据实现循环
SwipeCardAdapter swipeCardAdapter = (SwipeCardAdapter) mRecyclerView.getAdapter();
mDatas = swipeCardAdapter.getDatas();
int bg = mDatas.remove(viewHolder.getLayoutPosition());
mDatas.add(0,bg);
swipeCardAdapter.notifyDataSetChanged();
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
//最大移动距离
double maxMove = Math.hypot(recyclerView.getWidth()/2,recyclerView.getHeight()/2);
//当前移动距离
double currentMove = Math.hypot(dX,dY);
//当前动画的进度
double p = currentMove/maxMove>1?1:currentMove/maxMove;
int count = recyclerView.getChildCount();
for (int i = 0; i < count; i++) {
//执行
View item = recyclerView.getChildAt(i);
int tier = count - i - 1;
if (tier > 0) {
if (tier < mMaxShowCount) {
//将其他层级的item进行向中偏移并放大
item.setTranslationX((float) (1 - mTransLB * tier + p * mTransLB));
item.setTranslationY((float) -(1 - mTransLB * tier + p * mTransLB)/2);
item.setScaleX((float) (1 - mScaleRate * tier + p * mScaleRate));
item.setScaleY((float) (1 - mScaleRate * tier + p * mScaleRate));
}
}else{
if(dX!=0){
//根据item的移动方向的不同进行不同的角度反转
item.setRotation((float) (p * 45*dX/Math.abs(dX)));
}
}
}
}
//防止除了最顶上的item滑动外,其他层级的item也滑动,需要在LayoutManager中重新设置顶层item的滑动监听
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
//旋转后由于条目复用会使下一层的item也进行旋转
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
//重置角度
viewHolder.itemView.setRotation(0f);
}
}
还需要重置最上层的滑动监听,直接贴出SwipeCardLayoutManager代码
public class SwipeCardLayoutManager extends RecyclerView.LayoutManager {
//当前item最大显示数
private int mMaxShowCount = 4;
//向左右展示偏移
private int mOffsetRight = 20;
//item左下偏移
private int mTransLB;
//item缩放比例
private float mScaleRate = 0.05f;
private Context mContext;
private RecyclerView mRecyclerView;
private ItemTouchHelper mHelper;
public SwipeCardLayoutManager(Context context, RecyclerView recyclerView, ItemTouchHelper helper) {
mContext = context;
mTransLB = DensityUtils.dp2px(context,15);
// mOffsetRight = mTransLB*mMaxShowCount;
mRecyclerView = recyclerView;
mHelper = helper;
}
@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) {
super.onLayoutChildren(recycler, state);
//将所有的itemView先全部detach,放到Scrap集合里面,进行重新排布
detachAndScrapAttachedViews(recycler);
//获取当前recycler中的条目书
int count = getItemCount();
//取条目中前最大显示数的条目下标
int mShowItemPos;
if(count<=mMaxShowCount){
mShowItemPos = 0;
}else{
mShowItemPos = count-mMaxShowCount;
}
//遍历排布
for(int i = mShowItemPos;i
以上就完成了所有效果。
完整项目地址