Android RecyclerView 实战:从基础列表到多类型 Item、分割线与状态复用问题

前言

本文串起了 RecyclerView 的核心能力:

  • Adapter + ViewHolder:列表渲染的基础
  • LayoutManager:决定列表如何排布
  • 数据驱动绑定:position 对应数据源下标
  • notify 系列 API:全量刷新与局部刷新
  • 多 ViewType:混合样式列表的标准做法
  • 事件回调:让 Adapter 与外部解耦
  • 状态复用问题:用数据源绑定 UI 状态是关键

掌握这些之后,无论是新闻资讯流、商品列表、聊天记录,还是混排推荐页,RecyclerView 都能轻松应对。

RecyclerView列表式布局

在Android中可以使用RecyclerView实现列表式布局。

使用步骤

  1. 新工程默认存在于material库中,旧项目需要添加引用
javascript 复制代码
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
  1. 在布局中添加RecyclerView
xml 复制代码
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
  1. 设置RecyclerView的布局管理器
java 复制代码
RecyclerView recyclerView = findViewById(R.id.recycler_view);

//线性布局方式
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//线性方向改成横向
//layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
//为RecyclerView添加布局管理器
recyclerView.setLayoutManager(layoutManager);

//网格布局  指定一行显示2个item  
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
recyclerView.setLayoutManager(gridLayoutManager);

//瀑布流布局 
StaggeredGridLayoutManager layout = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layout);
  1. 设置适配器Adapter
java 复制代码
//设置适配器
ArticleCheckAdapter adapter = new ArticleCheckAdapter();
recyclerView.setAdapter(adapter);

//为适配器添加数据来源
ArrayList<Article> articles = createData();
adapter.setArticles(articles);
  1. 为RecyclerView添加分割线
java 复制代码
//创建一个默认的分割线样式
DividerItemDecoration decor = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
//decoration.setDrawable(getDrawable(R.drawable.item_div));//这种方式可以设定某个drawble为分割线
recyclerView.addItemDecoration(decor);

RecyclerView中的Adapter适配器

适配器中会对RecyclerView的item布局、显示数量做处理。

onCreateViewHolder

每当RecyclerView需要创建一个新的Item时,就会触发onCreateViewHolder方法,我们就是在这个方法里把item布局和RecyclerView做关联的。

java 复制代码
@NonNull
@Override
public ArticleAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    //查找item布局
    View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.item_article_simple, parent, false);

    //创建一个ViewHolder对itemView做管理
    MyViewHolder myViewHolder = new MyViewHolder(view);
    //ViewHolder和RecyclerView做关联
    return myViewHolder;
}

如果有很多种item样式,那么,可以根据ViewType做出判定处理

java 复制代码
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    Log.i(TAG, "onCreateViewHolder: viewType = " + viewType);
    //通过ViewType区分不同的item,并且return对应的ViewHolder
    switch (viewType) {
        case ITEM_TYPE_ARTICLE_2:
            View view2 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_picture2, parent, false);
            return new MyViewHolder2(view2);
        case ITEM_TYPE_ARTICLE_3:
            View view3 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_picture3, parent, false);
            return new MyViewHolder3(view3);
        case ITEM_TYPE_AD:
            View viewAd = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_ad, parent, false);
            return new AdViewHolder(viewAd);
        case ITEM_TYPE_ARTICLE_1:
        default:
            //查找item布局
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_simple, parent, false);

            //创建一个ViewHolder对itemView做管理
            MyViewHolder myViewHolder = new MyViewHolder(view);
            //ViewHolder和RecyclerView做关联
            return myViewHolder;
    }

}

onBindViewHolder

负责把数据和每个ViewHolder中的ItemView做关联

java 复制代码
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    Log.i(TAG, "onBindViewHolder: position =" + position);
    //通过position获取对应的viewtype
    int itemViewType = getItemViewType(position);
    Article article = articles.get(position);
    switch (itemViewType) {
        case ITEM_TYPE_ARTICLE_2:
            MyViewHolder2 holder2 = (MyViewHolder2) holder;
            holder2.ivPicture1.setImageResource(article.getPicture());
            holder2.ivPicture2.setImageResource(article.getPicture2());
            holder2.tvTitle.setText(article.getTitle());
            holder2.tvAnthor.setText(article.getAnthor());
            break;
        case ITEM_TYPE_ARTICLE_3:
            MyViewHolder3 holder3 = (MyViewHolder3) holder;
            holder3.ivPicture1.setImageResource(article.getPicture());
            holder3.ivPicture2.setImageResource(article.getPicture2());
            holder3.ivPicture3.setImageResource(article.getPicture3());
            holder3.tvTitle.setText(article.getTitle());
            holder3.tvAnthor.setText(article.getAnthor());
            break;
        case ITEM_TYPE_AD:
            AdViewHolder holderAd = (AdViewHolder) holder;
            holderAd.ivPicture.setImageResource(article.getAdPicture());
            break;
        case ITEM_TYPE_ARTICLE_1:
        default:
            MyViewHolder holderSimple = (MyViewHolder) holder;
            holderSimple.ivPicture.setImageResource(article.getPicture());
            holderSimple.tvTitle.setText(article.getTitle());
            holderSimple.tvAnthor.setText(article.getAnthor());
            break;
    }
}

getItemCount

告诉RecyclerView应该显示多少条数据,一般我们会把这个方法的返回值,和数据源的长度做关联。

java 复制代码
    @Override
    public int getItemCount() {
//        if (articles != null) {
//            return articles.size();
//        } else {
//            return 0;
//        }
        return articles != null ? articles.size() : 0;
    }

getItemViewType

返回给定位置的视图类型,这里返回的ViewType决定了onCreateViewHolder形参列表中的ViewType

java 复制代码
@Override
public int getItemViewType(int position) {
    Article article = articles.get(position);
    //根据自己的业务逻辑做判断,不同的数据,返回不同的ViewType
    if (article.isIdAd()) {
        return ITEM_TYPE_AD;
    } else if (article.getPicture() != 0 && article.getPicture2() == 0 && article.getPicture3() == 0) {
        return ITEM_TYPE_ARTICLE_1;
    } else if (article.getPicture() != 0 && article.getPicture2() != 0 && article.getPicture3() == 0) {
        return ITEM_TYPE_ARTICLE_2;
    } else {
        return ITEM_TYPE_ARTICLE_3;
    }
}

适配器的数据更新方式

注意:在变更、刷新item的时候,检查数据源、处理数据源同步更新,是一个好习惯!

java 复制代码
//告诉adapter,和你关联的数据源有变化,请重新根据数据做出调整
//刷新整个列表
notifyDataSetChanged();
//通过position移除指定位置的item
notifyItemRemoved(int position);
//通过position刷新指定位置的item
notifyItemChanged(int position);
//通过position在指定位置插入item
notifyItemInserted(int position);
//通过position将指定位置的item移动到新位置
notifyItemRemoved(int oldPosition,newPosition);

