当前位置: 代码迷 >> Android >> Android RecyclerView飞瀑流布局添加Footer实现上拉加载
  详细解决方案

Android RecyclerView飞瀑流布局添加Footer实现上拉加载

热度:69   发布时间:2016-04-27 22:03:30.0
Android RecyclerView瀑布流布局添加Footer实现上拉加载

这篇文章应该是晚到了好几个月,之前想写,但是中途遇到了一些棘手的问题,无奈没有去写。写这篇文章的最初来源是一个朋友问我在Android中如何实现瀑布流布局?,当时我的回答是使用RecyclerView,后来他又问我那怎么实现上拉加载并添加Footer呢?我想都没想就回答他根据type的不同去添加一个Footer,监听滚动事件,当滑动到最后显示footer并回调对应的接口,那么,这个过程就会遇到一个Footer布局显示的问题,就像下面这张图一样。

瀑布流布局

可以看到,我们 的 Footer布局并没有占据全屏,而是作为瀑布流布局的一部分了。这显然不是我们想要的。

当然,除了为瀑布流布局添加Footer实现上拉加载外,我们还想要对GridLayout添加Footer实现上拉加载,同样的,在GridLayout中,布局也不是我们想要的结果。效果也是这样。

网格布局

也把我们的Footer作为了GridLayout的一部分,并没有全屏进行显示。

那么接下来,我们需要解决几个问题,即可以实现正确的添加Footer并实现上拉加载功能了。

  • 如何实现上拉加载更多?这个解决方法很简单,就是为RecyclerView添加滚动监听事件,根据布局的不同编写对应的逻辑代码,实现滑到到底部加载更多的功能。
  • 如何正确的添加Footer,使其能够占据宽度为全屏显示?这个有点棘手,但是并不是不能解决,我们需要对适配器进行一些处理就能实现该功能。
  • 此外我们还想要添加一个Header,用于展示ViewPager或者图片等信息。这个原理和添加Footer是一样的。

首先定义一个处理上拉加载的接口

public interface OnLoadMoreListener<T> {    /**     * 加载更多前回调,比如显示Footer的操作     */    void onStart();    /**     * 加载更多业务处理,如网络请求数据     */    void onLoadMore();    /**     * 由于onLoadMore可能是异步调用的,所以onFinish需要手动调用,完成数据的刷新,隐藏Footer等     * @param list onLoadMore中返回的数据     */    void onFinish(List<T> list);}

然后我们自己定义一个抽象类,用于继承RecyclerView.OnScrollListener并实现我们定义的接口OnLoadMoreListener,如果需要上拉加载更多,直接为RecyclerView添加滚动监听为我们的实现类即可,就像这样子

mRecyclerView.addOnScrollListener(new OnRecyclerViewScrollListener<Content>(){            @Override            public void onStart() {            }            @Override            public void onLoadMore() {            }            @Override            public void onFinish(List<Content> contents) {            }        });

由于RecyclerView默认有三种布局,所以我们要对这三种布局分别进行判断上拉加载,处理的逻辑有点不同,首先添加如下定义

public abstract class OnRecyclerViewScrollListener<T extends RecyclerViewAdapter.Item> extends RecyclerView.OnScrollListener implements OnLoadMoreListener<T> {    public static enum layoutManagerType {        LINEAR_LAYOUT,        GRID_LAYOUT,        STAGGERED_GRID_LAYOUT    }    protected layoutManagerType mLayoutManagerType;    private boolean mIsLoadingMore = false;    public boolean isLoadingMore() {        return mIsLoadingMore;    }    public void setLoadingMore(boolean loadingMore) {        mIsLoadingMore = loadingMore;    }}

这个类是泛型的,接收一个实现了Item接口的类。主要是定义了一个枚举类,里面是布局的类型,然后是一个布尔变量,用于判断当前是否正在加载更多。

RecyclerViewAdapter.Item主要是一个接口,其定义如下

    public interface Item {        int TYPE_HEADER = 0;        int TYPE_FOOTER = 1;        /**         * 返回item类型,其值不能为0或者1;         *         * @return         */        int getType();    }

我们的RecyclerView的Item实体类需要实现Item接口,并返还item的类型,默认情况下header的类型为0,footer的类型为1。

