《Android 仿今日头条实战:从零搭建多布局新闻列表与自定义标题栏》

0 阅读14分钟

ListView控件

listview是 Android 中常用的列表控件,用于垂直滚动显示一组相同或相似结构的条目(如商品列表、联系人列表)。它本身只负责滚动和条目回收,具体每个条目显示什么内容由 适配器(Adapter)  提供。 常用属性如下:

屏幕截图 2026-04-02 224017.png

数据适配器(Adapter)

数据适配器是数据与视图-view之间的桥梁,它类似于一个转换器,将复杂的数据转换成用户可以接受的方式进行呈现。

  1. BaseAdapter BaseAdapter顾名思义是基本的适配器。它实际上是一个抽象类,通常在自定义适配器时会继承BaseAdapter,该类拥有四个抽象方法,根据这几个抽象方法对ListView控件进行数据适配。
  2. SimpleAdapter SimpleAdapter继承BaseAdapter,实现了BaseAdapter的四个抽象方法并进行封装。
  3. ArrayAdapter ArrayAdapter也是BaseAdapter的子类,用法与SimpleAdapter类似,开发者只需要在构造方法里面传入相应参数即可。ArrayAdapter通常用于适配TextView控件,ArrayAdapter有多个构造方法。 下面有个案例来演示如何通过ListView控件与数据适配器显示一个商品信息的列表:

屏幕截图 2026-04-05 222842.png

这是案例项目的结构

activity_main.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:orientation="vertical">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:text="购物商城"
        android:textSize="18sp"
        android:textColor="#FFFFFF"
        android:background="#FF8F03"
        android:gravity="center"/>
    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
</LinearLayout>
  • 顶部 TextView:作为标题栏,显示“购物商城”,橙色背景,文字居中。这只是界面的装饰部分,与列表功能无关。
  • ListView 控件

    • android:id="@+id/lv":在 Java 代码中通过 findViewById(R.id.lv)获取该控件。
    • android:layout_width="match_parent":宽度填满父容器。
    • android:layout_height="wrap_content":高度根据内部所有条目的总高度自动扩展。 注意:如果条目数量很多,建议改为 match_parent 或固定高度,否则 ListView 会尝试测量所有条目,可能导致性能问题或滚动异常。但在此示例中数据量小,wrap_content 可以工作。

这个布局简单明了:整个屏幕分为标题栏和列表区。

适配器(Adapter)如何将数据填充到 ListView 中

  1. 数据准备(在 Activity 中)
String[] titles = {"桌子", "苹果", "蛋糕", "线衣", "猕猴桃", "围巾"};
String[] prices = {"1800元", "10元/kg", "300元", "350元", "10元/kg", "280元"};
int[] icons = {R.drawable.table, R.drawable.apple, ...};

2. 适配器的 getView() 方法(关键)

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    if (convertView == null) {
        convertView = View.inflate(MainActivity.this, R.layout.list_item, null);
        holder = new ViewHolder();
        holder.title = convertView.findViewById(R.id.title);
        holder.price = convertView.findViewById(R.id.price);  // 注意:这里是 price,不是 tv_price
        holder.iv = convertView.findViewById(R.id.iv);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
    holder.title.setText(titles[position]);
    holder.price.setText(prices[position]);
    holder.iv.setImageResource(icons[position]);  // 或 setBackgroundResource
    return convertView;
}
  • position:当前正在创建的条目索引(0 到 5)。

  • convertView:历史回收的视图,用于复用,提升性能。

  • ViewHolder:缓存子控件引用,避免重复 findViewById

  • 绑定数据

    • 从 titles[position] 获取商品名 --> 设置到 holder.title
    • 从 prices[position] 获取价格字符串 --> 设置到 holder.price
    • 从 icons[position] 获取图片资源 ID --> 设置到 holder.iv
  1. 将适配器设置给 ListView

