Android 卡片效果(仿苏宁"易付宝钱包"首页)

来源:互联网 发布:数据挖掘 前景 编辑:程序博客网 时间:2024/04/24 19:45

一、模仿实际效果图



手指点击的事件未能在上图中体现,实际效果是:点一下卡片,卡片展开;再点一下卡片,卡片关闭。


二、设计图

1,卡片原始状态



2,卡片展开状态




三、详细设计说明

1,卡片的大小是根据1280*800设计的:

/** * <卡片>配置. *  *@Title: *@Description: *@Author:justlcw *@Version: */public class CardConfig{    /** <卡片>标题高度.  **/    public static final int ITEM_TITLE_HEIGHT = 130;    /** <卡片>高度.  **/    public static final int ITEM_HEIGHT = 900;    /** 头部View高度.  **/    public static final int HEADER_HEIGHT = 255;    /** 尾部View高度.  **/    public static final int FOOTER_HEIGHT = 300;    /** <卡片>隐藏时top.  **/    public static final int HIDE_TOP = 1000;    /** <卡片>隐藏top间隔.  **/    public static final int HIDE_TOP_MARGIN = 15;    /** <卡片>动画时长.  **/    public static final long DURATION = 800;    /** <卡片>动画加速器.  **/    public static final int INTERPOLATOR_ID = android.R.anim.accelerate_decelerate_interpolator;}

说明:


2,CardView的设计:

<1>根据设计的比例,计算出实际的比例:

设计size *  屏幕实际height /  设计height =  最终显示size

   /**     * 初始化     * @Description:     * @Author justlcw     * @Date 2014-4-23     */    private void init(Context context)    {        screenHeight = context.getResources().getDisplayMetrics().heightPixels;//屏幕高度                itemTitleHeight = CardConfig.ITEM_TITLE_HEIGHT * screenHeight/1280;//卡片标题高度        itemHeight = CardConfig.ITEM_HEIGHT * screenHeight/1280;//卡片高度        headerHeight = CardConfig.HEADER_HEIGHT * screenHeight/1280;//头部View高度        footerHeight = CardConfig.FOOTER_HEIGHT * screenHeight/1280;//尾部View高度        hideTop = CardConfig.HIDE_TOP * screenHeight/1280;//卡片隐藏时top    }

<2>onMeasure测量头部View、卡片、尾部View固定值:

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)    {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);                int childCount = getChildCount();        if(childCount < MIN_CHILD_COUNT)        {            Log.e(TAG, "error : this view need one header view, one footer view and one or more itemView at least.");            return;        }        for(int i=0; i<childCount; i++)        {            //既然<头><尾><卡片>的高度都被设定死了,这里只能往死里设定了            final int eHeightMeasureSpec;            View child = getChildAt(i);            if(i == 0)            {                eHeightMeasureSpec = MeasureSpec.makeMeasureSpec(headerHeight, MeasureSpec.EXACTLY);            }            else if(i == childCount-1)            {                eHeightMeasureSpec = MeasureSpec.makeMeasureSpec(footerHeight, MeasureSpec.EXACTLY);            }            else            {                eHeightMeasureSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);            }            child.measure(widthMeasureSpec, eHeightMeasureSpec);        }    }

<3>onSizeChanged初始化每个子View的位置:

   @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh)    {        super.onSizeChanged(w, h, oldw, oldh);                //当所有<子View>都添加的时候.        int childCount = getChildCount();        if(childCount < MIN_CHILD_COUNT)        {            Log.e(TAG, "error : this view need one header view, one footer view and one or more itemView at least.");            return;        }        //初始化<子View>的状态        for(int i=0; i<childCount; i++)        {            View child = getChildAt(i);                        CardItemStatus status;            if(i == 0)            {                //头部                status = new CardItemStatus(0, 0, -headerHeight, headerHeight, i);            }            else if(i == childCount-1)            {                //尾部                final int footerTop = getBottom() - footerHeight;                status = new CardItemStatus(footerTop, footerTop, screenHeight, footerHeight, i);            }            else            {                //卡片                status = new CardItemStatus(0, headerHeight+(i-1)*itemTitleHeight, hideTop, itemHeight, i);                child.setOnClickListener(mOnClickListener);            }            child.setTag(status);        }    }

CardItemStatus就是保存View的位置、状态等数据
       /**         * 构造方法         * @param expandTop 展开时top         * @param closeTop 关闭时top         * @param hideTop 隐藏时top         * @param height 卡片高度         * @param index 卡片位置         */        public CardItemStatus(int expandTop, int closeTop, int hideTop, int height, int index)        {            this.expandTop = expandTop;            this.closeTop = closeTop;            this.hideTop = hideTop;                        this.height = height;            this.index = index;                        //初始化数据            this.status = STATUS_CLOSE;            this.currentTop = closeTop;        }

<4>onLayout根据View的CardItemStatus布局View:

   @Override    protected void onLayout(boolean changed, int l, int t, int r, int b)    {        int childCount = getChildCount();        if(childCount < MIN_CHILD_COUNT)        {            Log.e(TAG, "error : this view need one header view, one footer view and one or more itemView at least.");            return;        }        final int w = r-l;                //根据<子View>自身的状态<布局>        for(int i=0; i<childCount; i++)        {            View child = getChildAt(i);            CardItemStatus status = (CardItemStatus) child.getTag();            child.layout(0, status.currentTop, w, status.currentTop+status.height);        }    }

3,动起来:

原理简介:只要不断的改变View的CardItemStatus,然后requestLayout,不就是可以动了么。注意上面的onLayout的方法,它是根据CardItemStatus来layout每个View的。


<1>自定义动画,不断改变CardItemStatus:

注意,CardItemStatus保存了View的起始top目标top,想到了什么?位移动画,只要在动画执行的期间不断的改变top,直到变成最终的目标top,期间不断的重新layout即可。

   /**     * 卡片点击动画     *@Title:     *@Description:     *@Author:justlcw     *@Version:     */    class CardClickAnimation extends Animation    {        /** 动画时长. **/        private static final long DURATION = CardConfig.DURATION;        /** 加速器. **/        private static final int INTERPOLATOR_ID = CardConfig.INTERPOLATOR_ID;                /** 卡片状态 **/        private CardItemStatus status;                /**         * 构造方法         * @param status {@link CardItemStatus}         */        public CardClickAnimation(CardItemStatus status)        {            this.status = status;                        setDuration(DURATION);            setInterpolator(getContext(), INTERPOLATOR_ID);        }                @Override        protected void applyTransformation(float interpolatedTime, Transformation t)        {            if(interpolatedTime == 0f && !hasStarted())            {                status.onAnimationStart();                onAnimationStart(status);                Log.d(TAG, "animation start");            }            else if(interpolatedTime >= 1f && hasEnded())            {                status.onAnimationEnd();                onAnimationEnd(status);                Log.d(TAG, "animation end");            }            else            {                if(status.status == CardItemStatus.STATUS_EXPANDING)                {                    //扩展阶段   (关闭状态top-展开状态top) * 当前运行时间 = 运行距离                    final float top = interpolatedTime*(float)(status.closeTop - status.expandTop);                    status.currentTop = status.closeTop - (int)top;//关闭状态top - 运行距离 = 当前top                }                else                {                    //关闭阶段 (关闭状态top-展开状态top) * 当前运行时间 = 运行距离                    final float top = interpolatedTime*(float)(status.closeTop - status.expandTop);                    status.currentTop = (int)top;//运行距离 = 当前top                }                //处理其他view                onAnimation(interpolatedTime, status);            }            //重新布局            requestLayout();        }    }

<2>卡片完全展开时候的CardItemStatus的改变

比如,当1展开了,那么CardItemStatus是不是要变一下?一开始肯定是未展开,那么点击的它的时候,要么展开,要么隐藏;如果展开or隐藏了,那么下一步就是恢复原状,所以需要在每次状态变更的时候,做一下记录,以便下一次的变动。

4,点击调用:

根据需求的不同,把动画启动的事情抛出来,让使用者处理。

当某个卡片被点击的时候,先获取View的CardItemStatus,然后进行判断:

原始状态   →   点击展开

隐藏状态   →   说明有其他卡片已经展开,关闭已经展开的卡片

展开状态   →   点击关闭


   /**     * 当需要展示<卡片>的时候调用.     *      * @param index 位置.     * @Description:     * @Author justlcw     */    public void onCardItemClick(int index)    {        int childCount = getChildCount();        if(childCount < MIN_CHILD_COUNT || onAnima)        {            return;        }        //目标<卡片>        View target = null;        //从卡片中(不包括头,尾),寻找目标<卡片>        for(int i=1; i<childCount-1; i++)        {            View child = getChildAt(i);            CardItemStatus status = (CardItemStatus) child.getTag();            if(status.status == CardItemStatus.STATUS_EXPAND)            {                //如果发现有<卡片>处于<展开>状态,关闭这个<卡片>                child.startAnimation(new CardClickAnimation(status));                return;            }            else if(status.index == index)            {                //如果 找到目标<卡片>,标记                target = child;            }        }        if(target != null)        {            //如果找到了,开始展开            CardItemStatus status = (CardItemStatus) target.getTag();            target.startAnimation(new CardClickAnimation(status));        }    }


四、总结

其实整理原理很简单,就是现实的数据的记录需要细心点。

当然,肯定还有其他的实现方法,如果疑问,欢迎指点讨论~

免费源码下载




0 0