自定义LayoutManager的详解及其使用

来源:互联网 发布:企业网站关键词优化 编辑:程序博客网 时间:2024/06/07 02:00

RecyclerView不断的普及,越来越多的人使用来代替传统的ListView,GridView等,为了跟进时代也要不断的学习RecyclerView的相关知识,下面就来了解一下RecyclerView的LayoutManger。

Recycler

RecyclerView内部有一个Recycler,顾名思义它就是一个回收的工具,当定义LayoutManager时,它可以访问到一个Recycler的实例,从而用于来回收或者获取View。当需要一个新的view时,调用getViewForPosition()这个方法,它会返回一个View,这个View可能是之前Recycler回收的View再利用,也可能是一个新的View。

两级缓存机制

Scrap和Recycle

在RecyclerView中有两级缓存机制:Scrap和Recycle。

Scrap Heap(垃圾堆)是一个轻量的集合,View不会经过适配器而是直接返回给LayoutManager,当需要一个View时首先回去Scrap缓存里面找有没有所需要的View,而这里面的View已经绑定了需要的数据所以无需适配直接使用。

Recycle Pool(回收池)这里面回收的View如果再次使用需要重新经过适配器绑定数据,即调用onBindViewHolder()进行绑定数据,当然如果Recycle Pool里面也没有View就只有重新创建View。

Detach和Remove

我们可以通过Detach和Remove决定把View缓存在Recycle或者Scrap。

使用Detach是把View缓存在Scrap,这种缓存方式可以方便如果还需要把缓存的View添加进来的场景,可以明显提高效率,可以调用detachAndScrapView()方法来实现。

Remove就是把View移除掉,放到Recycle里面,以备后面的再次利用,调用方法removeAndRecycleView()实现。

具体采取何种方法还是要根据你具体的需求来调用。


下面写个具体例子来实现如下效果





当然做法就是写一个类来继承RecyclerView.LayoutManager
首先看看几个重要的方法


generateDefaultLayoutParams()
这是一个必须重写的方法,当然仅仅实现这个方法不行,虽然能编译通过。这个方法是给RecyclerView的子View创建一个默认的LayoutParams,实现起来也十分简单。

onLayoutChildren
这个方法显然是用于放置子view的位置,十分重要的一个方法。


canScrollVertically()和canScrollHorizontally()
若想要RecyclerView能水平或者竖直滚动这两个方法需要重写返回true


scrollVerticallyBy()和scrollHorizontallyBy()
在水平或者竖直滚动时会分别调用这两个方法,dx,dy代表每次的增长值,返回值是真实移动的距离


下面贴出代码

