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)); } }
四、总结
其实整理原理很简单,就是现实的数据的记录需要细心点。
当然,肯定还有其他的实现方法,如果疑问,欢迎指点讨论~
免费源码下载
- Android 卡片效果(仿苏宁"易付宝钱包"首页)
- Android 卡片效果
- Android 卡片层叠效果
- Android 卡片翻转动画效果
- Android层叠式卡片效果实现!(高大上)
- Android---显示卡片翻转的动画效果
- android 卡片翻转效果的实现
- android类似扑克卡片翻转效果
- Android卡片式折叠交互效果
- Android程序首页Loading效果
- Android仿京东首页画轴效果
- Android程序首页Loading效果
- Android中实现Card Flip (卡片翻转)动画效果的探讨
- Android手把手教你实现卡片式瀑布流效果(RecyclerView+CardView,附源码)
- Android实现豆瓣FM的首页效果
- android 仿首页广告轮播效果
- Android APP常见首页效果的实现
- android 仿去哪儿首页效果
- 判断去电或来电已经接通
- 深入浅出Docker(一):Docker核心技术预览
- Java Web之五 会话Cookie Session
- 浅析PowerBuilder下动态SQL语句
- 给Jquery easyui 的datagrid 每行增加操作链接
- Android 卡片效果(仿苏宁"易付宝钱包"首页)
- iOS开发--UITableView全面解析
- Objective-C Runtime
- Activiti 5.16 流程图高亮追踪 中文乱码问题解决方法
- Java线程池的详细介绍
- jquery的each()详细介绍
- 深入浅出Docker(二):Docker命令行探秘
- Html.AntiForgeryToken() 防止CSRF攻击 的AJaX应用
- [C++ Primer]第一章 快速入门