mListView = findViewById(R.id.lv); MyBaseAdapter mAdapter = new MyBaseAdapter(); mListView.setAdapter(mAdapter);

  • setAdapter() 会让ListView向适配器询问:

    • 一共有多少个条目(getCount())--> 6 个。
    • 每个条目长什么样(反复调用 getView())-->生成 6 个不同的视图并显示。

RecyclerView控件的使用

  • RecyclerView与ListView控件相似,同样是以列表的形式展示数据,并且数据都是通过适配器加载的。
  • RecyclerView控件使用的是RecyclerView.Adapter,该数据适配器将BaseAdapter中getView()方法拆分为 onCreateViewHolder()和onBindViewHolder()方法;强制使用ViewHolder类,使代码编写规范化,避免出现初学者写的代码性能不佳的情况,避免了ListView出现的问题。
  • RecyclerView控件复用Item对象工作由该控件自己实现。类似convertview的功能已经实现了。
  • 通过setItemAnimator()方法为Item添加动画效果。 下面看个事例:

屏幕截图 2026-04-07 162900.png 在 Activity 中初始化 RecyclerView:

  1. 找到控件
  2. 设置布局管理器(LayoutManager)
  3. 创建并设置适配器(Adapter)

屏幕截图 2026-04-07 162908.png 接着这个适配器HomeAdapter继承了RecyclerView.Adapter,并且重写了三个方法来实现功能,分别是onCreateViewHolder()、onBindViewHolder()、getItemCount()。

  1. onCreateViewHolder()用于创建列表项视图 当 RecyclerView 需要一个新的列表项视图时调用。只负责 加载布局文件 并创建 ViewHolder对象。

屏幕截图 2026-04-07 162915.png 2. onBindViewHolder()这个方法用于绑定数据,将列表中的数据填充到holder所持有的控件上。

屏幕截图 2026-04-07 162920.png 3. RecyclerView 通过getItemCount()这个方法知道一共有多少个列表项。

  1. MyViewHolder这是个内部类,必须继承RecyclerView.ViewHolder,构造函数中通过view.findViewById()找到布局文件中的子控件,并保存起来,避免重复查找。

接下来演示如何使用RecyclerView控件,下面是个仿今日头条的推荐列表界面:

  1. 创建程序:创建一个名为HeadLine的应用程序,指定包名为cn.edu.headline
  2. 导入界面图片:将界面需要的图片导入到drawable-hdpi文件夹中
  3. 添加库文件:添加recyclerview-v7库
  4. 创建样式:创建文本样式和创建图片样式
  5. 添加颜色值:添加浅灰色颜色值,添加深灰色颜色值
  6. 去掉默认标题栏:修改theme属性的值为“@style/Theme.AppCompat.NoActionBar”
  7. 搭建界面: 1.创建标题栏2.搭建推荐列表界面3.搭建列表条目界面
  8. 封装实体类:创建NewsBean类,在该类中创建新闻信息属性对应的字段
  9. 编写推荐列表适配器:1.创建适配器NewsAdapter2.在适配器中加载条目视图、获取条目类型、绑定界面数据、获取条目总数
  10. 显示列表数据:1.创建setData()方法,将定义的数组中的数据添加到新闻数据集合NewsList中2.将新闻集合数据设置到适配器NewsAdapter中
  11. 运行结果

1. 创建程序:

项目结构如下:

屏幕截图 2026-04-08 200250.png 由style.xml中设置Headline的应用程序 屏幕截图 2026-04-08 194116.png

2. 导入界面图片:

image.png

3. 添加库文件:

image.png

4. 创建样式:

在style.xml中创建样式 style file.png 代码包含了appTheme,tvStyle,tvInfo,ivImg

  • appTheme中定义整个应用的基础视觉主题,其中parent="Theme.AppCompat.Light.DarkActionBar"继承自 AppCompat的浅色主题,自带深色标题栏。
  • tvStyle是文本样式,用于TextView 或继承自 TextView 的控件(如 Button、EditText)的通用样式。注意其中android:gravity="center"表示文字在控件内水平和垂直居中显示。
  • tvInfo是信息文本样式,专门用于显示次要信息或辅助文字的 TextView 样式(如时间、标签、说明)。
  • tvImg是图片样式,用于ImageView的样式,典型场景是在 LinearLayout 中通过权重分配宽度,同时在 RelativeLayout 中定位到某个控件右侧。注意:android:layout_toRightOf="@id/ll_info":相对布局专用属性,将本控件放置在 ID 为ll_info的视图右侧。

5. 添加颜色值:

color.xml中设置颜色样式 image.png

6. 去掉默认标题栏:

Androidmainifest.png

7. 搭建界面:

首先是标题栏,创建一个独立的布局文件title_bar.xml,在复用到当前界面。 整体高度固定在50dp,内边距左右各留10dp,背景是深红色。左侧标题文字(TextView)垂直居中,字体在22sp。右侧搜索框(EditText) 在主布局中,通过"include layout="@layout/title_bar"引入。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="#d33d3c"   //深红色
    android:orientation="horizontal"
    android:paddingLeft="10dp"   //内边距
    android:paddingRight="10dp">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="仿今日头条"
        android:textColor="@android:color/white"
        android:textSize="22sp" />
    <EditText
        android:layout_width="match_parent"
        android:layout_height="35dp"
        android:layout_gravity="center_vertical"  //在父容器中垂直居中
        android:layout_marginStart="15dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="15dp"
        android:background="@drawable/search_bg"  //引用一个自定义 Drawable资源
        android:gravity="center_vertical"  //输入框内文字在竖直方向居中
        android:textColor="@android:color/black"
        android:hint="搜你想搜的"  //灰色提示文字,引导用户操作
        android:textColorHint="@color/gray_color"
        android:textSize="14sp"
        android:paddingLeft="30dp"   //左侧内边距 30dp,用于给左侧的搜索图标留出空间/>
</LinearLayout>

推荐列表界面的搭建,分为水平Tab栏和RecyclerView.

  • 水平的线性布局,内部放置了多个TextView,所有文字都引用了之前定义的tvStyle,保证视觉的一致性。
  • RecyclerView这是推荐列表的核心控件,它负责高效地显示大量新闻条目。id定义为rv_list,方便在 Activity或Fragment中通过findViewById获取并设置适配器。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/light_gray_color"
    android:orientation="vertical">
    <include layout="@layout/title_bar" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="@android:color/white"
        android:orientation="horizontal">
        <TextView
            style="@style/tvStyle"
            android:text="推荐"
            android:textColor="@android:color/holo_red_dark" />
        <TextView
            style="@style/tvStyle"
            android:text="抗疫"
            android:textColor="@color/gray_color" />
        <TextView
            style="@style/tvStyle"
            android:text="小视频"
            android:textColor="@color/gray_color" />
        <TextView
            style="@style/tvStyle"
            android:text="北京"
            android:textColor="@color/gray_color" />
        <TextView
            style="@style/tvStyle"
            android:text="视频"
            android:textColor="@color/gray_color" />
        <TextView
            style="@style/tvStyle"
            android:text="热点"
            android:textColor="@color/gray_color" />
        <TextView
            style="@style/tvStyle"
            android:text="娱乐"
            android:textColor="@color/gray_color" />
    </LinearLayout>
    **<View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#eeeeee" />
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />**
</LinearLayout>

image.png

搭建列表条目有两种方式,因为布局的方式不同,后面会讲到。类型的加载和选择

8. 封装实体类:

image.png 这是一个典型的 Java 实体类(Entity / POJO),用于封装新闻相关的数据。

  • 私有字段+公有getter/setter:符合 JavaBean 规范,保证了数据的可控性(例如可以在 setter 中添加校验逻辑)。
  • 对外隐藏内部表示,提供统一的访问接口,便于后期修改字段名或类型(只需改 getter/setter,不影响调用方)

