自定义LayoutManager实现最美应用列表

3,370 阅读3分钟

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

EchelonLayoutManager

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

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

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

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

自定义LayoutManager步骤一般是:

  • 指定默认的LayoutParams,一般返回 return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); 就可以了
  • 测量Item的信息
  • 处理Item回收,使用RecyclerView的二级缓存机制就可以,多方便
  • 滑动处理逻辑

开启撸码模式

必须实现的方法

    @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