接下来最重要的事就是实现onScrolledonScrollStateChanged方法,根据布局的不同判断是否需要加载更多操作。

    private int[] lastPositions;    private int lastVisibleItemPosition;    private int currentScrollState = 0;    @Override    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {        super.onScrolled(recyclerView, dx, dy);        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();        if (mLayoutManagerType == null) {            if (layoutManager instanceof LinearLayoutManager) {                mLayoutManagerType = layoutManagerType.LINEAR_LAYOUT;            } else if (layoutManager instanceof GridLayoutManager) {                mLayoutManagerType = layoutManagerType.GRID_LAYOUT;            } else if (layoutManager instanceof StaggeredGridLayoutManager) {                mLayoutManagerType = layoutManagerType.STAGGERED_GRID_LAYOUT;            } else {                throw new RuntimeException("Unsupported LayoutManager used. Valid ones are LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager");            }        }        switch (mLayoutManagerType) {            case LINEAR_LAYOUT:                lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();                break;            case GRID_LAYOUT:                lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();                break;            case STAGGERED_GRID_LAYOUT:                StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;                if (lastPositions == null) {                    lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];                }                staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);                lastVisibleItemPosition = findMax(lastPositions);                break;            default:                break;        }    }    @Override    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {        super.onScrollStateChanged(recyclerView, newState);        currentScrollState = newState;        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();        int visibleItemCount = layoutManager.getChildCount();        int totalItemCount = layoutManager.getItemCount();        if (visibleItemCount > 0 && currentScrollState == RecyclerView.SCROLL_STATE_IDLE                && lastVisibleItemPosition >= totalItemCount - 1) {            if (!isLoadingMore()){                mIsLoadingMore =true;                onStart();                onLoadMore();            }        }    }    private int findMax(int[] lastPositions) {        int max = lastPositions[0];        for (int value : lastPositions) {            if (value > max) {                max = value;            }        }        return max;    }

具体逻辑见代码,LinearLayoutManager 和 GridLayoutManager的处理逻辑类似,只不过StaggeredGridLayoutManager 的处理稍微复杂一点,因为布局是错乱的,所以需要自己找到最底下的布局是哪一个,关键代码就是这两句

staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);lastVisibleItemPosition = findMax(lastPositions);

就这样子,其实已经出现了上拉加载更多的功能了,这时候你使用一下这个滚动监听,是完全没有什么问题的,只不过没有显示Footer布局而已。接下来我们最重要的事就是改造适配器。

public abstract class RecyclerViewAdapter<T extends RecyclerViewAdapter.Item> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {    public interface Item {        int TYPE_HEADER = 0;        int TYPE_FOOTER = 1;        /**         * 返回item类型,其值不能为0或者1;         *         * @return         */        int getType();    }}

这是最基本的结构,内部定义了上面提到的Item接口,我们的Item实体类需要实现该接口,用于判断Item的类型。

定义Getter和Setter方法

    protected List<T> list = null;    protected int headerViewRes;    protected int footerViewRes;    protected boolean hasHeader = false;    protected boolean hasFooter = false;    public List<T> getList() {        return list;    }    public void setList(List<T> list) {        this.list = list;    }    public boolean isHeader(int position) {        return hasHeader() && position == 0;    }    public boolean isFooter(int position) {        if(hasHeader()){            return hasFooter() && position == list.size() + 1;        }else {            return hasFooter() && position == list.size();        }    }    public int getHeaderView() {        return headerViewRes;    }    public int getFooterView() {        return footerViewRes;    }    public void setHeaderView(int headerViewRes) {        if (headerViewRes != 0) {            if (!hasHeader()){                this.headerViewRes = headerViewRes;                this.hasHeader = true;                notifyItemInserted(0);            }else{                this.headerViewRes = headerViewRes;                notifyDataSetChanged();            }        } else {            if (hasHeader()){                this.hasHeader = false;                notifyItemRemoved(0);            }        }    }    public void setFooterView(int footerViewRes) {        if (footerViewRes != 0) {            if (!hasFooter()){                this.footerViewRes = footerViewRes;                this.hasFooter = true;                if (hasHeader()){                    notifyItemInserted(list.size()+1);                }else{                    notifyItemInserted(list.size());                }            }else{                this.footerViewRes = footerViewRes;                notifyDataSetChanged();            }        } else {            if(hasFooter()){                this.hasFooter = false;                if (hasHeader()){                    notifyItemRemoved(list.size()+1);                }else{                    notifyItemRemoved(list.size());                }            }        }    }    public boolean hasHeader() {        return hasHeader;    }    public boolean hasFooter() {        return hasFooter;    }

