我想要说
RecycleView 它是一个多种视图的总称,它可以展示像我上个月学习到的 ListView、GirdView,还有探索的瀑布流、各种布局混杂在一起。之前在了解 RecycleView 的时候,它也确实能做到。不过最近我在做一个入门的 APP ,发现就是横向的 ListView 无法直接去实现,然后发现 RecycleView 就能够去实现我想要的布局,才发现 RecycleView 的重要性。
不过 RecycleView 的灵活程度也特别的高,视图的复用管理程度也很高,难度可能会高一些,自己实现想要的布局还需要多花费一些时间的学了大概两三周的 Android 发现绝大部分都是需要去自己继承类来重写某些方法,也相当于是对 java 面向对象开发基础部分的一个复习吧。感觉 RecycleView 的内容会有一些多,所以记录的内容也会很多。
目前学习了三种布局方式:LinearLayoutManager、GridLayoutManager 和 StaggeredGridLayoutManager。将会主要以这三种布局管理进行说明。
好啦,现在正式开始吧。
开始的前提
在开始 RecycleView 的学习之旅之前,在 build.gradle 中添加一下依赖
compile 'com.android.support:design:26.0.1'
注意:第一位的 26 对应的是 compileSdkVersion 的版本号。
可能出现的问题
RecyclerView 相关
在重新编译的时候发现找不到,可以参考以下的解决办法
通过修改版本号的后两位应该能够解决此问题。
OOM 问题
OOM,也就是 Out of memory,即内存超出。
当使用图片和 addItemDecoration 方法时可能会导致内存溢出,目前还没有找到具体的解决办法。但是为了学习 RecyclerView 暂时在使用 List 布局时使用
唯一的解决办法就是在使用图片时不使用 addItemDecoration 方法
可能出现的错误如下
02-19 10:50:44.230 4504-4787/top.bestguo.androidlayout E/Bitmap: OOM allocating Bitmap with dimensions 15859853 x 15895097
2 月 20 号,好吧,我知道我哪里有问题了。要正确的获取配置文件的像素单位需要用以下方法获取😭😭😭,这样就不会出现 OOM 的问题了。
getResources().getDimensionPixelOffset(R.dimen.xxx);
这也是导致出现 OOM 的原因,不排除有其它的可能导致 OOM 错误的发生。
AppCompatTextView 无法实例化
在 RecyclerView 的依赖导入进来,可能会爆出以下错误,虽然会出现 AppCompatTextView 无法实例化的情况,在经过测试的时候这并不会影响到程序的正常运行
所以,直接不管它。😜😜😜
RecyclerView 方法介绍
在学习时,我学习到 RecyclerView 的三种方法
setLayoutManager 方法
参数:LayoutManager
该方法用于设置哪种类型的布局,通过实例化布局类的方式来创建布局。
下面将会介绍三种布局管理类。到后面厉害的话,你就直接写一个自己个 LayoutManager 出来,给我们这群懒人用(bushi)
setAdapter 方法
在学习 RecyclerView 之前,我们已经使用过这个方法。并且还是继承了 BaseAdapter 这个方法,然后还自己写了一个 ViewHolder。Adapter 是用来将数据展示到 item 当中的,ViewHolder 就是将每一个 item 中的组件给封装起来,用于后续调用的。
但是,在这里面,我们不能够继承自 BaseAdapter,那要写很多东西。我们需要继承自 RecyclerView.Adapter 这个抽象类,该抽象类在 RecyclerView 的内部,所以是内部类,而且还是带泛型的,该类的泛型必须是继承自 Recycler.ViewHolder 类的。所以我们自己创建 ViewHolder 类时还需继承自该类。
需要实现的方法有这三个:
class XXXAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
继承自 ViewHolder 的时候,写法和之前自己写 ViewHolder 有些差别
private class LinearViewHolder extends RecyclerView.ViewHolder {
// StatementComponent......
LinearViewHolder(View itemView) {
super(itemView);
// findViewToDo......
}
}
后续的写法将在示例中详细的解释
setItemDecoration
这个方法用于设置 item 的样式,不过我只学习到利用这个来实现边框🤭🤭🤭,后面在研究的时候将会单独的写一篇日志,做一些好玩的东西出来。但是也正是因为设置了这个才导致了内存不足的问题。害,这个问题后续在解决吧。
布局管理类介绍
LinearLayoutManager
这是一个线性布局的管理类
构造方法
它有两种常用的构造方法
方法 | 说明 |
---|---|
public LinearLayoutManager(Context context) |
一个参数,传入当前的 Activity 对象,默认是垂直且顺序的排列下来。 |
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) |
除了可以传递 Activity 还能指定是水平排列和竖直排列,还能指定是否逆序。 |
在指定水平或者垂直的方式时,这些布局管理类中都有 2 个常量用来指定是否垂直和水平,水平布局就是按照
XXXManager.HORIZENTAL
的格式,垂直布局就是XXXManager.VERTICAL
的格式
它的展示效果和之前学习的 ListView 的效果是一样的。不过它比 ListView 更厉害,这可以水平排列的。在没有 RecyclerView 之前需要引入第三方的包来使得 ListView 横向排列,现在有了这个是非常方便的。
GridLayoutManager
这是一个网格布局的管理类,它有两种常用的构造方法。不过查看源码发现它是继承自 LinearLayoutManager 类的,嘻嘻。
构造方法
它也具有两种构造方法。
方法 | 说明 |
---|---|
GridLayoutManager(Context context, int spanCount) |
两个参数,传入当前的 Activity 对象并且指定一行几列,默认是垂直且顺序的排列下来。 |
public GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) |
除了可以传递以上参数以外,还能指定水平和垂直的方式,以及是否逆序 |
第二个构造方法也就是 LinearLayoutManger 中的大同小异。
StaggeredGridLayoutManager
这是瀑布流的一个管理类,不过它的本意并不是瀑布,而是错开的网格布局。瀑布流在 app 中特别常见,比如我们的淘宝,拼多多这种电商 app 中就使用了这种瀑布流的布局形式。
构造方法
它具有一种构造方法。
方法 | 说明 |
---|---|
StaggeredGridLayoutManager(int spanCount, int orientation) |
两个参数,一个指定一行几列,另一个指定排列方式。 |
不过很遗憾,RecyclerView 并不像 ListView 和 GridView 一样有点击事件。不过没关系,我们可以自己创造一个点击事件。利用接口回调的方式自己写一个点击事件的方法。
示例
以下的示例展示出 LinearLayoutManager、GridLayoutManager 和 StaggeredGridLayoutManager 三种布局的效果
示例1
布局文件
展示线性布局的文件
该 activity 的布局文件的内容非常简单,里面是一个 RecyclerView 标签
对应的 activity_linear_recycler_view.xml 如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:background="#eee"
android:id="@+id/rv_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
item 布局文件
在 item 中,我给它放了一个简单的 TextView 来放置 item 中的样式
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#fff">
<TextView
android:id="@+id/tv_recycle_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="测试文字"
android:textColor="@color/colorBlack"
android:textSize="20sp"/>
</LinearLayout>
java 代码
Activity 代码
获取到 RecyclerView 这个组件时,这两个步骤是必须的,顺序可以打乱。
- 设置布局管理类
- 设置 Adapter
可选步骤
3. 设置偏移量等布局方式
代码如下
public class LinearRecyclerViewActivity extends AppCompatActivity {
private RecyclerView rv_list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_linear_recycler_view);
rv_list = findViewById(R.id.rv_list);
// 添加布局管理器,这里添加的是线性布局管理器,默认为竖直方向
// rv_list.setLayoutManager(new LinearLayoutManager(this));
// 将其设置成水平方向,默认不逆序展示
rv_list.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
// 添加一个 Adapter 用于渲染全部的 item
rv_list.setAdapter(new LinearAdapter(this));
// 添加一个 Decoration
rv_list.addItemDecoration(new MyDecoration());
}
}
以上的代码指定了一个 LinearLayoutManager 的布局管理类,传入当前的实例对象,设置为水平摆放且不设置为逆序。
设置的 Adapter 需要我们继承自 RecyclerView.Adapter 类,该类是 RecyclerView 中的内部类。
在重写 Adapter 之前,我们需要自己写一个 ViewHolder,并且继承自 RecyclerView.ViewHolder。本示例自己写了一个 LinearViewHolder 类
相关的代码如下:
// 自定义 LinearViewHolder
class LinearViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
LinearViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_recycle_item);
}
TextView getTextView() {
return textView;
}
}
自定义 ViewHolder 完成之后,此时我们需要继承一个 RecyclerView.Adapter,本示例中自己编写了 LinearAdapter 类。
代码如下:
// 新建的 Adapter
class LinearAdapter extends RecyclerView.Adapter<LinearViewHolder> {
private Context context;
LinearAdapter(Context context){
this.context = context;
}
@Override
public LinearViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 将 layout_list_recycler_item.xml 布局文件填充到 RecyclerView 中。
return new LinearViewHolder(LayoutInflater.from(context).inflate(R.layout.layout_list_recycler_item, parent, false));
}
@Override
public void onBindViewHolder(LinearViewHolder holder, final int position) {
holder.getTextView().setText("菜单选项_" + position);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(LinearRecyclerViewActivity.this, "点击了第 " + position + " 项", Toast.LENGTH_SHORT).show();
}
});
}
// 设置多少个 item
@Override
public int getItemCount() {
return 20;
}
}
onCreateViewHolder 方法中将我们自己写好的 LinearViewHolder 类实例化出去即可,同时也能够使用 ViewType 来返回不同的 ViewHolder 实现更加复杂的布局方式。
onBindViewHolder 方法就是给对每一个 item 项进行设置。比如以上的示例,可以设置字体,设置一个点击的监听事件等等。
getItemCount 方法是返回多少个 item。
自己创建了一个 Decoration,在此之前,设置了一个灰色的背景色。通过偏移量将灰色的背景展示出来,从而使用户感受到一种错觉,感觉就是有边框一样。
代码如下:
// 创建一个自己的 Decoration
class MyDecoration extends RecyclerView.ItemDecoration {
// 设置偏移量
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(0, 0, 0, getResources().getDimensionPixelOffset(R.dimen.divideHeight));
}
}
运行效果
示例2
示例 2 和 示例 3 都差不多,继承的 Adapter 和 ViewHolder 都是来自 RecyclerView 中的内部类,很多部分,很多写法其实都是大同小异的。后面的布局方式也就不详细介绍了。
布局文件
展示网格布局的文件
activity_grid_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorGray">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_grid"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
item 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="@color/colorAccent"
android:scaleType="centerCrop"/>
<TextView
android:padding="7dp"
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="测试文字"
android:textSize="14dp"
android:gravity="center"/>
</LinearLayout>
java 代码
Activity 代码
当前示例中使用的 GridLayoutManager 类,传入了当前的 Activity 类,还指定了一行 3 列。如果有逆序和水平的需要,使用 HORIZENTAL 常量、设置成 true 即可
public class GridRecyclerViewActivity extends AppCompatActivity {
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_grid_recycler_view);
recyclerView = findViewById(R.id.recycler_view_grid);
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
recyclerView.setAdapter(new GridRecyclerViewAdapter(this));
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int gap = getResources().getDimensionPixelOffset(R.dimen.space);
outRect.set(gap, gap, gap, gap);
}
});
}
}
Adapter 代码
public class GridRecyclerViewAdapter extends RecyclerView.Adapter<GridRecyclerViewAdapter.GridViewHolder> {
private Context context;
@Override
public GridRecyclerViewAdapter.GridViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new GridViewHolder(LayoutInflater.from(context).inflate(R.layout.layout_grid_item, parent, false));
}
@Override
public void onBindViewHolder(GridRecyclerViewAdapter.GridViewHolder holder, final int position) {
// 加载网络图片
Glide.with(context).load("http://www.topacg.com/wp-content/uploads" +
"/2020/03/frc-11c619718c036bf579c246cdd07e6d77.jpeg").into(holder.imageView);
holder.textView.setText("灶门祢豆子");
}
@Override
public int getItemCount() {
return 20;
}
class GridViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
TextView textView;
public GridViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.header);
textView = itemView.findViewById(R.id.name);
}
}
}
运行效果
示例3
布局文件
展示瀑布流布局的文件
activity_pu_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorGray">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_pu"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
item 布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image_asd"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
java 代码
Activity 代码
当前示例中使用的 GridLayoutManager 类,指定了一行 3 列,设置成垂直的瀑布流效果。如果有水平的需要,使用 HORIZENTAL 常量即可
public class PuRecyclerViewActivity extends AppCompatActivity {
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pu_recycler_view);
recyclerView = findViewById(R.id.recycler_view_pu);
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
recyclerView.setAdapter(new PuAdapter(this));
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int gap = getResources().getDimensionPixelOffset(R.dimen.space);
outRect.set(gap, gap, gap, gap);
}
});
}
}
Adapter 代码
public class PuAdapter extends RecyclerView.Adapter<PuAdapter.PuViewHolder> {
private Context context;
public PuAdapter(Context context) {
this.context = context;
}
@Override
public PuViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new PuViewHolder(LayoutInflater.from(context).inflate(R.layout.layout_pu_recycler_item, parent, false));
}
@Override
public void onBindViewHolder(PuViewHolder holder, int position) {
switch (position % 3) {
case 1:
Glide.with(context).load("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattachments.gfan.com%2Fforum%2F201501%2F15%2F1" +
"05418z33cyjacahzodgc3.jpg&refer=http%3A%2F%2Fattachments.gfan.com&a" +
"pp=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg").into(holder.imageView);
break;
case 2:
Glide.with(context).load("https://ss1.baidu.com/9vo3dSag_xI4khGko9WTAnF" +
"6hhy/zhidao/pic/item/4610b912c8fcc3ce1a3113699045d688d53f20f3.jpg").into(holder.imageView);
break;
default:
Glide.with(context).load("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fblog%2F201307%2F" +
"23%2F20130723121038_WxiVJ.jpeg&refer=http%3A%2F%2Fb-ssl.duitang.com&app" +
"=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg").into(holder.imageView);
break;
}
}
@Override
public int getItemCount() {
return 21;
}
static class PuViewHolder extends RecyclerView.ViewHolder {
private ImageView imageView;
public PuViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.image_asd);
}
}
}
运行效果
事件
上文也提到过 RecyclerView 并没有针对 item 来专门指定事件监听的方法。但是我们可以自己写一个接口,然后去实现这个接口,最后进行接口回调。(其实所谓的接口回调就是利用了面向对象的多态性,子类可以类型提升为父类的类型,父类强制转换成子类。)这样的目的目前是可以省去你在自己的 Adapter 类中频繁的修改代码,同时也降低的代码的耦合性。
现在我们以示例 2 的代码来举例。在下面的代码中,我们需要加载网络图片,那加载网络图片的时候肯定是需要得到它的组件。所以,我们在加载网络图片之后可以设置其点击的监听事件。以下我设置了两个事件,一个短按,一个长按。
事先在内部类中写好两个接口,并且利用该类型声明好成员变量。代码如下
public class GridRecyclerViewAdapter extends RecyclerView.Adapter<GridRecyclerViewAdapter.GridViewHolder> {
private Context context;
private ItemOnClickListener clickListener; // 短按事件
private ItemOnLongClickListener longClickListener; // 长按事件
// 构造方法,一个用于传递 Activity 类,一个用于传入一个已实现的该接口的点击类,另一个用于传入一个已实现的该接口长按的点击类
public GridRecyclerViewAdapter(Context context, ItemOnClickListener clickListener, ItemOnLongClickListener longClickListener){
this.context = context;
this.clickListener = clickListener;
this.longClickListener = longClickListener;
}
......
@Override
public void onBindViewHolder(GridRecyclerViewAdapter.GridViewHolder holder, final int position) {
// 加载网络图片
Glide.with(context).load("http://www.topacg.com/wp-content/uploads" +
"/2020/03/frc-11c619718c036bf579c246cdd07e6d77.jpeg").into(holder.imageView);
holder.textView.setText("灶门祢豆子");
// 短按事件
holder.imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 短按事件接口回调
clickListener.click(v, position);
}
});
// 长按事件
holder.imageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// 长按事件接口回调
return longClickListener.click(v, position);
}
});
}
......
/**
* 点击事件
*/
interface ItemOnClickListener{
void click(View v, int position);
}
/**
* 长按点击事件
*/
interface ItemOnLongClickListener{
boolean click(View v, int position);
}
}
然后在 Activity 类中实现短按和长按的类,将其实例化传入构造方法中
public class GridRecyclerViewActivity extends AppCompatActivity {
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_grid_recycler_view);
recyclerView = findViewById(R.id.recycler_view_grid);
recyclerView.setLayoutManager(new GridLayoutManager(this, 3));
recyclerView.setAdapter(new GridRecyclerViewAdapter(this, new ClickListener(), new LongClickListener()));
}
......
// 实现短按的点击事件接口
private class ClickListener implements GridRecyclerViewAdapter.ItemOnClickListener {
@Override
public void click(View v, int position) {
Toast.makeText(GridRecyclerViewActivity.this, "我是祢豆子 " + (position + 1) + " 号", Toast.LENGTH_SHORT).show();
}
}
// 实现长按的点击事件接口
private class LongClickListener implements GridRecyclerViewAdapter.ItemOnLongClickListener {
@Override
public boolean click(View v, int position) {
ImageView imageView = v.findViewById(R.id.header);
Glide.with(GridRecyclerViewActivity.this).load("http://pic.17qq.com/img_qqtouxiang/87450489.jpeg").into(imageView);
Toast.makeText(GridRecyclerViewActivity.this, "嘻嘻,偷偷抱走" + (position + 1) + "号祢豆子", Toast.LENGTH_SHORT).show();
return true;
}
}
}
最后的效果如下,没错,又是我可爱的祢豆子还有善逸拿来举例😝😝😝
后记
项目的代码都在 AndroidStudy 的 github 仓库中。
本日志对应的代码:点我进入
请勿发布违反中国大陆地区法律的言论,请勿人身攻击、谩骂、侮辱和煽动式的语言。