Android技术分享| 自定义LayoutManager

效果预览

在这里插入图片描述

在这里插入图片描述

注:文本只是简单的Demo,功能单一,主要讲解流程与步骤,请根据特定的需求修改。

各属性图:

在这里插入图片描述

因为item随着滑动会有不同的缩放,所以实际normalViewGap会被缩放计算。

我们在自定义ViewGroup中,想要显示子View,无非就三件事:

  1. 添加 通过addView方法把子View添加进ViewGroup或直接在xml中直接添加;
  2. 测量 重写onMeasure方法并在这里决定自身尺寸以及每一个子View大小;
  3. 布局 重写onLayout方法,在里面调用子View的layout方法来确定它的位置和尺寸;

其实在自定义LayoutManager中,在流程上也是差不多的,我们需要重写onLayoutChildren方法,这个方法会在初始化或者Adapter数据集更新时回调,在这方法里面,需要做以下事情:

  1. 进行布局之前,我们需要调用detachAndScrapAttachedViews方法把屏幕中的Items都分离出来,内部调整好位置和数据后,再把它添加回去(如果需要的话);
  2. 分离了之后,我们就要想办法把它们再添加回去了,所以需要通过addView方法来添加,那这些View在哪里得到呢? 我们需要调用 Recycler的getViewForPosition(int position) 方法来获取;
  3. 获取到Item并重新添加了之后,我们还需要对它进行测量,这时候可以调用measureChild或measureChildWithMargins方法,两者的区别我们已经了解过了,相信同学们都能根据需求选择更合适的方法;
  4. 在测量完还需要做什么呢? 没错,就是布局了,我们也是根据需求来决定使用layoutDecorated还是layoutDecoratedWithMargins方法;
  5. 在自定义ViewGroup中,layout完就可以运行看效果了,但在LayoutManager还有一件非常重要的事情,就是回收了,我们在layout之后,还要把一些不再需要的Items回收,以保证滑动的流畅度;

布局实现

再看下相关参数:

在这里插入图片描述

如果去掉itemView的缩放,透明度动画,那么效果是这样的:

在这里插入图片描述

看到的效果与LinearLayoutManager一样,但本篇并不使用LinearLayoutManager,而是通过自定义LayoutManager来实现。

索引值为0的view 一次完全滑出屏幕所需要的移动距离,定位为firstChildCompleteScrollLength;非索引值为0的view滑出屏幕所需要移动的距离为:
firstChildCompleteScrollLength + onceCompleteScrollLength; item 之间的间距为 normalViewGap

我们在 scrollHorizontallyBy 方法中记录偏移量 dx,保存一个累计偏移量 mHorizontalOffset ,然后针对索引值为0与非0两种情况,在 mHorizontalOffset 小于 firstChildCompleteScrollLength 情况下,用该偏移量除以 firstChildCompleteScrollLength 获取到已经滚动了的百分比 fraction ;同理索引值非0的情况下,偏移量需要减去 firstChildCompleteScrollLength 来获取到滚动的百分比。根据百分比,怎么布局 childview 就很容易了。

接下来开始写代码,我们创建类:StackLayoutManager

StackLayoutManager 继承 RecyclerView.LayoutManager ,需要重写 generateDefaultLayoutParams 方法:

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

成员变量:

    /**
     * 一次完整的聚焦滑动所需要的移动距离
     */
    private float onceCompleteScrollLength = -1;

    /**
     * 第一个子view的偏移量
     */
    private float firstChildCompleteScrollLength = -1;

    /**
     * 屏幕可见第一个view的position
     */
    private int mFirstVisiPos;

    /**
     * 屏幕可见的最后一个view的position
     */
    private int mLastVisiPos;

    /**
     * 水平方向累计偏移量
     */
    private long mHorizontalOffset;

    /**
     * view之间的margin
     */
    private float normalViewGap = 30;

    private int childWidth = 0;

    /**
     * 是否自动选中
     */
    private boolean isAutoSelect = true;
    // 选中动画
    private ValueAnimator selectAnimator;

scrollHorizontallyBy 方法:

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
   
   
        // 手指从右向左滑动,dx > 0; 手指从左向右滑动,dx < 0;
        // 位移0、没有子View 当然不移动
        if (dx == 0 || getChildCount() == 0) {
   
   
            return 0;
        }

        // 误差处理
        float realDx = dx / 1.0f;
        if (Math.abs(realDx) < 0.00000001f) {
   
   
            return 0;
        }

        mHorizontalOffset += dx;

        dx = fill(recycler, state, dx);

        return dx;
    }

    private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, int dx) {
   
   
        int resultDelta = dx;
        resultDelta = fillHorizontalLeft(recycler, state, dx);
        recycleChildren(recycler);
        return resultDelta;
    }

    private int fillHorizontalLeft(RecyclerView.Recycler recycler, RecyclerView.State state, int dx) {
   
   
        if (dx < 0) {
   
   
            // 已到达左边界
            if (mHorizontalOffset < 0) {
   
   
                mHorizontalOffset = dx = 0;
            }
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值