内部逻辑看上去一大堆,其实并不复杂,关键是需要判断Header存不存在,Header存在与不存在的情况下Footer的位置是不同的,注意这一点,编写对应的逻辑即可,当然你的逻辑可以与我不同。

接下来是构造函数,传入我们的数据集,Header和Footer的布局资源

    public RecyclerViewAdapter(List<T> list) {        this.list = list;    }    public RecyclerViewAdapter(List<T> list, int headerViewRes) {        this.list = list;        setHeaderView(headerViewRes);    }    public RecyclerViewAdapter(List<T> list, int headerViewRes, int footerViewRes) {        this.list = list;        setHeaderView(headerViewRes);        setFooterView(footerViewRes);    }

实现我们的Header布局和Footer布局的ViewHolder,其实就是定义两个类

    static class HeaderViewHolder extends RecyclerView.ViewHolder {        public HeaderViewHolder(View itemView) {            super(itemView);        }    }    static class FooterViewHolder extends RecyclerView.ViewHolder {        public FooterViewHolder(View itemView) {            super(itemView);        }    }

重写getItemCountgetItemViewType方法

getItemCount中我们需要根据是否有Header和Footer来返回对应的Item数

  @Override    public int getItemCount() {        int count = 0;        count += (hasHeader() ? 1 : 0);        count += (hasFooter() ? 1 : 0);        count += list.size();        return count;    }

getItemViewType就需要根据判断位置判断是否具有Header来判断对应的Item的类型

 @Override    public int getItemViewType(int position) {        int size = list.size();        if (hasHeader()) {            if (position == 0) {                return Item.TYPE_HEADER;            } else {                if (position == size + 1) {                    return Item.TYPE_FOOTER;                } else {                    return list.get(position - 1).getType();                }            }        } else {            if (position == size) {                return Item.TYPE_FOOTER;            } else {                return list.get(position).getType();            }        }    }

创建ViewHolder,根据类型的不同创建对应的ViewHolder,如果不是Header和Footer之外的类型,交由抽象方法onCreateHolder处理

    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (hasHeader() && viewType == Item.TYPE_HEADER) {            View v = LayoutInflater.from(parent.getContext()).inflate(getHeaderView(), parent, false);            return new HeaderViewHolder(v);        } else if (hasFooter() && viewType == Item.TYPE_FOOTER) {            View v = LayoutInflater.from(parent.getContext()).inflate(getFooterView(), parent, false);            return new FooterViewHolder(v);        } else {            return onCreateHolder(parent, viewType);        }    }    public abstract RecyclerView.ViewHolder onCreateHolder(ViewGroup parent, int viewType);

绑定数据,同创建ViewHolder,根据位置的不同来获得item的类型,如果是Header就回调抽象方法onBindHeaderView,如果是Footer就回调抽象方法onBindFooterView,否则就回调抽象方法onBindItemView,将对应的holder和实体类传入。

 @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        if (getItemViewType(position) == Item.TYPE_HEADER) {            HeaderViewHolder headerHolder = (HeaderViewHolder) holder;            View headerView = headerHolder.itemView;            onBindHeaderView(headerView);        } else if (getItemViewType(position) == Item.TYPE_FOOTER) {            FooterViewHolder footerHolder = (FooterViewHolder) holder;            View footerView = footerHolder.itemView;            onBindFooterView(footerView);        } else {            T i = getItemByPosition(position);            onBindItemView(holder, i);        }    }    protected abstract void onBindHeaderView(View headerView);    protected abstract void onBindFooterView(View footerView);    protected abstract void onBindItemView(RecyclerView.ViewHolder holder, T item);

这样子,已经能够处理Header和Footer了,但是显示位置还是不正确的,接下来我们需要对GridLayout和StaggeredGridLayout做特殊处理。

定义抽象类GridLayoutAdapter继承RecyclerViewAdapter

public abstract class GridLayoutAdapter<T extends RecyclerViewAdapter.Item> extends RecyclerViewAdapter<T> {    public GridLayoutAdapter(List list) {        super(list);    }    public GridLayoutAdapter(List list, int headerViewRes) {        super(list, headerViewRes);    }    public GridLayoutAdapter(List list, int headerViewRes, int footerViewRes) {        super(list, headerViewRes, footerViewRes);    }}

