Android开发-RecyclerView控件高级的使用(含下拉刷新上拉加载分页)

前 言

Android RecyclerView控件是在2014年6月Google的I/O大会上推出的Android 5.0时特有的特性,该控件的功能非常强大,它代替了原来的ListView和GridView的控件,它除了能实现线性布局、网格布局样式外还可以实现瀑布流布局的样式。而且在RecyclerView在源码中实现了复用机制的功能,使在渲染界面和填充数据方面都比原来的ListView还要好,在滑动的过程中也比较顺畅。

那么下面就来看看如何使用RecyclerView吧,本文将介绍RecyclerView的线性布局样式、网格布局样式、瀑布流布局样式以及嵌套布局样式的使用。

RecyclerView线性布局样式的实现

在使用RecyclerView控件之前先在AS项目的build.gradle里引入相关的依赖包。

implementation 'com.android.support:recyclerview-v7:28.0.0'

从依赖包中可以看到,该依赖包是在v7包下的,所在layout目录下xml布局文件中使用该布局时要写全引用的路径:android.support.v7.widget.RecyclerView。布局如下:




    

    

    

        

        

    


在该布局中的控件com.scwang.smartrefresh.layout.SmartRefreshLayout是使用目前在GitHub上比较火的对RecyclerView进行实现下拉刷新上拉加载分页功能的开源库,具体的使用的方式可以去该开源的地址去看看,该开源库的地址为:https://github.com/scwang90/SmartRefreshLayout 。

在layout目录下创建一个xml布局,该布局是用来适配RecyclerView的线性布局加载的item条目的布局。该布局的样式如下:




    

        

    


    

    

    

    

    


接着创建一个Adapter来绑定视图、渲染和填充数据以及设置RecyclerView item条目的点击事件的监听接口。代码如下:

public class RecyclerViewAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private List mList;
    private MovieDataModel data;
    private OnItemClikListener mOnItemClikListener;

    public RecyclerViewAdapter(Context context, List mList) {
        this.mContext = context;
        this.mList = mList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_movie_layout, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        data = mList.get(position);
        RequestOptions options = new RequestOptions()
                .placeholder(R.drawable.img_default_movie)
                .error(R.drawable.img_default_movie);
        Glide.with(mContext).load(data.getDownimgurl()).apply(options).into(holder.home_item_movie_list_pic);
        holder.home_item_movie_list_title.setText(data.getDownLoadName());
        holder.home_item_movie_list_director.setText(data.getDirector());
        holder.home_item_movie_list_Starring.setText(data.getStarring());
        holder.home_item_movie_list_type.setText(data.getType());
        holder.home_item_movie_list_regions.setText(data.getRegions());

        if (mOnItemClikListener != null) {
            // 设置item条目短按点击事件的监听
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClikListener.onItemClik(holder.itemView, pos);
                }
            });

            // 设置item条目长按点击事件的监听
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClikListener.onItemLongClik(holder.itemView, pos);
                    return false;
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private ImageView home_item_movie_list_pic;
        private TextView home_item_movie_list_title;
        private TextView home_item_movie_list_director;
        private TextView home_item_movie_list_Starring;
        private TextView home_item_movie_list_type;
        private TextView home_item_movie_list_regions;

        public ViewHolder(View itemView) {
            super(itemView);
            home_item_movie_list_pic = (ImageView) itemView.findViewById(R.id.home_item_movie_list_pic);
            home_item_movie_list_title = (TextView) itemView.findViewById(R.id.home_item_movie_list_title);
            home_item_movie_list_director = (TextView) itemView.findViewById(R.id.home_item_movie_list_director);
            home_item_movie_list_Starring = (TextView) itemView.findViewById(R.id.home_item_movie_list_Starring);
            home_item_movie_list_type = (TextView) itemView.findViewById(R.id.home_item_movie_list_type);
            home_item_movie_list_regions = (TextView) itemView.findViewById(R.id.home_item_movie_list_regions);
        }
    }

    // 定义item条目点击事件接口
    public interface OnItemClikListener {
        void onItemClik(View view, int position);

        void onItemLongClik(View view, int position);
    }

    public void setItemClikListener(OnItemClikListener mOnItemClikListener) {
        this.mOnItemClikListener = mOnItemClikListener;
    }

}

