文章目录
【1】什么是RecyclerView?我们为什么要是用RecyclerView?
- 我是这样理解的:recyclerView是一个容器,含有多个itemView,其中的ViewHolder进行了对多个View滑动查看时的优化效果,关于这点,可以去看看recyclerView的回收机制。也就是说recyclerView是我们Android开发中在需要滑动组件时候的首选。
- 效果展示:
简单使用:
自定义ItemDecoration(高级使用):
【2】RecyclerView如何使用(简单基础版)
①在需要用到滑动组件的地方添加RecyclerView控件(容纳多个View的可滑动组件)
要实现上图,我们的recyclerView
要在activity_main.xml
中:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mRecycle"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
②在layout文件中添加item.xml文件(这是每一个item的布局文件)
我们创建item.xml
文件这是每个View的样式,我们可以创建多个不一样的,但是在这里我们只创建一个一样的就可以了。在这里我们使用RelativeLayout
布局,里面放一个TextView
就行了,记得给一个id,方便我们等会设置需要显示的文本。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent">
<TextView
android:id="@+id/rv_text"
android:layout_width="match_parent"
android:layout_height="60dp"
android:textSize="20sp"
android:gravity="center"/>
</RelativeLayout>
③创建适配器
创建一个类继承RecyclerView.Adapter
,这时还需要传入一个RecyclerView.ViewHolder
的子类作为泛型放到Adapter,我们通常直接在这个类中,写一个静态内部类继承自RecyclerView.ViewHolder
就达到目的了,同时,我们还必须重写3个方法onCreateViewHolder
、onBindViewHolder
、getItemCount
。通常我们从外部传入数据进来(我们需要显示的内容等信息)也就是这里的starList
,因此我们重写构造方法,将数据传入。
getItemCount: 返回值是这个recyclerView中有多少个子View,通常是传入数据的个数。
onCreateViewHolder: 返回值是创建的ViewHolder对象,通常我们通过LayoutInnflater
将item.xml
文件转换为View,再将这个View作为参数传给ViewHolder的构造方法。
同时我们在ViewHolder
(自己写的继承自RecyclerView.ViewHolder
)中通过findViewById
给对应的控件赋值,方便我们等下在onBindViewHolder中通过对应控件的id绑定数据。
onBindViewHolder: 绑定数据,显示数据。
public class StarAdapter extends RecyclerView.Adapter<StarAdapter.ViewHolder> {
private List<Star> starList;
private Context context;
public StarAdapter(List<Star> starList, Context context) {
this.starList = starList;
this.context = context;
}
@NonNull
@Override
public StarAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_text,null);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull StarAdapter.ViewHolder holder, int position) {
holder.textView.setText(starList.get(position).getName());
}
@Override
public int getItemCount() {
return starList == null ? 0 : starList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.rv_text);
}
}
}
④设置recyclerView的各种属性如itemDecoration、LayoutManager、适配器等。
public class MainActivity extends AppCompatActivity {
private List<Star> starList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.mRecycle);
init();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
//没有需求就可以不用装饰
recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
recyclerView.setAdapter(new StarAdapter(starList,this));
}
private void init() {
starList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 20; j++) {
if(i % 2 == 0){
starList.add(new Star("清华大学"+j,"C9联盟"+i));
}else{
starList.add(new Star("重庆大学"+j,"985高校"+i));
}
}
}
}
}
【1】通常我们不需要设置itemDecoration,要使用的话系统为我们提供了DividerItemDecoration类(基于LinearLayout)。
【2】设置布局管理器recyclerView.setLayoutManager(new LinearLayoutManager(this));
通常我们设置LinearLayoutManager就行了,有几个构造方法,最简单的就是直接给一个Context的
【3】最后设置adapter就行了recyclerView.setAdapter(new StarAdapter(starList,this));
⑤简单使用效果:
这里就没做动图了,和开篇一样就是增加了一个分割线recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
添加了一个系统的装饰器。
【3】RecyclerView的高阶使用(自定义Decoration)
理解三个方法:
onDraw
、onDrawOver
、getItemOffsets
我们来实现一个如下图功能的RecyclerView(吸顶效果):
其他的东西我们不需要改变,唯一需要改变的就是自定义ItemDecoration,并设置RecyclerView的装饰为自定义的装饰就可以了。话不多少,直接开始自定义ItemDecoration。
先贴上代码:
public class StarDecoration extends RecyclerView.ItemDecoration {
private int groupHeadHeight;
private Context context;
private Paint mPaint;
private Paint headPaint;
public StarDecoration(Context context) {
this.context = context;
groupHeadHeight = dp2px(context,100);
mPaint = new Paint();
headPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(50);
headPaint.setColor(Color.RED);
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter){
StarAdapter starAdapter = (StarAdapter) parent.getAdapter();
//当前屏幕的item个数
int count = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < count; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildLayoutPosition(view);
//是头部
if(starAdapter.isGroupHead(position) && view.getTop() - parent.getPaddingTop() - groupHeadHeight>=0){
c.drawRect(left, view.getTop()-groupHeadHeight,right,view.getTop(),headPaint);
c.drawText(starAdapter.getGroupName(position),left+10,view.getTop()-groupHeadHeight/2+10,
mPaint);
}
}
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if(parent.getAdapter() instanceof StarAdapter){
StarAdapter starAdapter = (StarAdapter) parent.getAdapter();
int left = parent.getPaddingLeft();
int top = parent.getPaddingTop();
int right = parent.getWidth() - parent.getPaddingRight();
int position = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
//获取对应VIew
View view = parent.findViewHolderForAdapterPosition(position).itemView;
boolean isGroupHead = starAdapter.isGroupHead(position+1);
if (isGroupHead){
int bottom = Math.min(groupHeadHeight,view.getBottom() - top);
c.drawRect(left,top,right,top+bottom,headPaint);
c.drawText(starAdapter.getGroupName(position),left+10,top + bottom - groupHeadHeight/2+10,mPaint);
}else{
c.drawRect(left,top,right,top+groupHeadHeight,headPaint);
c.drawText(starAdapter.getGroupName(position),left+10,top+groupHeadHeight/2+10,mPaint);
}
}
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if(parent.getAdapter() instanceof StarAdapter){
StarAdapter starAdapter = (StarAdapter) parent.getAdapter();
int position = parent.getChildLayoutPosition(view);
boolean isGroupHead = starAdapter.isGroupHead(position);
if(isGroupHead){
outRect.set(0,groupHeadHeight,0,0);
}else {
outRect.set(0,2,0,0);
}
}
}
private int dp2px(Context context,float dpValue){
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue *scale * 0.5f);
}
}
根据我的步骤来,保证你能懂:
- 我们重写时,只需要注意其中的三个方法就行了:
getItemOffsets
onDraw
onDrawOver
①我们先开看看getItemOffsets,其实理解了里面的outRect也就理解了他。
- 什么是outRect?
set的4个参数:左、上、右、下。不是说设置这个会让itemView变小,不是这样的,设置这个其实是一个预留的空间,比如设置左上右下全是50,那么每个itemView都如图所示:
现在应该懂了什么是outRect了吧。
②我们再来看看onDraw和onDrawOver有什么不同
我们可以这样理解:onDraw -> itemView -> onDrawOver。先绘制onDraw,绘制完成后再绘制每一个itemView,itemView绘制完后再绘制onDrawOver。onDrawOver在最上层,会覆盖掉前面所绘制的内容,这也是吸顶的精髓所在。
绘制思路:
-
【1】首先我们判断,这一个VIew是不是一个组的头部,如果是,我们在这个View的上面留一个大的空间,我们在这个大的空间中绘制组头。
-
【2】要想实现吸顶的效果那么顶部就一直有一个组头,直到下一个组头把他推上去,要想一直存在,那就应该是在最上层绘制,因为这样可以覆盖掉itemView和onDraw的绘制内容。一直显示在bar下面(在onDrawOver中绘制组头)
-
【3】难点来了,我们怎样实现一个组头把onDrawOver中的组头推上去?,推是一个动画,那么什么东西是改变的呢?bottom!对就是吸附在bar下面的组头的bottom是慢慢变小的,也就看起来像推了上去。
大概思路就是如此,剩下的就是明确的算法了。算法不太好讲,所以直接看上面的自定义ItemDecoration的代码。
整个项目工程的代码:
mainActivity:
public class MainActivity extends AppCompatActivity {
private List<Star> starList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = findViewById(R.id.mRecycle);
init();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.addItemDecoration(new StarDecoration(this));
recyclerView.setAdapter(new StarAdapter(starList,this));
}
private void init() {
starList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 20; j++) {
if(i % 2 == 0){
starList.add(new Star("清华大学"+j,"C9联盟"+i));
}else{
starList.add(new Star("重庆大学"+j,"985高校"+i));
}
}
}
}
}
Star:
public class Star {
private String name;
private String groupName;
public Star(String name, String groupName) {
this.name = name;
this.groupName = groupName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
}
StarAdapter:
public class StarAdapter extends RecyclerView.Adapter<StarAdapter.ViewHolder> {
private List<Star> starList;
private Context context;
public StarAdapter(List<Star> starList, Context context) {
this.starList = starList;
this.context = context;
}
@NonNull
@Override
public StarAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_text,null);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull StarAdapter.ViewHolder holder, int position) {
holder.textView.setText(starList.get(position).getName());
}
@Override
public int getItemCount() {
return starList == null ? 0 : starList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private TextView textView;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.rv_text);
}
}
/**
* 是否是组的头部
* @param position
* @return
*/
public boolean isGroupHead(int position){
if(position == 0){
return true;
}else{
String currentGroupName = getGroupName(position);
String preGroupName = getGroupName(position - 1);
if (currentGroupName.equals(preGroupName)){
return false;
}else
return true;
}
}
/**
*
* @param position
* @return
*/
public String getGroupName(int position){
return starList.get(position).getGroupName();
}
}
StarDecoration:
public class StarDecoration extends RecyclerView.ItemDecoration {
private int groupHeadHeight;
private Context context;
private Paint mPaint;
private Paint headPaint;
public StarDecoration(Context context) {
this.context = context;
groupHeadHeight = dp2px(context,100);
mPaint = new Paint();
headPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(50);
headPaint.setColor(Color.RED);
}
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
if (parent.getAdapter() instanceof StarAdapter){
StarAdapter starAdapter = (StarAdapter) parent.getAdapter();
//当前屏幕的item个数
int count = parent.getChildCount();
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < count; i++) {
View view = parent.getChildAt(i);
int position = parent.getChildLayoutPosition(view);
//是头部
if(starAdapter.isGroupHead(position) && view.getTop() - parent.getPaddingTop() - groupHeadHeight>=0){
c.drawRect(left, view.getTop()-groupHeadHeight,right,view.getTop(),headPaint);
c.drawText(starAdapter.getGroupName(position),left+10,view.getTop()-groupHeadHeight/2+10,
mPaint);
}
}
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
if(parent.getAdapter() instanceof StarAdapter){
StarAdapter starAdapter = (StarAdapter) parent.getAdapter();
int left = parent.getPaddingLeft();
int top = parent.getPaddingTop();
int right = parent.getWidth() - parent.getPaddingRight();
int position = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
//获取对应VIew
View view = parent.findViewHolderForAdapterPosition(position).itemView;
boolean isGroupHead = starAdapter.isGroupHead(position+1);
if (isGroupHead){
//如果屏幕上显示的第一个View的下一个View是组头的话
//即将推上去
int bottom = Math.min(groupHeadHeight,view.getBottom() - top);
c.drawRect(left,top,right,top+bottom,headPaint);
c.drawText(starAdapter.getGroupName(position),left+10,top + bottom - groupHeadHeight/2+10,mPaint);
}else{
//如果屏幕上显示的第一个View的下一个View不是组头的话,就画一个head一直显示在bar下面
c.drawRect(left,top,right,top+groupHeadHeight,headPaint);
c.drawText(starAdapter.getGroupName(position),left+10,top+groupHeadHeight/2+10,mPaint);
}
}
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if(parent.getAdapter() instanceof StarAdapter){
StarAdapter starAdapter = (StarAdapter) parent.getAdapter();
int position = parent.getChildLayoutPosition(view);
boolean isGroupHead = starAdapter.isGroupHead(position);
if(isGroupHead){
outRect.set(0,groupHeadHeight,0,0);
}else {
outRect.set(0,2,0,0);
}
}
}
private int dp2px(Context context,float dpValue){
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue *scale * 0.5f);
}
}
activity_main.xml
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mRecycle"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
item_text.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="wrap_content"
android:background="@color/colorAccent">
<TextView
android:id="@+id/rv_text"
android:layout_width="match_parent"
android:layout_height="60dp"
android:textSize="20sp"
android:gravity="center"/>
</RelativeLayout>
【4】效果: