自定义RecyclerView添加下拉刷新和上拉加载功能

自定义RecyclerView中遇见的问题

通用实现起来复杂,效率高则简单,但是如何实现高效和简单呢,就是把功能开源成库工程,这样的话既提高了效率,又提高了通用性。而且不要重复造轮子,要学会在别人的基础上,取其精华,弄清原理,根据自己的业务需求创造出属于自己的轮子。

第一步,建立安卓库工程

在Android Studio 中的菜单栏找到,File->New->New Module


然后填写一些基本资料

第二步,项目工程依赖库工程

库工程和普通项目Module不同之处是Build.gradle的一些配置选项不一样;

下面这个是库工程的配置

apply plugin: 'com.android.library'

下面这个是普通项目Module的配置

apply plugin: 'com.android.application'

在普通项目Module的配置文件bulid.gradle中添加,注意那个名字要用你建库工程时候的名称

compile project(path: ':myutillibrary')

需要掌握的基础知识

声明自定义的RecyclerView.Adapter,RecyclerVie.ViewHolder和DataModel;


注意RecyclerView的执行流程
1.RecyclerView通过Adapter的getItemCount来动态决定视图中显示的item个数;
2.getItemViewType用来决定每一个Item对应的布局;
3.createViewHolder用来创建ViewHolder,用来避免错位问题和提高加载效率。
4.bindViewHolder用来添加相关逻辑处理和UI数据界面的显示

这里写图片描述

MyDynamicsRvAdapter.java

public class MyDynamicsRvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private Context mContext;
    private List<MyDynamicsDataModel> modelList;
    private LayoutInflater mInflater;
    private int iconSize,screenSize,picHeight;
    public MyDynamicsRvAdapter(Context context , List<MyDynamicsDataModel> modelList) {
        this.mContext = context;
        this.modelList = modelList;
        mInflater = LayoutInflater.from(this.mContext);
    }


    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 给ViewHolder设置布局文件
        View v = mInflater.inflate(R.layout.item_mydynamics, parent, false);
        return new MyViewHolder(v);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        // 给ViewHolder设置元素
        MyDynamicsDataModel u = modelList.get(position);
        MyViewHolder mHolder = (MyViewHolder) holder;
        ...
        //相关逻辑处理
    }

    @Override
    public int getItemCount() {
        // 返回数据总数
        return modelList == null ? 0 : modelList.size();
    }

    @Override
    public int getItemViewType(int position) {
        return CustomRecyclerView.TYPE_NORMAL;//注意这个关键点要和自定义View一样
    }

    //重写的自定义ViewHolder
    class MyViewHolder extends RecyclerView.ViewHolder {
        private TextView mUsername,mTime,mState,mMoreContent;
        private ImageView mUserIcon,mPic,mFavoriteIcon,mCommentIcon,mThumbUpIcon;
        private CommentTextView commentTextView;

        private MyViewHolder(View v) {
            super(v);
            //绑定ID
        }
    }

}

MyDynamicsDataModel.java (根据自己业务需求编写)

public class MyDynamicsDataModel {

    public int userIcon;
    public String username;
    public String time;
    public int pic;
    public String state;
    public String[] commentList;//用户评论信息列表,index 0 为用户名 index 1 为对应评论内容

    public MyDynamicsDataModel(int userIcon, String username, String time, int pic,
                               String state, String[] commentList) {
        this.userIcon = userIcon;
        this.username = username;
        this.time = time;
        this.pic = pic;
        this.state = state;
        this.commentList = commentList;
    }

}

自定义RecyclerView需要考虑的问题

1.根据业务需求定制上拉加载和下拉刷新的形式。

决定头布局
header_holder.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:id="@+id/cv_item"
    android:foreground="?android:attr/selectableItemBackground"
    android:visibility="visible"
    card_view:cardCornerRadius="3dp"
    card_view:cardElevation="4dp"
    card_view:cardBackgroundColor="@color/white">

    <include
        android:id="@+id/rl_one"
        android:visibility="visible"
        layout="@layout/sample_common_list_header_loading"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <include
        android:id="@+id/rl_two"
        android:visibility="visible"
        layout="@layout/sample_common_list_header_finish"
        android:layout_width="match_parent"
        android:layout_height="50dp" />
