查看原文
其他

实现炫酷列表动画的最佳姿势

钉某人 鸿洋 2019-04-05

点击上方“鸿洋”,关注本号

优质技术文第一时间送达


本文作者

作者:钉某人

链接:

https://www.jianshu.com/p/20f16a4b4630

本文由作者授权发布。


1概述


RecyclerView的使用大家都很熟悉了,这里偶带大家来实现下面这种效果。


SkidRightLayoutManager


EchelonLayoutManager


对的,你猜的不错。这种效果只要操作LayoutManager就可以实现,然后就这样

mRecyclerView.setLayoutManager(new EchelonLayoutManager(getContext()));


完了??? 对,就是so easy.


关于如何自定义LayoutManager,网上有很多文章,可以去找百度君。


自定义LayoutManager步骤一般是:


  1. 指定默认的LayoutParams,一般返回 return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
    RecyclerView.LayoutParams.WRAP_CONTENT); 就可以了

  2. 测量Item的信息

  3. 处理Item回收,使用RecyclerView的二级缓存机制就可以,多方便

  4. 滑动处理逻辑


2开启撸码模式


必须实现的方法

@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
   return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
           RecyclerView.LayoutParams.WRAP_CONTENT);
}


第一次加载的时候,从这里获取我们需要的Item的数据,同时也要把我们的Item排列好,关键函数就是layoutChild(recycler),这个后面讲,主要策略是从最后一个Item加载。

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   if (state.getItemCount() == 0 || state.isPreLayout()) return;
   removeAndRecycleAllViews(recycler);
   mItemViewWidth = (int) (getHorizontalSpace() * 0.87f);
   mItemViewHeight = (int) (mItemViewWidth * 1.46f);
   mItemCount = getItemCount();
   //获取到所有Item的高度和
   mScrollOffset = Math.min(Math.max(mItemViewHeight, mScrollOffset), mItemCount * mItemViewHeight);
   layoutChild(recycler);
}


这里说说layoutChild(recycler)里面做了什么,看注释吧,亲们

private void layoutChild(RecyclerView.Recycler recycler) {
   if (getItemCount() == 0 ) return;
   //获取到最后一个Item的位置
   int bottomItemPosition = (int) Math.floor(mScrollOffset / mItemViewHeight);
   //获取到出去一个完整的Item的高度,还剩余多少空间
    int remainSpace = getVerticalSpace() - mItemViewHeight;
   //滑动的时候可以获取到最后一个Item在屏幕上还显示的高度
   int bottomItemVisibleHeight = mScrollOffset % mItemViewHeight;
   //最后一个Item显示高度相对于本身的比例
   final float offsetPercentRelativeToItemView = bottomItemVisibleHeight * 1.0f / mItemViewHeight;
   //把我们需要的Item添加到这个集合
   ArrayList<ItemViewInfo> layoutInfos = new ArrayList<>();
   for (int i = bottomItemPosition - 1, j = 1; i >= 0; i--, j++) {
       //计算偏移量
       double maxOffset = (getVerticalSpace() - mItemViewHeight) / 2 * Math.pow(0.8, j);
       //这个Item的top值
       int start = (int) (remainSpace - offsetPercentRelativeToItemView * maxOffset);
       //这个Item需要缩放的比例
       float scaleXY = (float) (Math.pow(mScale, j - 1) * (1 - offsetPercentRelativeToItemView * (1 - mScale)));
       float positonOffset = offsetPercentRelativeToItemView;
       //Item上面的距离占RecyclerView可用高度的比例
       float layoutPercent = start * 1.0f / getVerticalSpace();
       ItemViewInfo info = new ItemViewInfo(start, scaleXY, positonOffset, layoutPercent);
       layoutInfos.add(0, info);
       remainSpace = (int) (remainSpace - maxOffset);
       //在添加Item的同时,计算剩余空间是否可以容下下一个Item,如果不能的话,就不再添加了
       if (remainSpace <= 0) {
           info.setTop((int) (remainSpace + maxOffset));
           info.setPositionOffset(0);
           info.setLayoutPercent(info.getTop() / getVerticalSpace());
           info.setScaleXY((float) Math.pow(mScale, j - 1)); ;
           break;
       }
   }
   if (bottomItemPosition < mItemCount) {
       final int start = getVerticalSpace() - bottomItemVisibleHeight;
       layoutInfos.add(new ItemViewInfo(start, 1.0f, bottomItemVisibleHeight * 1.0f / mItemViewHeight, start * 1.0f / getVerticalSpace())
               .setIsBottom());
   } else {
       bottomItemPosition = bottomItemPosition - 1;//99
   }
   //这里做的是回收处理
   int layoutCount = layoutInfos.size();
   final int startPos = bottomItemPosition - (layoutCount - 1);
   final int endPos = bottomItemPosition;
   final int childCount = getChildCount();
   for (int i = childCount - 1; i >= 0; i--) {
       View childView = getChildAt(i);
       int pos = getPosition(childView);
       if (pos > endPos || pos < startPos) {
           removeAndRecycleView(childView, recycler);
       }
   }
   detachAndScrapAttachedViews(recycler);
   //这里主要是对需要显示的Item进行排列以及缩放
   for (int i = 0; i < layoutCount; i++) {
       View view = recycler.getViewForPosition(startPos + i);
       ItemViewInfo layoutInfo = layoutInfos.get(i);
       addView(view);
       measureChildWithExactlySize(view);
       int left = (getHorizontalSpace() - mItemViewWidth) / 2;
       layoutDecoratedWithMargins(view, left, layoutInfo.getTop(), left + mItemViewWidth, layoutInfo.getTop() + mItemViewHeight);
       view.setPivotX(view.getWidth() / 2);
       view.setPivotY(0);
       view.setScaleX(layoutInfo.getScaleXY());
       view.setScaleY(layoutInfo.getScaleXY());
   }
}


下面就是滑动的处理,如果想要RecyclerView滑动的话,就要打开这个开关。

@Override
public boolean canScrollVertically() {
   return true;
}


返回true,你就可以在竖直方向上滑动,相对的也有横向的开关,这里只关注竖向滑动。
滑动的处理在下面这个函数里

@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
   int pendingScrollOffset = mScrollOffset + dy;
   mScrollOffset = Math.min(Math.max(mItemViewHeight, mScrollOffset + dy), mItemCount * mItemViewHeight);
   //每次滑动都要对Item进行排列等操作
   layoutChild(recycler);
   return mScrollOffset - pendingScrollOffset + dy;
}


以上就是主要的逻辑,更多详细的请移步github ,包含更多效果 : 

https://github.com/DingMouRen/LayoutManagerGroup


推荐阅读

勇敢跨越,从0到1开发一个属于自己的App

熟悉又陌生的Context



扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存