定义一个内部类GridSpanSizeLookup 继承GridLayoutManager.SpanSizeLookup,调用父类isHeader和isFooter方法判断是否是头或者尾,如果是则返回gridManager.getSpanCount();即一个item占据一行的span数,否则就返回1

    class GridSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {        @Override        public int getSpanSize(int position) {            if (isHeader(position) || isFooter(position)) {                return gridManager.getSpanCount();            }            return 1;        }    }

最重要的一步就是重写onAttachedToRecyclerView,判断是否是GridLayout布局,然后通过setSpanSizeLookup设置为我们的内部类

    private GridSpanSizeLookup mGridSpanSizeLookup;    private GridLayoutManager gridManager;    @Override    public void onAttachedToRecyclerView(RecyclerView recyclerView) {        super.onAttachedToRecyclerView(recyclerView);        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();        if (manager instanceof GridLayoutManager) {            gridManager = ((GridLayoutManager) manager);            if (mGridSpanSizeLookup == null) {                mGridSpanSizeLookup = new GridSpanSizeLookup();            }            gridManager.setSpanSizeLookup(mGridSpanSizeLookup);        }    }

同理,瀑布流布局也需要进行同样的操作。

public abstract class StaggeredGridLayoutAdapter<T extends RecyclerViewAdapter.Item> extends RecyclerViewAdapter<T> {    public StaggeredGridLayoutAdapter(List<T> list) {        super(list);    }    public StaggeredGridLayoutAdapter(List<T> list, int headerViewRes) {        super(list, headerViewRes);    }    public StaggeredGridLayoutAdapter(List<T> list, int headerViewRes, int footerViewRes) {        super(list, headerViewRes, footerViewRes);    }}

但是 StaggeredGridLayoutManager中没有setSpanSizeLookup方法,庆幸的是StaggeredGridLayoutManager.LayoutParams中有setFullSpan方法可以达到同样的效果。

这时候重写的不再是onAttachedToRecyclerView方法而是onViewAttachedToWindow方法

  @Override    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {        super.onViewAttachedToWindow(holder);        if (isStaggeredGridLayout(holder)) {            handleLayoutIfStaggeredGridLayout(holder, holder.getLayoutPosition());        }    }    private boolean isStaggeredGridLayout(RecyclerView.ViewHolder holder) {        ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();        if (layoutParams != null && layoutParams instanceof StaggeredGridLayoutManager.LayoutParams) {            return true;        }        return false;    }    protected void handleLayoutIfStaggeredGridLayout(RecyclerView.ViewHolder holder, int position) {        if (isHeader(position) || isFooter(position)) {            StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();            p.setFullSpan(true);        }    }

基本上,到这里为止,就完成了所有的工作,在使用的时候要实现上拉加载显示Footer,如果是瀑布流布局,就需要继承StaggeredGridLayoutAdapter,如果是网格布局,就需要继承GridLayoutAdapter,其他情况下,继承RecyclerViewAdapter即可。

为了演示,这里简单进行使用,首先定义一个Item的实现类

public class Content implements RecyclerViewAdapter.Item {    private int TYPE = 2;    private String title;    private String desc;    private String url;    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public String getDesc() {        return desc;    }    public void setDesc(String desc) {        this.desc = desc;    }    public String getIconUrl() {        return url;    }    public void setIconUrl(String iconUrl) {        this.url = iconUrl;    }    @Override    public String toString() {        return "Content{" +                "title='" + title + '\'' +                ", desc='" + desc + '\'' +                ", icon=" + url +                '}';    }    @Override    public int getType() {        return TYPE;    }}

我们这里以瀑布流布局为例,因此继承StaggeredGridLayoutAdapter实现我们的适配器。

