RecyclerView 的基本使用
2017年1月21日
为了加强理解 RecyclerView 的使用方法,同时让自己的写作能力得到锻炼,所以实践一把,都是一些基本的使用,主要分析下拉刷新与上拉加载部分,细节很重要,以此来激励自己写出高质量的文章。
- 基本用法
- 基本介绍
- 代码实现
-
SwipeRefreshLayout 配合实现下拉刷新
- 基本介绍
- 代码实现
-
RecyclerView 实现上拉加载
- 滚动事件的分析
- 添加 FooterView 实现上拉加载
- 参考文章及总结
效果图
基本使用
RecyclerView 的基本介绍
RecyclerView 是谷歌V7包下新增的控件,用来替代 ListView 的使用,在 RecyclerView 标准化了 ViewHolder 类似于 ListView中 convertView 用来做视图缓存.
相信大家对于 ListView 都很熟悉,在我刚学 Android 开发的时候接触就是这个强大的控件,也被称为最难的控件之一。自从 RecyclerView 已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明 RecyclerView 拥有比 ListView,GridView 之类控件有很多的优点。
- 设置布局管理器以控制Item的布局方式,横向、竖向以及瀑布流方式
- 可设置Item操作的动画(删除或者添加等)
- 可设置Item的间隔样式(可绘制)
但是对于点击事件需要自己写回调借口实现,下面会详细介绍,并且没有了 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以上。基本使用的方法如下:
- setOnRefreshListener(OnRefreshListener):添加下拉刷新监听器
- setRefreshing(boolean):显示或者隐藏刷新进度条
- isRefreshing():检查是否处于刷新状态
- setColorSchemeResources():设置进度条的颜色主题,最多设置四种,以前的setColorScheme()方法已经弃用了
代码实现
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 的滑动状态
当前的recycleView不滑动(滑动已经停止时):
public static final int SCROLL_STATE_IDLE = 0;当前的recycleView被拖动滑动:
public static final int SCROLL_STATE_DRAGGING = 1;当前的recycleView在滚动到某个位置的动画过程,但没有被触摸滚动.调用 scrollToPosition(int) 应该会触发这个状态:
public static final int SCROLL_STATE_SETTLING = 2;
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) {}
}
});
以上的实现方式存在诸多问题,比如:
- itemView会在滑动过程中只显示一部分或者一半
- 检测数据不够时的RecycleView
3.2 对于显示不全的问题
也就是说:当某一个itemView只显示一部分的时候,此时已经算是一个position了,此时很可能去触发判断条件。所以官方 Api 里有一下两个方法:
- findFirstCompletlyVisibleItemPosition()
- findLastCompletlyVisibleItemPosition()
就可以尽可能的避免这种问题。
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;
}
}
终于分析完了,感觉滑动需要继续探究,,,嗯嗯,先就这样吧。
参考文章及总结
以上说对基础知识的一些梳理,存在诸多不足,请多指正。
主要参考文章: