RecyclerView 的基本使用

2017-01-22  本文已影响276人  大豆油

RecyclerView 的基本使用

2017年1月21日

为了加强理解 RecyclerView 的使用方法,同时让自己的写作能力得到锻炼,所以实践一把,都是一些基本的使用,主要分析下拉刷新与上拉加载部分,细节很重要,以此来激励自己写出高质量的文章。


效果图

基本使用

RecyclerView 的基本介绍

RecyclerView 是谷歌V7包下新增的控件,用来替代 ListView 的使用,在 RecyclerView 标准化了 ViewHolder 类似于 ListView中 convertView 用来做视图缓存.

相信大家对于 ListView 都很熟悉,在我刚学 Android 开发的时候接触就是这个强大的控件,也被称为最难的控件之一。自从 RecyclerView 已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明 RecyclerView 拥有比 ListView,GridView 之类控件有很多的优点。

但是对于点击事件需要自己写回调借口实现,下面会详细介绍,并且没有了 ListView 强大的 addFootView() 的方法,需要自己去实现,不过还好拓展性很强。

代码实现

1.添加库依赖:首先要用这个控件,你需要在gradle文件中添加包的引用(配合官方CardView使用)

compile 'com.android.support:recyclerview-v7:25.0.0'
compile 'com.android.support:cardview-v7:25.0.0'

2.然后在 xml 文件里实现

<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recycler_view"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true"/>

3.接着就是在 Activity 里面设置

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private RecyclerAdapter mAdapter;
    private LinearLayoutManager mLayoutManager;
    private List<ShareBean> mData = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();//初始化数据
        initView();//初始化布局
        setListener();//设置监听事件
    }

    private void initData() {
        for (int i = 0; i < 4; i++) {
            mData.add(new ShareBean(R.mipmap.ic_image, "Android vs IOS"));
        }
    }

    private void initView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
        //设置布局管理器
        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);
        //确保尺寸是一个常数,避免计算每个item的size
        mRecyclerView.setHasFixedSize(true);
        //设置显示动画
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mAdapter = new RecyclerAdapter(this, mData);
        mRecyclerView.setAdapter(mAdapter);
    }

    private void setListener() {
        //设置Items的点击事件
        mAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(final View view, int position) {
                onClick(view, position);
            }
        });
      }
}

4.适配器 Adapter 的代码

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

    public  OnItemClickListener itemClickListener;
    private List<ShareBean> mData = null;
    private LayoutInflater mInflater;

    public RecyclerAdapter(Context context, List<ShareBean> mData) {
        this.mData = mData;
        this.mInflater = LayoutInflater.from(context);
    }

    //将布局转化为 View 并传递给 RecyclerView 封装好的 ViewHolder
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){    
        // 实例化展示的view
        View view = mInflater.inflate(R.layout.item_recyclerview, parent, false);
        // 实例化viewholder
        return new ItemViewHolder(view);
    }

    //将数据与视图进行绑定
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ((ItemViewHolder) holder).mImageView.setImageResource(mData.get(position).getImage_id());
        ((ItemViewHolder) holder).mTextView.setText(mData.get(position).getName());
    }

    //返回 Item 的数量
    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();;
    }

    public  class ItemViewHolder extends RecyclerView.ViewHolder {

        CardView mCardView;
        CircleImageView mImageView;
        TextView mTextView;

        public ItemViewHolder(View itemView) {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.textView);
            mCardView = (CardView) itemView.findViewById(R.id.cardView);
            mImageView = (CircleImageView) itemView.findViewById(R.id.image);
            mCardView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (itemClickListener != null) {
                        itemClickListener.onItemClick(view, getPosition());
                    }
                }
            });
        }
    }

    //以下为点击事件的接口回调部分
    public void setOnItemClickListener(OnItemClickListener itemClickListener) {
        this.itemClickListener = itemClickListener;
    }

    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }
}

以上是适配器基本的写法,这个自定义 Adapter 和我们在使用 Listview 时候的 Adapter 相比还是有点不太一样的,首先这边我们需要继承 RecyclerView.Adaper 类,然后实现两个重要的方法 onBindViewHodler() 以及 onCreateViewHolder() ,这边我们看出来区别,使用 RecyclerView 控件我们就可以把 ItemView 视图创建和数据绑定这两步进行分来进行管理,用法就更加方便而且灵活了,并且我们可以定制打造千变万化的布局。同时这边我们还需要创建一个 ViewHolder 类,该类必须继承自 RecyclerView.ViewHolder 类,现在 Google 也要求我们必须要实现 ViewHolder 来承载 Item 的视图。

