**“HeadLine仿今日头条”**  项目

0 阅读15分钟

前言

在移动应用开发的学习过程中,新闻资讯类应用一直是绝佳的实战项目。它涵盖了UI布局的复杂组合、网络数据请求、列表的高效渲染以及用户交互的多样性。本次我们要剖析的  “HeadLine仿今日头条”  项目,正是这样一个麻雀虽小五脏俱全的典范。

本博客将重点聚焦于该项目的核心展示层——即新闻列表的实现。我们将深入探讨为什么在2024-2025年的Android开发趋势下,我们选择RecyclerView而不是ListView,以及如何通过布局资源(Layout Resources)和自定义控件,还原出今日头条那种“流式无限加载、视频混合穿插”的极致体验。

全文预估字数:24,000字左右
涉及核心知识点:  RecyclerView复用机制、LayoutManager布局管理器、Adapter适配器模式、CardView卡片布局、ConstraintLayout约束布局、ItemDecoration分割线、SwipeRefreshLayout下拉刷新。


第一章:项目概览与架构思维导图

1.1 仿今日头条的业务逻辑模型

今日头条的核心本质是什么?是“信息流”。无论底部的Tab如何切换(推荐、视频、热点、本地),顶部的频道如何滑动,归根结底我们面对的都是一个无限长度的垂直列表

在HeadLine项目中,我们将这个核心抽象为MainActivity(主容器) + NewsFragment(碎片容器) + RecyclerView(列表视图)。

1.2 文件结构解析

text

HeadLine/app/src/main/
├── java/cn.edu.imageview/          # 源代码目录
│   ├── MainActivity.kt (或.java)   # 入口Activity
│   ├── adapter/                     # 适配器包 (核心)
│   │   └── NewsAdapter.kt
│   ├── bean/                        # 数据实体类
│   │   └── NewsEntity.kt
│   └── utils/                       # 工具类 (图片加载)
├── res/                             # 资源目录 (布局核心)
│   ├── layout/                      # 布局文件夹
│   │   ├── activity_main.xml        # 主布局
│   │   ├── fragment_news.xml        # 新闻碎片布局
│   │   └── item_news_normal.xml     # RecyclerView的Item布局
│   ├── drawable/                    # 图标资源 (点赞、评论图标)
│   └── values/                      # 样式与颜色
└── AndroidManifest.xml              # 清单文件

7d4a51313817dd6495a7ade82a0066e7.jpg


第二章:布局资源(Layout Resources)—— 构建UI的画布

在Android开发中,布局资源决定了用户看到什么。在仿今日头条项目中,我们不仅需要一个容器来放列表,更需要定义列表里的每一项(Item)长什么样。

2.1 主容器布局:activity_main.xml 的架构设计

今日头条的首页通常包含:状态栏占位、搜索栏(TitleBar)、频道导航栏(TabLayout)、内容区域(ViewPager2 + Fragment)。

在HeadLine项目中,为了简化并突出RecyclerView,我们通常采用垂直的LinearLayout结构。

代码示例:activity_main.xml (约束布局版本)

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="match_parent"
    android:background="#F5F5F5">

    <!-- 1. 顶部搜索栏 (仿头条搜索框) -->
    <LinearLayout
        android:id="@+id/title_bar"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:background="#FFFFFF"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent">

        <!-- 搜索框占位图标 -->
        <ImageView
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:src="@drawable/ic_search"
            android:layout_marginLeft="16dp"/>
        
        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="搜你想搜的"
            android:textColor="#999999"
            android:textSize="14sp"/>
        
        <ImageView
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:src="@drawable/ic_user_avatar"
            android:layout_marginRight="16dp"/>
    </LinearLayout>

    <!-- 2. 核心区域:RecyclerView 占据剩余全部空间 -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_news_list"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:scrollbars="vertical"  <!-- 显示垂直滚动条 -->
        android:overScrollMode="never"  <!-- 去掉边缘发光效果,更贴近头条 -->
        app:layout_constraintTop_toBottomOf="@id/title_bar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

控件使用解析:

  1. ConstraintLayout: 作为根布局,它的主要优点是扁平化(减少View嵌套层级)。这里通过layout_constraintTop_toBottomOf将RecyclerView的顶部约束在标题栏下方,底部约束在父布局底部,实现了自适应填充。
  2. RecyclerView属性: android:scrollbars="vertical" 是为了给用户视觉反馈,告诉他们列表是可以滚动的。overScrollMode="never" 是模仿现代App,去掉拉伸回弹时的彩虹光圈。

ada9f8249d74eb83a9b794f8ded0b81c.png

2.2 Item布局:item_news_normal.xml —— 头条卡片的血肉

这是整个项目最精细的部分。今日头条的每条新闻都是一个独立的“卡片”。通常包含:标题、来源、评论数、时间、以及右侧的缩略图(ImageView)。

技术选型:  我们会使用CardView作为根布局,因为它自带圆角和阴影,能营造出卡片式的立体感。

代码示例:item_news_normal.xml

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="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="8dp"
    android:layout_marginRight="8dp"
    android:layout_marginTop="4dp"
    android:layout_marginBottom="4dp"
    app:cardCornerRadius="8dp"      <!-- 圆角半径8dp -->
    app:cardElevation="2dp"         <!-- 阴影高度2dp,增加立体感 -->
    app:cardUseCompatPadding="true">

    <!-- 根布局使用垂直LinearLayout,方便上下排列 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="12dp">

        <!-- 第一行:标题 (重点) -->
        <TextView
            android:id="@+id/tv_news_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="花菜有人掉水,有人直接炒,都错了,看饭店大厨如何做"
            android:textSize="18sp"
            android:textColor="#333333"
            android:textStyle="bold"
            android:maxLines="2"        <!-- 最多两行,多余省略 -->
            android:ellipsize="end"/>

        <!-- 第二行:图片区 (横向LinearLayout) -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:orientation="horizontal">

            <!-- 左侧文字信息区 -->
            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/tv_author"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="味美食记"
                    android:textSize="12sp"
                    android:textColor="#888888"/>

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="4dp"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/tv_comment_count"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="18评"
                        android:textSize="11sp"
                        android:textColor="#999999"/>

                    <TextView
                        android:id="@+id/tv_time"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="刚刚"
                        android:layout_marginLeft="12dp"
                        android:textSize="11sp"
                        android:textColor="#999999"/>
                </LinearLayout>
            </LinearLayout>

            <!-- 右侧:缩略图 (核心控件) -->
            <ImageView
                android:id="@+id/iv_thumbnail"
                android:layout_width="100dp"
                android:layout_height="70dp"
                android:layout_marginLeft="12dp"
                android:scaleType="centerCrop"   <!-- 中央裁剪,填满ImageView -->
                android:src="@drawable/placeholder_image" />
        </LinearLayout>
    </LinearLayout>
</androidx.cardview.widget.CardView>

布局与控件深度剖析:

  1. CardView的使用

    • 在传统的ListView时代,我们通常用普通的LinearLayout加背景来实现。但使用CardView,我们只用了两行属性(app:cardCornerRadiusapp:cardElevation)就实现了Material Design的卡片效果,这在视觉上非常贴近今日头条的“微质感”风格。
    • cardUseCompatPadding="true" 是为了保证在不同Android版本下,卡片内边距的一致性。
  2. TextView的精华

    • maxLines="2" 配合 ellipsize="end" 是新闻标题的标准做法。因为手机屏幕有限,超过两行的标题会影响信息流的浏览效率,末尾加...是用户习惯。
    • textStyle="bold" 加粗标题,区分于下方的辅助信息文字。
  3. ImageView的缩放

    • scaleType="centerCrop" 是最常用的属性。如果服务器返回的图片是横图或竖图比例不一,该属性会保证ImageView被填满,同时等比缩放并裁剪多余部分。这就保证了所有缩略图都是规整的100dp * 70dp矩形。
  4. LinearLayout的权重(Weight)

    • 在第二行中,左侧文字区设置layout_weight="1",右侧图片固定宽度100dp。这是一种自适应布局技巧。无论手机屏幕多宽(6.1寸或6.7寸),图片始终在右侧固定大小,左侧文字区自动拉伸填满剩余空间,不会挤压图片。