</android.support.v7.widget.CardView>

显示效果
这里写图片描述

决定尾布局
footer_holder.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/loading_view"
    android:background="@color/white"
    android:layout_width="match_parent"
    android:layout_height="38dp">

    <include
        android:id="@+id/loading_viewStub"
        layout="@layout/sample_common_list_footer_loading"
        android:visibility="visible"
        android:layout_width="match_parent"
        android:layout_height="38dp" />

    <include
        android:id="@+id/end_viewStub"
        android:visibility="visible"
        layout="@layout/sample_common_list_footer_end"
        android:layout_width="match_parent"
        android:layout_height="38dp" />

    <include
        android:id="@+id/network_error_viewStub"
        android:visibility="visible"
        layout="@layout/sample_common_list_footer_network_error"
        android:layout_width="match_parent"
        android:layout_height="38dp" />
    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/_grey"
        android:layout_gravity="bottom"
        />
</FrameLayout>

显示效果
这里写图片描述

2.如何添加个性化的footerView和HeaderView。

我的实现原理是通过封装一个内部adapter来,利用数学逻辑来计算viewType和position
关键代码如下

CustomRecyclerView.java

private class WrapAdapter extends Adapter<ViewHolder> {

        private Adapter mAdapter;//容器Adapter
        private View mHeaderView, mFooterView;

        private InnerFooterHolder mFooterHolder;
        private InnerHeaderHolder mHeaderHolder;
        private int mHeaderCount;
        private int mFooterCount;

        public WrapAdapter(Adapter adapter) {
            this.mAdapter = adapter;
            this.mHeaderCount = 0;
            this.mFooterCount = 0;
        }

        public WrapAdapter(Adapter adapter,View mHeaderView, View mFooterView) {
            this.mAdapter = adapter;
            this.mHeaderView = mHeaderView;
            this.mFooterView = mFooterView;
            this.mHeaderCount = 1;
            this.mFooterCount = 1;
        }


        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            switch (viewType) {
                case TYPE_NORMAL:
                    return this.mAdapter.onCreateViewHolder(parent,viewType);
                case TYPE_FOOTER:
                    mFooterHolder = new InnerFooterHolder(mFooterView);
                    return mFooterHolder;
                case TYPE_HEADER:
                    mHeaderHolder = new InnerHeaderHolder(mHeaderView);
                    return mHeaderHolder;
                default:
                    break;
            }
            return null;
        }

        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
            int viewType = getItemViewType(position);
            int realPosition;
            if((mFooterCount == 0 && mHeaderCount == 0) ||
                    (mFooterCount == 1 && mHeaderCount == 0)) {
                realPosition = position;
            } else {
                realPosition = position - 1;
            }
            switch (viewType) {
                case TYPE_NORMAL:
                    this.mAdapter.onBindViewHolder(holder,realPosition);
                    break;
                case TYPE_FOOTER:
                    //默认为Normal状态
                    ((InnerFooterHolder)holder).setState(FooterState.NORMAL);
                    break;
                case TYPE_HEADER:
                    ((InnerHeaderHolder)holder).setState(HeaderState.NONE);
                    break;
                default:
                    break;
            }
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public int getItemViewType(int position) {
            if(mFooterCount == 1 && mHeaderCount == 1) {
                return position == (getItemCount() - 1) ?
                        TYPE_FOOTER : (position == 0 ? TYPE_HEADER : TYPE_NORMAL);
            } else if(mFooterCount == 1 && mHeaderCount == 0) {
                return position == (getItemCount() - 1) ? TYPE_FOOTER : TYPE_NORMAL;
            } else  if(mFooterCount == 0 && mHeaderCount == 1) {
                return position == 0 ? TYPE_HEADER : TYPE_NORMAL;
            } else {
                return TYPE_NORMAL;
            }
        }

        @Override
        public int getItemCount() {
            return this.mAdapter.getItemCount() == 0 ? 0 : (this.mAdapter.getItemCount() + mFooterCount + mHeaderCount);
        }
 }