public class MyAdapter extends StaggeredGridLayoutAdapter<Content> {    public MyAdapter(List<Content> list, int headerViewRes) {        super(list, headerViewRes);    }    public MyAdapter(List<Content> list) {        super(list);    }    public MyAdapter(List<Content> list, int headerViewRes, int footerViewRes) {        super(list, headerViewRes, footerViewRes);    }    @Override    public RecyclerView.ViewHolder onCreateHolder(ViewGroup parent, int viewType) {        View view=LayoutInflater.from(parent.getContext()).inflate(R.layout.item_content,parent,false);        return new ItemViewHolder(view);    }    @Override    protected void onBindHeaderView(View headerView) {        Log.e("TAG","这是HeadView数据绑定的过程");        ImageView imageView= (ImageView) headerView.findViewById(R.id.icon);        Picasso.with(headerView.getContext()).load("http://img.my.csdn.net/uploads/201508/05/1438760758_3497.jpg").into(imageView);    }    @Override    protected void onBindFooterView(View footerView) {        Log.e("TAG","这是FootView数据绑定的过程");    }    @Override    protected void onBindItemView(RecyclerView.ViewHolder holder, Content item) {        ItemViewHolder itemViewHolder = (ItemViewHolder) holder;        Picasso.with(holder.itemView.getContext()).load(item.getIconUrl()).into( itemViewHolder.icon);        itemViewHolder.title.setText(item.getTitle());        itemViewHolder.desc.setText(item.getDesc());    }    static class ItemViewHolder extends RecyclerView.ViewHolder {        ImageView icon;        TextView title;        TextView desc;        public ItemViewHolder(View itemView) {            super(itemView);            icon = (ImageView) itemView.findViewById(R.id.icon);            title = (TextView) itemView.findViewById(R.id.title);            desc = (TextView) itemView.findViewById(R.id.desc);        }    }}

使用也很简单,在onStart中显示footer,在onLoadMore中加载数据,这里是模拟操作,异步返回数据后将数据传入onFinish进行回调,回调完成后记得调用 setLoadingMore(false);来通知当前处于没在加载的状态,通过Handler发送数据到主线程进行UI更新,并因此Footer

  public class MainActivity extends AppCompatActivity {    private RecyclerView mRecyclerView;    private List<Content> list = new ArrayList<Content>();    private RecyclerViewAdapter<Content> myAdapter;    private  ArrayList<Content> arrayList;    Handler mHandler=new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            List<Content> list= (List<Content>) msg.obj;            myAdapter.getList().addAll(list);            myAdapter.notifyDataSetChanged();            myAdapter.setFooterView(0);        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);        initData();        //mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));        mRecyclerView.setLayoutManager(new GridLayoutManager(this,2));       // mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2,StaggeredGridLayoutManager.VERTICAL));        myAdapter = new MyAdapter(list);        //myAdapter.setHeaderView(R.layout.item_header);        //myAdapter.setFooterView(R.layout.item_footer);        mRecyclerView.setAdapter(myAdapter);        arrayList=new ArrayList<Content>(myAdapter.getList());        mRecyclerView.addOnScrollListener(new OnRecyclerViewScrollListener<Content>(){            @Override            public void onStart() {                myAdapter.setFooterView(R.layout.item_footer);                if (myAdapter.hasHeader()){                    mRecyclerView.smoothScrollToPosition(myAdapter.getItemCount()+1);                }else{                    mRecyclerView.smoothScrollToPosition(myAdapter.getItemCount());                }            }            @Override            public void onLoadMore() {                new Thread(new Runnable() {                    @Override                    public void run() {                        try {                            Log.e("TAG","模拟网络请求数据");                            Thread.sleep(5000);                            //手动调用onFinish()                            onFinish(arrayList);                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                }).start();            }            @Override            public void onFinish(List<Content> contents) {                Message message=Message.obtain();                message.obj=contents;                mHandler.sendMessage(message);                setLoadingMore(false);            }        });    }    private void initData() {        Content c = new Content();        c.setIconUrl("http://p1.meituan.net/63.90/movie/7a29814fe6549b929df6e0ef9575ce699434172.jpg");        c.setTitle("摇滚水果");        c.setDesc("比基尼女郎,掀摇滚热浪。滨江区滨文路577号华润超市4楼。");        list.add(c);         //类似这样的添加数据的过程,还有很多数据。。这里不贴出来了    }}

最终的效果看动图,如下

效果图

最后上源代码

http://download.csdn.net/detail/sbsujjbcy/9312425

3楼u012466304昨天 23:04
这篇博客好有技术含量
2楼u010786678昨天 19:05
感谢楼主的分享,学习了!!
1楼qibin0506昨天 16:30
我擦, 点开博客第一个就是你的,好屌。
Re: sbsujjbcy昨天 18:34
回复qibin0506n你才屌,参考自你博客,哈哈
  相关解决方案