使用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);

}

已下是具体实现效果:

    

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

总结

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

使用ViewPager实现卡片叠加效果的更多相关文章

  1. 点餐系统sprint3总结

    转眼间,sprint3也结束了.意味着软件工程的课程结束了,我们的项目也完成了.在队友们的认真学习,专注打码,辛苦赶工后,我们的项目完成了.显然是仓促中完成的,没有完美的界面.没有无bug的项目,但是 ...

  2. 介绍三个Android支持库控件:TabLayout+ViewPager+RecyclerView

    本文主要介绍如下三个Android支持库控件的配合使用: TabLayout:android.support.design.widget.TabLayout ViewPager:android.sup ...

  3. Android零基础入门第70节:ViewPager轻松完成TabHost效果

    上一期学习了ViewPager的简单使用,本期一起来学习ViewPager的更多用法. 相信很多同学都使用过今日头条APP吧,一打开主界面就可以看到顶部有很多Tab,然后通过左右滑动来切换,就可以通过 ...

  4. Android零基础入门第69节:ViewPager快速实现引导页

    在很多APP第一次启动时都会出现引导页,在一些APP里面还会包括一些左右滑动翻页和页面轮播切换的情况.在之前也已经学习了AdapterViewFlipper和ViewFlipper,都可以很好的实现, ...

  5. android选择器汇总、仿最美应用、通用课程表、卡片动画、智能厨房、阅读客户端等源码

    Android精选源码 android各种 选择器 汇总源码 高仿最美应用项目源码 android通用型课程表效果源码 android实现关键字变色 Android ViewPager卡片视差.拖拽及 ...

  6. CardView之可切换式卡片

    今天我所要作的笔记是: 可切换式的卡片CardView. Java代码部分 1.我们要根据自己的当前版本号添加相对应的一个依赖: implementation 'com.android.support ...

  7. 踩石行动:ViewPager无限轮播的坑

    2016-6-19 前言 View轮播效果在app中很常见,一想到左右滑动的效果就很容易想到使用ViewPager来实现.对于像我们常说的banner这样的效果,具备无限滑动的功能是可以用ViewPa ...

  8. Android ViewPager打造3D画廊

    本文已授权微信公众号:鸿洋(hongyangAndroid)在微信公众号平台原创首发. 网上有很多关于使用Gallery来打造3D画廊的博客,但是在关于Gallery的官方说法中表明: This cl ...

  9. 卡片抽奖插件 CardShow

    这个小项目(卡片秀)是一个卡片抽奖特效插件,用开源项目这样的词语让我多少有些羞愧,毕竟作为一个涉世未深的小伙子,用项目的标准衡量还有很大差距.不过该案例采用 jQuery 插件方式编写,提供配置参数并 ...

随机推荐

  1. Go语言基础知识01-用Go打个招呼

    每一种编程语言,从读一本好书开始 每一种编程语言,也从Helloworld开始 1. 环境准备 1.1 安装golang 在Ubuntu下,直接输入命令可以安装最新版本: $ sudo apt-get ...

  2. centos8安装java jdk 13

    一,查看本地centos的版本 [root@localhost lib]# cat /etc/redhat-release CentOS Linux release 8.1.1911 (Core) 说 ...

  3. go 结构体与方法

    go 结构体与方法   go 结构体相当于 python 中类的概念,结构体用来定义复杂的数据结构,存储很多相同的字段属性 结构体的定义 1.结构体的定义以及简单实用 package main imp ...

  4. 第三十六章 Linux常用性能检测的指令

    作为一个Linux运维人员,介绍下常用的性能检测指令! 一.uptime 命令返回的信息: 19:08:17              //系统当前时间 up 127 days,  3:00     ...

  5. MySQL死锁系列-线上死锁问题排查思路

    前言 MySQL 死锁异常是我们经常会遇到的线上异常类别,一旦线上业务日间复杂,各种业务操作之间往往会产生锁冲突,有些会导致死锁异常.这种死锁异常一般要在特定时间特定数据和特定业务操作才会复现,并且分 ...

  6. Helium文档2-WebUI自动化-常用方法介绍

    学习思路: 查看github项目的源码,每个方法都有介绍及使用说明 https://github.com/mherrmann/selenium-python-helium/blob/master/he ...

  7. JavaScript实现异步的4中方法

    一:背景简介 Javascript语言的执行环境是"单线程"(single thread). 所谓"单线程",就是指一次只能完成一件任务.如果有多个任务,就必须 ...

  8. 更换Centos的yum源

    1.备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2.下载新的CentOS-Base ...

  9. Python基础知识,新手入门看过来

    1 下载和安装Python 在开始编程之前,你需要安装Python解析器软件(这里你可能需要找人帮忙).解析器是一个可以理解你用Python语言写的指令的程序.如果没有解析器,你的计算机不会理解这些指 ...

  10. VB 错误日志:MSForms.CommandButton 不是一个已加载的控件类等解决方法

    是由于缺少了fm20.dll这个必要组件 网上找到 然后在工程中引用 找到路径 完美解决