3.添加自定义的监听器监听RecyclerView的滚动变化。

关键原理是如何判断recyclerView到达了顶部和底部,我这里采用的是判断其第一个完全可见的Item位置是不是0,并且通过recyclerView.canScrollVertically(-1)来结合判断是否到达了顶部;
DataStateChangeCheck.java

@Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        switch (getLayoutManagerType()) {
            case LinearLayout:
                if(getFirstCompletelyVisibleItemPosition()==0) {
                    if(recyclerView.canScrollVertically(-1)) {
                        //到达顶部判定
                        mListener.onRefreshPage(recyclerView);
                    }
                }
                break;
            case GridLayout:
                if(recyclerView.getChildAt(0).getY()==0 && getFirstCompletelyVisibleItemPosition()==0) {
                    if(recyclerView.canScrollVertically(-1)) {
                        //到达顶部判定
                        mListener.onRefreshPage(recyclerView);
                    }
                }
                break;
            case StaggeredGridLayout:
                RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                int flag[] = ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(null);
                //达到这个条件就说明滑到了顶部
                if(recyclerView.getChildAt(0).getY()==0f && flag[0]==0&&
                        recyclerView.canScrollVertically(-1)) {
                    mListener.onRefreshPage(recyclerView);
                }
                break;
        }
    }

到达底部的判定,我这里采用的是判断可见item数要大于零,并且RecyclerView处于 RecyclerView.SCROLL_STATE_IDLE状态,并且最后一个可见Item为总数-1

DataStateChangeCheck.java

@Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);

        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        int visibleItemCount = layoutManager.getChildCount();
        int totalItemCount = layoutManager.getItemCount();
        //到达底部的判定
        if ((visibleItemCount > 0 && newState == RecyclerView.SCROLL_STATE_IDLE &&
                (getLastVisibleItemPosition()) >= totalItemCount - 1)) {
            mListener.onLoadNextPage(recyclerView);
        }
    }

自定义的CustomRecyclerView实现了该接口,DataStateChangeCheck.LoadDataListener

/**
     * 必须设置加载数据监听器
     * @param listener  
     */
    public void setLoadDataListener(DataStateChangeCheck.LoadDataListener listener) {
        this.mListener = listener;
    }

4.提供外部接口供外部调用,实现自定义View和外部容器Fragment和Activity的关联。

CustomRecyclerView.java

/**
     * 对外的数据操作接口
     */
    public interface DataOperation {
        void onLoadMore();
        void onRefresh();
    }

5.新数据到达后自定义View的数据的添加是正向还是逆向。

这里我的视线原理是通过将传入自定义adapter中的List定义为LinkedList,来实现正向和逆向添加

private LinkedList<MyDynamicsDataModel> dataModels = new LinkedList<>();

使用示例例代码

关键代码如下
//数据状态监听器
myRecyclerView.addHeaderView(getActivity());//添加头布局
myRecyclerView.addFooterView(getActivity());//添加尾布局
myRecyclerView.setTotalCount(TOTAL_COUNT);//设置总数
myRecyclerView.setRequestCount(REQUEST_COUNT);//设置一次请求数
myRecyclerView.setDataOperation(this);//实现数据操作接口
myRecyclerView.Initialize();//初始化

在新开的子线程刷新完毕后需要调用

myRecyclerView.finishComplete();
...
adapter = new MyDynamicsRvAdapter(getActivity(), dataModels);

完整使用代码

public class DynamicsFragment extends Fragment implements CustomRecyclerView.DataOperation{

    private static final String ARG = "arg";

    public static DynamicsFragment newInstance(String arg){
        DynamicsFragment fragment = new DynamicsFragment();
        Bundle bundle = new Bundle();
        bundle.putString(ARG, arg);
        fragment.setArguments(bundle);
        return fragment;
    }

