LayoutManager自定义

From here https://blog.csdn.net/zxt0601/article/details/52956504

照着写一遍,熟悉代码逻辑,修正那块看着脑袋晕,暂不考虑,复制作者代码就行。
下边是效果图,增加了一个boolean,默认为false,如果为true的话就是下图效果,每行控件都居中显示。


LayoutManager自定义_第1张图片
image.png

代码如下:

package com.charliesong.demo0327;

import android.graphics.Rect;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by charlie.song on 2018/5/8.
 */

public class FlowGravityLayoutManager extends RecyclerView.LayoutManager {

    private int mVerticalOffset;//竖直偏移量 每次换行时,要根据这个offset判断
    private int mFirstVisiPos;//屏幕可见的第一个View的Position
    private int mLastVisiPos;//屏幕可见的最后一个View的Position
    private OrientationHelper orientationHelper;//系统自带的类,这里选择的是垂直方向的,用来获取top,bottom,以及child的宽高等
    private SparseArray mItemRects;//key 是position,保存的是view的bound属性
    private boolean horizontalCenter=false;//同一行的控件是否居中显示
    public FlowGravityLayoutManager() {
        orientationHelper = OrientationHelper.createOrientationHelper(this, OrientationHelper.VERTICAL);
        mItemRects = new SparseArray<>();
      setAutoMeasureEnabled(true);
    }

    public void setHorizontalCenter(boolean horizontalCenter) {
        this.horizontalCenter = horizontalCenter;
    }
    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
    }

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getItemCount() == 0) {
            detachAndScrapAttachedViews(recycler);
            oneRowViews.clear();
            return;
        }
        if (getChildCount() == 0 && state.isPreLayout()) {
            return;
        }
        detachAndScrapAttachedViews(recycler);
        mVerticalOffset = 0;
        mFirstVisiPos = 0;
        mLastVisiPos = getItemCount() ;
        fill(recycler, state);
    }

    private void fill(RecyclerView.Recycler recycler, RecyclerView.State state) {
        fill(recycler, state, 0);
    }
    int leftOffset = getPaddingLeft();
    int lineMaxHeight = 0;//每行的最大高度,因为同一行的控件可能高度不一样,取最大值,好决定下一行的位置
    int topOffset;
    SparseArray oneRowViews=new SparseArray<>();//保存在同一行的view,方便在这一行控件加载完以后平移,只对居中对齐的情况下处理
    /**
     * 填充childView的核心方法,应该先填充,再移动。
     * 在填充时,预先计算dy的在内,如果View越界,回收掉。
     * 一般情况是返回dy,如果出现View数量不足,则返回修正后的dy.
     *
     * @param dy RecyclerView给我们的位移量,+,显示底端, -,显示头部,手指往上滑是正的,往下滑是负的
     * @return 修正以后真正的dy(可能剩余空间不够移动那么多了 所以return <|dy|)
     */
    private int fill(RecyclerView.Recycler recycler, RecyclerView.State state, int dy) {
         topOffset = orientationHelper.getStartAfterPadding();
        //回收越界子View
        if (getChildCount() > 0) {//滑动时进来的
            for (int i = getChildCount() - 1; i >= 0; i--) {
                View child = getChildAt(i);
                if (dy > 0) {//上滑,顶部的view可能消失
                    if (getDecoratedBottom(child) - dy < topOffset) {
                        removeAndRecycleView(child, recycler);
                        mFirstVisiPos++;
                    }
                } else if (dy < 0) {//手指往下滑,底部的view 可能消失
                    if (getDecoratedTop(child) - dy > orientationHelper.getEndAfterPadding()) {
                        removeAndRecycleView(child, recycler);
                        mLastVisiPos--;
                    }
                }
            }
        }

        //
        leftOffset = getPaddingLeft();
         lineMaxHeight = 0;
        //布局子View阶段
        if (dy >= 0) {
            int minPos = mFirstVisiPos;
            mLastVisiPos = getItemCount() - 1;
            if (getChildCount() > 0) {
                View lastVisibleView = getChildAt(getChildCount() - 1);
                minPos = getPosition(lastVisibleView) + 1;
                topOffset = getDecoratedTop(lastVisibleView);
                leftOffset = getDecoratedRight(lastVisibleView);
                lineMaxHeight = Math.max(lineMaxHeight, orientationHelper.getDecoratedMeasurement(lastVisibleView));
            }
            for (int i = minPos; i <=mLastVisiPos; i++) {
            //找recycler要一个childItemView,我们不管它是从scrap里取,还是从RecyclerViewPool里取,亦或是onCreateViewHolder里拿。
                View child = recycler.getViewForPosition(i);
                addView(child);
                measureChildWithMargins(child, 0, 0);
                if (leftOffset + orientationHelper.getDecoratedMeasurementInOther(child) <= getWidth() - getPaddingRight()) {//这一行可以装的下,不用换行
                    layoutChild(child,i);
                } else {
                    transViewX();
                    leftOffset = getPaddingLeft();
                    topOffset += lineMaxHeight;
                    lineMaxHeight = 0;
                    //新起一行的时候要判断一下边界
                    if (topOffset - dy > orientationHelper.getEndAfterPadding()) {
                        removeAndRecycleView(child, recycler);
                        mLastVisiPos=i-1;//已经跑到recyclerview最底部,不可见了,修改mLastVisPos,循环结束
                    } else {
                        layoutChild(child,i);
                    }
                }
            }
            //添加完后,判断是否已经没有更多的ItemView,并且此时屏幕仍有空白,则需要修正dy
            View lastVisibleView = getChildAt(getChildCount() - 1);
            int lastPosition = getPosition(lastVisibleView);
            if (lastPosition == getItemCount() - 1) {
                int gap = orientationHelper.getEndAfterPadding() - getDecoratedBottom(lastVisibleView);
                if (gap > 0) {
                    dy -= gap;
                }
            }
        } else {

            int maxPosition = getItemCount() - 1;
            mFirstVisiPos = 0;
            if (getChildCount() > 0) {
                View firstView = getChildAt(0);
                maxPosition = getPosition(firstView) - 1;
            }
            for (int i = maxPosition; i >= mFirstVisiPos; i--) {
                Rect rect = mItemRects.get(i);
                if (rect.bottom - mVerticalOffset - dy < getPaddingTop()) {
                    mFirstVisiPos = i + 1;
                    break;
                } else {
                    View child = recycler.getViewForPosition(i);
                    addView(child, 0);//将View添加至RecyclerView中,childIndex为1,但是View的位置还是由layout的位置决定
                    measureChildWithMargins(child, 0, 0);
                    layoutDecoratedWithMargins(child, rect.left, rect.top - mVerticalOffset, rect.right, rect.bottom - mVerticalOffset);
                }
            }

        }
        return  dy;
    }
    //换行或者add最后一个view的时候,平移下最终的位置,
    private void transViewX(){
        if(!horizontalCenter){
            return;
        }
        int transX=(getWidth()-getPaddingRight()-leftOffset)/2;
        for(int i=0;i 0){
            //利用最后一个子View比较修正
            View lastView=getChildAt(getChildCount()-1);
            if(getPosition(lastView)==getItemCount()-1){
                int gap = getHeight() - getPaddingBottom() - getDecoratedBottom(lastView);
                if (gap > 0) {
                    realOffset = -gap;
                } else if (gap == 0) {
                    realOffset = 0;
                } else {
                    realOffset = Math.min(realOffset, -gap);
                }
            }
        }

        realOffset= fill(recycler, state, realOffset);//先填充,再位移。

        mVerticalOffset += realOffset;//累加实际滑动距离

        offsetChildrenVertical(-realOffset);//滑动
        return realOffset;
    }

}

简单记录下修改代码碰到的问题:

居中显示

最开始我的思路是用个tag【默认是0】,然后每次换行,平移完控件以后,tag就修改为换行后的position
结果我发现没有效果,offsetLeftAndRight不行我换成layoutDecoratedWithMargins也不行,这就奇怪了。
后来想着可能这个child不是我们要的child,也就是说通过recycler.getViewForPosition(start),虽然里边的pisition是一样的,可获取到的child不是一个东西,所以操作没反应。
最后就是上边的代码了,将每行的child,都放到oneRowViews里,换行的时候取出来进行平移,果然没问题。

//这里的代码最早就是写在换行的逻辑里的,结果无效
                    int transX=(getWidth()-getPaddingRight()-leftOffset)/2;
                    for(int start=tag;start

你可能感兴趣的:(LayoutManager自定义)