9. 编写推荐列表适配器:

创建适配器NewsAdapter

onCreateViewHolder.png

  • mContext:用于后续获取LayoutInflater服务,这是将XML布局文件解析为Java View对象的必经之路。
  • NewsList:保存外部传入的数据源引用。

获取条目类型: getItemViewType(int position)

@Override
public int getItemViewType(int position) {
    return NewsList.get(position).getType();
}
  • RecyclerView 在渲染列表时,会先遍历可见范围内的每一个 position,调用 getItemViewType 确定该位置应当使用哪种视图布局。该方法的返回值会在内部传递给 onCreateViewHolder 的第二个参数 int viewType
  • 根据 NewsBean.getType() 的返回值(此处假设为 1 或 2),适配器将条目分为两类:
  • viewType == 1:对应 list_item_one.xml 布局,通常用于显示 单张图片 的新闻样式。
  • viewType == 2:对应 list_item_two.xml 布局,用于显示 三张图片 的新闻样式。

这种设计使得 RecyclerView 的视图池(RecycledViewPool)可以独立管理两种类型的 ViewHolder,避免单图布局的 ViewHolder 被错误地复用给三图条目。

创建条目视图: onCreateViewHolder图片在上面创建适配器后

LayoutInflater.inflate 详解

  • LayoutInflater.from(mContext) :获取布局解析器服务。
  • inflate(R.layout.list_item_one, parent, false)
    • 参数1:待解析的 XML 布局资源 ID。
    • 参数2:parent 即 RecyclerView 本身,用于获取布局参数的类型。
    • 参数3:false 表示 不立即将解析出的 View 添加到 parent 中。这是因为 RecyclerView 会根据内部布局算法在合适的时机调用 addView。如果此处传入 true,会导致视图层级混乱甚至崩溃。这是 RecyclerView 性能优化的核心阵地,也是 ViewHolder 模式的具体体现。

NewsAdapter还定义了两个内部类来充当 ViewHolder : 能将布局中需要频繁操作的子控件引用缓存在ViewHolder对象中。当条目滑出屏幕被回收后,这些控件引用依然保留,下次复用时无需再次执行耗时的findViewById遍历。 MyViewHolder.png

讲到这里又要回到之前搭建条目时候的两种布局了。

第一个是list_item_one.xml 这是左文右图的特点,左侧文字信息固定包裹内容,右侧图片利用layout_toRightOf占满剩余宽度。iv_top与底部信息栏处于同一水平线,它的显示与隐藏会直接影响tv_name等文字的起始位置。

list_itemone.png

第二个是list_item_two.xml 这是上文下图,并且三图并排的特点。垂直的结构进行排列,标题在上,图片在中,信息流在下。ivImg样式定义了layout_width="0dp"和layout_weight="1",使得三张图片在横向LinearLayout中均匀分配宽度。

list_itemtwo.png 对于位置position,应该用上面的哪一个布局来呈现呢? NewsBean实体类中就包含一个 type 字段,例如:

  • 单图新闻、纯文字新闻、带置顶标识的新闻返回1。
  • 三图新闻、多图新闻返回2。 刚好在MainActivity.java中就有体现。

绑定界面数据: onBindViewHolder 它的作用也显而易见,就是将NewsBean对象中的字符串和图片资源 ID,填入上述 ViewHolder 持有的控件中。

屏幕截图 2026-04-07 162915.png onBlindViewHolder.png