最后在Activity或者Fragment中进行对数据的解析和设置。代码如下:

/**
 * RecyclerView线性布局样式
 */
public class RecyclerViewActivity extends BaseActivity {

    @BindView(R.id.mTitleBar)
    TitleBar mTitleBar;
    @BindView(R.id.loading_view_ll)
    LinearLayout loading_view_ll;
    @BindView(R.id.loading_view)
    ImageView mLoadingView;
    @BindView(R.id.refreshLayout)
    RefreshLayout refreshLayout;
    @BindView(R.id.rvMovieList)
    RecyclerView rvMovieList;

    private boolean refreshType;
    private int page;
    private int oldListSize;
    private int newListSize;
    private int addListSize;
    private String viewType;
    private RecyclerViewAdapter adapter;

    private List mList = new ArrayList<>();

    @Override
    protected int getLayoutId() {
        return R.layout.activity_recycler_view;
    }

    @Override
    protected void initView() {

        ButterKnife.bind(this);
        Intent intent = getIntent();
        viewType = intent.getStringExtra("ViewType");
        mTitleBar.setOnTitleBarListener(new OnTitleBarListener() {

            @Override
            public void onLeftClick(View v) {
                finish();
            }

            @Override
            public void onTitleClick(View v) {
            }

            @Override
            public void onRightClick(View v) {
            }
        });
        AnimationDrawable anim = (AnimationDrawable) mLoadingView.getDrawable();
        anim.start();

    }

