本篇文章已授权微信公众号 code小生 发布 转载请表明出处: http://www.jianshu.com/p/4176c1247eed
效果图
最近开发中遇到这样的需求,recyclerview的item随滚动改变大小和透明度。这个效果看起来挺有动感的,似乎实现起来有点复杂,其实不然,接下来将带领大家手把手实现这个效果。
我们化整为零,将这个效果分解到一个item上来看其实是这样的:
item动画
图片缩放
1%->25%: 1.0->(b/a);
26%->50%: (b/a)->(c/a);
51%->75%: (c/a)->(b/a);
76%->100%: (b/a)->1.0;
新建一个CustomAnimation类,定义相应动画控件的id,并初始化:
// 无控件
private static final int NO_VIEW = -999;
// 透明度变化视图
private int mAlphaViewId = NO_VIEW;
// 图片变化视图
private int mImageViewId = NO_VIEW;
// 边距变化视图
private int mMarginViewId = NO_VIEW;
/**
* 设置透明度变化控件的ID
* @param resId
*/
public void setAlphaViewId(int resId) {
Log.i("animm", "setAlphaViewId");
mAlphaViewId = resId;
}
/**
* 设置图片变化控件的ID
* @param resId
*/
public void setImageViewId(int resId) {
Log.i("animm", "setImageViewId");
mImageViewId = resId;
}
/**
* 设置外边距变化控件的ID
* @param resId
*/
public void setMarginViewId(int resId) {
Log.i("animm", "setMarginViewId");
mMarginViewId = resId;
}
定义变量process,并通过传入process的值进行效果实现:
// 动画进度
private int mProcess = 0;
/**
* 通过进度值控制动画的进度
* @param viewGroup 父容器
* @param process 动画变化进度
*/
public void setAnimByProcess(ViewGroup viewGroup, int process) {
if (viewGroup == null) {
return;
}
mProcess = process;
/**
* 蒙版透明度设置
*/
if (enableAlpha && mAlphaViewId != NO_VIEW) {
View view = viewGroup.findViewById(mAlphaViewId);
if (process > 0 && process <= 25) {
float alpha = (25 - process) / 25.0f;
view.setAlpha(alpha);
} else if (process > 75 && process <= 100) {
float alpha = (process - 75) / 25.0f;
view.setAlpha(alpha);
}
}
/**
*
* 设置图片大小
*/ if (enableImage && mImageViewId != NO_VIEW) {
ImageView imageView = (ImageView) viewGroup.findViewById(mImageViewId);
float curWidth = 0;
if (process <= 25) {
float percent = process / 25.0f;
float marginHorizontal = mMarginHorizontal * percent;
curWidth = mImgOrgWidth + 2 * marginHorizontal;
} else if (process > 25 && process <= 50) {
float percent = (process - 25) / 25.0f;
float marginHorizontal = mMarginHorizontal * percent;
curWidth = mScreenWidth + 2 * marginHorizontal;
} else if (process > 50 && process <= 75) {
float percent = (75 - process) / 25.0f;
float marginHorizontal = mMarginHorizontal * percent;
curWidth = mScreenWidth + 2 * marginHorizontal;
} else {
float percent = (100 - process) / 25.0f;
float marginHorizontal = mMarginHorizontal * percent;
curWidth = mImgOrgWidth + 2 * marginHorizontal;
}
float scale = curWidth / mImgOrgWidth ;
scale *= 1.1f;
imageView.setScaleX(scale);
imageView.setScaleY(scale);
}
/**
* 设置外边距(横向)
*/
if (enableMargin && mMarginViewId != NO_VIEW) {
View view = viewGroup.findViewById(mMarginViewId);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view.getLayoutParams();
if (process > 0 && process <= 25) {
float percent = (25 - process) / 25.0f;
float marginHorizontal = mMarginHorizontal * percent;
lp.setMargins((int)marginHorizontal, (int)mMarginTop, (int)marginHorizontal, (int)mMarginBottom);
view.setLayoutParams(lp);
} else if (process > 75 && process <= 100) {
float percent = (process - 75) / 25.0f;
float marginHorizontal = mMarginHorizontal * percent;
lp.setMargins((int)marginHorizontal, (int)mMarginTop, (int)marginHorizontal, (int)mMarginBottom);
view.setLayoutParams(lp);
}
}
}
基于上述代码,我们基本实现动画的细节,接下来我们需要思考的是,如何将RecyclerView与process结合?思考这个问题前,我们来看一下这个效果:
列表滑动效果
这是我用简书的Markdown代码块语法实现的仿RecyclerView列表的效果,基于这个效果我想到将侧边栏的滑块和RecyclerView的Item结合起来,与动画的process变量相关联:
0%
50%
100%
通过右侧小滑块底部与Item顶部之间的距离占两个Item高度的百分比作为process的值:
手机屏幕坐标示意图
process = (turningLine - itemTop) / (2 * itemHeight);
如此,我们将此关系放入新建的类TurnProcess中:
public class TurnProcess {
/**
* 返回动画完成的进度
* @param itemTop
* @param turningLine
* @param itemHeight
* @return
*/
public static int getProcess(float itemTop, float turningLine, float itemHeight) {
if (turningLine < itemTop || turningLine > (itemHeight + itemTop)) {
return 0;
} else {
float percent = (turningLine - itemTop) / itemHeight;
return (int) (percent * 100);
}
}
}
得到了上一步滑动与process的关系,接下来我们来计算一下滑块底部到RecyclerView可见范围顶部的距离。
RecyclerView初始情况
我们可以将RecyclerView初始情况设想如上图,此时turningLine的值为0。当RecyclerView滑动时:
RecyclerView滚动高度与turningLine的关系
由上图,我们可得到turniingLine与RecyclerView滑动距离的关系,从而得到turningLine的值:
scrollY / totalScroll = turningLine / totalHeight;
turningLine = scrollY * totalHeight / totalScroll;
totalScroll的值可以通过RecyclerView总高度(包含不可见部分)与RecyclerView可见部分的高度相差得到;而scrollY则随着RecyclerView的滚动变化,因此需要对RecyclerView进行滚动事件的监听:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
float scrollY = getScrollDistance(recyclerView);
}
}
/**
* 获取滚动的距离
*/
private int getScrollDistance(RecyclerView recyclerView) {
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
View firstVisibleItem = recyclerView.getChildAt(0);
int firstItemPosition = layoutManager.findFirstVisibleItemPosition();
int itemHeight = firstVisibleItem.getHeight();
int firstItemBottom = layoutManager.getDecoratedBottom(firstVisibleItem);
return (firstItemPosition + 1) * itemHeight - firstItemBottom;}
如此,不断变化的turningLine与RecyclerView的滚动建立了关系;至此,动画与RecyclerView的逻辑关系梳理完毕。按照实现RecyclerView的套路一步步实现最基本的列表效果,然后将动画与滚动监听的关系放入Adapter中。需要强调的是:每一个Item都是随着RecyclerView的滚动进行变化的,所以每一个Item的ViewHolder中都注册RecyclerView的监听事件来监听RecyclerView的滑动。
这样的动画效果固然有趣,但是其仍存在很多不足,就自己发现的问题,列不足如下:
在此,期望有耐心将本文看完的小伙伴们在文章下方的评论里留下宝贵意见,一起来完善这个效果。另,若有小伙伴在Github上看到有这样效果的稳定的第三方库,希望可以在文章下方评论中留下链接。
代码已上传Github,欢迎访问Follow。
花两天写了本篇文章,原创不易,转载请注明链接:http://www.jianshu.com/p/4176c1247eed,谢谢!