仿支付宝银行卡选择页面

2020-03-19  本文已影响0人  DrChenZeng

实现代码:https://github.com/drchengit/alipay_card
感谢开源:https://github.com/loopeer/CardStackView
实现简单ReyclerView : https://juejin.im/post/5df72c5bf265da33b7553a40

看到支付宝选择银行卡页面,感觉很有趣,决定写篇文章。

效果:

image

实现:

image

实现思路:

表面分析:

实际思路

网上有类似的开源库:https://github.com/loopeer/CardStackView

源码分析

我之前写过一篇 如何实现一个简单的RecyclerView 中对自定义有一个简单图画解析,可以看一下。
这篇的我只对列表如何实现高度计算、布局、动画及还原过程的详细解释,我还是推荐先看看源码

高度计算

onMeasure中调用了checkContentHeightByParent()和** measureChild(),前者计算了view 的可显示区域;measureChild**中确定view所有子控件的高度:

 private void measureChild(int widthMeasureSpec, int heightMeasureSpec) {
    int maxWidth = 0;
    mTotalLength = 0;//总高度
    mTotalLength += getPaddingTop() + getPaddingBottom();

    for (int i = 0; i < getChildCount(); i++) {//遍历子item
        final View child = getChildAt(i);
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        final int totalLength = mTotalLength;

        final LayoutParams lp =
                (LayoutParams) child.getLayoutParams();


        if (needOpen(i)) {//不需要重叠的item计算时用原来的高度
            lp.mHeight = child.getMeasuredHeight();//这里mlHeight中储存了高度,布局时能用上
        } else {//需要折叠的item计算时用重叠后显示的高度
            lp.mHeight = mOverlapGaps;
        }
        mTotalLength = Math.max(totalLength, totalLength + lp.mHeight + lp.topMargin +
                lp.bottomMargin);//累积子item高度即可
        final int margin = lp.leftMargin + lp.rightMargin;
        final int measuredWidth = child.getMeasuredWidth() + margin;
        maxWidth = Math.max(maxWidth, measuredWidth);
    }


    int heightSize = mTotalLength;
    heightSize = Math.max(heightSize, mShowHeight);
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
            heightSizeAndState);
}  

onLayout布局,注意个布局,item 属性动画还原时要使用同样的布局方式。

private void layoutChild() {
     int childTop = getPaddingTop();
        int childLeft = getPaddingLeft();

    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        final int childWidth = child.getMeasuredWidth();
        int childHeight = child.getMeasuredHeight();
        final LayoutParams lp =
                (LayoutParams) child.getLayoutParams();
        childTop += lp.topMargin;
        child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);//按照高度布局
        childTop += lp.mHeight;//childTop实际是下一个item离上一个item顶部的距离,lp.mHight在onMeasure()中计算的时候就保存了高度 
    }
}

动画一:开启动画

开启动画内容,选中的item上移,其他卡片item下移折叠
CardStackView缓存了Adapter的viewHolder,通过他对卡片设置点击监听:

image
继续向下
image
点击后属性动画类AnimatorAdapter执行动画操作
image
先看开启动画onItemExpand()
 /**
 * 打开动画
 */
 public void onItemExpand(final CardStackView.ViewHolder viewHolder, int position) {
    if (mSet != null && mSet.isRunning()) return;
    initAnimatorSet();
    final int preSelectPosition = mCardStackView.getCardSelectPosition();
    final CardStackView.ViewHolder preSelectViewHolder = mCardStackView.getViewHolder(preSelectPosition);
    if (preSelectViewHolder != null) {
        preSelectViewHolder.onItemExpand(false);
    }

     mCardStackView.setCardSelectPosition(position);//设置当前选择卡片,隐藏头部和底部itemView
     itemExpandAnimatorSet(viewHolder, position);//执行动画操作,会发现是个空实现
  ...
    mSet.start();

}

itemExpandAnimatorSet(viewHolder, position) 是一个空实现,

