Skip to content

liudao01/MyCardView

Repository files navigation

MyCardView 实现类似支付宝卡包效果动画

@[toc]

这是应用内的效果

这是应用内的效果

这是demo的效果

这是demo的效果


思路

说下思路. 首先是一个线性布局,动态添加三个view,在添加的时候margin给设置一个负值,这样可以让卡片排列的时候看起来像是叠加排列的. 然后就是点击动画效果,使用属性动画即可实现. 有一个地方需要注意, 因为卡片叠加的时候,底部有个view是挨着上面的现象布局的,这样移动的时候卡片与卡片之间没有间距,如果有间距就会遮挡卡片.所以需要手动添加一个view 用来增加卡片与卡片间的间距. 具体代码如下

代码

package com.open.mycardview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private LinearLayout llEcardWallet;
    private RelativeLayout rvBottomView;

    private List<View> views;
    private View viewBottom;

    private View bottom_card;
    private boolean isOpenState = false;//默认没有展开
    private int topCard = 0;//那张卡在最上面
    Context context;
    private int cardMarginHeight = -100;


    private int recycleMoveHeight = 110;
    private int recycleBeginMarginHeight = 110;
    /**
     * 设置卡包  卡包界面
     */
    private void setCardInfoData() {

        llEcardWallet.removeAllViews();

        if (views != null && views.size() > 0) {
            views.clear();
        } else {
            views = new ArrayList<>();
        }
//        LogUtil.d("卡包里面的个数 = " + views.size());
        int size =3;
//        int size = res.size();
        if (size > 3) {
            size = 2;
        }
        if (size > 1) {
            isOpenState = false;
            setViewMargin(rvBottomView, true, 0, 0, -recycleBeginMarginHeight, 0);
            moveView(rvBottomView, recycleMoveHeight, 0, 0);//底部recycleview向上移动
        }
        for (int i = 0; i < size; i++) {
            View walletViewitem = View.inflate(this, R.layout.item_wallet_me_card, null);
//            VipCardInfoBean item = res.get(i);

            LinearLayout ll_recharge = walletViewitem.findViewById(R.id.ll_recharge);
            LinearLayout ll_ticket_top = walletViewitem.findViewById(R.id.ll_ticket_top);
            TextView iv_change_pwd = walletViewitem.findViewById(R.id.iv_change_pwd);
            LinearLayout ll_integration_top = walletViewitem.findViewById(R.id.ll_integration_top);
            LinearLayout money_top = walletViewitem.findViewById(R.id.money_top);
            TextView card_name_top = walletViewitem.findViewById(R.id.card_name_top);
            TextView card_num_top = walletViewitem.findViewById(R.id.card_num_top);
//            setTextViewStyles(card_name_top);


            TextView tv_money = walletViewitem.findViewById(R.id.tv_money);
            money_top.setTag(i);
            ll_ticket_top.setTag(i);
            ll_recharge.setTag(i);
            ll_integration_top.setTag(i);

            tv_money.setTag(i);
            TextView integration_top_tv = walletViewitem.findViewById(R.id.integration_top_tv);
            TextView ticket_top_tv = walletViewitem.findViewById(R.id.ticket_top_tv);
            TextView money_top_tv = walletViewitem.findViewById(R.id.money_top_tv);

            iv_change_pwd.setTag(i);
            card_num_top.setTag(i);
            integration_top_tv.setTag(i);
            ticket_top_tv.setTag(i);
            money_top_tv.setTag(i);

            RelativeLayout rl_ecard_root = walletViewitem.findViewById(R.id.rl_ecard_root);
            LinearLayout card_top_container = walletViewitem.findViewById(R.id.card_top_container);
            views.add(walletViewitem);
            llEcardWallet.addView(walletViewitem);

            if (i == 0) {
                rl_ecard_root.setBackgroundResource(R.mipmap.me_e_card1);
            } else if (i == 1) {
                rl_ecard_root.setBackgroundResource(R.mipmap.me_e_card2);
            } else if (i % 3 == 2) {
                rl_ecard_root.setBackgroundResource(R.mipmap.me_e_card3);
            }

            walletViewitem.setTag(i);
            rl_ecard_root.setTag(i);
            card_top_container.setTag(i);
            if (size > 1) {
                rl_ecard_root.setOnClickListener(onCardClickListener);
                card_top_container.setOnClickListener(onCardClickListener);
//                setViewMargin(recyclerView, true, 0, 0, cardMarginHeight, 0);
            }


            iv_change_pwd.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = (int) v.getTag();
                }
            });
            ll_integration_top.setTag(i);
            ll_integration_top.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