    private ImageView addFriend,publishDynamics;
    private View rootView;
    private CustomRecyclerView myRecyclerView;
    private RecyclerView.Adapter adapter;
    private LinkedList<MyDynamicsDataModel> dataModels = new LinkedList<>();
    private String[] states ={"[审核中]","[进行中]","[筹资中]","[已结束]"};
    private int[] pics ={R.mipmap.family,R.mipmap.family,R.mipmap.family,R.mipmap.family};
    private int[] userIcons ={R.mipmap.paintbox,R.mipmap.paintbox,
            R.mipmap.paintbox,R.mipmap.paintbox};
    private String[] userNames = {"大丸子","二丸子","三丸子","四丸子"};
    private String[] times = {"2017.6.11","2017.6.12","2017.6.13","2017.6.14"};
    private String[] commentOne = {"大丸子","一级棒"};
    private List<String[]> lists = new ArrayList<>();

    // 服务器端一共多少条数据
    private static final int TOTAL_COUNT = 10;
    // 每一页展示多少条数据
    private static final int REQUEST_COUNT = 2;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(rootView ==null) {
            rootView = inflater.inflate(R.layout.fragment_dynamics,container,false);
            init(rootView);
            initRecyclerView(rootView);
        }
        ViewGroup parent = (ViewGroup) rootView.getParent();
        if (parent != null) {
            parent.removeView(rootView);
        }
        return rootView;
    }

    private void init(View view) {
        lists.add(commentOne);
        addFriend = (ImageView) view.findViewById(R.id.iv_add_friend);
        publishDynamics = (ImageView) view.findViewById(R.id.iv_publish);
        myRecyclerView = (CustomRecyclerView) view.findViewById(R.id.my_recycler_view);

        addFriend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                IntentSkipUtil.skipToNextActivity(v.getContext(),FriendAddActivity.class);
            }
        });
        publishDynamics.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                IntentSkipUtil.skipToNextActivity(v.getContext(),DynamicPublishingActivity.class);
            }
        });

    }

    private void initRecyclerView(View view) {
        //获取RecyclerV
        myRecyclerView = (CustomRecyclerView) view.findViewById(R.id.my_recycler_view);
        //使用此设置,以提高性能,如果内容不改变RecyclerView的布局尺寸,也称为设置固定大小
        myRecyclerView.setHasFixedSize(true);
        //设置ITEM 动画管理者
        myRecyclerView.setItemAnimator(new DefaultItemAnimator());
        //初始化自定义适配器
        adapter = new MyDynamicsRvAdapter(getActivity(), dataModels);
        // specify(指定) an adapter (see also next example)
        myRecyclerView.setAdapter(adapter);

        myRecyclerView.addHeaderView(getActivity());
        myRecyclerView.addFooterView(getActivity());
        myRecyclerView.setTotalCount(TOTAL_COUNT);
        myRecyclerView.setRequestCount(REQUEST_COUNT);
        myRecyclerView.setDataOperation(this);
        myRecyclerView.Initialize();
        //网格布局管理器
        final GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(),1);
        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return 1;
            }
        });
        //网格布局管理器
        myRecyclerView.setLayoutManager(gridLayoutManager);
    }

    @Override
    public void onLoadMore() {
        new LoadMoreThread(new DataGetInterface() {
            @Override
            public void finishWork(Object obj) {
            }

            @Override
            public void interrupt(Exception e) {

            }
        }, dataModels).start();
    }

    @Override
    public void onRefresh() {
        new RefreshThread(new DataGetInterface() {
            @Override
            public void finishWork(Object obj) {
                myRecyclerView.finishComplete();
            }

            @Override
            public void interrupt(Exception e) {

            }
        },dataModels).start();
    }
}

显示效果
这里写图片描述
Notes:开源过程中的一些心得和收获

1.自定义View会根据业务需求的变化而变化,功能会越趋近于完善。

2.自定义View中避免使用过多的子线程,不仅使自定义代码复杂,而且会使其有意想不到的Bug。

Notes:开源过程中遇见的基本知识

1.Visibility的每个常量的取值问题

ConstantValue
VISIBLE0 (0x00000000)
INVISIBLE4 (0x00000004)
GONE8 (0x00000008)

博主的GitHub地址,会不定时完善更新,目前正在捣鼓如何上传的Maven仓库。更详细的代码在github,有兴趣的可以去看一下。
https://github.com/geminiyang/ShareTransilation/tree/master/easyrefreshandload

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值