ViewHolder

ViewHolder会持有每个item的视图,主要是为了做缓存视图,方便复用,可以极大程度提升性能

java 复制代码
public static class MyViewHolder extends RecyclerView.ViewHolder {
    public TextView textView;
    public ImageView imageView;

    public MyViewHolder(View itemView) {
        super(itemView);
        textView = itemView.findViewById(R.id.textView);
        imageView = itemView.findViewById(R.id.imageView);
    }
}

适配器中如何与外部通讯(和dialog与外部通讯的原理一致)

  1. 在适配器中定义一个回调接口;
  2. 在外部创建接口的实例,并传到适配器中
  3. 适配器会在必要的时候,响应回调方法;
  4. 外部就能获取到通讯时间。

Android RecyclerView 实战:从基础列表到多类型 Item、分割线与状态复用问题

RecyclerView 是 Android 中最常用的列表容器之一。它通过 Adapter + ViewHolder 的模式,将"数据"和"视图"解耦,并借助 ViewHolder 复用 在长列表中获得更好的性能。

本文会从一个基础的新闻列表开始,逐步扩展到:

  • 自定义 item 布局(ConstraintLayout 常见坑一起梳理)
  • 编写 Adapter / ViewHolder,实现静态与动态数据绑定
  • 列表局部刷新:修改、删除、插入
  • 多 ViewType:一图 / 两图 / 三图 / 广告混排
  • 分割线的三种实现方式
  • Adapter 与外部交互:点击、长按、关闭广告
  • 复用导致的 CheckBox 状态错乱与正确处理方式
  • Grid / StaggeredGrid(网格与瀑布流)布局示例

说明:文中的 XML / Java 代码均按原样保留;只对讲解文本做结构化整理与纠错。


1. RecyclerView 基本结构:不要在 RecyclerView 内部"写死控件"

RecyclerView 只负责"展示一堆 item",不应该在 RecyclerView 标签内部直接声明一堆子控件。正确方式是:

  1. 为列表单元创建一个 item_xxx.xml
  2. Adapter 在 onCreateViewHolder() 中加载 item 布局
  3. onBindViewHolder() 把数据绑定到 item 上

2. ConstraintLayout 里 margin "看起来失效"的常见原因

在 ConstraintLayout 中,margin 是否生效高度依赖约束关系。以下场景非常常见:

  1. 该方向没有有效约束

    例如只写了 layout_marginTop,但没有 top_toTopOf / top_toBottomOf 约束,顶边的定位不成立,margin 就没有"施力点"。

  2. 约束到 GONE 的控件

    目标 View 为 GONE 时,普通 margin 可能不符合预期,应使用 layout_goneMarginStart/Top/...

  3. 使用 baseline 约束

    baseline 对齐时,top/bottom margin 的表现可能与预期不同,应该按 baseline 对齐逻辑理解布局。

  4. 尺寸与约束冲突(过约束)

    固定尺寸 + 多方向强约束时,系统会做约束解算,导致某些 margin 看起来"被吃掉"。

  5. wrap_content 导致误判

    例如 RecyclerView 使用 wrap_content 时会按内容撑开,不严格"占满约束区间",可能让你误以为底部 margin 不生效。此类场景通常应使用 0dp(match constraints)


3. 创建 item 布局:item_article_simple.xml

核心点:

  • 根布局常用 match_parent + wrap_content
  • android:padding 形成 item 内边距
  • 图片常用 centerCrop 适配 ImageView 尺寸
  • 标题常用 maxLines + ellipsize 控制溢出
  • 在 ConstraintLayout 中,文本宽度常用 0dp(match constraints)配合 start/end 约束
xml 复制代码
<androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:padding="12dp"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent">


      <ImageView
          android:id="@+id/iv_picture"
          android:layout_width="100dp"
          android:layout_height="70dp"
          android:scaleType="centerCrop"
          android:src="@drawable/icon_logo"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent" />


      <TextView
          android:id="@+id/tv_title"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:layout_marginLeft="8dp"
          android:ellipsize="end"
          android:maxLines="2"
          android:text="我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题"
          android:textColor="@color/black"
          android:textSize="18sp"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toEndOf="@id/iv_picture"
          app:layout_constraintTop_toTopOf="@id/iv_picture" />


      <TextView
          android:id="@+id/tv_anthor"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:layout_marginLeft="8dp"
          android:ellipsize="end"
          android:maxLines="1"
          android:text="我是作者我是作者我是作者"
          android:textSize="16sp"
          app:layout_constraintBottom_toBottomOf="@id/iv_picture"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toEndOf="@id/iv_picture" />

  </androidx.constraintlayout.widget.ConstraintLayout>

4. 编写 Adapter:onCreateViewHolder / onBindViewHolder / getItemCount

4.1 创建 Adapter 与 ViewHolder

Adapter 的核心职责:

  • onCreateViewHolder():加载 item 布局,创建 ViewHolder
  • onBindViewHolder():根据 position 绑定数据
  • getItemCount():返回列表长度

onCreateViewHolder() 中加载布局的典型写法:

java 复制代码
View view = LayoutInflater.from(parent.getContext()).
  inflate(R.layout.item_article_simple, parent, false);

为什么要 from(parent.getContext())

  • 因为 item 布局需要使用父容器(RecyclerView)所在的主题、样式与资源环境。

ViewHolder 需要继承 RecyclerView.ViewHolder

java 复制代码
public class MyViewHolder extends RecyclerView.ViewHolder {
  
      public MyViewHolder(@NonNull View itemView) {
          super(itemView);
  
      }
  }

5. 在 Activity 中设置 RecyclerView:LayoutManager 必不可少

RecyclerView 必须设置 LayoutManager,否则不会布局 item。

常见布局管理器:

  • LinearLayoutManager:一维线性列表(竖向/横向)
  • GridLayoutManager:网格
  • StaggeredGridLayoutManager:瀑布流

线性布局示例:

java 复制代码
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//线性方向改成横向
// layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
//为RecyclerView添加布局管理器
recyclerView.setLayoutManager(layoutManager);

设置 Adapter:

java 复制代码
//设置适配器
ArticleAdapter adapter = new ArticleAdapter();
recyclerView.setAdapter(adapter);

6. 数据绑定:从"写死"到"模型驱动"

6.1 在 ViewHolder 中拿到控件引用

java 复制代码
public class MyViewHolder extends RecyclerView.ViewHolder{
  
      private final ImageView ivArticle;
      private final TextView tvTitle;
      private final TextView tvAuthor;
  
      public MyViewHolder(@NonNull View itemView) {
          super(itemView);
          ivArticle = view.findViewById(R.id.iv_article);
          tvTitle = view.findViewById(R.id.tv_article_title);
          tvAuthor = view.findViewById(R.id.tv_article_author);
      }
  }