image
AllMoveDownAnimatorAdapter继承实现了这个方法:
/**
 * 打开动画
 */
protected void itemExpandAnimatorSet(final CardStackView.ViewHolder viewHolder, int position) {
    final View itemView = viewHolder.itemView;
    viewHolder.beforeOpenHeight = itemView.getMeasuredHeight();//记录一下没有完全展开后的高度,恢复时要用
    viewHolder.itemView.clearAnimation();
    //选中卡片飞到顶部
    ObjectAnimator oa = ObjectAnimator.ofFloat(itemView, View.Y, itemView.getY(), mCardStackView.getScrollY() + mCardStackView.getPaddingTop());
    mSet.play(oa);
    ...
    int collapsePosition = mCardStackView.getNumBottomShow();//底部折叠的个数
    ...
    for (int i = 0; i < mCardStackView.getChildCount(); i++) {//遍历未选中的卡片
    ...//这里跳过已经选中的卡片
        if (i < collapsePosition) {//前面规定的折叠卡片到底部
            childTop = mCardStackView.getShowHeight() - getCollapseStartTop(collapseShowItemCount) + mCardStackView.getScrollY();
            ObjectAnimator oAnim = ObjectAnimator.ofFloat(child, View.Y, child.getY(), childTop);
            mSet.play(oAnim);
            collapseShowItemCount++;
        } else {//其他移动到屏幕外
            ObjectAnimator oAnim = ObjectAnimator.ofFloat(child, View.Y, child.getY(), mCardStackView.getShowHeight() + mCardStackView.getScrollY());
            mSet.play(oAnim);
        }
    }
}

动画二:还原

onItemCollapseAllMoveDownAnimatorAdapter也实现了还原动画itemCollapseAnimatorSet这个方法:
查看:

  /**
 * 关闭动画
 * @param viewHolder  之前选中的viewHolder
 */
public void onItemCollapse(final CardStackView.ViewHolder viewHolder) {
    if (mSet != null && mSet.isRunning()) return;
    initAnimatorSet();
    ...
    itemCollapseAnimatorSet(viewHolder);//依然是空实现
    mSet.addListener(new AnimatorListenerAdapter() {

        @Override
        public void onAnimationEnd(Animator animation) {
        ...
            mCardStackView.setCardSelectPosition(CardStackView.DEFAULT_SELECT_POSITION);//动画完成取消选中状态,头部和底部view显示
          }
    });
    mSet.start();
}

itemCollapseAnimatorSet还原动画中实质是按照CardStackViewonLayout的布局进行了还原:

/**
 * 关闭动画
 *  @param viewHolder  之前选中的viewHolder
 */
@Override
protected void itemCollapseAnimatorSet(CardStackView.ViewHolder viewHolder) {

    int childTop = mCardStackView.getPaddingTop();
    for (int i = 0; i < mCardStackView.getChildCount(); i++) {
        View child = mCardStackView.getChildAt(i);
        child.clearAnimation();
        final CardStackView.LayoutParams lp =
                (CardStackView.LayoutParams) child.getLayoutParams();
        childTop += lp.topMargin;

            ObjectAnimator oAnim = ObjectAnimator.ofFloat(child, View.Y, child.getY(), childTop);
            mSet.play(oAnim);
            if(mCardStackView.getChildSeletPosition()==i&&mCardStackView.needOpen(i)){
                childTop += viewHolder.beforeOpenHeight;//这里的高度有点问题做了一些处理,因为打开时高度包含详情,测量高度不对

            }else {
                childTop +=lp.mHeight;//这个childTop 跟onLayout()是一毛一样的
            }

    }
}

总结:

实现代码:https://github.com/drchengit/alipay_card
感谢开源:https://github.com/loopeer/CardStackView
实现简单ReyclerView : https://juejin.im/post/5df72c5bf265da33b7553a40

我是drchen,一个温润的男子,版权所有,未经允许不得抄袭。

上一篇下一篇

猜你喜欢

热点阅读