• RecyclerView实例-实现可下拉刷新上拉加载更多并可切换线性流和瀑布流模式(1)


    摘要

    最近项目有个列表页需要实现线性列表和瀑布流展示的切换,首先我想到的就是上
    [RecyclerView],他本身已经很好的提供了三种布局方式,只是简单做个切换应该是很简单的事情,如果要用RecyclerView的方式来实现,那就是目前的设计方案(listView)都不能用,更改为RecyclerView,我需要做如下工作:

    • 下拉刷新,
    • 上拉自动加载更多,
    • 同时支持切换布局方式。

    自定义RecyclerView实现自动加载

    为了实现自动加载更多的功能,我选择自定义实现一个特殊的RecyclerView来实现,网络上也有自定义一个包含RecyclerView和loading_more的布局,现在我想试试adapter处理的方式,所以有了这个自定义实现类

    java片段1: RecyclerView实现LoadMoreRecyclerView

    /**
     * Alipay.com Inc.
     * Copyright (c) 2004-2015 All Rights Reserved.
     */
    package com.leaf8.alicx.myapplication;
    
    import android.content.Context;
    import android.support.v7.widget.GridLayoutManager;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    import android.support.v7.widget.StaggeredGridLayoutManager;
    import android.util.AttributeSet;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    
    /**
     * 支持上拉加载更多的
     *
     * @author 肖肖
     * @version $$Id: LoadMoreRecyclerView.java, v 0.1 11/17/15 10:07 alicx Exp $$
     */
    public class LoadMoreRecyclerView extends RecyclerView {
        /**
         * item 类型
         */
        public final static int TYPE_NORMAL = 0;
        public final static int TYPE_HEADER = 1;//头部--支持头部增加一个headerView
        public final static int TYPE_FOOTER = 2;//底部--往往是loading_more
        public final static int TYPE_LIST = 3;//代表item展示的模式是list模式
        public final static int TYPE_STAGGER = 4;//代码item展示模式是网格模式
    
        private boolean mIsFooterEnable = false;//是否允许加载更多
    
        /**
         * 自定义实现了头部和底部加载更多的adapter
         */
        private AutoLoadAdapter mAutoLoadAdapter;
        /**
         * 标记是否正在加载更多,防止再次调用加载更多接口
         */
        private boolean mIsLoadingMore;
        /**
         * 标记加载更多的position
         */
        private int mLoadMorePosition;
        /**
         * 加载更多的监听-业务需要实现加载数据
         */
        private LoadMoreListener mListener;
    
        public LoadMoreRecyclerView(Context context) {
            super(context);
            init();
        }
    
        public LoadMoreRecyclerView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init();
        }
    
        public LoadMoreRecyclerView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init();
        }
    
        /**
         * 初始化-添加滚动监听
         * <p/>
         * 回调加载更多方法,前提是
         * <pre>
         *    1、有监听并且支持加载更多:null != mListener && mIsFooterEnable
         *    2、目前没有在加载,正在上拉(dy>0),当前最后一条可见的view是否是当前数据列表的最好一条--及加载更多
         * </pre>
         */
        private void init() {
            super.addOnScrollListener(new OnScrollListener() {
    
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                }
    
                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    if (null != mListener && mIsFooterEnable && !mIsLoadingMore && dy > 0) {
                        int lastVisiblePosition = getLastVisiblePosition();
                        if (lastVisiblePosition + 1 == mAutoLoadAdapter.getItemCount()) {
                            setLoadingMore(true);
                            mLoadMorePosition = lastVisiblePosition;
                            mListener.onLoadMore();
                        }
                    }
                }
            });
        }
    
        /**
         * 设置加载更多的监听
         *
         * @param listener
         */
        public void setLoadMoreListener(LoadMoreListener listener) {
            mListener = listener;
        }
    
        /**
         * 设置正在加载更多
         *
         * @param loadingMore
         */
        public void setLoadingMore(boolean loadingMore) {
            this.mIsLoadingMore = loadingMore;
        }
    
        /**
         * 加载更多监听
         */
        public interface LoadMoreListener {
            /**
             * 加载更多
             */
            void onLoadMore();
        }
    
        /**
         *
         */
        public class AutoLoadAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    
            /**
             * 数据adapter
             */
            private RecyclerView.Adapter mInternalAdapter;
    
            private boolean mIsHeaderEnable;
            private int mHeaderResId;
    
            public AutoLoadAdapter(RecyclerView.Adapter adapter) {
                mInternalAdapter = adapter;
                mIsHeaderEnable = false;
            }
    
            @Override
            public int getItemViewType(int position) {
                int headerPosition = 0;
                int footerPosition = getItemCount() - 1;
    
                if (headerPosition == position && mIsHeaderEnable && mHeaderResId > 0) {
                    return TYPE_HEADER;
                }
                if (footerPosition == position && mIsFooterEnable) {
                    return TYPE_FOOTER;
                }
                /**
                 * 这么做保证layoutManager切换之后能及时的刷新上对的布局
                 */
                if (getLayoutManager() instanceof LinearLayoutManager) {
                    return TYPE_LIST;
                } else if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
                    return TYPE_STAGGER;
                } else {
                    return TYPE_NORMAL;
                }
            }
    
            @Override
            public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                if (viewType == TYPE_HEADER) {
                    return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                            mHeaderResId, parent, false));
                }
                if (viewType == TYPE_FOOTER) {
                    return new FooterViewHolder(
                            LayoutInflater.from(parent.getContext()).inflate(
                                    R.layout.list_foot_loading, parent, false));
                } else { // type normal
                    return mInternalAdapter.onCreateViewHolder(parent, viewType);
                }
            }
    
            public class FooterViewHolder extends RecyclerView.ViewHolder {
    
                public FooterViewHolder(View itemView) {
                    super(itemView);
                }
            }
    
            public class HeaderViewHolder extends RecyclerView.ViewHolder {
                public HeaderViewHolder(View itemView) {
                    super(itemView);
                }
            }
    
            @Override
            public void onBindViewHolder(ViewHolder holder, int position) {
                int type = getItemViewType(position);
                if (type != TYPE_FOOTER && type != TYPE_HEADER) {
                    mInternalAdapter.onBindViewHolder(holder, position);
                }
            }
    
            /**
             * 需要计算上加载更多和添加的头部俩个
             *
             * @return
             */
            @Override
            public int getItemCount() {
                int count = mInternalAdapter.getItemCount();
                if (mIsFooterEnable) count++;
                if (mIsHeaderEnable) count++;
    
                return count;
            }
    
            public void setHeaderEnable(boolean enable) {
                mIsHeaderEnable = enable;
            }
    
            public void addHeaderView(int resId) {
                mHeaderResId = resId;
            }
        }
    
        @Override
        public void setAdapter(RecyclerView.Adapter adapter) {
            if (adapter != null) {
                mAutoLoadAdapter = new AutoLoadAdapter(adapter);
            }
            super.swapAdapter(mAutoLoadAdapter, true);
        }
    
        /**
         * 切换layoutManager
         *
         * 为了保证切换之后页面上还是停留在当前展示的位置,记录下切换之前的第一条展示位置,切换完成之后滚动到该位置
         * 另外切换之后必须要重新刷新下当前已经缓存的itemView,否则会出现布局错乱(俩种模式下的item布局不同),
         * RecyclerView提供了swapAdapter来进行切换adapter并清理老的itemView cache
         *
         * @param layoutManager
         */
        public void switchLayoutManager(LayoutManager layoutManager) {
            int firstVisiblePosition = getFirstVisiblePosition();
    //        getLayoutManager().removeAllViews();
            setLayoutManager(layoutManager);
            //super.swapAdapter(mAutoLoadAdapter, true);
            getLayoutManager().scrollToPosition(firstVisiblePosition);
        }
    
        /**
         * 获取第一条展示的位置
         *
         * @return
         */
        private int getFirstVisiblePosition() {
            int position;
            if (getLayoutManager() instanceof LinearLayoutManager) {
                position = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
            } else if (getLayoutManager() instanceof GridLayoutManager) {
                position = ((GridLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
            } else if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
                StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) getLayoutManager();
                int[] lastPositions = layoutManager.findFirstVisibleItemPositions(new int[layoutManager.getSpanCount()]);
                position = getMinPositions(lastPositions);
            } else {
                position = 0;
            }
            return position;
        }
    
        /**
         * 获得当前展示最小的position
         *
         * @param positions
         * @return
         */
        private int getMinPositions(int[] positions) {
            int size = positions.length;
            int minPosition = Integer.MAX_VALUE;
            for (int i = 0; i < size; i++) {
                minPosition = Math.min(minPosition, positions[i]);
            }
            return minPosition;
        }
    
        /**
         * 获取最后一条展示的位置
         *
         * @return
         */
        private int getLastVisiblePosition() {
            int position;
            if (getLayoutManager() instanceof LinearLayoutManager) {
                position = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
            } else if (getLayoutManager() instanceof GridLayoutManager) {
                position = ((GridLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
            } else if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
                StaggeredGridLayoutManager layoutManager = (StaggeredGridLayoutManager) getLayoutManager();
                int[] lastPositions = layoutManager.findLastVisibleItemPositions(new int[layoutManager.getSpanCount()]);
                position = getMaxPosition(lastPositions);
            } else {
                position = getLayoutManager().getItemCount() - 1;
            }
            return position;
        }
    
        /**
         * 获得最大的位置
         *
         * @param positions
         * @return
         */
        private int getMaxPosition(int[] positions) {
            int size = positions.length;
            int maxPosition = Integer.MIN_VALUE;
            for (int i = 0; i < size; i++) {
                maxPosition = Math.max(maxPosition, positions[i]);
            }
            return maxPosition;
        }
    
        /**
         * 添加头部view
         *
         * @param resId
         */
        public void addHeaderView(int resId) {
            mAutoLoadAdapter.addHeaderView(resId);
        }
    
        /**
         * 设置头部view是否展示
         * @param enable
         */
        public void setHeaderEnable(boolean enable) {
            mAutoLoadAdapter.setHeaderEnable(enable);
        }
    
        /**
         * 设置是否支持自动加载更多
         *
         * @param autoLoadMore
         */
        public void setAutoLoadMoreEnable(boolean autoLoadMore) {
            mIsFooterEnable = autoLoadMore;
        }
    
        /**
         * 通知更多的数据已经加载
         *
         * 每次加载完成之后添加了Data数据,用notifyItemRemoved来刷新列表展示,
         * 而不是用notifyDataSetChanged来刷新列表
         *
         * @param hasMore
         */
        public void notifyMoreFinish(boolean hasMore) {
            setAutoLoadMoreEnable(hasMore);
            getAdapter().notifyItemRemoved(mLoadMorePosition);
            mIsLoadingMore = false;
        }
    }
    

    实现原理是利用RecyclerView.Adapter的getItemType来区别不一样的item布局,这里是实现了一个支持头部和底部的额外itemVIew的apdater壳子,将数据的adapter放到这个壳子中去代理,保证业务方实现的adapter的纯净。
    另外LoadMoreRecyclerView管理了5个itemType,除了TYPE_HEADER和TYPE_FOOTER之外,其余三个会传递给业务方的onCreateViewHolder方法,业务方根据这个参数决定使用布局,当然业务方页可以用自己的方式实现判断条件:

    java片段2: 业务方可能的onCreateViewHolder实现

    @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == LoadMoreRecyclerView.TYPE_STAGGER) {
                View view = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.fragment_item_staggel, parent, false);
                return new StaggerViewHolder(view);
            } else {
                View view = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.fragment_item, parent, false);
                return new ViewHolder(view);
            }
        }
    

    实现下拉刷新

    下拉刷新的实现,我也是直接用了google提供的support-v4包种的SwipeRefreshLayout,只要实现下拉刷新的获取数据动作即可,

    layout1:

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.leaf8.alicx.myapplication.LoadMoreRecyclerView
             android:id="@+id/list"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             app:layoutManager="LinearLayoutManager"
             tools:listitem="@layout/fragment_item" />
    </android.support.v4.widget.SwipeRefreshLayout>
    

    java片段3: refreshlistener

    swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.refresh_layout);
    swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
         @Override
         public void onRefresh() {
               swipeRefreshLayout.setRefreshing(false);
               page = 0;
               myItemRecyclerViewAdapter.setData(DummyContent.generyData(page));
               recyclerView.setAutoLoadMoreEnable(DummyContent.hasMore(page));
               myItemRecyclerViewAdapter.notifyDataSetChanged();
          }
    });
    

    SwipeRefreshLayout没什么可以多说的,大家都知道的东西。

    实现上拉加载更多

    我自己实现了一个LoadMoreRecyclerView,来完成自动加载的功能。代码见#java片段1#,这个LoadMoreRecyclerView需要业务方设置LoadMoreListener,当分页结束还需要设置不再继续自动加载,LoadMoreRecyclerView开放了一个接口notifyMoreFinish来支持该设置。

    java片段4

    recyclerView.setLoadMoreListener(new LoadMoreRecyclerView.LoadMoreListener() {
          @Override
          public void onLoadMore() {
              recyclerView.postDelayed(new Runnable() {
                   @Override
                   public void run() {
                        swipeRefreshLayout.setRefreshing(false);
                        myItemRecyclerViewAdapter.addDatas(DummyContent.generyData(++page));
                        recyclerView.notifyMoreFinish(DummyContent.hasMore(page));
                   }
              }, 1000);
         }
    });
    

    这样完了之后,就能实现上拉自动加载,并且加载中还有一个加载中...显示。

    在notifyMoreFinish方法中为了实现新数据被刷新到页面上,用了adapter.notifyItemRemoved(lastvisiblePosition),发现用notifyDataSetChanged在此时不能起作用,原因应该是加载中...这个,在列表中是一个正常的view,而新数据加上之后,其实是在这个view的位置插入数据的操作,所以此处adapter.notifyItemInsert/adapter.notifyItemRemoved都能起作用,但是notifyDataSetChanged不会重新执行该position的CreateView(原因应该是他认为该position的view已经被缓存了,所以直接展示了缓存中的view)。这点在日常运用中实现对数据的增删的时候也有遇到。

    实现线性及瀑布流切换

    新版列表还要做的一个事情是要实现瀑布流和线性流的切换,这个开始做之前我认为应该很简单,但是当我真的去实施的时候,发现其实还有很多细节问题。

    demo中我只是对这个切换简单的做了一个文案按钮,点击实现切换:

    java片段5

    if (1 == mColumnCount) {
        mColumnCount = 2;
        ((TextView) v).setText(R.string.list_mode_stagger);
        myItemRecyclerViewAdapter.switchMode(true);
        recyclerView.switchLayoutManager(new StaggeredGridLayoutManager(mColumnCount, StaggeredGridLayoutManager.VERTICAL));
    } else {
         mColumnCount = 1;
         ((TextView) v).setText(R.string.list_mode_list);
         myItemRecyclerViewAdapter.switchMode(false);
         recyclerView.switchLayoutManager(new LinearLayoutManager(getActivity()));
    }
    

    看看switchLayoutManager方法实现:

    java片段6

    int firstVisiblePosition = getFirstVisiblePosition();
    //        getLayoutManager().removeAllViews();
    setLayoutManager(layoutManager);
    //super.swapAdapter(mAutoLoadAdapter, true);
    getLayoutManager().scrollToPosition(firstVisiblePosition);
    

    实现原则就是切换布局之后,页面还是停留在当前浏览的position位置,所以切换之前,我得先记录下当前的第一个可见的item的位置,代码不贴了,就看LoadMoreRecyclerView.getFirstVisiblePosition()方法。然后我setLayoutManager设置布局,然后我通过scrollToPosition滚动到先前停留的位置,结果发现切换之后页面上会出现俩种布局错乱。恩应该是LoadMoreRecyclerView中还保留了先前布局的item view cache导致,必须要让他知道我的模板已经过时了需要重新生成了,通过什么方法告知我需要刷新布局呢,这个我尝试了几种方法,如上注释掉的,但是还是不顶用,最后我想到adapter.getItemType(),这个应该可行,于是我针对俩种布局添加了俩个类型TYPE_LIST和TYPE_STAGGER,传递到业务的onCreateViewHolder,让业务知道当前的不同布局标识,结果证实该方案可行。

    待解问题 解决了切换布局混乱的问题,又遇到了一个新问题,从list切换到StaggerGride,如果当前的position是在第二屏,切换过来之后,滑动到第一屏发现瀑布流的第一行俩个列是不对齐的,右边一列会滑动上去的一个过程。这个时候再上拉翻到第三屏就会出现一个IndexOutOfBoundsException的crash异常,但是当我切换过来之后,不去下滑,而是继续上拉,则不会出现这个问题,但是下滑到第一屏还是会有一个第二列往上移动的过程。感觉是这个移动的过程造成了后续数据的一个position错乱,目前还在排查这个问题

    无图无真相

    • 列表模式

    列表模式

    • 瀑布流模式

    瀑布流模式

    • 问题:切换之后滑动到第一屏出现不对齐现象

    切换之后滑动到第一屏出现不对齐现象

    总结

    RecyclerView某些情况下确实很好用的,很方便的就实现了一些特性。但是因为是个新东西,很多方面还是欠缺了一些成熟性,不可控性,在实际应用中还是需要多多演练才行。

    Demo地址

    打上我的demo地址:供大家参考-->

    :因为业务需要,该业务涉及的list需要放到Fragment中实现,所以本demo也是在这种架构中实现

  • 相关阅读:
    Spring工厂方法(factory-bean)配置bean
    subline关联linux系统
    第五篇 scrapy安装及目录结构,启动spider项目
    第八篇 编写spider爬取jobbole的所有文章
    第七篇 css选择器实现字段解析
    第六篇 xpath的用法
    爬虫 主要基础知识
    在ubuntu16下安装virtualenv+virtualenvwrapper
    git 和github简介
    git stash封存分支 以及关于开发新功能的处理
  • 原文地址:https://www.cnblogs.com/xiaoyaoxia/p/4977125.html
Copyright © 2020-2023  润新知