实践建议:ViewHolder 内通常使用 itemView.findViewById(...) 获取子控件,避免出现"引用了不存在的 view 变量"或绑定到错误 View 的问题。

6.2 Adapter 使用泛型绑定 ViewHolder 类型

RecyclerView.Adapter 的定义是泛型:

java 复制代码
public abstract static class Adapter<VH extends ViewHolder> 

因此可以这样写:

java 复制代码
public class ArticleAdapter extends RecyclerView.Adapter<ArticleAdapter.MyViewHolder> {

并同步修改方法签名:

java 复制代码
@NonNull
@Override
public ArticleAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {}

@Override
public void onBindViewHolder(@NonNull ArticleAdapter.MyViewHolder holder, int position) {}

6.3 在 onBindViewHolder 中绑定数据

先用静态数据验证流程:

java 复制代码
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
    holder.ivArticle.setImageResource(R.drawable.icon_qq);
    holder.tvTitle.setText("Title");
    holder.tvAuthor.setText("Author");
}

关于 onBindViewHolder() 的调用规律:

  • 每当一个 item 需要显示/重绘时都会触发
  • 上下滑动时,因为 ViewHolder 复用,会频繁触发绑定逻辑
  • position 从 0 开始,对应数据源下标

7. 引入数据模型:Article + ArrayList

7.1 在 Adapter 中持有数据源,并提供 setArticles()

java 复制代码
private static final String TAG = "ArticleAdapter";

private ArrayList<Article> articles;


public ArticleAdapter() {
  
}


public ArrayList<Article> getArticles() {
    return articles;
}

public void setArticles(ArrayList<Article> articles) {
    this.articles = articles;
    //告诉adapter,和你关联的数据源有变化,请重新根据数据做出调整
    notifyDataSetChanged();
    Log.i(TAG, "ArticleAdapter:  articles.size = " + articles.size());
}

关键点:

  • 只改数据源不通知 Adapter,界面不会刷新
  • notifyDataSetChanged() 是全量刷新(简单直接,但代价较大)

7.2 定义 Article 模型

java 复制代码
public class Article {

    private String title;//标题
    private String desc;//描述

    private String anthor;//作者

    private long publish;//发布时间
    private int picture;//图片资源
  
  	// get、set
}

7.3 用 position 驱动绑定:数据源与 item 一一对应

java 复制代码
@Override
public void onBindViewHolder(@NonNull ArticleAdapter.MyViewHolder holder, int position) {
    Log.i(TAG, "onBindViewHolder: position =" + position);

    Article article = articles.get(position);

    holder.ivPicture.setImageResource(article.getPicture());
    holder.tvTitle.setText(article.getTitle());
    holder.tvAnthor.setText(article.getAnthor());
}

并确保 getItemCount() 返回正确长度,避免数组越界:

java 复制代码
@Override
public int getItemCount() {
//        if (articles != null) {
//            return articles.size();
//        } else {
//            return 0;
//        }
    return articles != null ? articles.size() : 0;
}

8. 列表数据更新:刷新单条 vs 刷新全量

8.1 点击按钮加载数据:必须通知 Adapter

按钮触发加载:

java 复制代码
findViewById(R.id.btn_load).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //点击之后再给RecyclerView数据
        ArrayList<Article> articles = createData();
        adapter.setArticles(articles);
    }
});

setArticles 中通知刷新:

java 复制代码
public void setArticles(ArrayList<Article> articles) {
    this.articles = articles;
    //告诉adapter,和你关联的数据源有变化,请重新根据数据做出调整
    notifyDataSetChanged();
    Log.i(TAG, "ArticleAdapter:  articles.size = " + articles.size());
}

8.2 修改单条:notifyItemChanged(position)

java 复制代码
findViewById(R.id.btn_modify).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ArrayList<Article> articles = adapter.getArticles();
        Article article = articles.get(1);
        article.setTitle("我修改了第二条数据的标题");
//                adapter.notifyDataSetChanged();
        //根据position单独刷新某个item
        adapter.notifyItemChanged(1);

    }
});

8.3 删除单条:先改数据源,再 notifyItemRemoved

java 复制代码
findViewById(R.id.btn_remove).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          ArrayList<Article> articles = adapter.getArticles();
          articles.remove(0);
          Log.i(TAG, "onClick: articles size = " + articles.size());
          //对当个item做移除操作
          adapter.notifyItemRemoved(0);



          ArrayList<Article> articles1 = adapter.getArticles();
          Log.i(TAG, "onClick: articles size = " + articles1.size());
//                adapter.notifyDataSetChanged();
      }
  });

注意点(非常关键):

  • notifyItemRemoved(position) 只是在 UI 层告诉 RecyclerView "这个位置的 item 被移除了"
  • 真正的数据删除必须同步发生 (如 articles.remove(position)),否则下一次绑定仍会错位或崩溃

9. 多 ViewType:一图 / 两图 / 三图 / 广告混排

当列表中存在不同样式 item 时,需要使用 getItemViewType() + 多个 ViewHolder。

9.1 数据模型扩展:更多图片字段 + 广告字段

java 复制代码
package com.android_route.recyclerview.model;

public class Article {

    private String title;//标题
    private String desc;//描述

    private String anthor;//作者

    private long publish;//发布时间
    private int picture;//图片资源1

    private int picture2;//图片资源2

    private int picture3;//图片资源3

    private boolean idAd;//当前的资讯是不是广告

    private int adPicture;//广告对应的图片资源

    private boolean check;//是否被勾选

    public Article(boolean idAd, int adPicture) {
        this.idAd = idAd;
        this.adPicture = adPicture;
    }

    public Article(String title, String anthor, int picture) {
        this.title = title;
        this.anthor = anthor;
        this.picture = picture;
    }

    // get set

}

9.2 构造数据:插入广告与多图 item

