美文网首页Android开发Android技术知识Android开发经验谈
超强赛亚版RecyclerView(多种布局)的使用封装

超强赛亚版RecyclerView(多种布局)的使用封装

作者: 鹿小纯0831 | 来源:发表于2018-09-07 10:03 被阅读45次

    想想还是有必要上一波官话的:

    • RecyclerViewsupport:recyclerview-v7中提供的控件,最低兼容到android 3.0版本。
    • RecyclerViewListView的更高级和灵活的版本。
    • 提供了LinearLayoutManager(类listview)、GridLayoutManager(类gridview)、StaggeredGridLayoutManager(瀑布流)三种排列方式。

    简单来说,RecyclerView是控件里的C位也不为过了。

    一、Why And What?

    RecyclerView是我平时开发工作中可以说是最常用的组件了,所以做适当并且适合的封装是必须的。
    目前关于RecyclerView的封装可以说不要太多哦,为什么我还要选择自己封装呢?
    其实在做这个封装之前我也浏览过很多大神对于RecyclerView的封装,但是普遍存在两个基本的问题:

    • 只针对某一方面做封装
    • 封装内容过多且复杂,很多都是我用不上的
    我自己对于RecyclerView的需求有哪些
    1. 万用型的ViewHolder,不用再每个界面写ViewHolder
    2. 继承自RecyclerView.Adapter的基类Adapter,负责管理RecyclerView的数据。
    3. 下拉刷新和上拉加载(这个麻烦的地方在于要有一定的特效,虽然很多封装都实现了,但是大多简陋,优美的少,很影响用户体验)。
    4. 多种布局的实现,即列表中存在不同的布局。
    5. 简洁地实现item中数据和view的绑定。
    6. 将一个RecyclerView置于一个BaseFragment中,通过BaseFragment来实现一个最简单RecyclerView的页面。

    二、ViewHolder和RecyclerView.Adapter

    public class ViewHolderRocket extends RecyclerView.ViewHolder {
        private SparseArray<View> views;
        private View mItemView;
    
        public ViewHolderRocket(View itemView) {
            super(itemView);
            views = new SparseArray<>();
            mItemView = itemView;
        }
    
        public View getmItemView() {
            return mItemView;
        }
    
        public View getView(int resId) {
            return retrieveView(resId);
        }
    
        protected <V extends View> V retrieveView(int viewId){
            View view = views.get(viewId);
            if(view == null){
                view = mItemView.findViewById(viewId);
                views.put(viewId,view);
            }
            return (V) view;
        }
    
        public TextView getTextView(int resId){
            return retrieveView(resId);
        }
    
        public ImageView getImageView(int resId){
            return retrieveView(resId);
        }
    
        public Button getButton(int resId){
            return retrieveView(resId);
        }
    
        public LinearLayout getLinearLayout(int resId){return retrieveView(resId);}
    
        public void setText(int resId,CharSequence text){
            getTextView(resId).setText(text);
        }
    
        public void setText(int resId,int strId){
            getTextView(resId).setText(strId);
        }
    }
    

    ViewHolderRocket继承自RecyclerView.ViewHolder,将itemview存储在SparseArray数组中,避免了重复的findViewById,提高了效率和性能。

    关于BaseAdapter,最主要的作用就是对于数据的操作,包括初始设置、新增、删除和清除所有。
    一下代码展示了除了这些之外的另一个重要作用,那就是用于不同布局的实现:

    public class RVBaseAdapter<C extends RVCell> extends RecyclerView.Adapter<ViewHolderRocket> {
        private static final String TAG = "RVBaseAdapter";
        protected List<C> mData;
    
        public RVBaseAdapter() {
            mData = new ArrayList<>();
        }
    
        public List<C> getData() {
            return mData;
        }
    
        @Override
        public int getItemViewType(int position) {
            return mData.get(position).getItemType();
        }
    
        @Override
        public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
        }
    
        @Override
        public void onBindViewHolder(ViewHolderRocket holder, int position) {
            mData.get(position).bindViewHolder(holder, position);
        }
    
        @Override
        public void onViewDetachedFromWindow(ViewHolderRocket holder) {
            super.onViewDetachedFromWindow(holder);
            Log.e(TAG, "onViewDetachedFromWindow invoke...");
            //释放资源
            int position = holder.getAdapterPosition();
            //越界检查
            if (position < 0 || position >= mData.size()) {
                return;
            }
            mData.get(position).releaseResource();
        }
    
        @Override
        public int getItemCount() {
            return mData == null ? 0 : mData.size();
        }
    
        /**
         * add one cell
         *
         * @param cell
         */
        public void add(C cell) {
            mData.add(cell);
            int index = mData.indexOf(cell);
            notifyItemInserted(index);
        }
    
        public void add(int index, C cell) {
            mData.add(index, cell);
            notifyItemInserted(index);
        }
    
        /**
         * remove a cell
         *
         * @param cell
         */
        public void remove(C cell) {
            int indexOfCell = mData.indexOf(cell);
            remove(indexOfCell);
        }
    
        public void remove(int index) {
            mData.remove(index);
            notifyItemRemoved(index);
        }
    
        /**
         * @param start
         * @param count
         */
        public void remove(int start, int count) {
            if ((start + count) > mData.size()) {
                return;
            }
            mData.subList(start, start + count).clear();
            notifyItemRangeRemoved(start, count);
        }
    
    
        /**
         * add a cell list
         *
         * @param cells
         */
        public void addAll(List<C> cells) {
            if (cells == null || cells.size() == 0) {
                return;
            }
            Log.e(TAG, "addAll cell size:" + cells.size());
            mData.addAll(cells);
            notifyItemRangeInserted(mData.size() - cells.size(), mData.size());
        }
    
        public void addAll(int index, List<C> cells) {
            if (cells == null || cells.size() == 0) {
                return;
            }
            mData.addAll(index, cells);
            notifyItemRangeInserted(index, index + cells.size());
        }
    
        public void clear() {
            mData.clear();
            notifyDataSetChanged();
        }
    }
    

    关于不同布局的实现,我们留到后面再讲。

    三、下拉刷新和上拉加载

    想要实现下拉刷新和上拉加载,说简单也简单,Google官方自己提供了SwipeRefreshLayout控件来帮助我们实现下拉刷新,上拉加载也可以通过监控列表中最后一个数据的位置来判断是否要加载。
    但是效果也就是一般般,对于已经习惯了微博、微信、支付宝那些强大的刷新加载功能的用户来说,他们当然不会满足于这种最原始的方式。
    想要做的酷炫,就要做出炫酷的动画特效,自己做的话实在是力不从心啊,没有扎实的view基础,真的望洋兴叹的感觉。
    还有有大神帮我们解决了这个问题:

    强大且易用的SmartRefreshLayout

    RvFragment中有可以设置Header和Footer的方法,以及设置是否支持下拉刷新和上拉加载的方法:

     /**
         * 是否启用下拉刷新功能
         *
         * @param enable
         */
        protected void setEnableRefresh(boolean enable) {
            refreshLayout.setEnableRefresh(enable);
        }
    
        /**
         * 是否启用上拉加载功能
         *
         * @param enable
         */
        protected void setEnableLoadMore(boolean enable) {
            refreshLayout.setEnableLoadMore(enable);
        }
    
     /**
         * 设置下拉刷新Header
         *
         * @param header
         */
        protected void setRefreshHeader(RefreshHeader header) {
            refreshLayout.setRefreshHeader(header);
        }
    
        /**
         * 设置上拉加载Footer
         * @param footer
         */
        protected void setRefreshFooter(RefreshFooter footer) {
            refreshLayout.setRefreshFooter(footer);
        }
    

    感兴趣的同学可以看看,有很强大的HeaderFooter样式。

    四、多种布局的实现

    这本来是产品经理提出的需求

    • 用在数据页面,能够在列表里面根据某些特殊的数据来展示不同的页面
    • 用在首页,一个RecyclerView就展示全复杂的主页
    public interface Cell {
        /**
         * 回收资源
         */
        void releaseResource();
    
        /**
         * 获取viewType
         *
         * @return
         */
        int getItemType();
    
        /**
         * 绑定ViewHolder
         *
         * @param holder
         * @param position
         * @return
         */
        void bindViewHolder(RVBaseViewHolder holder, int position);
    
    }
    
    public abstract class RVCell<T> implements Cell {
        public T mData;
    
        public RVCell(T t) {
            mData = t;
        }
    }
    

    实现:

    public class TestCell extends RVCell<PersonBean> {
    
        public TestCell(PersonBean bean) {
            super(bean);
        }
    
        @Override
        public void releaseResource() {
    
        }
    
        @Override
        public int getItemType() {
            return R.layout.rv_item;
        }
    
        @Override
        public void bindViewHolder(final ViewHolderRocket holder, final int position) {
            holder.setText(R.id.store_name_tv, mData.getName());
            holder.setText(R.id.store_address_tv, mData.getAge());
            holder.setText(R.id.store_owner_tv, mData.getPhone());
            final View mItemView = holder.getmItemView();
            mItemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText( mItemView.getContext(), position + "", Toast.LENGTH_SHORT).show();
                    Intent intent = new Intent(mItemView.getContext(), MainActivity.class);
                    mItemView.getContext().startActivity(intent);
                }
            });
        }
    }
    

    这时候我们再回头看上面的RVBaseAdapter

    RVBaseAdapter<C extends RVBaseCell> extends RecyclerView.Adapter<RVBaseViewHolder>{
        protected List<C> mData;
        public RVBaseAdapter() {
            mData = new ArrayList<>();
        }
    }
    

    RVBaseCell持有数据mData和视图holder,即一个Item
    接下来我们看RVAdapter的三个重要方法:

      @Override
        public int getItemViewType(int position) {
            return mData.get(position).getItemType();
        }
    
        @Override
        public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
            return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
        }
    
        @Override
        public void onBindViewHolder(ViewHolderRocket holder, int position) {
            mData.get(position).bindViewHolder(holder, position);
        }
    
    1. CellgetItemType()方法返回的是item的布局layout
    2. RVAdaptergetItemViewType方法,return “布局的id”。
    3. onCreateViewHolder(ViewGroup parent, int viewType)中的viewType即是getItemViewType方法返回的布局id,根据这个id获取视图,并返回ViewHolderRocket
    4. onBindViewHolder中调用mDatabindViewHolder方法,然后在TestCell(实际的实现方式)实现该方法。
    5. mDatabindViewHolder方法中实现item中数据和view的绑定。

    五、基于RecyclerViewRvBaseFragment

    public abstract class RvFragment<T> extends Fragment {
        public static final String TAG = "RvFragment";
        private FrameLayout titleLayout;
        protected RecyclerView mRecyclerView;
        protected RVAdapter mBaseAdapter;
        protected SmartRefreshLayout refreshLayout;
        protected int pageNumber = 1;
    
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            return inflater.inflate(R.layout.rv_base_fragment_layout, null);
        }
    
        @Override
        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            initView(view);
            initRefreshListener();
            initRecycleView();
            onRecyclerViewInitialized();
        }
    
        private void initView(View view) {
            titleLayout = view.findViewById(R.id.title_layout);
            refreshLayout = view.findViewById(R.id.refreshLayout);
            mRecyclerView = view.findViewById(R.id.base_fragment_rv);
            mRecyclerView.setLayoutManager(initLayoutManger());
        }
    
        private void initRefreshListener() {
            refreshLayout.setOnRefreshListener(new OnRefreshListener() {
                @Override
                public void onRefresh(@NonNull RefreshLayout refreshlayout) { //下拉刷新
                    refreshlayout.finishRefresh(500);
                    pageNumber = 1;
                    RvFragment.this.onRefresh();
                }
            });
            refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
                @Override
                public void onLoadMore(@NonNull RefreshLayout refreshlayout) { //上拉加载
                    refreshlayout.finishLoadMore(500);
                    pageNumber++;
                    RvFragment.this.onLoadMore();
                }
            });
            refreshLayout.setRefreshHeader(new ClassicsHeader(this.getActivity()).setPrimaryColor(colorUtil.getColorPrimary(this.getActivity())));
            refreshLayout.setRefreshFooter(new ClassicsFooter(this.getActivity()).setPrimaryColor(getResources().getColor(R.color.material_gray_300)));
        }
    
        /**
         * 设置下拉刷新Header
         *
         * @param header
         */
        protected void setRefreshHeader(RefreshHeader header) {
            refreshLayout.setRefreshHeader(header);
        }
    
        /**
         * 设置上拉加载Footer
         * @param footer
         */
        protected void setRefreshFooter(RefreshFooter footer) {
            refreshLayout.setRefreshFooter(footer);
        }
    
        private void initRecycleView() {
            mBaseAdapter = initAdapter();
            mRecyclerView.setAdapter(mBaseAdapter);
        }
    
        /**
         * 设置头布局,不在列表中
         *
         * @param view
         */
        protected void addTitleView(View view) {
            if (view == null) {
                return;
            }
            titleLayout.addView(view);
        }
    
        /**
         * 子类可以自己指定Adapter,如果不指定默认RVSimpleAdapter
         *
         * @return
         */
        protected RVAdapter initAdapter() {
            return new RVAdapter();
        }
    
        /**
         * 子类自己指定RecyclerView的LayoutManager,如果不指定,默认为LinearLayoutManager,VERTICAL 方向
         *
         * @return
         */
        protected RecyclerView.LayoutManager initLayoutManger() {
            LinearLayoutManager manager = new LinearLayoutManager(getContext());
            manager.setOrientation(LinearLayoutManager.VERTICAL);
            return manager;
        }
    
        /**
         * 是否启用下拉刷新功能
         *
         * @param enable
         */
        protected void setEnableRefresh(boolean enable) {
            refreshLayout.setEnableRefresh(enable);
        }
    
        /**
         * 是否启用上拉加载功能
         *
         * @param enable
         */
        protected void setEnableLoadMore(boolean enable) {
            refreshLayout.setEnableLoadMore(enable);
        }
    
        /**
         * RecyclerView 初始化完毕,可以在这个方法里绑定数据
         */
        public abstract void onRecyclerViewInitialized();
    
        /**
         * 下拉刷新
         */
        public abstract void onRefresh();
    
        /**
         * 上拉加载更多
         */
        public abstract void onLoadMore();
    
        /**
         * 根据实体生成对应的Cell
         *
         * @param list 实体列表
         * @return cell列表
         */
        protected abstract List<Cell> getCells(List<T> list);
    
    }
    

    实际使用:

    public class RecyclerViewFragment extends RvFragment {
    
        public static RecyclerViewFragment newInstance() {
            RecyclerViewFragment fragment = new RecyclerViewFragment();
            return fragment;
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        @Override
        public void onRecyclerViewInitialized() {
            mBaseAdapter.showLoading();
            ArrayList<PersonBean> dataList = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                PersonBean bean = new PersonBean();
                bean.setName("姓名" + i + "");
                bean.setAge("20");
                bean.setPhone("18956321458");
                dataList.add(bean);
            }
            mBaseAdapter.removeLoading();
            mBaseAdapter.addAll(getCells(dataList));
        }
    
        @Override
        public void onRefresh() {
            mBaseAdapter.clear();
            ArrayList<PersonBean> dataList = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                PersonBean bean = new PersonBean();
                bean.setName("姓名" + i + "");
                bean.setAge("20");
                bean.setPhone("18956321458");
                dataList.add(bean);
            }
            mBaseAdapter.addAll(getCells(dataList));
            Toast.makeText(this.getActivity(), "更新啦", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onLoadMore() {
            ArrayList<PersonBean> dataList = new ArrayList<>();
            for (int i = 10 * (pageNumber - 1); i < pageNumber * 10; i++) {
                PersonBean bean = new PersonBean();
                bean.setName("姓名" + i + "");
                bean.setAge("20");
                bean.setPhone("18956321458");
                dataList.add(bean);
            }
            mBaseAdapter.addAll(getCells(dataList));
        }
    
        @Override
        protected List<Cell> getCells(List list) {
            List<Cell> cells = new ArrayList<>();
            for (int i = 0; i < list.size(); i++) {
                PersonBean bean = (PersonBean) list.get(i);
                cells.add(new TestCell(bean));
            }
            return cells;
        }
    }
    

    相关文章

      网友评论

        本文标题:超强赛亚版RecyclerView(多种布局)的使用封装

        本文链接:https://www.haomeiwen.com/subject/fjcnwftx.html