使用RecyclerView实现一个画廊效果,主要是使用support库中最新加入的PagerSnapHelper类,通过计算滑动偏移来计算scale的值。

基本实现

首先需要为RecyclerView添加一个滚动监听,然后为RecyclerView的第一个与最后一个itemView添加一个ItemDecoration,使位于第一个与最后一个itemView的位置居中对齐。


public void attachToRecyclerView(final RecyclerView recyclerView) {
this.recyclerView = recyclerView;
snapHelper.attachToRecyclerView(recyclerView);
recyclerView.addOnScrollListener(scrollListener);
recyclerView.addItemDecoration(new ScalableCardItemDecoration());
recyclerView.post(new Runnable() {
@Override
public void run() {
pageScrolled();
}
});
}

ScalableCardItemDecoration用于计算itemView左右剩余空间,然后为第一个itemView与最后一个itemView添加偏移量。


private static class ScalableCardItemDecoration extends RecyclerView.ItemDecoration { @Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
int position = holder.getAdapterPosition() == RecyclerView.NO_POSITION ? holder.getOldPosition() : holder.getAdapterPosition();
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
int itemCount = layoutManager.getItemCount(); if(position != 0 && position != itemCount - 1){
return;
} int peekWidth = getPeekWidth(parent, view);
boolean isVertical = layoutManager.canScrollVertically();
//移除item时adapter position为-1。 if (isVertical) {
if (position == 0) {
outRect.set(0, peekWidth, 0, 0);
} else if (position == itemCount - 1) {
outRect.set(0, 0, 0, peekWidth);
} else {
outRect.set(0, 0, 0, 0);
}
} else {
if (position == 0) {
outRect.set(peekWidth, 0, 0, 0);
} else if (position == itemCount - 1) {
outRect.set(0, 0, peekWidth, 0);
} else {
outRect.set(0, 0, 0, 0);
}
}
}
}

在为item添加ItemDecoration时,需要计算两边的空间。这里需要手动测量itemView的宽高, 然后计算第一个itemView左边的偏移与最后一个itemView右边的偏移。这里碰到个问题,如果LayoutMangaer的方面是垂直或水平的且RecyclerView对应的的高度或宽度设为wrap_content时,这里计算的值不会正确。

 public static int getPeekWidth(RecyclerView recyclerView, View itemView) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
boolean isVertical = layoutManager.canScrollVertically();
int position = recyclerView.getChildAdapterPosition(itemView);
//TODO RecyclerView使用wrap_content时,获取的宽度可能会是0。
int parentWidth = recyclerView.getMeasuredWidth();
int parentHeight = recyclerView.getMeasuredHeight(); //有时会拿到0
parentWidth = parentWidth == 0 ? recyclerView.getWidth() : parentWidth;
parentHeight = parentHeight == 0 ? recyclerView.getHeight() : parentHeight;
int parentEnd = isVertical ? parentHeight : parentWidth;
int parentCenter = parentEnd / 2; int itemSize = isVertical ? itemView.getMeasuredHeight() : itemView.getMeasuredWidth(); if (itemSize == 0) { ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
int widthMeasureSpec =
RecyclerView.LayoutManager.getChildMeasureSpec(parentWidth,
layoutManager.getWidthMode(),
recyclerView.getPaddingLeft() + recyclerView.getPaddingRight(),
layoutParams.width, layoutManager.canScrollHorizontally()); int heightMeasureSpec =
RecyclerView.LayoutManager.getChildMeasureSpec(parentHeight,
layoutManager.getHeightMode(),
recyclerView.getPaddingTop() + recyclerView.getPaddingBottom(),
layoutParams.height, layoutManager.canScrollVertically()); itemView.measure(widthMeasureSpec, heightMeasureSpec);
itemSize = isVertical ? itemView.getMeasuredHeight() : itemView.getMeasuredWidth();
} /*
计算ItemDecoration的大小,确保插入的大小正好使view的start + itemSize / 2等于parentCenter。
*/
int startOffset = parentCenter - itemSize / 2;
int endOffset = parentEnd - (startOffset + itemSize); return position == 0 ? startOffset : endOffset;
}

添加完ItemDecoration后,我们需要在RecyclerView每次滚动的时候计算左、中、右3个itemView的缩放比例。