    @Override
    protected void initData() {

        // 开启自动加载功能(非必须)
        refreshLayout.setEnableAutoLoadMore(true);
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(@NonNull final RefreshLayout refreshLayout) {
                refreshLayout.getLayout().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        refreshType = true;
                        page = 1;
                        parsingMovieListJson();
                        refreshLayout.finishRefresh();
                        refreshLayout.resetNoMoreData();//setNoMoreData(false);
                    }
                }, 2000);
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(@NonNull final RefreshLayout refreshLayout) {
                refreshLayout.getLayout().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        refreshType = false;
                        if (page > 2) {
                            ToastUtil.showToast("暂无更多的数据啦");
                            // 将不会再次触发加载更多事件
                            refreshLayout.finishLoadMoreWithNoMoreData();
                            return;
                        }
                        parsingMovieListJson();
                        refreshLayout.setEnableLoadMore(true);
                        refreshLayout.finishLoadMore();
                    }
                }, 2000);
            }
        });
        //触发自动刷新
        refreshLayout.autoRefresh();

    }

    private void parsingMovieListJson() {

        try {
            // 从assets目录中获取json数据,在真实的项目开发中需要通过网络请求从服务器json数据
            String jsonData = BaseTools.getAssetsJson(this, "movie" + page + ".json");
            if (refreshType && mList != null) {
                mList.clear();
                oldListSize = 0;
            } else {
                oldListSize = mList.size();
            }
            // 使用Google的Gson开始解析json数据
            Gson gson = new Gson();
            MovieBaseModel movieBaseModel = gson.fromJson(jsonData, MovieBaseModel.class);
            List movieDataModelList = movieBaseModel.getData();
            for (MovieDataModel movieDataModel : movieDataModelList) {
                MovieDataModel data = new MovieDataModel();
                data.setMovClass(movieDataModel.getMovClass());
                data.setDownLoadName(movieDataModel.getDownLoadName());
                data.setDownimgurl(movieDataModel.getDownimgurl());
                data.setDownLoadUrl(movieDataModel.getDownLoadUrl());
                data.setMvdesc(movieDataModel.getMvdesc());
                OtherBaseModel otherModelDesc = gson.fromJson(movieDataModel.getMvdesc(), OtherBaseModel.class);
                List headerList = otherModelDesc.getHeader();
                data.setDirector(headerList.get(1));
                data.setStarring(headerList.get(2));
                data.setType(headerList.get(3));
                data.setRegions(headerList.get(4));
                mList.add(data);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        newListSize = mList.size();
        addListSize = newListSize - oldListSize;

        if (refreshType) {
            // 设置RecyclerView样式为竖直线性布局
            rvMovieList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
            adapter = new RecyclerViewAdapter(this, mList);
            if (viewType.equals("NoDividingLine")) {
                mTitleBar.setTitle("线性布局样式");
            } else {
                mTitleBar.setTitle("线性布局(有分割线)样式");
                // 设置分割线
                rvMovieList.addItemDecoration(new CustomDecoration(
                        this, LinearLayoutManager.VERTICAL, R.drawable.divider_mileage, 15));
            }
            rvMovieList.setAdapter(adapter);
        } else {
            adapter.notifyItemRangeInserted(mList.size() - addListSize, mList.size());
            adapter.notifyItemRangeChanged(mList.size() - addListSize, mList.size());
        }
        page++;

        rvMovieList.setVisibility(View.VISIBLE);
        loading_view_ll.setVisibility(View.GONE);

        // item条目的点击事件回调
        adapter.setItemClikListener(new RecyclerViewAdapter.OnItemClikListener() {

            // 短按点击事件回调
            @Override
            public void onItemClik(View view, int position) {
                String videoTitle = mList.get(position).getDownLoadName();
                ToastUtil.showToast(videoTitle);
            }

            // 长按点击事件回调
            @Override
            public void onItemLongClik(View view, int position) {

            }
        });

    }

}

从以上代码可以看到,refreshLayout.setOnRefreshListener(new OnRefreshListener()方法是实现下拉刷新数据的功能,refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener()是实现上拉加载分页数据的功能。而 rvMovieList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false))这段代码是设置RecyclerView样式为竖直线性布局。 rvMovieList.addItemDecoration(new CustomDecoration(this, LinearLayoutManager.VERTICAL, R.drawable.divider_mileage, 15))这段代码是实现RecyclerView item条目之间的分割线的效果,而RecyclerView与ListView不同是RecyclerView默认是没有分割线的,如果开发者需要实现分割线功能的话只能自己定义。最后adapter.setItemClikListener(new RecyclerViewAdapter.OnItemClikListener()该回调的接口是实现RecyclerView item条目的点击事件回调。

在Activity里模拟以网络请求的方式从服务端获取json数据并解析设置到视图界面上,json的数据结构如下:

{
    "Code":200,
    "Msg":"数据获取成功",
    "Data":[
        {
            "movClass":"科幻片",
            "downLoadName":"海市蜃城",
            "downLoadUrl":"",
            "mvdesc":"{"header": ["别名:", "导演:孙田", "主演:刘端端,王滢,高强,刘依琳", "类型:科幻片 悬疑 科幻", "地区:大陆", "语言:国语", "上映:2019", "片长:91", "更新:2019-05-20 00:42:56", "总播放量:", "今日播放量:0", "总评分数:0", "评分次数:0"], "desc": "阿可是一个游戏公司的CEO兼游戏设计师,最近一直在设计一款即将上市的游戏《魔界通缉》,这款游戏为玩家提供了更加真实的体验,在最后的一次玩家测试时,一位玩家差点因为游戏丢了性命,且一直未查明原因。压力很大的阿可开始生活中经常出现幻觉。同时眼睛也出了问题。游戏设计如期完成,期间阿可一直阻止公司召开游戏发布会,希望能查清游戏的安全性,但被一个神秘人阻挠。游戏发布会如期召开,引起粉丝疯狂捧场,神秘人在发布会上也代替了阿可的发言。游戏上线后阿可发现游戏开始慢慢控制玩家,同时发生了越来越多的不可思议的事情。当阿可想删除游戏源代码时,所有玩家对阿可开始了真正的通缉……"}",
            "downimgurl":"https://tupian.tupianzy.com/pic/upload/vod/2019-05-20/201905201558283504.jpg",
            "mv_update_time":"2019-05-20",
            "downdtitle":"zuidam3u8,zuidall,迅雷下载",
            "id":"10313",
            "mv_md5_id":"4b406e86b4eaeea68fb7fc7c0608c348",
            "type":"00000000001"
        }
    ]
}

界面运行效果图如下:

其中的第一张图是RecyclerView线性布局样式默认没有分割线的运行效果图,第二种是自定义有分割线的运行效果图。

RecyclerView网格布局样式的实现

RecyclerView网格布局样式的实现只需要修改item布局以及部分Adapter、Activity的代码。

item布局的xml布局代码如下:




    

        

        

        

    

修改以下Activity中的代码:

// 设置RecyclerView样式为网格布局,一行显示3列
rvMovieList.setLayoutManager(new GridLayoutManager(this, 3));
adapter = new GridLayoutRVAdapter(this, mList);
rvMovieList.setAdapter(adapter);

可以看到,GridLayoutManager是设置RecyclerView样式为网格布局,其中的第二个参数设置一行显示3列item条目。

界面运行效果图如下:

RecyclerView瀑布流布局样式的实现

RecyclerView瀑布流布局样式可能在实际的项目开发中用的比较少,比较常用的是前两种的线性布局和网格布局,不过瀑布流样式在电商的App中用的比较多,比如淘宝的Android端App的首页就用到RecyclerView瀑布流布局样式。如下图:
Android开发-RecyclerView控件高级的使用(含下拉刷新上拉加载分页)_第1张图片
RecyclerView瀑布流布局样式可以在网格布局样式的基础来修改实现,首先修改item条目的布局样式,布局代码如下:




    

        

        

        

    

从布局文件代码中可以看到,瀑布流的代码和网格的布局有两处的不同,就是把图像的控件ImageView的高度修改为"wrap_content"值,而网格是改为固定值,还有一处就是把在瀑布流的图像控件ImageView添加了android:adjustViewBounds="true"属性,这个属性的作用就是使显示图片能够自适合来显示,使瀑布流样式的效果更好看。

接着修改Activity中的代码,修改的代码如下:

// 设置RecyclerView样式为瀑布流布局,一行显示2列
rvMovieList.setLayoutManager(new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL));
adapter = new StaggeredGridRVAdapter(this, mList);
rvMovieList.setAdapter(adapter);