特别注意一下最下面的点击事件的回调函数,主要是对接口的熟悉程度,Java 是硬伤 55555.

同时 RecyclerView 有三种实现方式(上面介绍有),通过一下代码设置

mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//这里用线性显示 类似于listview
mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//这里用线性宫格显示 类似于grid view
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流


SwipeRefreshLayout 配合实现下拉刷新

基本介绍

SwipeRefrshLayout 是 Google 官方更新的一个 Widget ,可以实现下拉刷新的效果。该控件集成自 ViewGroup 在 support-v4 兼容包下,不过我们需要升级 supportlibrary 的版本到19.1以上。基本使用的方法如下:

代码实现

1.首先先看一下 xml 布局,在 RecyclerView 布局外部嵌套一层 SwipeRefreshLayout 布局即可。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="visible"/>

    </android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

2.在 Activity 中获取 SwipeRefreshLayout 控件并且设置 OnRefreshListener 监听器,同时实现里边的 onRefresh() 方法,在该方法中进行网络请求最新数据,然后刷新 RecyclerView 列表同时设置 SwipeRefreshLayout 的进度Bar的隐藏或者显示效果。具体代码如下:

mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
          @Override
          public void onRefresh() {
              addTopData();
          }
      });

  private void addTopData() {
           new Handler().postDelayed(new Runnable() {
               @Override
               public void run() {
                   for (int i = 0; i < 5; i++) {
                       mData.add(i, new ShareBean(R.mipmap.ic_image_h, "下拉刷新数据" + i));
                   }
                   mAdapter.notifyDataSetChanged();
                   mSwipeRefreshLayout.setRefreshing(false);
               }
           }, 1000);
       }

RecyclerView 实现上拉加载

滚动事件的分析

1.RecyclerView 本身已经提供了滑动的监听接口,OnScrollListener,这个接口包含了以下的方法,代码注释介绍。

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
      //当recycleView的滑动状态改变时回调
       @Override
       public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
           super.onScrollStateChanged(recyclerView, newState);
       }

      //当RecycleView滑动之后被回调
       @Override
       public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
           super.onScrolled(recyclerView, dx, dy);
      }
});

2.RecyclerView 的滑动状态

3.滑动位置的监听
3.1 以下是最简单的顶部/底部的判断方式:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
      //当recycleView的滑动状态改变时回调
       @Override
       public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
           super.onScrollStateChanged(recyclerView, newState);

          //获取最后一个可见view的位置
          int lastItemPosition = linearManager.findLastVisibleItemPosition();
          //获取第一个可见view的位置
          int firstItemPosition =linearManager.findFirstVisibleItemPosition();

          if (newState == RecyclerView.SCROLL_STATE_IDLE && lastItemPosition + 1 == adapter.getItemCount()) {
          //最后一个itemView的position为adapter中最后一个数据时,说明该itemView就是底部的view了
          //需要注意position从0开始索引,adapter.getItemCount()是数据量总数
          }

          //同理检测是否为顶部itemView时,只需要判断其位置是否为0即可
          if (newState == RecyclerView.SCROLL_STATE_IDLE && firstItemPosition == 0) {}
       }
});

以上的实现方式存在诸多问题,比如:

3.2 对于显示不全的问题
也就是说:当某一个itemView只显示一部分的时候,此时已经算是一个position了,此时很可能去触发判断条件。所以官方 Api 里有一下两个方法:

就可以尽可能的避免这种问题。

3.3 检测数据不够时的状态

如果为了避免这种情况,我们就需要对在检测时多进行一步,若当前的RecycleView显示的itemView不满屏的情况下,其实并不存在滑动的说法(没有加载更多,因为根本数据还没有满屏显示),至于下拉刷新的,还是可以的,但一般都会使用SwipeRefreshLayout实现下拉刷新了.因此这里主要考虑的是关于滑动到底部加载更多的问题.

3.4 上拉加载更多代码实现

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (dy > 0) {//向下滚动
            int visibleItemCount = mLayoutManager.getChildCount();
            int totalItemCount = mLayoutManager.getItemCount();
            int pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();

            if (!loading && (visibleItemCount + pastVisiblesItems) == totalItemCount) {
                loading = true;
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 5; i++) {
                            mData.add(new ShareBean(R.mipmap.ic_image_s, "上拉加载数据" + i));
                        }
                        mAdapter.notifyDataSetChanged();
                        loading = false;
                    }
                }, 3000);
            }
        }
    }
});
添加 FooterView 实现上拉加载