private void pageScrolled() {
if (recyclerView == null || recyclerView.getChildCount() == 0)
return; RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); View snapingView = snapHelper.findSnapView(layoutManager);
int snapingViewPosition = recyclerView.getChildAdapterPosition(snapingView);
View leftSnapingView = layoutManager.findViewByPosition(snapingViewPosition - 1);
View rightSnapingView = layoutManager.findViewByPosition(snapingViewPosition + 1); float leftSnapingOffset = calculateOffset(recyclerView, leftSnapingView);
float rightSnapingOffset = calculateOffset(recyclerView, rightSnapingView);
float currentSnapingOffset = calculateOffset(recyclerView, snapingView); if (snapingView != null) {
snapingView.setScaleX(currentSnapingOffset);
snapingView.setScaleY(currentSnapingOffset);
} if (leftSnapingView != null) {
leftSnapingView.setScaleX(leftSnapingOffset);
leftSnapingView.setScaleY(leftSnapingOffset);
} if (rightSnapingView != null) {
rightSnapingView.setScaleX(rightSnapingOffset);
rightSnapingView.setScaleY(rightSnapingOffset);
} if(snapingView != null && currentSnapingOffset >= 1){
OnPageChangeListener listener = pageChangeListenerRef != null ? pageChangeListenerRef.get(): null;
if(listener != null)
listener.onPageSelected(snapingViewPosition);
} Log.d(TAG, String.format("left: %f, right: %f, current: %f", leftSnapingOffset, rightSnapingOffset, currentSnapingOffset));
}

计算缩放比例时是根据左、中、右三个itemView的中间点与RecyclerView中间点的距离来计算的。


private float calculateOffset(RecyclerView recyclerView, View view) {
if (view == null)
return -1; RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
boolean isVertical = layoutManager.canScrollVertically();
int viewStart = isVertical ? view.getTop() : view.getLeft();
int viewEnd = isVertical ? view.getBottom() : view.getRight();
int centerX = isVertical ? recyclerView.getHeight() / 2 : recyclerView.getWidth() / 2;
int childCenter = (viewStart + viewEnd) / 2;
int distance = Math.abs(childCenter - centerX); if (distance > centerX)
return STAY_SCALE; float offset = 1.f - (distance / (float) centerX);
return (1.f - STAY_SCALE) * offset + STAY_SCALE;
}

项目地址: https://github.com/yjwfn/recyclerview-gallery



《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

最新文章

  1. MFC中成员变量的声明顺序与析构顺序
  2. 快速掌握、学习HTML的方法
  3. Java 根据当前时间获取明天、当前周的周五、当前月的最后一天
  4. 【转】向HTML中插入视频并兼容所有浏览器的方法
  5. 『BASH』——文件权限批量恢复脚本——「Permission Revovery」
  6. Poj 3259 Wormholes(spfa判负环)
  7. CF_Lucky Sum
  8. css优先级及权重值
  9. HTTP协议&SOCKET协议
  10. Spring ——简介及环境搭建跑通Hello
  11. ISP PIPLINE (一) BLC 以及 线性化
  12. 子类父类(虚函数下的 引用指针 对象)->看来没有子类指针这回事
  13. JAVA_全局配置文件(配置网址,url等等)_第一种方式
  14. 【矩阵快速幂】【杭电OJ1757】
  15. linux查看与开启sshd服务
  16. C#资源释放及Dispose、Close和析构方法
  17. Vue 2.0学习(七)方法与事件
  18. 图的第k短路
  19. 266. Palindrome Permutation 重新排列后是否对称
  20. Qt打包过大

热门文章

  1. unity_UGUI养成之路03
  2. Leetcode之深度优先搜索(DFS)专题-494. 目标和(Target Sum)
  3. 分布式任务调度框架 Azkaban —— Flow 2.0 的使用
  4. Spring Cloud Alibaba | Nacos动态网关路由
  5. 自制轮子 canvas tree
  6. UVA P12101 【Prime Path】
  7. HDU- 3605 - Escape 最大流 + 二进制压位
  8. hdu6312 2018杭电多校第二场 1004 D Game 博弈
  9. 深入分析Mybatis 使用useGeneratedKeys获取自增主键
  10. JXOI 2017 颜色 题解