单图模式绑定(MyViewHolder1)

  • 在 list_item_one.xml中,iv_top和底部信息流是重叠或并列在同一个RelativeLayout中的。虽然我们只看到android:layout_toRightOf="@id/iv_top",实际上这意味:

  • 当iv_top显示(VISIBLE)时,它占据 20dp 宽度,底部的LinearLayout(包含作者、评论)会被挤到右侧。

  • 当iv_top隐藏(GONE)时,底部的LinearLayout会左对齐。

  • 互斥处理:代码中特意将iv_img设置为 GONE 当显示置顶时。这虽然与iv_top不是同一个位置,但结合布局来看,当显示置顶图标时,通常意味着该条目是纯文字头条,不需要显示右侧缩略图。这是一种常见的 UI 变体处理。

  • 底部的三个 TextView 均引用了 style="@style/tvInfo"。回顾前文样式定义,它规定了字体大小 14sp、灰色、垂直居中。这意味着在绑定数据时,我们无需在 Java 代码中重复设置这些视觉属性,只需关注文本内容本身。

  • if (bean.getImgList().size() == 0) return; 是一道简单的防线。但严格来说,更优雅的做法是设置一个默认的占位图,而不是直接跳过设置(可能导致显示上一轮复用的残留图片)。

三图模式绑定(MyViewHolder2)

  • 该样式定义了layout_width="0dp"、layout_weight="1"、layout_height="90dp"。因此,无论原图尺寸如何,显示区域都被严格限制为等宽90dp高的方块。这种样式先行的策略大大简化了适配器代码。
  • 代码直接调用 get(0)、get(1)、get(2),这隐含了imgList必须包含至少 3 个元素的契约。若后端数据异常返回了仅 2 张图的数组,应用将在该位置崩溃。
  • 改进建议:在绑定前判断 imgList.size(),如果小于 3,可将多余的 ImageView 设为 GONE,或者加载默认图

总结: getItemViewType是分岔路口的路牌,onCreateViewHolder是制造不同车型的工厂,而onBindViewHolder则是那位熟练的装配工——它不问车型,只看零件槽位(findViewById的 ID),准确地将数据螺栓拧入对应的螺孔中。

获取条目总数: getItemCount

getItemCount.png

  • 它的作用很简单:RecyclerView 通过此返回值决定需要渲染的 Item 数量。如果NewsList为null,此处将抛出空指针异常。

10. 显示列表数据:

1.创建setData()方法,将定义的数组中的数据添加到新闻数据集合NewsList中

在调用setData()之前,在 Activity 的成员变量中定义一系列数组,用于模拟后端返回的 JSON 数据。

屏幕截图 2026-04-07 214841.png

屏幕截图 2026-04-07 214903.png

    1. 初始化集合容器
    1. 遍历标题数组(代表新闻条目数)
    1. 通用字段赋值(每个条目都有的属性)
    1. 根据不同位置构建不同的图片列表(模拟真实接口的不规则数据)
    1. 将构建好的Bean加入集合

2.将新闻集合数据设置到适配器NewsAdapter中

数据准备完成后,需要将NewsList传递给 NewsAdapter,并关联到 RecyclerView。 onCreate.png

  • 加载主布局文件(即前文包含 <include layout="@layout/title_bar"/> 和 RecyclerView 的那个布局),使后续 findViewById 能正确找到控件。
  • 执行我们之前分析的静态数据组装逻辑,生成NewsList集合对象。注意:NewsList通常为该 Activity 的成员变量(如 private List<NewsBean> NewsList;)。
  • 从布局中获取android:id="@+id/rv_list"的 RecyclerView 控件实例。
  • RecyclerView 不预设任何排列方式,需要指定一个LayoutManager来定义条目是垂直滚动、水平滚动、网格还是瀑布流。此处使用线性垂直布局。
  • 调用适配器构造函数,将当前上下文和构建好的新闻数据集合传递进去。适配器内部保存了NewsList的引用。
  • 触发渲染。RecyclerView 拿到适配器后,会立即调用getItemCount()确定条目总数,并根据屏幕可见范围调用onCreateViewHolder和onBindViewHolder绘制首屏内容。

11.结果运行:

image.png