- 原文地址:Workcation App – Part 3. RecyclerView interaction with Animated Markers
- 原文做者:Mariusz Brona
- 译文出自:掘金翻译计划
- 译者:龙骑将杨影枫
- 校对者:Vivienmm、张拭心
欢迎阅读本系列文章的第三篇,此系列文章和我前一段时间完成的“研发”项目有关。在文章里,我会针对开发中遇到的动画问题分享一些解决办法。javascript
Part 1: 自定义 Fragment 转场html
Part 2: 带有动画的标记(Animating Markers) 与 MapOverlayLayout 前端
Part 3: 带有动画的标记(Animated Markers) 与 RecyclerView 的互动java
Part 4: 场景(Scenes)和 RecyclerView 的共享元素转场动画(Shared Element Transition)react
项目的 Git 地址: Workcation Appandroid
动画的 Dribbble 地址: dribbble.com/shots/28812…ios
几个月前咱们开了一个部门会议,在会议上个人朋友 Paweł Szymankiewicz 给我演示了他在本身的“研发”项目上制做的动画。我很是喜欢这个动画,会后决定用代码实现它。我可没想到到我会摊上啥...git
GIF 1 “动画效果”github
就像上面 GIF 动画展现的,须要作的事情有不少。后端
在点击底部菜单栏最右方的菜单后,咱们会跳转到一个新界面。在此界面中,地图经过缩放和渐显的转场动画在屏幕上方加载,Recycleview 的 item 随着转场动画从底部加载,地图上的标记点在转场动画执行的同时被添加到地图上.
当滑动底部的 RecycleView item 的时候,地图上的标记会经过闪烁来显示它们的位置(译者注:原文是show their position on the map,我的认为 position 有两层含义:一表明标记在地图上的位置,二表明标记所对应的 item 在 RecycleView 里的位置。)
在点击一个 item 之后,咱们会进入到新界面。在此界面中,地图经过动画方式来显示出路径以及起始/结束标记。同时此 RecyclerView 的item 会经过转场动画展现一些关于此地点的描述,背景图片也会放大,还附有更详细的信息和一个按钮。
当后退时,详情页经过转场变成普通的 RecycleView Item,全部的地图标记再次显示,同时路径一块儿消失。
就这么多啦,这就是我准备在这一系列文章中向你展现的东西。在本文中,我会解决如何让标记与 RecycleView 产生互动。
RecyclerView 有一些本地工具来管理自身的状态。咱们能够设置 ItemAnimator 或者 ItemDecorator 来添加一些不错的动画效果,经过 ViewHolder 和 LayoutManager 来控制布局的尺寸和位置。咱们还有 listener 来监听 RecyclerView 的特殊状态。
如上所示,这是一个横向的 RecyclerView,该 RecycleView 包含一组记录巴厘岛周边详情的 CardViews。当滑动 RecyclerView 的时候,对应的标记要作出闪烁。因此如何实现呢?固然是有一些问题须要解决的 🙂!
OnScrollListener 是一个容许咱们在 RecyclerView 的滑动事件被触发时接收回调的类(参见此处)。该类有 onScrolled 方法 —— 这是联系滚动位置(position)和标记的关键。该回调方法监听滚动事件。让咱们看一看它长啥样:
Java
@Override
public void onScrolled(final RecyclerView recyclerView,final int dx,final int dy){
super.onScrolled(recyclerView,dx,dy);
}复制代码
如咱们所见,此回调传入一个RecyclerView对象做为参数,还有整数型参数 dx 和 dy。“dx” 是横移量,“dy”是纵移量。在本项目中,咱们只对 recycleview 参数感兴趣.
好吧,既然咱们已经有了含有 onScrolled 方法的 OnScrollListener 类,那就不复杂了吧?咱们须要判断某个 RecycleView 的 item 是否处于正中心,若是是的话就通知对应的标记闪烁。简单不?确实很简单,可是无论用 🙂。再看一下动画,第一个 item 和最后一个 item 永远不会到达 RecycleView 的中心。
该怎么作呢?触发标记闪烁的触发点是随着 RecyclerView 的滑动而移动的。因此这个触发点的起始位置应该在第一个 item 的中心,最终位置应该在最后一个 item 的中心。咱们须要作些数学计算来判断触发点和闪烁标记的关联。
管用吗?
仍是无论用 🙂。 onScrolle 方法不是每个像素都被触发的。若是咱们滑动 RecycleView 的速度太快,收到的回调就不多。那么应该怎么办呢?
很简单。既然不能计算移动的触发点 —— 由于看起来它不会包含“偏移量”的参数,那就移动“范围”。当该范围覆盖好比说 70% 的 RecycleView 子布局时,触发标记的闪烁。不妨把它想一想成一个从左至右移动的矩形。让咱们看看实现吧:
Java
public class HorizontalRecyclerViewScrollListener extends RecyclerView.OnScrollListener{
private static final int OFFSET_RANGE = 50;
private static final double COVER_FACTOR = 0.7;
private int[] itemBounds = null;
private final OnItemCoverListener listener;
public HorizontalRecyclerViewScrollListener(final OnItemCoverListener listener){
this.listener=listener;
}
@Override
public void onScrolled(final RecyclerView recyclerView,final int dx,final int dy){
super.onScrolled(recyclerView,dx,dy);
if(itemBounds == null)
fillItemBounds(recyclerView.getAdapter().getItemCount(),recyclerView);
for(int i=0;i<itemBounds.length;i++){
if(isInChildItemsRange(recyclerView.computeHorizontalScrollOffset(),itemBounds[i],OFFSET_RANGE))
listener.onItemCover(i);
}
}
private void fillItemBounds(final int itemsCount,final RecyclerView recyclerView){
itemBounds=new int[itemsCount];
int childWidth=(recyclerView.computeHorizontalScrollRange()-recyclerView.computeHorizontalScrollExtent())/itemsCount;
for(inti=0;i<itemsCount;i++){
itemBounds[i]=(int)(((childWidth*i+childWidth*(i+1))/2)*COVER_FACTOR);
}
}
private boolean isInChildItemsRange(final int offset,final int itemBound,final int range){
int rangeMin=itemBound-range;
int rangeMax=itemBound+range;
return (Math.min(rangeMin,rangeMax)<=offset) && (Math.max(rangeMin,rangeMax)>=offset);
}
public interface OnItemCoverListener{
void onItemCover(final int position);
}
}复制代码
首先,咱们不但愿新代码和 Fragment/Activity 混到一块儿,所以继承 RecyclerView.OnScrollListener 的类并重写必要的方法。在构造函数中传一个 listener 进去,当 RecycleView 的 item 的范围符合时条件时就调用该 listener 的 onItemCover 方法。在 onScrolled 方法中,若是 itemBounds 为空咱们能够调用 fillItemBounds 进行初始化。不然循环判断全部的边距,判断 RecycleView 的 item 是否被指定的范围覆盖。
方法 fillItemBounds 以 RecyclerView 的 item 个数为长度建立了一个整数数组。接下来它计算了子布局的宽度(也就是 RecyclerView 的 item 的宽度)。在最后它用“item 的范围”给数组赋值 —— 事实上,这些就是用来计算 RecycleView 是否处于子布局内的“中心”点。
当调用 onScrolled 方法时,咱们遍历 RecyclerView 的 item,并使用 isInChildItemsRange 方法来判断他们所处的位置是否在范围内。该方法实际上就是当咱们移动 RecycleView 时候的“矩形”。该方法计算 item 的区域(也就是咱们计算并保存在 itemBounds里的中心点)与当前的偏移量是否重叠。若是符合条件的话,OnItemCoverListener 会调用 onItemCover 方法,传递指定的位置(position) 。经过此参数,咱们就能够拿到判断当前的地图标记是哪一个,让它进行闪烁。
//Implementation of the HorizontalRecyclerViewScrollListener
// HorizontalRecyclerViewScrollListener 的具体实现
...
recyclerView.addOnScrollListener(new HorizontalRecyclerViewScrollListener(this));
}
//OnItemCoverListener method implementation
// 实现 OnItemCoverListener 的方法
@Override
public void onItemCover(final int position){
mapOverlayLayout.showMarker(position);// 在此处刷新标记
}
//PulseOverlayLayout - see the 2nd article from the series
//PulseOverlayLayout - 参见系列的第二篇
public void showMarker(final int position){
((PulseMarkerView)markersList.get(position)).pulse();
}
//PulseMarkerView - see the 2nd article from the series
//PulseOverlayLayout - 参见系列的第二篇
public void pulse(){
startAnimation(scaleAnimation);
}复制代码
效果以下
如咱们所见,Android Framework 中有一些了不得的工具,可是在不少状况下仍是须要思考怎么调用才能把事情按咱们所想的实现。最开始的时候还不是很明确,可是如今咱们已经找到解决办法了 😉。
多谢阅读!最后一篇会在星期二 4.04 发布。若是有疑问的话欢迎评论,若是以为有用的话必定要分享哟!
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。