代码中的StaggeredGridLayoutManager是设置RecyclerView样式为网格布局,其中的第一个参数设置一行显示2列item条目,第二个参数设置竖直显示。

界面运行效果图如下:

RecyclerView嵌套布局样式的实现

在实现RecyclerView嵌套布局样式之前先看一下嵌套布局的json数据结构:

{
    "reason":"成功的返回",
    "data":[
        {
            "title":"高清电影",
            "content":[
                {
                    "name":"异类侵袭",
                    "tag":"地区:大陆",
                    "url":"",
                    "thumbnail_pic_s":"https://tupian.tupianzy.com/pic/upload/vod/2019-05-19/201905191558252942.png"
                }
            ]
        },
        {
            "title":"热门剧集",
            "content":[
                {
                    "name":"筑梦情缘[DVD版]",
                    "tag":"地区:大陆",
                    "url":"",
                    "thumbnail_pic_s":"https://tupian.tupianzy.com/pic/upload/vod/2019-05-10/201905101557455675.jpg"
                }
            ]
        }
    ],
    "error_code":0
}

从json的数据结构中可以看出,"data"字段是一个数组字段,数组字段中包含若干个的集合字段,可以把数组字段当作是一个父布局的RecyclerView,若干个集合字段当作是一个子布局的RecyclerView。也就是说嵌套布局是由一个RecyclerView布局嵌套一个或多个RecyclerView的。

而嵌套布局样式主要是Adapter设置数据绑定时比较麻烦,首次定义一个父Adapter,在由父Adapter去映射到子Adapter。父Adapter代码如下:

public class MovieTypesAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private List mList;
    private MovieTypesDataModel data;
    private OnItemClikListener mOnItemClikListener;

    public MovieTypesAdapter(Context context, List mList) {
        this.mContext = context;
        this.mList = mList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.movie_types_title, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {

        data = mList.get(position);
        holder.movieTypesTitle.setText(data.getTitle());

        MovieTypesChildAdapter childAdapter = (MovieTypesChildAdapter) holder.movieTypesChild.getAdapter();
        //适配器复用
        if (childAdapter == null) {
            RecyclerView.LayoutManager manager = new GridLayoutManager(mContext, 3);
            holder.movieTypesChild.setLayoutManager(manager);
            holder.movieTypesChild.setAdapter(new MovieTypesChildAdapter(mContext, data.getContent(), position));
        } else {
            childAdapter.setData(data.getContent()); //重新设置数据
            childAdapter.notifyDataSetChanged();
        }

        if (mOnItemClikListener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClikListener.onItemClik(holder.itemView, pos);
                }
            });

            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    int pos = holder.getLayoutPosition();
                    mOnItemClikListener.onItemLongClik(holder.itemView, pos);
                    return false;
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return mList == null ? 0 : mList.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private TextView movieTypesTitle;
        private RecyclerView movieTypesChild;

        public ViewHolder(View itemView) {
            super(itemView);
            movieTypesTitle = (TextView) itemView.findViewById(R.id.movieTypesTitle);
            movieTypesChild = (RecyclerView) itemView.findViewById(R.id.movieTypesChild);
        }
    }

    public interface OnItemClikListener {
        void onItemClik(View view, int position);

        void onItemLongClik(View view, int position);
    }

    public void setItemClikListener(OnItemClikListener mOnItemClikListener) {
        this.mOnItemClikListener = mOnItemClikListener;
    }

}

子Adapter代码如下:

public class MovieTypesChildAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private List childList;
    private int mPosition;
    private MovieTypesDataModel.MovieTypesContentModel data;
    private OnItemClikListener mOnItemClikListener;

    public MovieTypesChildAdapter(Context context, List childList) {
        this.mContext = context;
        this.childList = childList;
    }

    public MovieTypesChildAdapter(Context context, List childList, int position) {
        this.mContext = context;
        this.childList = childList;
        this.mPosition = position;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_movie_grid, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        data = childList.get(position);
        RequestOptions options = new RequestOptions()
                .placeholder(R.drawable.img_default_movie)
                .error(R.drawable.img_default_movie);
        Glide.with(mContext).load(data.getThumbnail_pic_s()).apply(options).into(holder.iv_photo);
        holder.tv_name.setText(data.getName());
        holder.tv_regions.setText(data.getTag());
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int pos = holder.getLayoutPosition();
                NestRVActivity.instance.OnClickListener(mPosition, pos);

            }
        });

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
        });

    }

    @Override
    public int getItemCount() {
        return childList == null ? 0 : childList.size();
    }

    public void setData(List childList) {
        this.childList = childList;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        private ImageView iv_photo;
        private TextView tv_name;
        private TextView tv_regions;

        public ViewHolder(View itemView) {
            super(itemView);
            iv_photo = (ImageView) itemView.findViewById(R.id.iv_photo);
            tv_name = (TextView) itemView.findViewById(R.id.tv_name);
            tv_regions = (TextView) itemView.findViewById(R.id.tv_regions);
        }
    }

    public interface OnItemClikListener {
        void onItemClik(View view, int position);

        void onItemLongClik(View view, int position);
    }

    public void setItemClikListener(OnItemClikListener mOnItemClikListener) {
        this.mOnItemClikListener = mOnItemClikListener;
    }

}

界面运行效果图如下:

apk安装包下载体验地址

可以扫描以下二维码进行下载安装,或者点击以下链接 https://fukaimei.top/apk/RecyclerViewTest.apk 进行下载安装体验。

———————— The end ————————

如果您觉得这篇博客写的比较好的话,赞赏一杯咖啡吧~~
Android开发-RecyclerView控件高级的使用(含下拉刷新上拉加载分页)_第2张图片


Demo程序源码下载地址一(GitHub)
Demo程序源码下载地址二(码云)

你可能感兴趣的:(Android移动开发随笔)