接下来完善UI,让用户知道确实在上拉加载的过程。用RecyclerView实现,使用 getItemType() 方法,就与 ListView 差不多了。不说多了,直接上完整代码(注释很清晰):

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

    public  OnItemClickListener itemClickListener;
    private List<ShareBean> mData = null;
    private LayoutInflater mInflater;

    //1.加入布局状态标志-用来判断此时加载是普通Item还是footView
    //没有更多了
    public static final int NO_DATA_MORE = 0;
    //上拉加载更多
    public static final int PULLUP_LOAD_MORE = 1;
    //正在加载中
    public static final int LOADING_MORE = 2;

    private int load_more_status = 0;

    private static final int TYPE_ITEM = 0;  //普通Item View
    private static final int TYPE_FOOTER = 1;  //顶部FootView

    public RecyclerAdapter(Context context, List<ShareBean> mData) {
        this.mData = mData;
        this.mInflater = LayoutInflater.from(context);
    }

    //4.接着onCreateViewHolder(ViewGroup parent,int viewType)加载布局的时候根据viewType的类型来选择指定的布局创建,返回即可:
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//        将布局转化为View并传递给Recycler封装好的ViewHolder
        if (viewType == TYPE_ITEM) {
            View view = mInflater.inflate(R.layout.item_recyclerview, parent, false);
            return new ItemViewHolder(view);
        } else if (viewType == TYPE_FOOTER) {
            View foot_view = mInflater.inflate(R.layout.part_footer, parent, false);
            return new BottomViewHolder(foot_view);
        }
        return null;
    }

    //5.最后进行判断数据的时候(onBindViewHolder),判断holder的类型来进行判定数据即可.
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof ItemViewHolder) {
            ((ItemViewHolder) holder).mImageView.setImageResource(mData.get(position).getImage_id());
            ((ItemViewHolder) holder).mTextView.setText(mData.get(position).getName());
        } else if (holder instanceof BottomViewHolder) {
            BottomViewHolder footViewHolder = (BottomViewHolder) holder;
            switch (load_more_status) {
                case PULLUP_LOAD_MORE:
                    footViewHolder.mFooterTv.setText("上拉加载更多...");
                    footViewHolder.mProgressBar.setVisibility(View.VISIBLE);
                    break;
                case LOADING_MORE:
                    footViewHolder.mFooterTv.setText("正在加载中...");
                    footViewHolder.mProgressBar.setVisibility(View.VISIBLE);
                    break;
                case NO_DATA_MORE:
                    footViewHolder.mFooterTv.setText("----我是有底线的----");
                    footViewHolder.mProgressBar.setVisibility(View.GONE);
                    break;
            }
        }
    }

    //3.重写getItemViewType方法来判断返回加载的布局的类型
    @Override
    public int getItemViewType(int position) {
        if (position + 1 == getItemCount()) {
            return TYPE_FOOTER;
        } else {
            return TYPE_ITEM;
        }
    }

    //2.返回item数量
    @Override
    public int getItemCount() {
        return mData.size() + 1;
    }

    /**
     * //上拉加载更多
     * PULLUP_LOAD_MORE=0;
     * //正在加载中
     * LOADING_MORE=1;
     * //加载完成已经没有更多数据了
     * NO_MORE_DATA=2;
     *
     * @param status
     */
    public void changeMoreStatus(int status) {
        load_more_status = status;
        notifyDataSetChanged();
    }

    public  class ItemViewHolder extends RecyclerView.ViewHolder {

        CardView mCardView;
        CircleImageView mImageView;
        TextView mTextView;

        public ItemViewHolder(View itemView) {
            super(itemView);
            mTextView = (TextView) itemView.findViewById(R.id.textView);
            mCardView = (CardView) itemView.findViewById(R.id.cardView);
            mImageView = (CircleImageView) itemView.findViewById(R.id.image);
            mCardView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (itemClickListener != null) {
                        itemClickListener.onItemClick(view, getPosition());
                    }
                }
            });
        }
    }

     /**
     * 底部FootView布局
     */
    public class BottomViewHolder extends RecyclerView.ViewHolder {

        ProgressBar mProgressBar;
        TextView mFooterTv;

        public BottomViewHolder(View itemView) {
            super(itemView);
            mProgressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar);
            mFooterTv = (TextView) itemView.findViewById(R.id.footer_tv);
        }
    }

    public void setOnItemClickListener(OnItemClickListener itemClickListener) {
        this.itemClickListener = itemClickListener;
    }

}

终于分析完了,感觉滑动需要继续探究,,,嗯嗯,先就这样吧。


参考文章及总结

以上说对基础知识的一些梳理,存在诸多不足,请多指正。

主要参考文章:

上一篇 下一篇

猜你喜欢

热点阅读