//                    ToastUtils.showCenter("积分");
                }
            });
            money_top.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
//                    ToastUtils.showCenter("储值卡");
                    int position = (int) v.getTag();

                }
            });
            ll_ticket_top.setTag(i);
            ll_ticket_top.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = (int) v.getTag();
                }
            });
            ll_recharge.setTag(i);
            ll_recharge.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
//                    ToastUtils.showCenter("充值");
                    int position = (int) v.getTag();
                }
            });

        }


        //手动再添加一个view 用于撑大电子钱包布局
        bottom_card = View.inflate(this, R.layout.bottom_card, null);
        LinearLayout card_bottom = bottom_card.findViewById(R.id.card_bottom);
        llEcardWallet.addView(card_bottom);


        for (int i = 0; i < views.size(); i++) {
            if (i != 0) {
                setViewMargin(views.get(i), true, 0, 0, cardMarginHeight, 0);
            }
        }


    }

    private int cardMoveHeight = 50;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = getApplicationContext();

        llEcardWallet = findViewById(R.id.ll_ecard_wallet);
        rvBottomView = findViewById(R.id.rv_bottom_view);

        viewBottom = findViewById(R.id.view_bottom);

        setCardInfoData();

    }
    /**
     * 根据手机的分辨率从 dp 的单位转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }


    View.OnClickListener onCardClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int position = (int) v.getTag();
//            VipCardInfoBean vipCardInfoBean = res.get(position);
//            CardMoneyBean cardMoneyBean = vipCardInfoBean.getCardMoneyBean();
            Intent intent;
            switch (v.getId()) {
                case R.id.card_top_container://
//                    itemAllOnclick(position);
                    cardOnclick(position);
//                    ToastUtils.showCenter("点击card_top_container");
                    break;
            }

        }
    };

    private void cardOnclick(int tag) {
//        ToastUtils.showCenter("点击 = " + tag);
        if (isOpenState) {//展开
            viewBottom.setVisibility(View.GONE);
        } else {//收起
            viewBottom.setVisibility(View.VISIBLE);
        }
        //当前收起状态
        if (!isOpenState) {

            //展开卡包
            for (int i = 0; i < views.size(); i++) {
//                LogUtil.d("展开卡包 点击第" + tag + " 个" + ",当前第 i" + i);
                if (i > tag) {
//                    LogUtil.d("向下移动 第 " + i + "个 距离 " + recycleMoveHeight);
                    //向下移动
                    moveView(views.get(i), 0, recycleMoveHeight);
                } else if (i < tag) {

                    //向下移动
//                    LogUtil.d("向下移动 i < tag 第 " + i + "个 距离 " + recycleMoveHeight);
                    moveView(views.get(i), 0, recycleMoveHeight + cardMoveHeight);
                } else if ((i == tag)) {
//                    LogUtil.d("第 " + i + "个 向上移动  距离 -" + recycleMoveHeight* i);
                    //向上移动
                    moveView(views.get(i), 0, -cardMoveHeight * i);
                }
            }
            isOpenState = true;
            //让view展开
            moveView(rvBottomView, 0, recycleMoveHeight);//底部recycleview向下移动
        } else if (tag == topCard && isOpenState) {

            //点击卡片和顶部的相同就收起卡包
            for (int i = 0; i < views.size(); i++) {
                if (i > tag) {
                    //向上移动
                    moveView(views.get(i), recycleMoveHeight, 0);
                } else if (i < tag) {
                    //向下移动
                    moveView(views.get(i), recycleMoveHeight + cardMoveHeight, 0);
                } else if ((i == tag)) {
                    //向下移动
                    moveView(views.get(i), -cardMoveHeight * i, 0);
                }
            }
            //当前展开状态 让view收起
            isOpenState = false;
            moveView(rvBottomView, recycleMoveHeight, 0);//底部recycleview向上移动
        } else {
            //交换卡包
//            LogUtil.d("交换卡片 点击第" + tag + " 个 顶部是第" + topCard + "个 ");
            changeItem(topCard, tag);


        }
        topCard = tag;
//        isShowChangePWD(isOpenState);
    }

    /**
     * 切换
     *
     * @param topCard
     * @param onclickCard
     */
    private void changeItem(int topCard, int onclickCard) {
//        LogUtil.d("切换条目 topCard = " + topCard + " onclickCard = " + onclickCard);
        //处理顶部
        switch (topCard) {
            case 0:
                moveView(views.get(topCard), 0, recycleMoveHeight + cardMoveHeight + 5);
                break;
            case 1:
                if (onclickCard == 2) {
                    moveView(views.get(topCard), -cardMoveHeight, recycleMoveHeight + cardMoveHeight);
                } else {
                    moveView(views.get(topCard), -cardMoveHeight, recycleMoveHeight + cardMoveHeight * onclickCard);
                }
                break;
            case 2://调试

                if (onclickCard == 0) {
                    moveView(views.get(0), recycleMoveHeight, -cardMoveHeight);
                    moveView(views.get(1), recycleMoveHeight + cardMoveHeight, recycleMoveHeight);
                } else if (onclickCard == 1) {
//                    moveView(views.get(1), recycleMoveHeight, -cardMoveHeight);
//                    moveView(views.get(0), changeThreeHeight, -changeOneHeight);
//                    moveView(views.get(topCard),  - recycleMoveHeight, recycleMoveHeight);
                }
                moveView(views.get(topCard), -recycleMoveHeight, recycleMoveHeight);
                break;
        }
        //处理点击
        switch (onclickCard) {
            case 0:
                moveView(views.get(onclickCard), recycleMoveHeight + cardMoveHeight + 5, 0);
                break;
            case 1:
                if (topCard == 2) {
                    moveView(views.get(onclickCard), recycleMoveHeight + cardMoveHeight, -cardMoveHeight);
                } else {
                    moveView(views.get(onclickCard), recycleMoveHeight, -cardMoveHeight);
                }
//                moveView(views.get(1), changeThreeHeight, -changeTwoHeight);//正常
                break;
            case 2://调试
                moveView(views.get(onclickCard), recycleMoveHeight, -cardMoveHeight * onclickCard);
//                moveView(views.get(2), moveThreeHeight, -moveThreeHeight);
                if (topCard == 0) {
                    moveView(views.get(1), recycleMoveHeight, recycleMoveHeight + cardMoveHeight);
                } else if (topCard == 1) {
//                    moveView(views.get(0), changeThreeHeight, changeOneHeight);
                }
                break;
        }
    }

    /**
     * 移动动画
     *
     * @param view
     * @param begin
     * @param end
     */
    private void moveView(View view, int begin, int end) {
        final ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "translationY", getPxFromDpi(begin), getPxFromDpi(end));
        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }
        });
        animator1.setStartDelay(100);//设置动画延迟执行
        animator1.setDuration(500);//设置动画时间
        animator1.start();
    }

    /**
     * 移动动画
     *
     * @param view
     * @param begin
     * @param end
     */
    private void moveView(View view, int begin, int end, int duration) {
        final ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "translationY", getPxFromDpi(begin), getPxFromDpi(end));
        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }
        });
//        animator1.setStartDelay(100);//设置动画延迟执行
        animator1.setDuration(duration);//设置动画时间
        animator1.start();
    }

    /**
     * 设置某个View的margin
     *
     * @param view   需要设置的view
     * @param isDp   需要设置的数值是否为DP
     * @param left   左边距
     * @param right  右边距
     * @param top    上边距
     * @param bottom 下边距
     * @return
     */
    public ViewGroup.LayoutParams setViewMargin(View view, boolean isDp, int left, int right, int top, int bottom) {
        if (view == null) {
            return null;
        }

        int leftPx = left;
        int rightPx = right;
        int topPx = top;
        int bottomPx = bottom;
        ViewGroup.LayoutParams params = view.getLayoutParams();
        ViewGroup.MarginLayoutParams marginParams = null;
        //获取view的margin设置参数
        if (params instanceof ViewGroup.MarginLayoutParams) {
            marginParams = (ViewGroup.MarginLayoutParams) params;
        } else {
            //不存在时创建一个新的参数
            marginParams = new ViewGroup.MarginLayoutParams(params);
        }

        //根据DP与PX转换计算值
        if (isDp) {
            leftPx = getPxFromDpi(left);
            rightPx = getPxFromDpi(right);
            topPx = getPxFromDpi(top);
            bottomPx = getPxFromDpi(bottom);
        }
        //设置margin
        marginParams.setMargins(leftPx, topPx, rightPx, bottomPx);
        view.setLayoutParams(marginParams);
        return marginParams;
    }

    private int getPxFromDpi(int left) {
        final int i = dip2px(getApplicationContext(), left);
        Log.d(TAG, "getPxFromDpi: i = " + i);
        return i;
    }


}

About

实现类似支付宝卡包效果动画

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages