使用ViewPager实现卡片叠加效果

背景

在开发项目时,需要对 App的某个资源模块进行界面重构,其中在资源展示部分中新的交互以卡片叠加的效果替代了原来的资源组织树门禁展示方式。在新的资源展示方式中,每一个新的卡片都是在最上面的,其顺序以栈的形式存储在内存。卡片支持叠加效果,左右滑动切换到下一页或上一页,且卡片中的资源是以列表的形式展示,支持上下滑动,上拉刷新,下拉加载更多。目前网上存在的卡片布局第三方库,并不能满足我们的项目需求,有的是无法达到叠加效果,有的会是卡片中不能有列表,否则会产生View滑动事件冲突,导致列表无法滑动,因此考虑使用已有的知识,自己实现这样的功能。

实现

在Android系统中,没有能直接能实现该效果的控件,可以实现左右滑动切换页面的控件首先想到ViewPager,但ViewPager并不能直接实现页面叠加效果,通过查阅资料,发现可以自定义ViewPager.PageTransformer接口去控制ViewPager中各个页面的偏移显示效果。

编码尝试:

1、创建基本界面结构:

首先我们先创建一个Activity,配置好页面,就像以下效果。一个ViewPager,里面放fragment,由于卡片是圆角的,考虑到圆角可以使用CardView实现,所以在fragment里面再放一个CardView。还需要给ViewPager的setOffscreenPageLimit一个大一点的值,这样可以使Viewpager预加载多个页面。

正常情况下,ViewPager里面的内容是水平排列的,如下图:

现在要做的第一步,就是将ViewPager里面所有的view都显示在同一个位置,那么就需要自定义PageTransformer去实现了。

自定义PageTransformer:

PageTransformer介绍,当ViewPager中页面滑动切换时,将会回调方法transformPage(View page, float position);该方法有两个参数,第一个view当然就是当前正在滑动的页面,第二个是一个float类型的值,不是我们平常见到的position位置,而是当前滑动状态的表示,相对于当前position的position。它有三个临界值-1 0 1,0代表当前屏幕显示的view的position,1代表当前view的下一个view所在的position,-1代表当前view的前一个view所在的position。

当前view左滑、右滑时各个view positon的变化情况: 

既然ViewPager里面的View默认是水平排列的,那么只要将每个view的x轴坐标更改为:view的宽度乘以下标的负数,这样就排列在一起了,为了方便起见,还给view增加了一个透明度。代码如下:

public void transformPage(View page, float position) {

//设置透明度

page.setAlpha(0.5f);

//设置每个View在中间,即设置相对原位置偏移量

page.setTranslationX((-page.getWidth() * position));

}

具体实现效果如下:

卡片都叠加在了一起,说明X方向水平偏移达到了预期效果,然后还需要实现卡片在Y方向垂直偏移,和卡片大小的缩放操作,就可以实现叠加效果了,定义了一个变量mOffset表示偏移量,赋值为40px。

代码如下:

//设置水平方向偏移量

page.setTranslationX((-page.getWidth() * position));

//缩放比例

float scale = (page.getWidth() - mOffset * position) / (float) (page.getWidth());

//设置水平方向缩放

page.setScaleX(scale);

//设置竖直方向缩放

page.setScaleY(scale);

//设置竖直方向偏移量

page.setTranslationY(mOffset * position);

至此,卡片叠加效果已经达到了我们的预期效果,但此时左右卡片滑动时,却发现不管怎么滑动都是没有效果的。这是为什么呢?因为没有处理划出去的那一页,无论该接口传过来参数值的是多少,我们都只是让页面叠加排列,因此需要增加一个下标判断,即当position<= 0的情况下就是表示当前页面在翻页,接下来看代码:

public void transformPage(View page, float position) {

if (position <= 0.0f) {

//被滑动的那页,设置水平位置偏移量为0,即无偏移

page.setTranslationX(0f);

} else {//未被滑动的页

page.setTranslationX((-page.getWidth() * position));

//缩放比例

float scale = (page.getWidth() - mOffset * position) / (float) (page.getWidth());

page.setScaleX(scale);

page.setScaleY(scale);

page.setTranslationY(mOffset * position);

}

}

效果虽然是达到了,但是为什么会留一个角呢?因为里面的view移动的是一个屏幕的宽度,当我们平移的时候刚好移动到了屏幕的外面,当然没有问题。但是旋转却是以中心为原点进行旋转的,所以自然,就会漏出一个角了。

  解决方法是view进行旋转的同时,将view的X轴进行减少,减少多少呢?从上图看,大概⅓就差不多能够移动到屏幕外面了。

代码:

public void transformPage(View page, float
position) {

if (position <= 0.0f) {//被滑动的那页  position 是-下标~ 0

page.setTranslationX(0f);

//旋转角度  45° * -0.1 = -4.5°

page.setRotation((45 * position));

//X轴偏移 li:  300/3 * -0.1 = -10

page.setTranslationX((page.getWidth() /
3 * position));

} else {

//缩放比例

float scale = (page.getWidth() -
mScaleOffset * position) / (float) (page.getWidth());

page.setScaleX(scale);

page.setScaleY(scale);

page.setTranslationX((-page.getWidth()
* position));

page.setTranslationY((mScaleOffset *
0.8f) * position);

}

}

应用

如下图是资源页面卡片层叠效果结合业务逻辑的具体实现:

在cardpager包中,CardPageTransformer实现了ViewPager.PageTransformer接口,用于控制ViewPager中页面的偏移效果。DoorResourceView则是用于显示整个资源页面的根View,DoorResourceView里面包含一个ViewPager,该ViewPager中包含多个ResourceFragment,一个ResourceFragment就代表一个卡片的实现,每个卡片ResourceFragment中又包含一个列表控件(RecyclerView)用于显示门禁点、区域或中心资源。

在卡片资源页面中,在某一卡片页面下拉刷新时,按照产品业务逻辑是需要将该卡片之后的卡片都从viewpager中移除,并且在点击每个区域或中心都需要新开启一个卡片,也需要移除该页面之后的卡片,如下是实现代码:

/**

* 移除之后的的fragment

*

* @param index 位置

*/

private void removeFragment(int index) {

if (mFragments.size() > index + 1
&& index > -1) {

for (int i = mFragments.size() - 1;
i > index; i--) {

mFragments.remove(i);

}

mAdapter.notifyDataSetChanged();

}

}

在实际操作中,发现当开启到第三个卡片之后,卡片层叠明显出现较大的偏差,有些卡片叠加效果并不显示,与预期不符。并且在刷新过程中,动态增删卡片后,都有可能会导致叠加效果突然消失,如下图所示,本应该有多层卡片叠加效果,但在刷新后只剩下一层卡片:

经过多次打印日志、断点调试后发现,在每次对卡片进行增加或删除时,需要通过调用transformPage(..)方法对所有的卡片重新排序,完美的解决了该问题,实际代码如下:

//避免刷新时卡片消失

//1.当前最前面的一页缩放正确,层级显示正确

int currIndex = mPager.getCurrentItem();

View view = mFragments.get(currIndex).getView();

mCardPageTransformer.transformPage(view, -9.999259E-4f);

//2.后面的页缩放正确,层级显示正确

for (int i = 0; i <= currIndex; i++) {

    mCardPageTransformer.transformPage(mFragments.get(i).getView(),
-currIndex + i);

}

已下是具体实现效果:

    

无论如何刷新资源,动态增删卡片页面,叠加效果也不会突然消失了。

总结

在开发新功能时,要多动脑思考,从原理上掌握实现方法,这样当遇到问题时,就可以迅速定位,从根本上解决问题,保证了代码的质量和功能的稳定性。

最新文章

  1. OpenCV2:Mat
  2. Android SDK下载和更新失败的解决方法
  3. HTML 5表单应用小结
  4. jboss:在standalone.xml中设置系统属性(system-properties)
  5. Asp.Net Web API 2第十五课——Model Validation(模型验证)
  6. 【原】MyEclipse8.5集成Tomcat7时启动错误:Exception in thread “main” java.lang.NoClassDefFoundError
  7. struts2中struts.xml配置文件详解【未整理】
  8. 清除行内元素之间HTML空白的几种解决方案
  9. [ActionScript 3.0] as3可以通过CDATA标签声明多行字符串
  10. Java8 Lambda sample (iwantmoon.com出品)
  11. IOS UIActivityIndicatorView 等待指示器
  12. PHP小记录
  13. 浅谈DevExpress&lt;二&gt;:设计一个完整界面(2)
  14. Hadoop-2.7.2集群的搭建——集群学习日记
  15. C#-判断Shift,Alt,Ctrl是否被按下,确定所按下的组合键
  16. open live writer实现多博客同步发送
  17. Leetcode_160_Intersection of Two Linked Lists
  18. 大型进销存管理系统源码 家电业 电器类进销存 asp.net C#框架
  19. 基于selenium+phantomJS的动态网站全站爬取
  20. poi 升级至4.x 的问题总结(POI Excel 单元格内容类型判断并取值)

热门文章

  1. go读取excel表格数据
  2. spring boot:用rocketmq发送延时消息用来取消订单(spring boot 2.3.3)
  3. 2020年的UWP(2)——In Process App Service
  4. .net core autofac asyncinterceptor 异步拦截器开发
  5. Docker 也是本地开发的一神器:部署单机版 Pulsar 和集群架构 Redis
  6. matplotlib中plt用法实例
  7. js根据ip地址获取城市地理位置
  8. 【tensorflow】VMware Ubuntu+Tensorflow配置和使用
  9. mshadow入门指南
  10. DTU是什么 常见的DTU有哪些