相信大家在切换手机launcher图标的时候,都会注意到后面的壁纸,仿佛是一张很大的壁纸在后面,每次切换图标页,壁纸会有一定的视差滚动效果。
那么这样一个效果是如何制作的呢?
那么本文的主要目的就是介绍这样的一个效果的制作原理。
首先看下效果图:
恩,其实这个是我在为 wanandroid.com 收录文章的时候发现的开源项目,项目地址是:
https://github.com/zhe525069676/BgMoveViewPager
当然本文不是造轮子,而是我觉得这样的效果有一定的实用价值,特地写出来分享下。
类似的库还有:
https://github.com/andraskindler/parallaxviewpager
如果大家有兴趣,建议大家在阅读完本文后,可以去看看上述两个项目的源码。
最后,需要说明本文的代码与上述库代码有一定的出入。
其实写起来还是蛮简单的,只是如果找不到切入点就会觉得毫无头绪。
值得开心的是,在阅读完本文后,遇到类似的效果,你可能会会心一笑。
首先我们考虑下思路:
对于控件的选择,肯定首选ViewPager了,对于这样一个效果,仅仅将图片设置为ViewPager的背景是肯定不行的。
通过效果图,可以看得出来,图片可以伴随控件的移动,能够进行同步的移动。
所谓同步的移动,就是随着控件的scorllX的改变,绘制图片的区域会发生变化,那么我们在ViewPager滑动的过程中,我们需要找到一个方法,这个方法可以全程监听滑动,并且能够提供一定的参数帮助我们去定位图片该显示的区域。
这样的话,我们好像有思路了,我们可以为ViewPager设置setOnPageChangeListener,然后在onPageScrolled方法中,根据position和positionOffset的值,就可以确定图片需要显示的位置了,然后调用invalidate,去绘制指定的区域即可。
这样的确是可以的, parallaxviewpager 就是这么做的,有兴趣的可以去看下代码。
除此以外还有别的思路。
既然ViewPager在滑动的时候,会不断的改变scrollX,那么我们可以直接在onDraw或者dispatchDraw中,去读取scrollX,根据scrollX确定需要绘制图像的区域,直接使用canvas绘制出来即可。
该思路为今天的代码主要演示的,对于onDraw和dispatchDraw,由于我们是ViewGroup,我们可以选择dispatchDraw来实现;当然,onDraw也是可以的,注意如果有必要记得调用setWillNotDraw(false)。
这样的话思路就明确了,我们重写dispatchDraw,再其内部根据scrollX区计算需要显示的区域,然后就想绘制,那么下面看代码。
是不是感觉代码很短~
的确不长,为了方便,我们直接在类中加载了作为背景的图。
可以看到我们首先拿到图片的宽高,然后除以item的个数,确定每个item可以显示的图片宽度 widthForItem 。
然后根据scrollX,比如初始在第1页(currentItem=0),scrollX为0,那么绘制的图片宽度区域就为:(0,widthForItem).
随着scrollX的变化,那么绘制的区域随之变为
(scrollX * widthForPerPx, scrollX * widthForPerPx + widthForItem)
widthForPerPx 的意思是,当控件移动一个像素,图片需要移动的像素值。
确定了需要绘制的图片区域,那么绘制的目标宽度区域,相比就很简单了,肯定是:
(scrollX , scrollX + getWidth());
认真分析完代码,感觉没什么问题,赶紧跑起来,效果已经实现了。
但是,不要高兴的太早,代码里面有个潜在的问题,不知道大家发现没有。
3.2 处理存在的问题
上述代码非常依赖scrollX,我们预期scrollX是从:
0 -> 控件width -> 控件width*2 这样变化。
但是,假设我们默认ViewPager设置为第2页(currentItem=1),你会发现效果图极其奇怪。
造成这样的原因是什么呢?
其实是因为,当currentItem=1时,scrollX的变化为:
-控件width -> 0 -> 控件width
当currentItem=2时,scrollX的变化为:
-控件width * 2 -> -控件width -> 0
而我们上述代码一直是以scrollX从0到最大来计算的。
所以,我们这里需要对scrollX进行处理:
怎么处理呢?
if分类判断么,那太夸张了,页面一多得写多少if呀。
如果你对数字比较敏感,应该可以看出,上述虽然currentItem不同,但是变化的规律还是很明显的,我们可以总结规律,将其统一转化为:
0->控件width->控件width*2
那么就简单了,首先我们得到currentItem的值,然后让其乘以控件宽度,再加上scrollX,即:
int x = getScrollX() + mFirstPos * getWidth();
相关于给原本的变化统一加上了mFirstPos * getWidth(),看看上面的变化,恰好完成我们的转化需求。
那么最后完善下代码:
这样就基本完成所有的代码,当然很多细节的地方需要你自己去完善。
不过从该例,可以发现针对一个问题,找到切入点,先从最简单的情况出发,编写完成后,在针对各种情况做调整即可。
最后,我们根据上例效果,可以做的事情还很多,假设我们有一张宽度大概为1.6倍屏幕宽度的图片,ViewPager包含3个Item。
均分为3等分拉伸效果肯定不好,其实你可以不拉伸,让当前屏幕显示同等宽度的图片,每切换一页,你图片移动总宽度为屏幕宽度的0.3即可,相信根据上文的代码你应该可以做出来。
昨天的推送文章被指出太简单,有人发出类似疑问:
你是没有东西写了么?能不能发点干货?
这里我简单解释下,首先我感谢提出的意见,相比大家关注我的公众号肯定都是为了能够学到一些东西的,当然不排除那些为了和我搞基的。
为了这样一个目标,我也保持着每周3-5篇,尽可能推送 `看起来不太费劲` 又能 `学到知识` 的文章。
需要抱歉的是,我目前没有任何办法做到每天的文章都能符合所有人的口味,所以我希望如果文章对你来说太简单,你可以选择不看或者简单的看一下,权当巩固下记忆也好。
过分的抱怨会伤害到我以及原创作者,毕竟一篇文章,能够从作者写出来到推送到大家的手机上消耗了大量的时间与精力。并且个人长期的坚持分享与大家的支持是分不开的。
你可以试想下,你有个花了了大量时间与精力做的东西,给别人分享时,被别人泼冷水的时感受。
所以有可能的话,尽量可以:不要吐槽,随手点赞,给作者一点支持和鼓励。
当然,合理的意见是没问题的,比如你可以换个语气:
最近的文章好像有点偏简单,我最近在研究 长得太帅的人如何与他人相处 ,可否推送相关文章?
我会根据大家的留言,对文章的选择有个大致的方向,并且我可以承诺,所有的文章我都有细心的编辑,在我所能做到的情况下,保证大家有个良好的阅读体验。
最后衷心的感谢投稿的作者、长期支持我的你们、默默关注我的你们以及给我提建议的朋友。
源码地址:
https://github.com/hongyangAndroid/Android_Blog_Demos