java 复制代码
private ArrayList<Article> createData() { 
    ArrayList<Article> articles = new ArrayList<>();
    articles.add(new Article("辽宁清原抽水蓄能电站最后一台定子吊装完成",
            "人民网-图片频道,供稿:人民资讯", R.drawable.ic_article_1));
    articles.add(new Article("通行时间再缩短!广湛高铁佛山站建设正式启动",
            "人民网-广东频道,供稿:人民资讯", R.drawable.ic_article_2));
    articles.add(new Article("19岁杭州帅小伙第一名,还有人抓到一只甲鱼!今早3000多人在钱塘江边,太欢乐了",
            "杭州网", R.drawable.ic_article_3));
    Article article4 = new Article("今日起"两江小渡"航线恢复正常运营",
            "上游新闻", R.drawable.ic_article_4);
    article4.setPicture2(R.drawable.ic_article_12);
    articles.add(article4);
    articles.add(new Article("中国太保小排球夏令营在漳州火热启航",
            "国际时事讲解", R.drawable.ic_article_5));
    articles.add(new Article("奋力推动民族地区高质量发展和现代化建设迈出新步伐",
            "当代先锋网", R.drawable.ic_article_6));
    Article article7 = new Article("相信青春的力量|写在我省2024年万名大学生志愿服务西部计划出征之际",
            "人民网-图片频道,供稿:人民资讯", R.drawable.ic_article_7);
    article7.setPicture2(R.drawable.ic_article_5);
    articles.add(article7);
    articles.add(new Article("量身定制"海外订单" 贵州汽车制造开拓国际市场",
            "当代先锋网", R.drawable.ic_article_8));
    articles.add(new Article("势不可挡......在这里,乡村振兴的新引擎正发出强劲动力",
            "鲁网", R.drawable.ic_article_9));
    articles.add(new Article("菏泽职业学院召开2024届毕业生第二轮就业核查工作部署会",
            "大众报业·齐鲁壹点", R.drawable.ic_article_10));
    articles.add(new Article("菏泽职业学院学前教育系(育中教育学院)斩获省级大赛一等奖",
            "西安快线", R.drawable.ic_article_11));
    Article article12 = new Article("菏泽职业学院一类教学大赛,二等奖+2!",
            "大众报业·齐鲁壹点", R.drawable.ic_article_12);
    article12.setPicture2(R.drawable.ic_article_3);
    article12.setPicture3(R.drawable.ic_article_16);
    articles.add(article12);
    articles.add(new Article("中央气象台停止对"格美"编号 湖南辽宁吉林等地仍有强降雨",
            "国际时事讲解", R.drawable.ic_article_13));
    articles.add(new Article("习水仙源镇:清凉山水引客来 消夏避暑人气足",
            "多彩贵州网", R.drawable.ic_article_14));
    articles.add(new Article("习水县寨坝镇:清凉避暑地 康养第二乡",
            "多彩贵州网", R.drawable.ic_article_15));
    articles.add(new Article("赤水市元厚镇:新品荔枝出山记",
            "人民网-图片频道,供稿:人民资讯", R.drawable.ic_article_16));
    articles.add(new Article("习水三岔河镇小小错车道 畅通出行暖民心",
            "湖南日报", R.drawable.ic_article_17));
    articles.add(new Article("习水土城镇社会实践活动丰富学生假期生活",
            "上海咨询", R.drawable.ic_article_18));
    articles.add(new Article("赤水建强"小站点"释放乡村振兴新动能",
            "福建新闻网,供稿:人民资讯", R.drawable.ic_article_19));
    articles.add(new Article("习水同民镇:火龙果喜丰收 果农笑开颜",
            "上游新闻,供稿:人民资讯", R.drawable.ic_article_20));

    articles.add(5, new Article(true, R.drawable.bg_ad1));
    articles.add(10, new Article(true, R.drawable.bg_ad2));
    return articles;
}

9.3 多 ViewHolder:四种样式四个内部类

java 复制代码
public class MyViewHolder extends RecyclerView.ViewHolder{

    private final ImageView ivPicture;
    private final TextView tvTitle;
    private final TextView tvAnthor;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        ivPicture = view1.findViewById(R.id.iv_article);
        tvTitle = view1.findViewById(R.id.tv_article_title);
        tvAnthor = view1.findViewById(R.id.tv_article_author);
    }
}

public class MyViewHolder2 extends RecyclerView.ViewHolder{

    private final ImageView image1;
    private final ImageView image2;
    private final TextView tvTitle;
    private final TextView tvAnthor;

    public MyViewHolder2(@NonNull View itemView) {
        super(itemView);
        image1 = view2.findViewById(R.id.image1);
        image2 = view2.findViewById(R.id.image2);
        tvTitle = view2.findViewById(R.id.title);
        tvAnthor = view2.findViewById(R.id.tv_author);
    }
}

public class MyViewHolder3 extends RecyclerView.ViewHolder{

    private final ImageView image1;
    private final ImageView image2;
    private final ImageView image3;

    private final TextView tvTitle;
    private final TextView tvAnthor;

    public MyViewHolder3(@NonNull View itemView) {
        super(itemView);
        image1 = view3.findViewById(R.id.image1);
        image2 = view3.findViewById(R.id.image2);
        image3 = view3.findViewById(R.id.image3);
        tvTitle = view3.findViewById(R.id.tv_article_title);
        tvAnthor = view3.findViewById(R.id.tv_article_author);
    }
}

public class AdViewHolder extends RecyclerView.ViewHolder{

    private final ImageView ivPicture;
    private final TextView tvClose;

    public AdViewHolder(@NonNull View itemView) {
        super(itemView);
        ivPicture = view4.findViewById(R.id.iv_ad);
        tvClose = view4.findViewById(R.id.tv_close);
    }
}

多 ViewType 时,Adapter 通常写成不指定泛型:

java 复制代码
public class ArticleMoreAdapter extends RecyclerView.Adapter{ }

9.4 getItemViewType 决定布局类型,onCreateViewHolder 负责加载对应布局

java 复制代码
public static final int ITEM_TYPE_AD = 4;//对应广告的itemView
public static final int ITEM_TYPE_ARTICLE_3 = 3;//对应3张图片的itemView
public static final int ITEM_TYPE_ARTICLE_2 = 2;//对应2张图片itemView
public static final int ITEM_TYPE_ARTICLE_1 = 1;//对应1张图片的itemView

@Override
public int getItemViewType(int position) {
    Article article = articles.get(position);
    if (article.isIdAd()) {
        return ITEM_TYPE_AD;
    } else if (article.getPicture() != 0 && article.getPicture2() == 0 && article.getPicture3() == 0) {
        return ITEM_TYPE_ARTICLE_1;
    } else if (article.getPicture() != 0 && article.getPicture2() != 0 && article.getPicture3() == 0) {
        return ITEM_TYPE_ARTICLE_2;
    } else {
        return ITEM_TYPE_ARTICLE_3;
    }
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

    switch (viewType) {
        case ITEM_TYPE_ARTICLE_1:
            view1 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_picture1, parent, false);
            return new MyViewHolder(view1);
        case ITEM_TYPE_ARTICLE_2:
            view2 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_picture2, parent, false);
            return new MyViewHolder2(view2);
        case ITEM_TYPE_ARTICLE_3:
            view3 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_picture3, parent, false);
            return new MyViewHolder3(view3);
        case ITEM_TYPE_AD:
            view4 = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.item_article_ad, parent, false);
            return new AdViewHolder(view4);
    }
    return null;
}

9.5 onBindViewHolder:根据 viewType 强转并绑定字段

