前言
既然在 Android 中已经有了 ListView 和 GridView 这样的控件,那么我们为什么还要去使用 RecyclerView 这个控件呢?首先的一点就在于之前的 ListView 和 GridView 的性能并不是非常好。尽管两者都有自己的缓存机制,但还是有可以改进的空间,比如使用 ViewHolder 来避免过多使用 findViewById 这样一个非常耗时的操作。而在 RecyclerView 中,直接将 ViewHolder 作为 RecyclerView 的一个子类,强制开发者使用它进行代码的优化。
RecyclerView 用法
RecyclerView 只关注与 item 的创建与回收,对于 item 的布局方式、item 的点击事件处理、动画效果、item 分隔等等一律不关注,这就是为什么 RecyclerView 的流畅度特别好的原因。
1. 申明布局控件
同 ListView 相似,首先需要在布局文件中申明 RecyclerView 这个控件。因为该控件是 Google 新推出的一个控件,为了兼容之前的 Android 版本,所以这个控件是放在 support.v7 的兼容库当中,因此需要添加相应的依赖,并且在引用时需要写完整的包名。
compile 'com.android.support:recyclerview-v7:24.2.1'
<android.support.v7.widget.RecyclerView
android:id="@+id/id_recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
2. 在代码中引用控件,自定义 item 布局
//在 Activity 中通过 findViewById 来找到控件
recycleView = (RecyclerView) findViewById(R.id.id_recyclerView);
建立自定义的 item 布局和 ListView 中过程完全一致,在此不贴代码了,附上效果图:
3. 新建 Adapter
根据 demo 的内容,我们新建 DramaAdapter 继承自 RecyclerView.Adapter,泛型为自定义的继承自 RecyclerView.ViewHolder 的内部类 DramaHolder,其父类中已经有了一个元素 itemView 对应列表中的每一个 item,所以我们自己只需要在 holder 中添加 item 布局中的每个控件,比如 TextView,ImageView 等等。同时需要复写自定义 ViewHolder 的构造方法,通过 findViewById 的方法将 holder 的各个属性与相应的控件绑定在一起,这样就可以避免过多的 findViewById 的操作,例如在我的 demo 中:
public DramaHolder(View itemView) {
super(itemView);
icon = (ImageView) itemView.findViewById(R.id.item_icon);
name = (TextView) itemView.findViewById(R.id.item_name);
progress = (TextView) itemView.findViewById(R.id.item_progress);
}
Adapter 中有三个方法需要复写,getItemCount (),onCreateViewHolder (),onBindViewHolder ()。
在 Adapter 中应有 List 类型成员变量,用于接收 Activity 中的数据源,getItemCount 返回 List 的长度即可,例如:
Override
public int getItemCount() {
return mDramaList.size();
}
Adapter 中最为关键的两个方法就是 onCreateViewHolder 和 onBindViewHolder,这两个方法的作用相当与我们在使用 ListView 自定义 Adapter 继承自 BaseAdapter 时复写的 getView () 方法作用是一致的,但是有所区别的就是 ListView 所接收的都是 View 类型的对象,而 RecyclerView 接收的都是 ViewHolder 对象。
在 onCreateViewHolder() 法中通过 LayoutInflater 将自定义的 item 布局转为 View 对象,再将该对象与一个 Viewholder 对象相关联,并返回该 ViewHolder 对象。
@Override
public DramaAdapter.DramaHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_list_item,parent,false);
DramaHolder holder = new DramaHolder(view);
return holder;
}
onBindViewHolder() 方法是真正将数据源与视图关联起来的函数所在了,根据传入的 position 拿到数据源中的 item 的各项数据,并通过 holder 的各个属性设置相应的数据,完成 Adapter 的功能。
@Override
public void onBindViewHolder(final DramaAdapter.DramaHolder holder, int position) {
Drama drama = mDramaList.get(position);
holder.icon.setImageResource(drama.getIconId());
holder.name.setText(drama.getName());
holder.progress.setText(drama.getProgress());
}
4. 设置 RecyclerView 的布局方式
我们现在所完成的工作是将数据与视图进行绑定,但是不要忘了我们之前所说的 RecyclerView 只关注于 item 的创建与回收,而对于布局等等问题并不关注。所以为了解决布局的问题,Android 中使用布局管理器(LayoutManager)来设置 RecyclerView 的布局样式, LayoutManager 是一个抽象类,Android 中内置了几种它的实现类,分别呈现不同的布局效果。
LinearLayoutManager
线性的布局,有 HORIZONTAL 和 VERTICAL 两种,可以实现普通的 ListView 的效果与横向 ListView 的效果,如以下两张图:
GridLayoutManager
实现之前 GridView 的效果
StaggeredGridLayoutManager
实现酷炫的瀑布流的效果
也正是由于布局样式与 Adapter 之间是分开的,所以我们可以修改极小部分的代码就可以在 ListView,GridView 的样式中自由切换,还可以瞬间实现极为炫酷的瀑布流的效果。只需让我们的 RecyclerView 设置不同的 LayoutManager 就好了。
recycleView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));
recycleView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));
recycleView.setLayoutManager(new GridLayoutManager(this,3));
recycleView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
5. 处理 item 的点击事件
在 RecyclerView 中并没有 ListView 中的 OnItemClickListener 和 OnItemLongClickListener 的接口,所以我们需要自己在 Adapter 中实现 item 的点击事件处理。虽然这样有一点麻烦,但也是有好处的,在 ListView 中,如果一个 item 中有一个 Button 时,那么如何处理这个 Button 的点击事件是非常麻烦的,而在 RecyclerView 中我们就可以为 item 中的任何一个控件设置独有的点击事件处理逻辑。这个点击事件的处理逻辑很显然应该在 onBindViewHolder() 方法中进行实现。
- 直接实现
//在这里我就对 item 中的图片设置了一个点击事件的处理,以及对整个 item 的 Click 处理和 longClick 处理
@Override
public void onBindViewHolder(final DramaAdapter.DramaHolder holder, int position) {
Drama drama = mDramaList.get(position);
holder.icon.setImageResource(drama.getIconId());
holder.name.setText(drama.getName());
holder.progress.setText(drama.getProgress());
holder.icon.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(),"你点击了第"+holder.getLayoutPosition()+"项的图片",Toast.LENGTH_SHORT).show();
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(),"你点击了第"+holder.getLayoutPosition()+"项",Toast.LENGTH_SHORT).show();
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
removeItem(holder.getLayoutPosition());
return true;
}
});
}
- 使用接口实现
通过接口将点击事件的处理转移到 Activity 中而不是在 Adapter 中显然是一种更好的方式。
首先定义一个接口:
public interface OnItemClickListener{
void onItemClick(View v, int position);
void onItemLongClick(View v, int position);
}
给我们自定义的 Adapter 增加 OnItemClickListener 类型的成员变量,并添加 setter 方法:
class DramaAdapter extends RecyclerView.Adapter<DramaViewHolder> {
OnItemClickListener mListener;
public void setOnItemClickListener(OnItemClickListener listener){
this.mListener = listener;
}
}
在 onBindViewHolder() 方法中,在 holder 的 itemView 等控件的点击或长点击事件中,调用接口成员变量 mListener 的 onItemClick() 或 onItemLongClick() 方法。
@Override
public void onBindViewHolder(final DramaAdapter.DramaHolder holder, int position) {
···
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mListener != null){
mListener.onItemClick(v, position);
}
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(mListener != null){
mListener.onItemLongClick(v, position);
return true;
}
return false;
}
});
}
6. item 的增加和删除
在 Adapter 中实现两个权限为 public 的方法供在 Activity 中调用:
//添加子项
public void addItem(int position){
Drama newOne = new Drama(R.mipmap.ic_launcher,"新的剧集","新的进度");
mDramaList.add(position,newOne);
notifyItemInserted(position);
}
public void removeItem(int position){
if (position < mDramaList.size()){
mDramaList.remove(position);
notifyItemRemoved(position);
}
}
在这里需要重点说明的是最后的
notifyItemInserted(position);
notifyItemRemoved(position);
在执行添加 item 必须通过 notifyItemInserted(position) 来刷新视图
在执行删除 item 必须通过 notifyItemRemoved(position) 来刷新视图
而不是之前在 ListView 中使用 notifyDataSetChanged() 的方法。
结束语
在上述六个步骤完成后,一个简单的 RecyclerView 的 demo 也就完成了,而对于 item 之间的分隔以及添加、删除 item 的动画效果等,在这里暂不做介绍,在我进一步学习之后会有新的手记来记录这方面的知识。关于 item 的分隔,一个简单的实现的方法是在自定义的 item 布局中添加整体的 margin;而对于动画效果,有默认的样式,个人觉得效果还是不错的,不做深入研究的话是已经可以满足需求了。
热门评论
hello world