package com.lzy.lzy_layoutmanager;import android.graphics.Rect;import android.support.v4.util.SparseArrayCompat;import android.support.v7.widget.RecyclerView;import android.view.View;import android.view.ViewGroup;/** * Created by lzy on 2016/10/18. */public class NyLayoutManager extends RecyclerView.LayoutManager {    private static final String TAG = "lzy";    //保存所有item的偏移信息    private SparseArrayCompat<Rect> itemFrames = new SparseArrayCompat<>();    //总的高度和宽度    private int mTotalHeight;    private int mTotalWidth;    private int verticalOffset;//竖直方向的偏移    private int horizontalOffset;//水平方向的偏移    @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) {            return;        }        detachAndScrapAttachedViews(recycler);        int totalHeight = 0;        int totalWidth = 0;        int offsetX = 0;        int offsetY = 0;        //计算每个item的位置信息,存储在itemFrames里面        for (int i = 0; i < getItemCount(); i++) {            //从缓存中取出            View view = recycler.getViewForPosition(i);            //添加到RecyclerView中            addView(view);            //测量            measureChildWithMargins(view, 0, 0);            //获取测量后的宽高            int height = getDecoratedMeasuredHeight(view);            int width = getDecoratedMeasuredWidth(view);            //把每一个子View的宽高加起来获得总的            totalHeight += height;            totalWidth += width;            //边界信息保存到Rect里面            Rect rect = itemFrames.get(i);            if (rect == null) {                rect = new Rect();            }            rect.set(offsetX, offsetY, offsetX + width, offsetY + height);            itemFrames.put(i, rect);            //横竖方向的偏移            offsetX += width;            offsetY += height;        }        mTotalHeight = Math.max(totalHeight, getVerticalSpace());        mTotalWidth = Math.max(totalWidth, getHorizontalSpace());        fill(recycler, state);    }    //回收不必要的view(超出屏幕的),取出需要的显示出来    private void fill(RecyclerView.Recycler recycler, RecyclerView.State state) {        //获得屏幕的边界信息        Rect displayFrame = new Rect(horizontalOffset, verticalOffset, horizontalOffset + getHorizontalSpace(),                verticalOffset + getVerticalSpace());        //滑出屏幕回收到缓存中        Rect childFrame = new Rect();        for (int i = 0; i < getChildCount(); i++) {            View view = getChildAt(i);            childFrame.left = getDecoratedLeft(view);            childFrame.top = getDecoratedTop(view);            childFrame.right = getDecoratedRight(view);            childFrame.bottom = getDecoratedBottom(view);            //判断是否在显示区域里面            if (!Rect.intersects(displayFrame, childFrame)) {                removeAndRecycleView(view, recycler);            }        }        //在屏幕上显示出        for (int i = 0; i < getItemCount(); i++) {            if (Rect.intersects(displayFrame, itemFrames.get(i))) {//判断是否在屏幕中                View view = recycler.getViewForPosition(i);                measureChildWithMargins(view, 0, 0);                addView(view);                Rect rect = itemFrames.get(i);                layoutDecorated(view, rect.left - horizontalOffset, rect.top - verticalOffset,                        rect.right - horizontalOffset, rect.bottom - verticalOffset);            }        }    }    @Override    public boolean canScrollVertically() {        return true;    }    @Override    public boolean canScrollHorizontally() {        return true;    }    @Override    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {        detachAndScrapAttachedViews(recycler);        if (verticalOffset + dy < 0) {//滑动到最顶部            dy = -verticalOffset;        } else if (verticalOffset + dy > mTotalHeight - getVerticalSpace()) {//滑动到底部            dy = mTotalHeight - getVerticalSpace() - verticalOffset;        }        offsetChildrenVertical(-dy);        fill(recycler, state);        verticalOffset += dy;        return dy;    }    @Override    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {        detachAndScrapAttachedViews(recycler);        if (horizontalOffset + dx < 0) {//滑动到最左边            dx = -horizontalOffset;        } else if (horizontalOffset + dx > mTotalWidth - getHorizontalSpace()) {//滑动到最右边            dx = mTotalWidth - getHorizontalSpace() - horizontalOffset;        }        offsetChildrenHorizontal(-dx);        fill(recycler, state);        horizontalOffset += dx;        return dx;    }    //获取控件的竖直高度    private int getVerticalSpace() {        return getHeight() - getPaddingBottom() - getPaddingTop();    }    //获取控件的水平宽度    private int getHorizontalSpace() {        return getWidth() - getPaddingLeft() - getPaddingRight();    }}

代码中都有注释就不多说了。所以我们通过自定义的LayoutManager就可以实现各种我们所想要的效果了!


要实现自定义LayoutManager,首先实现generateDefaultLayoutParams()方法给child添加默认的LayoutParams,在onLayoutChildren这个方法里面首先detach掉界面上的view缓存到scrap里面,然后重新进行布局,调用getViewForPosition取出缓存的view,添加到RecyclerView里面并测量,接着把它的边界信息设置为我们所想要的样子,通过layoutDecorated()方法把需要显示的子view布局到界面上。

如果需要滑动,把canScrollVertically()和canScrollHorizontally()按需返回true,重写scrollVerticallyBy()或者scrollHorizontallyBy()方法,这两个方法需要返回真实的偏移距离,返回的dx或者dy可能并不是真实的移动距离,因为当滑动到边缘的时候真实移动距离可能就不是dx或者dy,所以在这个时候需要判断处理,移动就调用offsetChildrenHorizontal()或者offsetChildrenVertical()实现,然后重新布局到界面上就可以实现。

Demo下载


1 0
原创粉丝点击