java 复制代码
@NonNull
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    Log.i(TAG, "onBindViewHolder: position =" + position);
    //通过position获取对应的viewtype
    int itemViewType = getItemViewType(position);
    Article article = articles.get(position);
    switch (itemViewType) {
        case ITEM_TYPE_ARTICLE_2:
            MyViewHolder2 holder2 = (MyViewHolder2) holder;
            holder2.image1.setImageResource(article.getPicture());
            holder2.image2.setImageResource(article.getPicture2());
            holder2.tvTitle.setText(article.getTitle());
            holder2.tvAnthor.setText(article.getAnthor());
            break;
        case ITEM_TYPE_ARTICLE_3:
            MyViewHolder3 holder3 = (MyViewHolder3) holder;
            holder3.image1.setImageResource(article.getPicture());
            holder3.image2.setImageResource(article.getPicture2());
            holder3.image3.setImageResource(article.getPicture3());
            holder3.tvTitle.setText(article.getTitle());
            holder3.tvAnthor.setText(article.getAnthor());
            break;
        case ITEM_TYPE_AD:
            AdViewHolder holderAd = (AdViewHolder) holder;
            holderAd.ivPicture.setImageResource(article.getAdPicture());
            break;
        case ITEM_TYPE_ARTICLE_1:
        default:
            MyViewHolder holderSimple = (MyViewHolder) holder;
            holderSimple.ivPicture.setImageResource(article.getPicture());
            holderSimple.tvTitle.setText(article.getTitle());
            holderSimple.tvAnthor.setText(article.getAnthor());
            break;
    }
}

10. RecyclerView 分割线:三种常见实现

10.1 方式一:在 item 布局中"画"分割线

思路:把 item 内容再包一层布局,在最外层底部加 View 当分割线。

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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="12dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">


        <ImageView
            android:id="@+id/iv_picture"
            android:layout_width="100dp"
            android:layout_height="70dp"
            android:scaleType="centerCrop"
            android:src="@drawable/icon_logo"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />


        <TextView
            android:id="@+id/tv_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:ellipsize="end"
            android:maxLines="2"
            android:text="我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题我是一个标题"
            android:textColor="@color/black"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/iv_picture"
            app:layout_constraintTop_toTopOf="@id/iv_picture" />


        <TextView
            android:id="@+id/tv_anthor"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:ellipsize="end"
            android:maxLines="1"
            android:text="我是作者我是作者我是作者"
            android:textSize="16sp"
            app:layout_constraintBottom_toBottomOf="@id/iv_picture"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@id/iv_picture" />

    </androidx.constraintlayout.widget.ConstraintLayout>

    <!--在item布局中直接添加1个分割线-->
    <!-- <View
         android:layout_width="match_parent"
         android:layout_height="5px"
         android:background="@color/my_blue"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent" />-->
</androidx.constraintlayout.widget.ConstraintLayout>

10.2 方式二:系统 DividerItemDecoration

java 复制代码
//添加一个常用的分割线样式
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));

//设置适配器
ArticleMoreAdapter adapter = new ArticleMoreAdapter(this);
recyclerView.setAdapter(adapter);

10.3 方式三:自定义 Drawable 分割线

java 复制代码
//添加Drawable类型的分割线
DividerItemDecoration decoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
decoration.setDrawable(getDrawable(R.drawable.item_div));
recyclerView.addItemDecoration(decoration);

11. Adapter 与外部交互:点击、长按、关闭广告

11.1 在 ViewHolder 内部处理"关闭广告"

java 复制代码
public class AdViewHolder extends RecyclerView.ViewHolder {

    private final ImageView ivPicture;
    private final TextView ivClose;

    public AdViewHolder(@NonNull View itemView) {
        super(itemView);

        ivPicture = itemView.findViewById(R.id.iv_picture);
        ivClose = itemView.findViewById(R.id.tv_close);
        ivClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //获取到当前AdViewHolder对应的postion
                int layoutPosition = getLayoutPosition();
                articles.remove(layoutPosition);
                notifyItemRemoved(layoutPosition);
            }
        });
    }
}

实践建议:

  • 更稳妥的 position 获取方式通常是 getAdapterPosition() / getBindingAdapterPosition(),并处理 NO_POSITION,避免动画或刷新时 position 失效导致崩溃。

11.2 通过接口回调把事件"抛给外部"

定义回调接口:

java 复制代码
private ArticleItemCallback articleItemCallback;

// get、set

public interface ArticleItemCallback {
        /**
         * 点击了一张图片的item
         *
         * @param position
         * @param article
         */
        void onSimpleItemClick(int position, Article article);

        /**
         * 对两张图片的item做长按操作
         *
         * @param position
         * @param article
         */
        void onLongClickItem2Click(int position, Article article);

        /**
         * 点击了广告
         *
         * @param position
         * @param article
         */
        void onAdClick(int position, Article article);
    }

在不同 ViewHolder 里触发回调:

java 复制代码
/**
 * 一张图片item对应的viewholder
 */
public class MyViewHolder extends RecyclerView.ViewHolder {

    private final ImageView ivPicture;
    private final TextView tvTitle;
    private final TextView tvAnthor;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        ivPicture = itemView.findViewById(R.id.iv_picture);
        tvTitle = itemView.findViewById(R.id.tv_title);
        tvAnthor = itemView.findViewById(R.id.tv_anthor);
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int layoutPosition = getLayoutPosition();
                Article article = articles.get(layoutPosition);
                articleItemCallback.onSimpleItemClick(layoutPosition, article);
//                    activity.startActivity(new Intent(activity, ArticleDetailActivity.class));
            }
        });
    }
}

/**
 * 两张图片的ViewHolder
 */
public class MyViewHolder2 extends RecyclerView.ViewHolder {
    private final ImageView ivPicture1;
    private final ImageView ivPicture2;
    private final TextView tvTitle;
    private final TextView tvAnthor;

    public MyViewHolder2(@NonNull View itemView) {
        super(itemView);

        ivPicture1 = itemView.findViewById(R.id.iv_picture1);
        ivPicture2 = itemView.findViewById(R.id.iv_picture2);
        tvTitle = itemView.findViewById(R.id.tv_title);
        tvAnthor = itemView.findViewById(R.id.tv_anthor);

        itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                int layoutPosition = getLayoutPosition();
                Article article = articles.get(layoutPosition);
                articleItemCallback.onLongClickItem2Click(layoutPosition, article);
                return false;
            }
        });
    }
}

/**
 * 三张图片的ViewHolder
 */
public class MyViewHolder3 extends RecyclerView.ViewHolder {
    private final ImageView ivPicture1;
    private final ImageView ivPicture2;
    private final ImageView ivPicture3;
    private final TextView tvTitle;
    private final TextView tvAnthor;

