卡片式切换效果

今天跟大家分享一个卡片切换的效果:

卡片式切换效果_第1张图片

一个说不上酷炫但很有意思的效果

首先大体思路是:效果有多个View和动画组成的集合,既然是多个View那么复用肯定是首先要想到的,我这边用到的是RecycleView ,控件的叠加效果可以通过自定义RecycleView的LayoutManager来实现,而RecycleView并没有提供相关Api来支持拖拽效果的监听,好在SDK提供的一个强大的工具类ItemTouchHelper 来让我们实现RecycleView条目的拖拽效果。

现在让我们一步步实现:
先来一波常规操作:
卡片式切换效果_第2张图片

这里就不贴代码了就是写了几个带圆角的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位置均一样所给给覆盖住

卡片式切换效果_第3张图片

我们需要根据不同层级进行不同的位置和缩放即可实现叠加效果,代码如下:

 //当前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);

卡片式切换效果_第4张图片

层级效果已经完成然后就是左右切换的效果
切换效果需要我们自己定义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

以上就完成了所有效果。

完整项目地址

你可能感兴趣的:(卡片式切换效果)