最初看到网易LOFTER的首页的视差滚动效果, 觉得很漂亮, 想要模仿一下
在写代码之前我先百度了一下, 看有没有人已经完成了类似的这种效果, 一看果然有.然后我就把他们的代码clone了下来, 看了一下, 理解之后自己去实现了一番.所以本篇不是原创, 只记录原理和实现.以下是参考资料:
Android视图滚动差—ParallaxScrollImageView
高仿寺库View滑动页面
ParallaxRecyclerView
实现原理
首先需要写一个图片列表, 用listView或者recyclerView都可以.然后监听列表的滚动, 计算出图片的中心线和recyclerView的中心线之间的距离, 用这个距离乘以一个比例(这个比例自己定义, 效果合适即可)得到一个偏移量, 然后使用matrix给图片内容加上偏移量.
设置滚动监听
首先以recyclerView举例来说, 给它设置滚动监听1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23mRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
}
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
// 获取第一个可见条目的position
int firstVisibleItem = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
// 获取所有可见条目的数量
int visibleItemCount = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition() - firstVisibleItem + 1;
for (int i = 0; i < visibleItemCount; i++) {
View childView = recyclerView.getChildAt(i);
RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(childView);
if (viewHolder instanceof ParallaxViewHolder) {
ParallaxViewHolder parallaxViewHolder = (ParallaxViewHolder) viewHolder;
parallaxViewHolder.animateImage();
}
}
}
});
上面代码中的ParallaxViewHolder是一个继承了RecyclerView.ViewHolder的自定义ViewHolder
1 | public abstract class ParallaxViewHolder extends RecyclerView.ViewHolder implements ParallaxImageView.ParallaxImageListener { |
这里首先ParallaxViewHolder会获取ParallaxImageView(下面会说明)的id, 然后根据id获取parallaxImageView.然后给parallaxImageView设置回调方法, ParallaxViewHolder实现requireValuesForTranslate()方法, 在滚动的时候parallaxImageView会调用这个方法, 获取条目的高度
, 条目的在屏幕上的y坐标
, recyclerView的高度
, recyclerView在屏幕上的高度
这四个参数
ParallaxImageView
这个自定义控件是实现效果的重点.
首先继承ImageView
1 | public class ParallaxImageView extends AppCompatImageView { |
可以看到初始化的时候, 给它的scaleType设置了matrix, 这是为什么呢?
因为我们可以看到, 想要lofter那种效果, 是需要图片只露出一部分, 如下图中所示, 红框中代表ParallaxImageView, 蒙层部分表示不可见
系统提供的几种scaleType中, 没有一个能实现这种效果, 那就只能设置scaleType为ScaleType.MATRIX
, 然后自己使用maxtrix做变换了.
关于scaleType, 如果还不熟悉, 可以看这篇文章
Android ImageView的scaleType属性与adjustViewBounds属性
ImageView的默认scaleType是fitcenter.
设置scaleType为matrix之后, 会从ImageView的左上角开始绘制原图, 大概是像这个样子, 红色区域代表ParallaxImageView, 黑色区域代表图像.
设置缩放
首先要做的是计算一个缩放比例, 使缩放之后的drawable的宽度等于ParallaxImageView的宽度
1 | /** |
然后按照这个比例进行变换
1 | Matrix imageMatrix = getImageMatrix(); |
现在的效果是这样
使图片居中
接下来要做的是这种变换, 是视图内容居于ImageView的中间
首先计算出视图内容的中心线和ImageView中心线之间的距离
1 | private float computeDistance(float scale) { |
然后使用matrix的postTranslate()
方法进行y方向上的偏移
1 | Matrix imageMatrix = getImageMatrix(); |
变换后的效果, 可以看到已经居中了
加上偏移量
然后我们就可以计算每张图片的中线与列表的中线之间的距离, 然后乘以一个适当的比例设置给matrix
1 | // translate是recyclerView中心线和itemView中心线之间的距离 |
现在的效果
边界修正
但是看上图的第二个条目, 把ImageView的红色背景露出来了(我给ImageView设置的红色的background).
如上图所示, 视图内容不断往下偏移(红色框框看成不动), 当在这种边界条件下视图内容继续往下偏移时, 就会把ImageView的背景露出来.所以计算然后限制边界条件
1 | float maxTranslate = drawableHeight * 0.5f - viewHeight * 0.5f; |
最终效果
github地址
https://github.com/mundane799699/AndroidProjects/tree/master/ParallaxRecyclerView