e0904981d17a4871a25fb67c847d86f0.png


第三章:RecyclerView的深度使用 —— 核心灵魂

在HeadLine项目中,如果不用RecyclerView,理论上也可以使用ListView。但为什么我们坚持使用RecyclerView?请看下表对比:

特性ListViewRecyclerView在仿头条项目中的意义
布局样式仅支持垂直列表垂直、水平、网格、瀑布流头条不仅有信息流,还有视频频道(网格)和关注频道(可能瀑布流)
ViewHolder非强制,但推荐使用强制使用,内置于Adapter极大提升滑动流畅度,防止卡顿
分割线内置divider属性需自定义ItemDecoration灵活控制每个Item之间的间距和样式
动画基本无自带添加/删除默认动画实现“点赞”飘心或插入广告时的平滑过渡
局部刷新需手动操作notifyItemChanged(pos)仅刷新某一条新闻的阅读状态,而不刷新整个列表

3.1 配置依赖(Gradle)

在开始编码前,必须确保build.gradle (Module: app)中导入了RecyclerView依赖。

groovy

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
    // 为了使用CardView,也需要导入
    implementation 'androidx.cardview:cardview:1.0.0'
    // 图片加载库(Glide),用于高效加载网络图片
    implementation 'com.github.bumptech.glide:glide:4.16.0'
}

626c6c12480d345058e178ce761bc7be.png

3.2 数据实体类(Bean)的构建

在Java目录下创建bean/NewsEntity.java。根据仿头条的业务,我们需要如下字段:

java

package cn.edu.imageview.bean;

public class NewsEntity {
    private String title;        // 标题
    private String author;       // 作者/来源
    private String commentCount; // 评论数 (如 "9884评")
    private String time;         // 发布时间 (如 "6小时前")
    private String imageUrl;     // 缩略图URL

    // 构造方法
    public NewsEntity(String title, String author, String commentCount, String time, String imageUrl) {
        this.title = title;
        this.author = author;
        this.commentCount = commentCount;
        this.time = time;
        this.imageUrl = imageUrl;
    }

    // Getter 和 Setter 方法 (为节约篇幅,此处省略,实际项目必须写)
    // ...
}

3.3 适配器(Adapter)的编写 —— 最核心的20%代码

RecyclerView的强大在于它的适配器模式。适配器就像一座桥梁,连接着Java数据(ArrayList<NewsEntity>)和XML布局(item_news_normal.xml)。

代码示例:NewsAdapter.java

java

package cn.edu.imageview.adapter;

import android.content.Context;
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.bumptech.glide.Glide;
import java.util.List;
import cn.edu.imageview.R;
import cn.edu.imageview.bean.NewsEntity;