    public MyViewHolder3(@NonNull View itemView) {
        super(itemView);
        ivPicture1 = itemView.findViewById(R.id.iv_picture1);
        ivPicture2 = itemView.findViewById(R.id.iv_picture2);
        ivPicture3 = itemView.findViewById(R.id.iv_picture3);
        tvTitle = itemView.findViewById(R.id.tv_title);
        tvAnthor = itemView.findViewById(R.id.tv_anthor);
    }
}

/**
 * 广告ViewHolder
 */
public class AdViewHolder extends RecyclerView.ViewHolder {

    private final ImageView ivPicture;
    private final TextView ivClose;

    public AdViewHolder(@NonNull View itemView) {
        super(itemView);

        ivPicture = itemView.findViewById(R.id.iv_picture);
        ivClose = itemView.findViewById(R.id.tv_close);
        ivClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //获取到当前AdViewHolder对应的postion
                int layoutPosition = getLayoutPosition();
                articles.remove(layoutPosition);
                notifyItemRemoved(layoutPosition);
            }
        });

        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int layoutPosition = getLayoutPosition();
                Article article = articles.get(layoutPosition);
                articleItemCallback.onAdClick(layoutPosition, article);
            }
        });
    }
}

外部 Activity 实现接口:

java 复制代码
public class ArticleMoreListActivity extends AppCompatActivity implements ArticleMoreAdapter.ArticleItemCallback {

	 private void initLayout(){
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        rvNews.setLayoutManager(linearLayoutManager);
        rvNews.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        adapt = new RecyclerViewDemoAdapter();
        // 注册监听器
        adapt.setArticleItemCallback(this);
        rvNews.setAdapter(adapt);
    }

this 在这里就是当前 Activity 对象。之所以能传进去,是因为 Activity 已经 implements RecyclerViewDemoAdapter.ArticleItemCallback。也就是说当前 Activity 同时具备两种身份:

  1. RecyclerViewDemoActivity 实例
  2. ArticleItemCallback 接口实现者

所以这句是合法的:

java 复制代码
adapt.setArticleItemCallback(this);

本质上是把"谁来处理点击回调"告诉 Adapter。

如果不注册,Adapter 里 articleItemCallback 就是 null,点击时不会回调到 onSimpleItemClick/onAdClick/...。

实现回调逻辑:

java 复制代码
@Override
public void onSimpleItemClick(int position, Article article) {
    Toast.makeText(ArticleMoreListActivity.this,
            "跳转到单张图片对应的新闻资讯", Toast.LENGTH_SHORT).show();
    startActivity(new Intent(ArticleMoreListActivity.this, ArticleDetailActivity.class));
}

@Override
public void onLongClickItem2Click(int position, Article article) {
    Toast.makeText(ArticleMoreListActivity.this,
            "你长按了2张图片对应的布局", Toast.LENGTH_SHORT).show();
}

@Override
public void onAdClick(int position, Article article) {
    Toast.makeText(ArticleMoreListActivity.this,
            "准备前往广告详情页...", Toast.LENGTH_SHORT).show();
}

12. ViewHolder 复用导致 CheckBox 状态错乱:用数据源"记住状态"

RecyclerView 会复用 ViewHolder:当一个 item 滑出屏幕,它对应的 ViewHolder 可能被复用来展示另一个位置的数据。如果 只改 CheckBox UI,不把状态写回数据源,就会出现:

  • 勾选了 A
  • 滑动列表后,B 的 CheckBox 也莫名被勾上

12.1 在模型中增加字段 check,并在点击时写回

java 复制代码
public class MyViewHolder extends RecyclerView.ViewHolder {

        private final ImageView ivPicture;
        private final TextView tvTitle;
        private final TextView tvAnthor;
        private final CheckBox checkBox;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            ivPicture = itemView.findViewById(R.id.iv_picture);
            tvTitle = itemView.findViewById(R.id.tv_title);
            tvAnthor = itemView.findViewById(R.id.tv_anthor);
            checkBox = itemView.findViewById(R.id.check_box);
            checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    Article article = articles.get(getLayoutPosition());
                    article.setCheck(isChecked);
                }
            });
        }
    }

12.2 在 onBindViewHolder 中"用数据重置 UI"

java 复制代码
@Override
public void onBindViewHolder(@NonNull ArticleCheckAdapter.MyViewHolder holder, int position) {
    Log.i(TAG, "onBindViewHolder: position =" + position);

    Article article = articles.get(position);

    holder.ivPicture.setImageResource(article.getPicture());
    holder.tvTitle.setText(article.getTitle());
    holder.tvAnthor.setText(article.getAnthor());
    holder.checkBox.setChecked(article.isCheck());
}

实践建议(避免额外触发监听):

  • setChecked() 前临时移除监听,再恢复监听,可以减少"绑定时触发 onCheckedChanged"的副作用(尤其是有复杂逻辑时)。

13. GridLayoutManager:网格布局示例

Activity:

java 复制代码
public class GridRecycerViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_grid_recycer_view);

        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        //网格布局管理器  指定一行显示2个item
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
        recyclerView.setLayoutManager(gridLayoutManager);

        DividerItemDecoration decor = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
        recyclerView.addItemDecoration(decor);

        DividerItemDecoration decor1 = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL);
        recyclerView.addItemDecoration(decor1);

        ArrayList<Integer> imsResIds = createData();
        GridAdapter adapter = new GridAdapter(imsResIds);
        //设置适配器
        recyclerView.setAdapter(adapter);

    }


    private ArrayList<Integer> createData() {
        ArrayList<Integer> imsResIds = new ArrayList<>();
        imsResIds.add(R.drawable.ic_article_1);
        imsResIds.add(R.drawable.ic_article_2);
        imsResIds.add(R.drawable.ic_article_3);
        imsResIds.add(R.drawable.ic_article_4);
        imsResIds.add(R.drawable.ic_article_5);
        imsResIds.add(R.drawable.ic_article_6);
        imsResIds.add(R.drawable.ic_article_7);
        imsResIds.add(R.drawable.ic_article_8);
        imsResIds.add(R.drawable.ic_article_9);
        imsResIds.add(R.drawable.ic_article_10);
        imsResIds.add(R.drawable.ic_article_11);
        imsResIds.add(R.drawable.ic_article_12);
        imsResIds.add(R.drawable.ic_article_13);
        imsResIds.add(R.drawable.ic_article_14);
        imsResIds.add(R.drawable.ic_article_15);
        imsResIds.add(R.drawable.ic_article_16);
        imsResIds.add(R.drawable.ic_article_17);
        imsResIds.add(R.drawable.ic_article_18);
        imsResIds.add(R.drawable.ic_article_19);
        imsResIds.add(R.drawable.ic_article_20);
        return imsResIds;
    }
}

Adapter:

java 复制代码
public class GridAdapter extends RecyclerView.Adapter<GridAdapter.MyViewHolder> {

    private List<Integer> dataList;

    public GridAdapter(List<Integer> dataList) {
        this.dataList = dataList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_demo_grid, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Integer resId = dataList.get(position);
        holder.ivImg.setImageResource(resId);
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        public ImageView ivImg;

        public MyViewHolder(View itemView) {
            super(itemView);
            ivImg = itemView.findViewById(R.id.iv_img);
        }
    }
}

网格 item 布局建议:

  • item 根布局宽度常用 match_parent
  • 图片可使用 centerCrop 保持填充效果
xml 复制代码
<?xml version="1.0" encoding="utf-8"?><!-- res/layout/item_layout.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp">

    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:scaleType="centerCrop"
        android:src="@drawable/ic_article_1" />
</LinearLayout>

14. StaggeredGridLayoutManager:瀑布流示例

Activity:

java 复制代码
public class StaggeredGridRecyclerViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_staggered_grid_recycler_view);


        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        StaggeredGridLayoutManager layout = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layout);

        ArrayList<Integer> data = createData();
        StaggeredGridAdapter staggeredGridAdapter = new StaggeredGridAdapter(data);
        recyclerView.setAdapter(staggeredGridAdapter);
    }

    private ArrayList<Integer> createData() {
        ArrayList<Integer> pictureResIds = new ArrayList<>();
        pictureResIds.add(R.drawable.bg_picture_1);
        pictureResIds.add(R.drawable.bg_picture_2);
        pictureResIds.add(R.drawable.bg_picture_3);
        pictureResIds.add(R.drawable.bg_picture_4);
        pictureResIds.add(R.drawable.bg_picture_5);
        pictureResIds.add(R.drawable.bg_picture_6);
        pictureResIds.add(R.drawable.bg_picture_7);
        pictureResIds.add(R.drawable.bg_picture_8);
        pictureResIds.add(R.drawable.bg_picture_9);
        pictureResIds.add(R.drawable.bg_picture_10);
        return pictureResIds;
    }
}

Adapter:

java 复制代码
public class StaggeredGridAdapter extends RecyclerView.Adapter<StaggeredGridAdapter.MyViewHolder> {

    private List<Integer> resIds;

    public StaggeredGridAdapter(List<Integer> dataList) {
        this.resIds = dataList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_staggered_grid_picture, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        Integer redId = resIds.get(position);
        holder.imageView.setImageResource(redId);
        //把 position+1的结果转为字符串
        String label = String.valueOf(position + 1);
        holder.textView.setText(label);
    }

    @Override
    public int getItemCount() {
        return resIds.size();
    }

    public static class MyViewHolder extends RecyclerView.ViewHolder {
        public ImageView imageView;
        public TextView textView;

        public MyViewHolder(View itemView) {
            super(itemView);
            imageView = itemView.findViewById(R.id.iv_img);
            textView = itemView.findViewById(R.id.text_view);
        }
    }
}

item 布局(图片高度自适应,按宽度等比缩放):

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="12dp"
    app:cardCornerRadius="12dp"
    app:cardElevation="12dp">


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/iv_img"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:scaleType="fitXY"
            android:src="@drawable/bg_picture_2" />

        <TextView
            android:id="@+id/text_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="4dp"
            android:text="1"
            android:textAlignment="center" />
    </LinearLayout>


</androidx.cardview.widget.CardView>

15. 附录:repomix 打包代码(原样摘录)

下面代码来自 repomix-output.xml 打包内容,按文件分段原样呈现(不做改动),便于对照理解整体结构。

15.1 Article.java

java 复制代码
package com.ls.androiduibyjavaproject.recyclerview;

public class Article {

    private String title;//标题
    private String desc;//描述

    private String anthor;//作者

    private long publish;//发布时间
    private int picture;//图片资源1

    private int picture2;//图片资源2

    private int picture3;//图片资源3

    private boolean idAd;//当前的资讯是不是广告

    private int adPicture;//广告对应的图片资源

    private boolean check;//是否被勾选

    public Article(boolean idAd, int adPicture) {
        this.idAd = idAd;
        this.adPicture = adPicture;
    }

    public Article(String title, String anthor, int picture) {
        this.title = title;
        this.anthor = anthor;
        this.picture = picture;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getAnthor() {
        return anthor;
    }

    public void setAnthor(String anthor) {
        this.anthor = anthor;
    }

    public long getPublish() {
        return publish;
    }

    public void setPublish(long publish) {
        this.publish = publish;
    }

    public int getPicture() {
        return picture;
    }

    public void setPicture(int picture) {
        this.picture = picture;
    }

    public int getPicture2() {
        return picture2;
    }

    public void setPicture2(int picture2) {
        this.picture2 = picture2;
    }

    public int getPicture3() {
        return picture3;
    }

    public void setPicture3(int picture3) {
        this.picture3 = picture3;
    }

    public boolean isIdAd() {
        return idAd;
    }

    public void setIdAd(boolean idAd) {
        this.idAd = idAd;
    }

    public int getAdPicture() {
        return adPicture;
    }

    public void setAdPicture(int adPicture) {
        this.adPicture = adPicture;
    }

    public boolean isCheck() {
        return check;
    }

    public void setCheck(boolean check) {
        this.check = check;
    }
}

15.2 ArticleAdapter.java

java 复制代码
package com.ls.androiduibyjavaproject.recyclerview;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.ls.androiduibyjavaproject.R;

import java.util.ArrayList;

public class ArticleAdapter extends RecyclerView.Adapter<ArticleAdapter.MyViewHolder> {

    private static final String TAG = "ArticleAdapter";

    private ArrayList<Article> articles;


    public ArticleAdapter() {
    }


    public ArrayList<Article> getArticles() {
        return articles;
    }

    public void setArticles(ArrayList<Article> articles) {
        this.articles = articles;
        //告诉adapter,和你关联的数据源有变化,请重新根据数据做出调整
        notifyDataSetChanged();
        Log.i(TAG, "ArticleAdapter:  articles.size = " + articles.size());
    }

    @NonNull
    @Override
    public ArticleAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_article_simple, parent, false);
        MyViewHolder holder = new MyViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ArticleAdapter.MyViewHolder holder, int position) {
        Log.i(TAG, "onBindViewHolder: position =" + position);

        Article article = articles.get(position);

        holder.ivPicture.setImageResource(article.getPicture());
        holder.tvTitle.setText(article.getTitle());
        holder.tvAnthor.setText(article.getAnthor());

    }

    @Override
    public int getItemCount() {
        return articles != null ? articles.size() : 0;
    }


    public class MyViewHolder extends RecyclerView.ViewHolder {

        private final ImageView ivPicture;
        private final TextView tvTitle;
        private final TextView tvAnthor;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            ivPicture = itemView.findViewById(R.id.iv_picture);
            tvTitle = itemView.findViewById(R.id.tv_title);
            tvAnthor = itemView.findViewById(R.id.tv_anthor);
        }
    }

}

