RecyclerView之自定义LayoutManager和SnapHelper

本文详细介绍了如何在Android中自定义LayoutManager和SnapHelper以实现类似抖音翻页效果,包括自定义LayoutManager的布局过程,以及如何利用SnapHelper实现按页滑动。文中还涉及到RecyclerView的缓存机制和关键方法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要是实现类似于抖音翻页的效果,但是有有点不同,需要在底部漏出后面的view,这样说可能不好理解,看下Demo,按页滑动,后面的View有放大缩放的动画,滑动速度过小时会有回到原位的效果,下滑也是按页滑动的效果。

 

有的小伙伴可能说这个用 SnapHelper就可以了,没错,翻页是要结合这个,但是也不是纯粹靠这个,因为底部需要漏出来后面的view,所以LayoutManager就不能简单的使用LinearLayoutManager,需要去自定义LayoutManager,然后再自定义SnapHelper

先看下自定义LayoutManager

1.自定义LayoutManager

Android系统给我们提供了常用的几个LayoutManager,比如LinearLayoutManager:用于水平或者竖直滑动
GridLayoutManager:用于表格布局,一行可以有多列
StaggeredGridLayoutManager:瀑布流布局

但是在我们上面那个界面就用不了,因为在第一页界面底部需要漏出后面的Item,所以我们就需要自定义。

一般自定义LayoutManager需要实现三个方法:

第一个方法是generateDefaultLayoutParams,这个用来定义布局参数的,一般宽高都WRAP_CONTENT就行。

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

第二个方法根据需要水平或者竖直滑动区分,我们这里是竖直滑动,重写canScrollVertically

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

聪明的你肯定已经知道如果水平滑动,就是重写canScrollHorizontally.

前面两个方法都很简单,最麻烦的就是第三个方法,重写LayoutManager就是需要自己去布局,所以需要重写

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (state.getItemCount() == 0 || state.isPreLayout()) {
            return;
        }

        if (!hasChild) {
            hasChild = true;
        }

        mItemCount = getItemCount();
        // 滑动过的距离
        mScrollOffset = Math.min(Math.max(0, mScrollOffset), (mItemCount - 1) * itemHeight);

        layoutChild(recycler);
    }

首先如果没有item或者第一次layout进来就直接返回,mScrollOffset是滑动过的距离,初始值为0,滑动到最后一个就不能进行滑动,这里其实可以加个下拉刷新,以后有机会再加,不是今天的主题。

接下来大头就是layoutChild.

    private void layoutChild(RecyclerView.Recycler recycler) {
        if (getItemCount() == 0) {
            return;
        }
        int firstItemPosition = (int) Math.floor(mScrollOffset / itemHeight);
        if (firstItemPosition > commonAdapter.getItemCount() - 1) {
            return;
        }

        int firstItemScrolledHeight = mScrollOffset % itemHeight;
        final float firstItemScrolledHeightPercent = firstItemScrolledHeight * 1.0f / itemHeight;
        ArrayList<PageItemViewInfo> layoutInfos = new ArrayList<>();

        // 计算view位置
        int tmpCount = Math.min(VISIBLE_EMOTICON_COUNT, commonAdapter.getItemCount() - firstItemPosition - 1);
        for (int i = 0; i <= tmpCount; i++) {
            // 用于计算偏移量
            int tmp = i + 1;
            double maxOffset = (getVerticalSpace()
                    - itemHeight - firstItemScrolledHeightPercent) / 2 * Math.pow(0.65, tmp);
            if (maxOffset <= 0) {
                break;
            }
            int start;
            if (i == 0) {
                start = getPaddingTop() - firstItemScrolledHeight;
            } else {
                start = (int) (getPaddingTop() + i * maxOffset + i * ITEM_OFFSET);
            }
            float mScale = 0.95f;
            float scaleXY = (float) (Math.pow(mScale, i) * (1 - firstItemScrolledHeightPercent * (1 - mScale)));
            PageItemViewInfo info = new PageItemViewInfo(start, scaleXY);
            layoutInfos.add(0, info);
        }

        // 回收View
        int layoutCount = layoutInfos.size();
        final int endPos = firstItemPosition + VISIBLE_EMOTICON_COUNT;
        final int childCount = getChildCount();
        for (int i = childCount - 1; i >= 0; i--) {
            View childView = getChildAt(i);
            if (childView == null) {
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值