public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {

    private Context context;
    private List<NewsEntity> newsList;

    // 构造函数:传入数据和上下文
    public NewsAdapter(Context context, List<NewsEntity> newsList) {
        this.context = context;
        this.newsList = newsList;
    }

    /**
     * 1. 创建ViewHolder:相当于把item布局文件“ inflate(加载)”进来,生成一个View对象。
     *    这个方法只会在创建足够显示屏幕的ViewHolder时调用,滑动复用时不再调用。
     */
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        // 从布局资源中加载 item_news_normal.xml
        View view = LayoutInflater.from(context).inflate(R.layout.item_news_normal, parent, false);
        return new ViewHolder(view);
    }

    /**
     * 2. 绑定数据:将数据源中 position 位置的数据,绑定到 ViewHolder 的控件上。
     *    这是RecyclerView复用的核心。滑动时,系统会把旧的View交给你,你只需要把新的数据填进去。
     */
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        NewsEntity news = newsList.get(position);

        // 绑定TextView数据
        holder.tvTitle.setText(news.getTitle());
        holder.tvAuthor.setText(news.getAuthor());
        holder.tvCommentCount.setText(news.getCommentCount());
        holder.tvTime.setText(news.getTime());

        // 使用Glide加载图片(处理网络图片,缓存,占位图)
        // 这是今日头条高频滑动不卡顿的关键
        Glide.with(context)
                .load(news.getImageUrl())      // 图片URL
                .placeholder(R.drawable.ic_placeholder) // 加载中显示的图
                .error(R.drawable.ic_error)             // 加载失败显示的图
                .into(holder.ivThumbnail);              // 填入ImageView
    }

    /**
     * 3. 获取总数:告诉RecyclerView一共有多少条数据。
     */
    @Override
    public int getItemCount() {
        return newsList == null ? 0 : newsList.size();
    }

    /**
     * 内部类:ViewHolder
     * 作用:缓存item布局中的所有子控件,避免重复的 findViewById 调用,提升性能。
     * 这也是RecyclerView比ListView快的奥秘之一。
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvTitle, tvAuthor, tvCommentCount, tvTime;
        ImageView ivThumbnail;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            // 通过itemView.findViewById找到布局中的控件
            tvTitle = itemView.findViewById(R.id.tv_news_title);
            tvAuthor = itemView.findViewById(R.id.tv_author);
            tvCommentCount = itemView.findViewById(R.id.tv_comment_count);
            tvTime = itemView.findViewById(R.id.tv_time);
            ivThumbnail = itemView.findViewById(R.id.iv_thumbnail);
        }
    }
}

代码逻辑深度推演:

  1. onCreateViewHolder:当RecyclerView需要一个新的Item视图时调用。比如屏幕一次能显示5条新闻,开始时它会连续调用5次这个方法。注意inflate中的参数parent, falseparent是RecyclerView本身,false表示暂时不附加到父布局,由RecyclerView自己管理,这是正确的做法。
  2. onBindViewHolder:这是最繁忙的方法。当用户向上滑动,第1条新闻滑出屏幕,第6条新闻进入屏幕。系统会将第1条新闻的ViewHolder对象传给onBindViewHolder,并把position设为5(第6条)。你只需要执行holder.tvTitle.setText(新数据)即可。这就是复用——没有重新创建View,只是修改了内容。
  3. Glide的介入:如果直接在onBindViewHolder里使用ImageView.setImageBitmap(),会引发OOM(内存溢出)和滑动卡顿。Glide自动处理了生命周期、图片缓存、内存复用。这保证了仿头条项目在快速滑动时依然丝滑流畅。

3.4 在Activity中组装RecyclerView

现在万事俱备,我们需要在MainActivity.java中将数据、适配器、布局管理器(LayoutManager)组合起来。

代码示例:MainActivity.java

java

package cn.edu.imageview;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
import cn.edu.imageview.adapter.NewsAdapter;
import cn.edu.imageview.bean.NewsEntity;

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private NewsAdapter newsAdapter;
    private List<NewsEntity> newsDataList;

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

        // 1. 初始化视图
        recyclerView = findViewById(R.id.rv_news_list);

        // 2. 准备模拟数据(在实际项目中,这里应该是网络请求回来的数据)
        initMockData();

        // 3. 创建适配器
        newsAdapter = new NewsAdapter(this, newsDataList);

        // 4. 设置布局管理器(这一步决定了列表是垂直、水平还是网格)
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(RecyclerView.VERTICAL); // 垂直滚动
        recyclerView.setLayoutManager(layoutManager);

        // 5. 绑定适配器
        recyclerView.setAdapter(newsAdapter);

        // 6. 【可选】添加分割线(模仿头条的细灰线)
        // 这里使用系统自带的DividerItemDecoration
        DividerItemDecoration divider = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
        divider.setDrawable(getResources().getDrawable(R.drawable.divider_line));
        recyclerView.addItemDecoration(divider);
    }

    private void initMockData() {
        newsDataList = new ArrayList<>();
        // 模拟您提供的截图中的数据
        newsDataList.add(new NewsEntity("各地餐企齐行动,杜绝餐饮浪费", "央视新闻客户端", "9884评", "6小时前", "https://example.com/img1.jpg"));
        newsDataList.add(new NewsEntity("花菜有人掉水,有人直接炒,都错了,看饭店大厨如何做", "味美食记", "18评", "刚刚", "https://example.com/img2.jpg"));
        newsDataList.add(new NewsEntity("睡觉时,双脚突然一下,有踩空感,像从高楼坠落,是咋回事?", "民富康健康", "78评", "1小时前", "https://example.com/img3.jpg"));
        // 为了测试滚动效果,我们添加更多条目
        for (int i = 0; i < 20; i++) {
            newsDataList.add(new NewsEntity("测试新闻标题 " + i, "测试作者", i + "评", "今天", "https://example.com/default.jpg"));
        }
    }
}

3.5 高级技巧:添加点击事件(Item Click)

真实的今日头条点击新闻会跳转到详情页。RecyclerView没有像ListView那样提供setOnItemClickListener,官方推荐在ViewHolder中自己实现。

改造方案(在Adapter中添加接口):

java

// 在 NewsAdapter.java 中添加
public interface OnItemClickListener {
    void onItemClick(int position);
}

private OnItemClickListener listener;

public void setOnItemClickListener(OnItemClickListener listener) {
    this.listener = listener;
}

// 在 ViewHolder 的构造函数中添加点击逻辑
public ViewHolder(@NonNull View itemView) {
    super(itemView);
    // ... 初始化控件 ...
    itemView.setOnClickListener(v -> {
        if (listener != null) {
            listener.onItemClick(getAdapterPosition());
        }
    });
}

这样,在MainActivity中就可以处理跳转逻辑了。

【此处应插入截图5:运行时的真机或模拟器截图,展示列表成功加载数据,包含图片和文字的效果】

bd54fdcc5dd57f168dc3c9a0ed0c6700.png


第四章:ListView vs RecyclerView —— 为什么HeadLine项目必须用后者?

虽然我们使用了RecyclerView,但理解ListView有助于我们理解Android列表控件的进化史。基于您提供的 2416ebb28832c8eb007092e281eeaa9d.jpg 截图内容,那个界面如果硬要用ListView实现,会遇到以下痛点:

  1. 布局单一:ListView只能垂直滚动。如果需要实现“小视频”Tab里的横向滑动频道,或者双列瀑布流,ListView完全做不到,必须嵌套HorizontalScrollView,性能极差。
  2. ViewHolder非强制:在ListView的早期代码中,很多人忘记复用convertView,导致滑动时GC频繁,掉帧严重。RecyclerView强制ViewHolder,从语法层面规范了开发者的行为。
  3. 动画缺失:当你在仿头条项目中实现“删除不感兴趣”的新闻时,RecyclerView可以轻松调用notifyItemRemoved(position)并伴随默认的淡出动画,而ListView需要手动写复杂的动画逻辑。

第五章:布局与控件的协同 —— 从ViewGroup到View

根据您提供的 9ec0de14b66d60e18c427eae629cc59a.png 结构图,Android的UI体系是树状结构。

在HeadLine项目中,整个屏幕就是一棵View树:

  • 根节点 (ViewGroup) : activity_main 中的 ConstraintLayout

    • 子节点1 (ViewGroup) : 顶部搜索栏 LinearLayout

      • 叶子节点 (View) : 搜索图标 ImageView
      • 叶子节点 (View) : 提示文字 TextView
    • 子节点2 (ViewGroup) : RecyclerView (虽然它继承自ViewGroup,但它内部管理着成千上万个Item)。

      • Item根节点 (ViewGroup) : CardView

        • 叶子节点 (View) : 标题 TextView
        • 叶子节点 (View) : 作者 TextView
        • 叶子节点 (View) : 缩略图 ImageView

控件是如何工作的?

  1. 测量(Measure) : 系统从根布局开始,询问RecyclerView要多大空间。RecyclerView说:“我要填满父布局(match_parent)”。
  2. 布局(Layout) : 系统告诉RecyclerView它的坐标是(0, 顶部栏高度) 到 (屏幕宽度, 屏幕底部)。
  3. 绘制(Draw) : RecyclerView开始工作,它问LayoutManager:“第一个Item应该画在哪?” LayoutManager算出位置,RecyclerView就把CardView画上去。循环往复,由于屏幕只够画5个,它只画5个。滑动时,坐标改变,画面重绘,旧的移出,新的移入。

第六章:性能优化与避坑指南(实战经验)

在开发HeadLine项目的过程中,针对RecyclerView,有几个高频错误必须避免:

6.1 避免在 onBindViewHolder 中执行耗时操作

onBindViewHolder 是在UI线程执行的。如果在里面进行数据库查询或复杂计算,滑动就会卡顿。图片加载我们已经交给了Glide(它自动切线程)。

6.2 嵌套滑动冲突

如果RecyclerView的Item里还有横向滑动的控件(比如头条里的“热门评论”横向滚动),需要调用:

java

recyclerView.setNestedScrollingEnabled(false);

6.3 布局优化的黑科技:setHasFixedSize

如果您的RecyclerView的高度是固定的,且每个Item的高度不会因为数据变化而改变布局参数,请在代码中添加:

java

recyclerView.setHasFixedSize(true);

这告诉RecyclerView:“列表尺寸不会变,你不用每次重新计算每个Item的宽高!” 这能大幅提升渲染效率。

第七章:扩展 —— 如何实现“无限下拉刷新”?

仅仅展示静态数据是不够的。我们可以通过集成SwipeRefreshLayout来模拟下拉刷新,通过滚动监听来实现上拉加载更多。

7.1 下拉刷新

修改activity_main.xml,在RecyclerView外层包裹SwipeRefreshLayout

xml

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
    android:id="@+id/swipe_refresh"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintTop_toBottomOf="@id/title_bar"
    app:layout_constraintBottom_toBottomOf="parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_news_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

在Activity中设置监听:

java

swipeRefreshLayout.setOnRefreshListener(() -> {
    // 模拟网络请求,2秒后添加一条新数据在最顶部
    new Handler().postDelayed(() -> {
        newsDataList.add(0, new NewsEntity("最新热点新闻", "头条热榜", "9999评", "刚刚", "..."));
        newsAdapter.notifyItemInserted(0); // 局部刷新,不带动画卡顿
        recyclerView.scrollToPosition(0);   // 滚动到顶部
        swipeRefreshLayout.setRefreshing(false);
    }, 2000);
});

7.2 上拉加载更多(无限滚动)

通过给RecyclerView添加滚动监听,当滑到底部时自动加载下一页。

java

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
        int lastVisibleItem = manager.findLastVisibleItemPosition();
        int totalItemCount = manager.getItemCount();
        
        // 如果滑到了倒数第二个,且当前没有在加载中,则加载更多
        if (lastVisibleItem >= totalItemCount - 1 && !isLoading) {
            isLoading = true;
            loadMoreData(); // 模拟加载更多
        }
    }
});

第八章:总结 —— 从仿写到创新

通过以上两万多字的深度剖析,我们完整地拆解了“HeadLine仿今日头条”项目中关于列表展示的全部技术细节。

回顾关键点:

  1. 布局资源: 我们学会了用CardView做卡片,用ConstraintLayout做扁平化约束,用LinearLayout的权重机制实现响应式布局。
  2. 控件使用: 深入理解了TextView的截断逻辑、ImageViewcenterCrop缩放模式。
  3. RecyclerView: 掌握了它相比ListView的优势,亲手编写了AdapterViewHolder,理解了onCreateViewHolderonBindViewHolder在复用机制中的不同角色。
  4. 交互增强: 实现了点击事件、下拉刷新和上拉加载。