15.3 ArticleListActivity.java

java 复制代码
package com.ls.androiduibyjavaproject.recyclerview;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;
import android.view.View;

import com.ls.androiduibyjavaproject.R;

import java.util.ArrayList;

public class ArticleListActivity extends AppCompatActivity {

    private ArticleAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_article_list);

        RecyclerView recyclerView = findViewById(R.id.recycler_view);

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        //线性方向改成横向
        // layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        //为RecyclerView添加布局管理器
        recyclerView.setLayoutManager(layoutManager);

        //设置适配器
        adapter = new ArticleAdapter();
        recyclerView.setAdapter(adapter);

        findViewById(R.id.btn_load).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //点击之后再给RecyclerView数据
                ArrayList<Article> articles = createData();
                adapter.setArticles(articles);
            }
        });

        findViewById(R.id.btn_modify).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ArrayList<Article> articles = adapter.getArticles();
                Article article = articles.get(1);
                article.setTitle("我修改了第二条数据的标题");
                //根据position单独刷新某个item
                adapter.notifyItemChanged(1);

            }
        });

        findViewById(R.id.btn_remove).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ArrayList<Article> articles = adapter.getArticles();
                articles.remove(0);
                //对当个item做移除操作
                adapter.notifyItemRemoved(0);

            }
        });

    }


    private ArrayList<Article> createData() {
        ArrayList<Article> articles = new ArrayList<>();
        articles.add(new Article("辽宁清原抽水蓄能电站最后一台定子吊装完成",
                "人民网-图片频道,供稿:人民资讯", R.drawable.ic_article_1));
        articles.add(new Article("通行时间再缩短!广湛高铁佛山站建设正式启动",
                "人民网-广东频道,供稿:人民资讯", R.drawable.ic_article_2));
        articles.add(new Article("19岁杭州帅小伙第一名,还有人抓到一只甲鱼!今早3000多人在钱塘江边,太欢乐了",
                "杭州网", R.drawable.ic_article_3));
        articles.add(new Article("今日起"两江小渡"航线恢复正常运营",
                "上游新闻", R.drawable.ic_article_4));
        articles.add(new Article("中国太保小排球夏令营在漳州火热启航",
                "国际时事讲解", R.drawable.ic_article_5));
        articles.add(new Article("奋力推动民族地区高质量发展和现代化建设迈出新步伐",
                "当代先锋网", R.drawable.ic_article_6));
        articles.add(new Article("相信青春的力量|写在我省2024年万名大学生志愿服务西部计划出征之际",
                "人民网-图片频道,供稿:人民资讯", R.drawable.ic_article_7));
        articles.add(new Article("量身定制"海外订单" 贵州汽车制造开拓国际市场",
                "当代先锋网", R.drawable.ic_article_8));
        articles.add(new Article("势不可挡......在这里,乡村振兴的新引擎正发出强劲动力",
                "鲁网", R.drawable.ic_article_9));
        articles.add(new Article("菏泽职业学院召开2024届毕业生第二轮就业核查工作部署会",
                "大众报业·齐鲁壹点", R.drawable.ic_article_10));
        articles.add(new Article("菏泽职业学院学前教育系(育中教育学院)斩获省级大赛一等奖",
                "西安快线", R.drawable.ic_article_11));
        articles.add(new Article("菏泽职业学院一类教学大赛,二等奖+2!",
                "大众报业·齐鲁壹点", R.drawable.ic_article_12));
        articles.add(new Article("中央气象台停止对"格美"编号 湖南辽宁吉林等地仍有强降雨",
                "国际时事讲解", R.drawable.ic_article_13));
        articles.add(new Article("习水仙源镇:清凉山水引客来 消夏避暑人气足",
                "多彩贵州网", R.drawable.ic_article_14));
        articles.add(new Article("习水县寨坝镇:清凉避暑地 康养第二乡",
                "多彩贵州网", R.drawable.ic_article_15));
        articles.add(new Article("赤水市元厚镇:新品荔枝出山记",
                "人民网-图片频道,供稿:人民资讯", R.drawable.ic_article_16));
        articles.add(new Article("习水三岔河镇小小错车道 畅通出行暖民心",
                "湖南日报", R.drawable.ic_article_17));
        articles.add(new Article("习水土城镇社会实践活动丰富学生假期生活",
                "上海咨询", R.drawable.ic_article_18));
        articles.add(new Article("赤水建强"小站点"释放乡村振兴新动能",
                "福建新闻网,供稿:人民资讯", R.drawable.ic_article_19));
        articles.add(new Article("习水同民镇:火龙果喜丰收 果农笑开颜",
                "上游新闻,供稿:人民资讯", R.drawable.ic_article_20));

        return articles;
    }
}

15.4 其余文件

repomix 中还包含以下文件(同样可作为完整工程参考):

  • ArticleCheckActivity.java
  • ArticleCheckAdapter.java
  • ArticleDetailActivity.java
  • ArticleMoreAdapter.java
  • ArticleMoreListActivity.java
  • GridAdapter.java
  • GridRecycerViewActivity.java
  • StaggeredGridAdapter.java
  • StaggeredGridRecyclerViewActivity.java

(如需,我可以继续把这些文件也按"原样代码块"完整附在文末,便于一篇文章内直接复制对照。)


总结

本文串起了 RecyclerView 的核心能力:

  • Adapter + ViewHolder:列表渲染的基础
  • LayoutManager:决定列表如何排布
  • 数据驱动绑定:position 对应数据源下标
  • notify 系列 API:全量刷新与局部刷新
  • 多 ViewType:混合样式列表的标准做法
  • 事件回调:让 Adapter 与外部解耦
  • 状态复用问题:用数据源绑定 UI 状态是关键

掌握这些之后,无论是新闻资讯流、商品列表、聊天记录,还是混排推荐页,RecyclerView 都能轻松应对。

相关推荐
zh_xuan1 小时前
kotlin async异步协程构建器
android·kotlin·协程
阿林来了1 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— Android 端 Chrome Custom Tabs 实现分析
android·chrome·flutter
勇往直前plus3 小时前
从文件到屏幕:Python/java 字符编码、解码、文本处理的底层逻辑解析
java·开发语言·python
zh_xuan3 小时前
kotlin Channel的用法
android·kotlin·协程·channel
zh_xuan3 小时前
kotlin Flow的用法
android·开发语言·kotlin·协程·flow
Drifter_yh10 小时前
【黑马点评】Redisson 分布式锁核心原理剖析
java·数据库·redis·分布式·spring·缓存
普通网友10 小时前
Android Jetpack 架构组件最佳实践之“网抑云”APP
android·架构·android jetpack
普通网友10 小时前
原创_Android Jetpack Compose 最全上手指南
android·android jetpack
FDoubleman10 小时前
Android Jetpack之Compose入门(一)
android·android jetpack