Android常见界面控件

1 简单控件的使用

控件是界面组成的主要元素,Android 系统提供各类控件用于显示输入框、图片、文字等信息,按使用复杂程度可分为简单控件和列表控件,其中简单控件包含 TextView、Button、EditText、ImageView、RadioButton、CheckBox、Toast,掌握其使用可独立搭建注册界面。

1.1 TextView 控件

用于显示文本信息,可在 XML 布局文件中通过属性控制其样式,核心属性如下:

属性名称 功能描述
android:layout_width 设置 TextView 控件的宽度
android:layout_height 设置 TextView 控件的高度
android:id 设置 TextView 控件的唯一标识
android:background 设置 TextView 控件的背景
android:layout_margin 设置当前控件与屏幕边界 / 周围控件 / 布局的距离
android:padding 设置 TextView 控件与控件内内容的距离
android:text 设置文本内容
android:textColor 设置文字显示的颜色
android:textSize 设置文字大小,推荐单位为 sp
android:gravity 设置文本内容的位置
android:maxLength 设置文本最大长度,超出部分不显示
android:lines 设置文本的行数,超出部分不显示
android:maxLines 设置文本的最大行数,超出部分不显示
android:ellipsize 设置文本超出规定范围的显示方式
android:drawableTop 在文本的顶部显示图像
android:lineSpacingExtra 设置文本的行间距
android:textStyle 设置文本样式,如 bold(粗体)、italic(斜体)、normal(正常)

案例:实现文本居中且为斜体显示

  1. 创建名为 TextView 的应用程序,包名 cn.edu.textview;
  2. 在 res/layout/activity_main.xml 中放置 TextView 控件,配置gravity="center"textStyle="italic"等属性。

1.2 EditText 控件

表示编辑框,是 TextView 的子类,支持用户输入信息,除继承 TextView 的属性外,独有常用属性如下:

属性名称 功能描述
android:hint 控件内容为空时显示的提示文本
android:textColorHint 提示文本的颜色
android:password 输入内容显示为 "."
android:phoneNumber 限制输入内容仅为数字
android:maxLines 设置文本的最大行数
android:scrollHorizontally 文本超出宽度时是否显示横拉条
android:editable 设置控件是否可编辑

案例:实现姓名输入编辑框

  1. 创建名为 EditText 的应用程序,包名 cn.edu.edittext;
  2. 在 activity_main.xml 中放置 TextView(标题)和 EditText(输入框),配置hint="请输入姓名"maxLines="2"等属性。

1.3 Button 控件

表示按钮,继承 TextView 控件,可显示文本 / 图片,支持点击操作,点击时有动态背景切换效果。

点击事件设置方式(三种)

  1. 布局文件指定 onClick 属性
xml 复制代码
<Button
    ......
    android:onClick="click" />
  1. 匿名内部类方式
java 复制代码
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // 点击事件实现代码
    }
});
  1. Activity 实现 OnClickListener 接口
java 复制代码
public class Activity extends AppCompatActivity implements View.OnClickListener{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ......
        btn.setOnClickListener(this);
    }
    @Override
    public void onClick(View view) {
        // 点击事件实现代码
    }
}

注意:前两种适合按钮数量少的场景,按钮较多时推荐第三种方式。

案例:三种方式实现按钮点击后文本变化

  1. 创建名为 Button 的应用程序,包名 cn.edu.button;
  2. 在布局中放置 3 个 Button 控件;
  3. 在 MainActivity 中分别通过三种方式实现点击事件,点击后按钮文本发生变化。

1.4 ImageView 控件

表示图片控件,继承自 View,可加载各类图片资源,常用属性如下:

属性名称 功能描述
android:layout_width 设置 ImageView 控件的宽度
android:layout_height 设置 ImageView 控件的高度
android:id 设置 ImageView 控件的唯一标识
android:background 设置 ImageView 控件的背景
android:layout_margin 设置当前控件与屏幕边界 / 周围控件的距离
android:src 设置需要显示的图片资源
android:scaleType 缩放 / 移动图片以适配控件宽高
android:tint 将图片渲染成指定颜色

案例:显示图片资源

  1. 创建名为 ImageView 的应用程序,包名 cn.edu.imageview;
  2. 将图片导入 drawable-hdpi 文件夹;
  3. 在布局中放置 2 个 ImageView 控件并配置图片资源属性。

1.5 RadioButton 控件

表示单选按钮,是 Button 的子类,有 "选中 / 未选中" 两种状态,由android:checked属性指定(true 为选中,false 为未选中),需与 RadioGroup 配合使用实现单选功能。

布局语法格式

xml

xml 复制代码
<RadioGroup
        android:属性名称 ="属性值"
        ......>
    <RadioButton
          android:属性名称 ="属性值"
                    ...... />
    ......
</RadioGroup>

案例:实现性别单选功能

  1. 创建名为 RadioButton 的应用程序,包名 cn.edu.radiobutton;
  2. 在布局中放置 1 个 RadioGroup(内含 2 个 RadioButton:男 / 女)和 1 个 TextView(显示选中结果);
  3. 在 MainActivity 中设置 RadioGroup 的监听事件,点击单选按钮后,下方 TextView 显示选中的文本信息。

1.6 CheckBox 控件

表示复选框,是 Button 的子类,用于实现多选功能,通过android:checked属性指定状态(true 选中,false 未选中)。

案例:统计用户兴趣爱好

  1. 创建名为 CheckBox 的应用程序,包名 cn.edu.checkbox;
  2. 在布局中放置 2 个 TextView(标题 / 结果)和 3 个 CheckBox(羽毛球 / 篮球 / 乒乓球);
  3. 在 MainActivity 中实现 CheckBox 的点击事件,勾选后界面显示选中的兴趣爱好信息。

1.7 Toast 类

Android 系统的轻量级信息提醒机制,显示在应用界面最上层,一段时间后自动消失,不打断操作、不获取焦点。

核心使用代码
java 复制代码
Toast.makeText(Context,Text,Time).show();
参数说明
  • Context:当前组件的上下文环境(应用程序环境信息);
  • Text:提示的字符串信息;
  • Time:显示时长,可选Toast.LENGTH_SHORT(短时间)、Toast.LENGTH_LONG(长时间)。

示例:提示 WIFI 断开

java 复制代码
Toast.makeText(MainActivity.this, "WIFI已断开", Toast.LENGTH_SHORT).show();

1.8 实战演练 --- 实现注册界面效果

实现步骤

  1. 创建名为 Register 的应用程序,包名 cn.edu.register;
  2. 将注册界面图片导入 drawable-hdpi 文件夹;
  3. 创建样式:分割线、文本、输入框样式;
  4. 放置界面控件:9 个 TextView、8 个 View、1 个 ImageView、3 个 EditText、2 个 RadioButton、3 个 CheckBox、1 个 Button;
  5. 去掉默认标题栏:修改 theme 属性值;
  6. 实现注册功能:获取界面控件、设置单选按钮点击事件;
  7. 运行程序:输入注册信息,点击 "提交" 按钮,提示注册成功。

2 列表控件的使用

核心目标:掌握 ListView 控件使用以搭建购物商城界面,掌握 RecyclerView 控件使用以搭建仿今日头条推荐列表界面。

2.1 ListView 控件的使用

以列表形式展示数据,可根据列表高度自适应屏幕显示,常用属性如下:

属性名称 功能描述
android:listSelector 条目被点击后修改背景颜色
android:divider 设置分割线的颜色
android:dividerHeight 设置分割线的高度
android:scrollbars 是否显示滚动条
android:fadingEdge 去掉上下边的黑色阴影

布局中添加 ListView 示例

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
        ......>
   <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:listSelector="#fefefefe"
        android:scrollbars="none">
   </ListView>
</RelativeLayout>

2.2 常用数据适配器(Adapter)

数据与视图之间的桥梁,将复杂数据转换为用户可接受的呈现形式,是 ListView 加载数据的核心,常用适配器均基于 BaseAdapter 实现:

  1. BaseAdapter:抽象类,自定义适配器时需继承,重写 4 个抽象方法实现数据适配:
方法名称 功能描述
public int getCount() 获取列表条目的总数
public Object getItem(int position) 根据位置获取条目的对象
public long getItemId(int position) 根据位置获取条目的 id
public View getView(int position, View convertView, ViewGroup parent) 获取对应位置的条目视图,convertView 复用旧视图,parent 加载 XML 布局
  1. SimpleAdapter:继承 BaseAdapter,封装了 4 个抽象方法,构造方法:
java 复制代码
public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,int resource, String[] from, int[] to)
  • context:上下文对象;
  • data:数据集合,每一项对应 ListView 一个条目的数据;
  • resource:条目布局的资源 id;
  • from:Map 集合中的 key 值;
  • to:条目布局中对应的控件。
  1. ArrayAdapter:BaseAdapter 的子类,常用于适配 TextView 控件,有多个构造方法,核心参数为上下文、条目布局 id、TextView 控件 id、待适配数据(数组 / List)。

2.3 实战演练 --- 购物商城(ListView 实现)

实现步骤

  1. 创建名为 ListView 的应用程序,包名 cn.edu.listview;
  2. 将商品图片导入 drawable-hdpi 文件夹;
  3. 放置界面控件:1 个 TextView(标题)、1 个 ListView(商品列表);
  4. 创建列表条目布局文件 list_item.xml;
  5. 在 MainActivity 中自定义 MyBaseAdapter,实现 ListView 数据适配,显示商品名称和价格。

实现代码

  1. MainActivity.java
java 复制代码
package cn.edu.listview;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;



public class MainActivity extends Activity {
    private ListView mListView;
    //商品名称与价格数据集合
    private String[] titles = {"桌子", "苹果", "蛋糕", "线衣", "猕猴桃", "围巾"};
    private String[] prices = {"1800元", "10元/kg", "300元", "350元", "10元/kg",
            "280元"};
    //图片数据集合
    private int[] icons = {R.drawable.table, R.drawable.apple, R.drawable.cake,
            R.drawable.wireclothes, R.drawable.kiwifruit, R.drawable.scarf};

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = findViewById(R.id.lv); //初始化ListView控件
        MyBaseAdapter mAdapter = new MyBaseAdapter(); //创建一个Adapter的实例
        mListView.setAdapter(mAdapter);                  //设置Adapter
    }

    class MyBaseAdapter extends BaseAdapter {
        @Override
        public int getCount() {   //获取条目的总数
            return titles.length; //返回条目的总数
        }

        @Override
        public Object getItem(int position) {
            return titles[position]; //返回条目的数据对象
        }

        @Override
        public long getItemId(int position) {
            return position; //返回条目的Id
        }
        //获取条目的视图
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null) {
                //将list_item.xml文件找出来并转换成View对象
                convertView = View.inflate(MainActivity.this, R.layout.list_item, null);
                //找到list_item.xml中创建的TextView
                holder = new ViewHolder();
                holder.title =  convertView.findViewById(R.id.title);
                holder.price = convertView.findViewById(R.id.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.setBackgroundResource(icons[position]);
            return convertView;
        }
        class ViewHolder {
            TextView title, price;
            ImageView iv;
        }
    }
}
  1. cn/edu/listview/ExampleInstrumentedTest.java
java 复制代码
package cn.edu.listview;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("cn.edu.listview", appContext.getPackageName());
    }
}
  1. cn/edu/listview/ExampleUnitTest.java
java 复制代码
package cn.edu.listview;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
  1. res/layout/activity_main.xml
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>
  1. res/layout/list_item.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
    <ImageView
        android:id="@+id/iv"
        android:layout_width="120dp"
        android:layout_height="90dp"
        android:layout_centerVertical="true"/>
    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/iv"
        android:layout_centerVertical="true">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="桌子"
            android:textSize="20sp"
            android:textColor="#000000" />
        <TextView
            android:id="@+id/tv_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="价格:"
            android:textSize="20sp"
            android:layout_marginTop="10dp"
            android:layout_below="@+id/title"
            android:textColor="#FF8F03" />
        <TextView
            android:id="@+id/price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="1000"
            android:textSize="20sp"
            android:layout_below="@+id/title"
            android:layout_toRightOf="@+id/tv_price"
            android:textColor="#FF8F03"
            android:layout_marginTop="10dp"/>
    </RelativeLayout>
</RelativeLayout>
  1. res/values/colors.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>
  1. res/values/strings.xml
xml 复制代码
<resources>
    <string name="app_name">ListView</string>
</resources>
  1. res/values/styles.xml
xml 复制代码
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>
  1. 在res/drawable下放入图片资源
大致效果
ListView 优化

卡顿原因 :滑动时不断创建条目对象、反复执行 findViewById () 初始化控件。优化方式

  1. 使用 ViewHolder 类,减少 findViewById () 调用;
  2. 复用 convertView,避免重复创建条目对象,减少内存消耗和屏幕渲染。

3.2.4 RecyclerView 控件的使用

与 ListView 类似,以列表形式展示数据,通过适配器加载数据,功能更强大,核心优势:

  1. 展示效果:支持横向 / 竖向列表、瀑布流、GridView 效果;
  2. 适配器:强制使用 ViewHolder 类,代码编写更规范;
  3. 复用效果:控件自身实现条目对象复用,无需手动处理;
  4. 动画效果:通过 setItemAnimator () 方法为条目添加动画。
案例 --- 显示动物列表
  1. 创建名为 RecyclerView 的应用程序,包名cn.edu.recyclerview;
  2. 导入图片资源到 drawable-hdpi;
  3. 添加 recyclerview-v7 库依赖;
  4. 布局中放置 1个RecyclerView 控件;
  5. 创建条目布局 recycler_item.xml(1个ImageView、2个TextView);
  6. 在 MainActivity 中实现数据适配,将动物信息显示到列表。
代码实现
  1. cn/edu/recyclerview/MainActivity.java
java 复制代码
package cn.edu.recyclerview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private HomeAdapter mAdapter;
    private String[] names = {"小猫", "哈士奇", "小黄鸭", "小鹿", "老虎"};
    private int[] icons = {R.drawable.cat, R.drawable.siberianhusky,
            R.drawable.yellowduck, R.drawable.fawn, R.drawable.tiger};
    private String[] introduces = {
            "猫,属于猫科动物,分家猫、野猫,是全世界家庭中较为广泛的宠物。",
            "西伯利亚雪橇犬,常见别名哈士奇,昵称为二哈。",
            "鸭的体型相对较小,颈短,一些属的嘴要大些。腿位于身体后方,因而步态蹒跚。",
            "鹿科是哺乳纲偶蹄目下的一科动物。体型大小不等,为有角的反刍类。",
            "虎,大型猫科动物;毛色浅黄或棕黄色,满有黑色横纹;头圆、耳短,耳背面黑色,中央有一白斑甚显著;四肢健壮有力;尾粗长,具黑色环纹,尾端黑色。"
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = findViewById(R.id.id_recyclerview);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new HomeAdapter();
        mRecyclerView.setAdapter(mAdapter);
    }
    class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder> {
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            MyViewHolder holder = new MyViewHolder(LayoutInflater.from(MainActivity.this).inflate(
                    R.layout.recycler_item, parent, false));
            return holder;
        }

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.name.setText(names[position]);
            holder.iv.setImageResource(icons[position]);
            holder.introduce.setText(introduces[position]);
        }
        @Override
        public int getItemCount() {
            return names.length;
        }
        class MyViewHolder extends RecyclerView.ViewHolder {
            TextView name;
            ImageView iv;
            TextView introduce;
            public MyViewHolder(View view) {
                super(view);
                name = view.findViewById(R.id.name);
                iv = view.findViewById(R.id.iv);
                introduce = view.findViewById(R.id.introduce);
            }
        }
    }
}
  1. cn/edu/recyclerview/ExampleInstrumentedTest.java
java 复制代码
package cn.edu.recyclerview;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("cn.edu.recyclerview", appContext.getPackageName());
    }
}
  1. cn/edu/recyclerview/ExampleUnitTest.java
java 复制代码
package cn.edu.recyclerview;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
  1. res/layout/activity_main.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/id_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>
</RelativeLayout>
  1. res/layout/recycler_item.xml
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="wrap_content"
    android:padding="16dp"
    android:gravity="center"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/iv"
        android:layout_width="120dp"
        android:layout_height="90dp"
        android:src="@drawable/siberiankusky"/>
    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="5dp">
        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:textColor="#FF8F03"
            android:text="哈士奇"/>
        <TextView
            android:id="@+id/introduce"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:layout_marginTop="10dp"
            android:layout_below="@+id/name"
            android:textColor="#FF716C6D"
            android:maxLines="2"
            android:ellipsize="end"
            android:text="西伯利亚雪橇犬,常见别名哈士奇,昵称为二哈。"/>
    </RelativeLayout>
</LinearLayout>
  1. res/values/colors.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
</resources>
  1. res/values/strings.xml
xml 复制代码
<resources>
    <string name="app_name">RecyclerView</string>
</resources>
  1. res/values/styles.xml
xml 复制代码
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>
  1. 在资源目录下放入相关资源文件。
效果预览

2.5 实战演练 --- 仿今日头条推荐列表(RecyclerView 实现)

实现步骤

  1. 创建名为 HeadLine 的应用程序,包名 cn.edu.headline;
  2. 导入界面图片到 drawable-hdpi;
  3. 添加 recyclerview-v7 库依赖;
  4. 创建样式:文本、图片样式;
  5. 添加颜色值:浅灰色、深灰色;
  6. 去掉默认标题栏:修改 theme 为@style/Theme.AppCompat.NoActionBar
  7. 搭建界面:标题栏、推荐列表界面、列表条目界面;
  8. 封装实体类:创建 NewsBean 类,定义新闻信息属性字段;
  9. 编写适配器:创建 NewsAdapter,实现加载条目视图、绑定数据、获取条目总数 / 类型;
  10. 显示列表数据:创建 setData () 方法加载新闻数据,将数据设置到适配器;
  11. 运行程序,查看仿今日头条推荐列表效果。

具体代码实现

  1. cn/edu/headline/MainActivity.java
java 复制代码
package cn.edu.headline;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private String[] titles = {"各地餐企齐行动,杜绝餐饮浪费",
            "花菜有人焯水,有人直接炒,都错了,看饭店大厨如何做",
            "睡觉时,双脚突然蹬一下,有踩空感,像从高楼坠落,是咋回事?",
            "实拍外卖小哥砸开小吃店的卷帘门救火,灭火后淡定继续送外卖",
            "还没成熟就被迫提前采摘,8毛一斤却没人要,果农无奈:不摘不行",
            "大会、大展、大赛一起来,北京电竞"好嗨哟""};
    private String[] names = {"央视新闻客户端", "味美食记", "民富康健康", "生活小记",
            "禾木报告", "燕鸣"};
    private String[] comments = {"9884评", "18评", "78评", "678评", "189评",
            "304评"};
    private String[] times = {"6小时前", "刚刚", "1小时前", "2小时前", "3小时前",
            "4个小时前"};
    private int[] icons1 = {R.drawable.food, R.drawable.takeout,
            R.drawable.e_sports};
    private int[] icons2 = {R.drawable.sleep1, R.drawable.sleep2, R.drawable.sleep3,
            R.drawable.fruit1,R.drawable.fruit2, R.drawable.fruit3};
    //新闻类型,1表示置顶新闻或只有1张图片的新闻,2表示包含3张图片的新闻
    private int[] types = {1, 1, 2, 1, 2, 1};
    private RecyclerView mRecyclerView;
    private NewsAdapter mAdapter;
    private List<NewsBean> NewsList;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setData();
        mRecyclerView = findViewById(R.id.rv_list);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAdapter = new NewsAdapter(MainActivity.this, NewsList);
        mRecyclerView.setAdapter(mAdapter);
    }
    private void setData() {
        NewsList = new ArrayList<NewsBean>();
        NewsBean bean;
        for (int i = 0; i < titles.length; i++) {
            bean = new NewsBean();
            bean.setId(i + 1);
            bean.setTitle(titles[i]);
            bean.setName(names[i]);
            bean.setComment(comments[i]);
            bean.setTime(times[i]);
            bean.setType(types[i]);
            switch (i) {
                case 0: //置顶新闻的图片设置
                    List<Integer> imgList0 = new ArrayList<>();
                    bean.setImgList(imgList0);
                    break;
                case 1://设置第2个条目的图片数据
                    List<Integer> imgList1 = new ArrayList<>();
                    imgList1.add(icons1[i - 1]);
                    bean.setImgList(imgList1);
                    break;
                case 2://设置第3个条目的图片数据
                    List<Integer> imgList2 = new ArrayList<>();
                    imgList2.add(icons2[i - 2]);
                    imgList2.add(icons2[i - 1]);
                    imgList2.add(icons2[i]);
                    bean.setImgList(imgList2);
                    break;
                case 3://设置第4个条目的图片数据
                    List<Integer> imgList3 = new ArrayList<>();
                    imgList3.add(icons1[i - 2]);
                    bean.setImgList(imgList3);
                    break;
                case 4://设置第5个条目的图片数据
                    List<Integer> imgList4 = new ArrayList<>();
                    imgList4.add(icons2[i - 1]);
                    imgList4.add(icons2[i]);
                    imgList4.add(icons2[i + 1]);
                    bean.setImgList(imgList4);
                    break;
                case 5://设置第6个条目的图片数据
                    List<Integer> imgList5 = new ArrayList<>();
                    imgList5.add(icons1[i - 3]);
                    bean.setImgList(imgList5);
                    break;
            }
            NewsList.add(bean);
        }
    }
}
  1. cn/edu/headline/NewsAdapter.java
java 复制代码
package cn.edu.headline;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private Context mContext;
    private List<NewsBean> NewsList;
    public NewsAdapter(Context context,List<NewsBean> NewsList) {
        this.mContext = context;
        this.NewsList=NewsList;
    }
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
                                                      int viewType) {
        View itemView=null;
        RecyclerView.ViewHolder holder=null;
        if (viewType == 1){
            itemView = LayoutInflater.from(mContext).inflate(R.layout.
                    list_item_one, parent, false);
            holder= new MyViewHolder1(itemView);
        }else if (viewType == 2){
            itemView = LayoutInflater.from(mContext).inflate(R.layout.
                    list_item_two, parent, false);
            holder= new MyViewHolder2(itemView);
        }
        return holder;
    }
    @Override
    public int getItemViewType(int position) {
        return NewsList.get(position).getType();
    }
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder,
                                 int position) {
        NewsBean bean=NewsList.get(position);
        if (holder instanceof MyViewHolder1){
            if (position==0) {
                ((MyViewHolder1) holder).iv_top.setVisibility(View.VISIBLE);
                ((MyViewHolder1) holder).iv_img.setVisibility(View.GONE);
            } else {
                ((MyViewHolder1) holder).iv_top.setVisibility(View.GONE);
                ((MyViewHolder1) holder).iv_img.setVisibility(View.VISIBLE);
            }
            ((MyViewHolder1) holder).title.setText(bean.getTitle());
            ((MyViewHolder1) holder).name.setText(bean.getName());
            ((MyViewHolder1) holder).comment.setText(bean.getComment());
            ((MyViewHolder1) holder).time.setText(bean.getTime());
            if (bean.getImgList().size()==0)return;
            ((MyViewHolder1) holder).iv_img.setImageResource(bean.getImgList()
                    .get(0));
        }else if (holder instanceof MyViewHolder2){
            ((MyViewHolder2) holder).title.setText(bean.getTitle());
            ((MyViewHolder2) holder).name.setText(bean.getName());
            ((MyViewHolder2) holder).comment.setText(bean.getComment());
            ((MyViewHolder2) holder).time.setText(bean.getTime());
            ((MyViewHolder2) holder).iv_img1.setImageResource(bean.getImgList()
                    .get(0));
            ((MyViewHolder2) holder).iv_img2.setImageResource(bean.getImgList()
                    .get(1));
            ((MyViewHolder2) holder).iv_img3.setImageResource(bean.getImgList()
                    .get(2));
        }
    }
    @Override
    public int getItemCount() {
        return NewsList.size();
    }
    class MyViewHolder1 extends RecyclerView.ViewHolder {
        ImageView iv_top,iv_img;
        TextView title,name,comment,time;
        public MyViewHolder1(View view) {
            super(view);
            iv_top = view.findViewById(R.id.iv_top);
            iv_img = view.findViewById(R.id.iv_img);
            title = view.findViewById(R.id.tv_title);
            name = view.findViewById(R.id.tv_name);
            comment = view.findViewById(R.id.tv_comment);
            time = view.findViewById(R.id.tv_time);
        }
    }
    class MyViewHolder2 extends RecyclerView.ViewHolder {
        ImageView iv_img1,iv_img2,iv_img3;
        TextView title,name,comment,time;
        public MyViewHolder2(View view) {
            super(view);
            iv_img1 = view.findViewById(R.id.iv_img1);
            iv_img2 = view.findViewById(R.id.iv_img2);
            iv_img3 = view.findViewById(R.id.iv_img3);
            title = view.findViewById(R.id.tv_title);
            name = view.findViewById(R.id.tv_name);
            comment = view.findViewById(R.id.tv_comment);
            time = view.findViewById(R.id.tv_time);
        }
    }
}
  1. cn/edu/headline/NewsBean.java
java 复制代码
package cn.edu.headline;
import java.util.List;
public class NewsBean {
    private int id;                   //新闻id
    private String title;            //新闻标题
    private List<Integer> imgList; //新闻图片
    private String name;             //用户名
    private String comment;         //用户评论
    private String time;             //新闻发布时间
    private int type;                 //新闻类型
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getComment() {
        return comment;
    }
    public void setComment(String comment) {
        this.comment = comment;
    }
    public String getTime() {
        return time;
    }
    public void setTime(String time) {
        this.time = time;
    }
    public List<Integer> getImgList() {
        return imgList;
    }
    public void setImgList(List<Integer> imgList) {
        this.imgList = imgList;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
}
  1. cn/edu/headline/ExampleInstrumentedTest.java
java 复制代码
package cn.edu.headline;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("cn.edu.headline", appContext.getPackageName());
    }
}
  1. cn/edu/headline/ExampleUnitTest.java
java 复制代码
package cn.edu.headline;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() {
        assertEquals(4, 2 + 2);
    }
}
  1. res/layout/activity_main.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/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>
  1. res/layout/list_item_one.xml
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="90dp"
    android:layout_marginBottom="8dp"
    android:background="@android:color/white"
    android:padding="8dp">
    <LinearLayout
        android:id="@+id/ll_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="280dp"
            android:layout_height="wrap_content"
            android:maxLines="2"
            android:textColor="#3c3c3c"
            android:textSize="16sp" />
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <ImageView
                android:id="@+id/iv_top"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_alignParentBottom="true"
                android:src="@drawable/top" />
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_toRightOf="@id/iv_top"
                android:orientation="horizontal">
                <TextView
                    android:id="@+id/tv_name"
                    style="@style/tvInfo" />
                <TextView
                    android:id="@+id/tv_comment"
                    style="@style/tvInfo" />
                <TextView
                    android:id="@+id/tv_time"
                    style="@style/tvInfo" />
            </LinearLayout>
        </RelativeLayout>
    </LinearLayout>
    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:layout_toRightOf="@id/ll_info"
        android:padding="3dp" />
</RelativeLayout>
  1. res/layout/list_item_two.xml
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:layout_marginBottom="8dp"
    android:background="@android:color/white">
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLines="2"
        android:padding="8dp"
        android:textColor="#3c3c3c"
        android:textSize="16sp" />
    <LinearLayout
        android:id="@+id/ll_img"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_title"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/iv_img1"
            style="@style/ivImg"/>
        <ImageView
            android:id="@+id/iv_img2"
            style="@style/ivImg"/>
        <ImageView
            android:id="@+id/iv_img3"
            style="@style/ivImg"/>
    </LinearLayout>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/ll_img"
        android:orientation="vertical"
        android:padding="8dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_name"
                style="@style/tvInfo" />
            <TextView
                android:id="@+id/tv_comment"
                style="@style/tvInfo" />
            <TextView
                android:id="@+id/tv_time"
                style="@style/tvInfo" />
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>
  1. res/layout/title_bar.xml
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="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"
        android:gravity="center_vertical"
        android:textColor="@android:color/black"
        android:hint="搜你想搜的"
        android:textColorHint="@color/gray_color"
        android:textSize="14sp"
        android:paddingLeft="30dp" />
</LinearLayout>
  1. res/values/colors.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
    <color name="light_gray_color">#eeeeee</color>
    <color name="gray_color">#828282</color>
</resources>
  1. res/values/strings.xml
xml 复制代码
<resources>
    <string name="app_name">HeadLine</string>
</resources>
  1. res/values/styles.xml
xml 复制代码
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    <style name="tvStyle" >
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:padding">10dp</item>
        <item name="android:gravity">center</item>
        <item name="android:textSize">15sp</item>
    </style>
    <style name="tvInfo" >
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_marginLeft">8dp</item>
        <item name="android:layout_gravity">center_vertical</item>
        <item name="android:textSize">14sp</item>
        <item name="android:textColor">@color/gray_color</item>
    </style>
    <style name="ivImg" >
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">90dp</item>
        <item name="android:layout_weight">1</item>
        <!--ll_info为布局文件list_item_one.xml中的id -->
        <item name="android:layout_toRightOf">@id/ll_info</item>
    </style>
</resources>

项目分析

在正式编写代码前,我们需要完成项目创建、资源导入、依赖添加、主题修改、资源定义等前期工作,这是 Android 项目开发的标准流程,也是保证项目正常运行的基础。

创建 Android 项目
  1. 打开 Android Studio,选择「Start a new Android Studio Project」;

  2. 选择「Empty Activity」模板,点击 Next;

  3. 配置项目参数:

    • Name:HeadLine
    • Package name:cn.edu.headline
    • Save location:自定义项目存储路径
    • Language:Java
    • Minimum SDK:API 19 (Android 4.4)
  4. 点击 Finish,完成项目创建。

项目创建完成后,Android Studio 会自动生成基础代码结构,包含默认的 MainActivity、布局文件、资源文件等。

导入图片资源

今日头条列表需要展示新闻图片、置顶标识、搜索框背景等图片,我们需要将图片资源导入项目的drawable-hdpi目录:

  1. 在项目视图中切换到「Project」模式;
  2. 找到app/src/main/res/drawable-hdpi文件夹;
  3. 将准备好的图片(top.png置顶图标、search_bg.png搜索框背景、food.png/takeout.png/e_sports.png/sleep1.png等新闻图片)复制粘贴到该目录下;
  4. 确保图片名称为小写字母、无中文、无特殊字符,符合 Android 资源命名规范。

本项目用到的图片资源:

  • 置顶图标:top
  • 搜索框背景:search_bg
  • 新闻图片:foodtakeoute_sportssleep1sleep2sleep3fruit1fruit2fruit3
添加 RecyclerView 依赖

RecyclerView 是 Android 支持库中的控件,默认不包含在基础框架中,必须手动添加依赖才能使用。

传统 Gradle 配置(Android Studio 3.x 版本)

打开项目的app/build.gradle文件(Module 级别),在dependencies节点下添加 RecyclerView 依赖:

gradle 复制代码
dependencies {
    // 基础依赖
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    
    // 核心:添加RecyclerView-v7依赖
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
AndroidX 兼容配置(Android Studio 4.0 + 版本)

如果你的项目使用 AndroidX(默认配置),则使用 AndroidX 的 RecyclerView 依赖:

gradle 复制代码
dependencies {
    // AndroidX基础依赖
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    
    // AndroidX RecyclerView依赖
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
    
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

添加完成后,点击右上角「Sync Now」同步项目,等待依赖下载完成即可。

隐藏系统默认标题栏

今日头条使用自定义标题栏,需要隐藏系统自带的 ActionBar / 标题栏,修改项目主题:

  1. 打开app/src/main/res/values/styles.xml文件;
  2. 修改 AppTheme 的父主题为Theme.AppCompat.NoActionBar
xml 复制代码
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>
  1. 打开AndroidManifest.xml文件,确认 Application 节点使用该主题:
xml 复制代码
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

修改完成后,运行项目即可看到系统标题栏已隐藏。

定义颜色资源

为了统一管理界面颜色,避免硬编码,我们在res/values/colors.xml中定义项目所需颜色:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 系统默认颜色 -->
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>
    
    <!-- 项目自定义颜色 -->
    <color name="light_gray_color">#eeeeee</color>  <!-- 浅灰色:列表背景 -->
    <color name="gray_color">#828282</color>        <!-- 深灰色:辅助文字 -->
</resources>

颜色说明

  • light_gray_color:用于主界面背景,区分列表条目白色背景;
  • gray_color:用于新闻发布者、评论数、时间等辅助文字,提升 UI 层次感。
定义样式资源

为了复用控件属性 ,减少重复代码,我们在res/values/styles.xml中定义三种核心样式:

xml 复制代码
<resources>
    <!-- 应用基础主题 -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <!-- 1. 顶部频道栏文字样式 -->
    <style name="tvStyle" >
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:padding">10dp</item>
        <item name="android:gravity">center</item>
        <item name="android:textSize">15sp</item>
    </style>

    <!-- 2. 新闻信息栏文字样式(发布者、评论、时间) -->
    <style name="tvInfo" >
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_marginLeft">8dp</item>
        <item name="android:layout_gravity">center_vertical</item>
        <item name="android:textSize">14sp</item>
        <item name="android:textColor">@color/gray_color</item>
    </style>

    <!-- 3. 三图条目图片样式 -->
    <style name="ivImg" >
        <item name="android:layout_width">0dp</item>
        <item name="android:layout_height">90dp</item>
        <item name="android:layout_weight">1</item>
        <item name="android:layout_toRightOf">@id/ll_info</item>
    </style>
</resources>

样式作用

  1. tvStyle:统一顶部频道栏(推荐、抗疫、小视频等)文字的大小、间距、对齐方式;
  2. tvInfo:统一所有新闻条目底部信息文字的样式,避免重复编写属性;
  3. ivImg:统一三图条目的图片大小、权重,实现三等分布局。

样式复用是 Android 开发的最佳实践,能大幅减少 XML 代码量,提升维护效率。

定义字符串资源

res/values/strings.xml中定义应用名称,遵循 Android 资源管理规范:

xml 复制代码
<resources>
    <string name="app_name">HeadLine</string>
</resources>

至此,项目前期准备工作全部完成,接下来进入核心环节:布局资源编写与控件解析

项目布局资源全解析(XML 文件 + 控件用法)

Android 的界面开发基于XML 布局文件 ,布局文件负责定义界面的结构和控件位置,Java 代码负责逻辑处理。本项目共包含5 个核心布局文件,分别对应不同的界面模块,我们将逐个解析布局结构、所用控件、控件属性及用法。

布局文件总览

表格

布局文件名 作用 核心控件
activity_main.xml 主界面布局(标题栏 + 频道栏 + RecyclerView) LinearLayout、TextView、RecyclerView、View
title_bar.xml 自定义标题栏 LinearLayout、TextView、EditText
list_item_one.xml 单图 / 置顶新闻条目布局 RelativeLayout、LinearLayout、ImageView、TextView
list_item_two.xml 三图新闻条目布局 RelativeLayout、LinearLayout、ImageView、TextView
核心布局控件基础

在解析布局前,先介绍本项目用到的核心布局容器基础控件,这是理解布局的基础:

核心布局容器
  1. LinearLayout(线性布局)

    • 作用:按照 水平(horizontal)垂直(vertical) 方向依次排列子控件;

    • 核心属性:

      • android:orientation:排列方向(horizontal/vertical);
      • android:layout_weight:权重,用于平分剩余空间;
      • android:gravity:子控件对齐方式;
    • 本项目用途:标题栏、频道栏、信息栏、三图布局。

  2. RelativeLayout(相对布局)

    • 作用:以相对位置排列子控件(相对于父容器、相对于其他控件);

    • 核心属性:

      • android:layout_below:位于某控件下方;
      • android:layout_toRightOf:位于某控件右侧;
      • android:layout_alignParentBottom:与父容器底部对齐;
    • 本项目用途:新闻条目布局,灵活控制控件位置。

核心界面控件
  1. TextView(文本控件)

    • 作用:显示文字内容;
    • 核心属性:text(文字)、textSize(文字大小)、textColor(文字颜色)、maxLines(最大行数)、gravity(文字对齐);
    • 本项目用途:标题、频道、新闻标题、信息栏文字。
  2. ImageView(图片控件)

    • 作用:显示图片资源;
    • 核心属性:src(图片资源)、layout_width/height(宽高)、visibility(可见性);
    • 本项目用途:新闻图片、置顶图标。
  3. EditText(输入框控件)

    • 作用:接收用户输入;
    • 核心属性:hint(提示文字)、background(背景)、padding(内边距);
    • 本项目用途:标题栏搜索框。
  4. View(基础视图控件)

    • 作用:绘制分割线、占位符;
    • 本项目用途:频道栏与列表之间的分割线。
  5. RecyclerView(列表控件)

    • 作用:展示大量列表数据,支持复用、多布局;
    • 核心属性:id(唯一标识)、layout_width/height(宽高);
    • 本项目用途:新闻推荐列表。

自定义标题栏布局:title_bar.xml
布局代码
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="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"
            android:gravity="center_vertical"
            android:textColor="@android:color/black"
            android:hint="搜你想搜的"
            android:textColorHint="@color/gray_color"
            android:textSize="14sp"
            android:paddingLeft="30dp" />
    </LinearLayout>
3.3.2 布局解析
  1. 根布局LinearLayout,水平方向,高度固定 50dp,背景色为今日头条红色(#d33d3c),左右内边距 10dp;

  2. TextView 控件

  • 作用:显示「仿今日头条」标题;
  • 属性:layout_gravity="center"垂直居中,白色文字,22sp 字号;
  1. EditText 控件
  • 作用:搜索框,模拟今日头条搜索功能;

  • 核心属性:

    • background="@drawable/search_bg":设置自定义搜索框背景;
    • hint="搜你想搜的":默认提示文字;
    • textColorHint="@color/gray_color":提示文字颜色为深灰色;
    • paddingLeft="30dp":左内边距,避免文字紧贴背景图标;
  1. 布局复用 :该布局通过<include>标签引入主界面,实现模块化开发。
主界面布局:activity_main.xml
布局代码
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/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" />

    <!-- 核心:新闻列表RecyclerView -->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
布局解析
  1. 根布局 :垂直方向 LinearLayout,全屏宽高,背景为浅灰色(light_gray_color);

  2. 标题栏引入<include layout="@layout/title_bar" />,复用标题栏布局;

  3. 频道栏

    • 水平 LinearLayout,白色背景,高度 40dp;
    • 7 个 TextView,全部使用@style/tvStyle样式,统一外观;
    • 「推荐」文字为红色(选中状态),其余为深灰色;
  4. 分割线:View 控件,高度 1dp,浅灰色背景,分隔频道栏和列表;

  5. RecyclerView 控件

    • 核心列表控件,id="@+id/rv_list"(Java 代码中绑定的唯一标识);
    • 宽高全屏,填充剩余所有空间;
    • 注意:使用 support 库时标签为android.support.v7.widget.RecyclerView,AndroidX 为androidx.recyclerview.widget.RecyclerView
控件用法总结
  • include 标签:模块化布局,减少代码冗余,适合复用标题栏、底部栏;
  • View 分割线:轻量级分割线,比 ImageView 更高效;
  • 样式复用 :频道栏 TextView 统一使用tvStyle,无需重复编写宽高、间距;
  • RecyclerView:仅需定义 id 和宽高,具体布局、数据由 Java 代码控制。

单图 / 置顶新闻条目布局:list_item_one.xml

该布局对应新闻类型 1,用于展示置顶新闻或单张图片新闻,是项目中最常用的条目布局。

布局代码
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="90dp"
    android:layout_marginBottom="8dp"
    android:background="@android:color/white"
    android:padding="8dp">

    <!-- 左侧文字信息区域 -->
    <LinearLayout
        android:id="@+id/ll_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <!-- 新闻标题 -->
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="280dp"
            android:layout_height="wrap_content"
            android:maxLines="2"
            android:textColor="#3c3c3c"
            android:textSize="16sp" />

        <!-- 底部信息栏(置顶图标+发布者+评论+时间) -->
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <!-- 置顶图标 -->
            <ImageView
                android:id="@+id/iv_top"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_alignParentBottom="true"
                android:src="@drawable/top" />
            <!-- 信息文字 -->
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_toRightOf="@id/iv_top"
                android:orientation="horizontal">
                <TextView
                    android:id="@+id/tv_name"
                    style="@style/tvInfo" />
                <TextView
                    android:id="@+id/tv_comment"
                    style="@style/tvInfo" />
                <TextView
                    android:id="@+id/tv_time"
                    style="@style/tvInfo" />
            </LinearLayout>
        </RelativeLayout>
    </LinearLayout>

    <!-- 右侧新闻图片 -->
    <ImageView
        android:id="@+id/iv_img"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:layout_toRightOf="@id/ll_info"
        android:padding="3dp" />
</RelativeLayout>
布局解析
  1. 根布局:RelativeLayout,固定高度 90dp,白色背景,底部外边距 8dp(分隔条目),内边距 8dp;

  2. 左侧信息区域(ll_info)

    • 垂直 LinearLayout,包含新闻标题和底部信息栏;
    • 新闻标题(tv_title) :宽度 280dp,最大显示 2 行(maxLines="2"),黑色文字,16sp 字号,避免文字过长撑开布局;
    • 置顶图标(iv_top) :20dp×20dp,与父容器底部对齐,默认显示置顶图标;
    • 信息文字 :发布者、评论数、时间,统一使用tvInfo样式,水平排列;
  3. 右侧图片(iv_img)

    • 位于左侧信息区域右侧(layout_toRightOf="@id/ll_info");
    • 高度 90dp,与条目高度一致,内边距 3dp,避免图片紧贴边框;
  4. 核心逻辑

    • 置顶新闻:隐藏图片(iv_img),显示置顶图标(iv_top);
    • 单图新闻:隐藏置顶图标,显示新闻图片;
    • 该逻辑由 Java 代码中的setVisibility()控制。
3.5.3 所用控件与属性
控件 ID 作用 核心属性
RelativeLayout - 条目根容器 固定高度、外边距、内边距
LinearLayout ll_info 左侧文字容器 垂直排列
TextView tv_title 新闻标题 maxLines="2"、文字大小 / 颜色
ImageView iv_top 置顶图标 底部对齐、图片资源
TextView tv_name/tv_comment/tv_time 信息文字 样式复用
ImageView iv_img 新闻单图 右侧布局、宽高
三图新闻条目布局:list_item_two.xml

该布局对应新闻类型 2,用于展示包含三张图片的新闻,还原今日头条三图条目样式。

布局代码
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:layout_marginBottom="8dp"
    android:background="@android:color/white">

    <!-- 新闻标题 -->
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLines="2"
        android:padding="8dp"
        android:textColor="#3c3c3c"
        android:textSize="16sp" />

    <!-- 三张新闻图片 -->
    <LinearLayout
        android:id="@+id/ll_img"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_title"
        android:orientation="horizontal">
        <ImageView
            android:id="@+id/iv_img1"
            style="@style/ivImg"/>
        <ImageView
            android:id="@+id/iv_img2"
            style="@style/ivImg"/>
        <ImageView
            android:id="@+id/iv_img3"
            style="@style/ivImg"/>
    </LinearLayout>

    <!-- 底部信息栏 -->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/ll_img"
        android:orientation="vertical"
        android:padding="8dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_name"
                style="@style/tvInfo" />
            <TextView
                android:id="@+id/tv_comment"
                style="@style/tvInfo" />
            <TextView
                android:id="@+id/tv_time"
                style="@style/tvInfo" />
        </LinearLayout>
    </LinearLayout>
</RelativeLayout>
布局解析
  1. 根布局 :RelativeLayout,高度自适应(wrap_content),白色背景,底部外边距 8dp;

  2. 新闻标题:全屏宽度,最大 2 行,内边距 8dp,与单图条目标题样式一致;

  3. 三图区域(ll_img)

    • 水平 LinearLayout,位于标题下方(layout_below="@id/tv_title");

    • 3 个 ImageView,统一使用ivImg样式:

      • 宽度 0dp,权重 1,实现三等分屏幕宽度;
      • 高度 90dp,保证图片大小统一;
  4. 底部信息栏 :与单图条目完全一致,复用tvInfo样式,保证 UI 统一性;

  5. 布局优势:高度自适应,三张图片自动平分宽度,UI 简洁美观。

控件用法总结
  • 权重适配layout_weight="1"是实现多控件平分宽度的核心属性;
  • 相对定位layout_below实现控件上下排列,无需固定高度;
  • 样式复用:图片和文字全部使用样式,保证全项目 UI 统一;
  • 自适应高度 :根布局使用wrap_content,适配不同长度的标题。

布局资源总结

本项目所有布局均采用模块化、复用化设计,核心特点:

  1. 根布局选择:标题栏 / 频道栏用 LinearLayout(线性排列),条目布局用 RelativeLayout(灵活定位);
  2. 控件复用:TextView、ImageView 全部使用样式,减少重复代码;
  3. 适配优化:使用 dp 作为尺寸单位,sp 作为文字单位,适配不同屏幕;
  4. 视觉优化:添加内边距、外边距、分割线,提升 UI 层次感;
  5. 唯一 ID:所有需要 Java 代码绑定的控件都设置了唯一 id,方便 findViewById。
数据模型封装:NewsBean 实体类

在 Android 开发中,** 实体类(Bean)** 用于封装业务数据,是连接数据和界面的核心模型。本项目的新闻数据包含多个字段,我们创建NewsBean类统一封装。

实体类作用
  1. 封装新闻的所有属性(id、标题、图片、发布者、评论数、时间、类型);
  2. 提供 get/set 方法,供 Java 代码读取和修改数据;
  3. 作为 RecyclerView 列表的数据源,统一数据格式。
NewsBean 代码实现
java 复制代码
package cn.edu.headline;
import java.util.List;

public class NewsBean {
    // 新闻ID
    private int id;
    // 新闻标题
    private String title;
    // 新闻图片集合(支持多张图片)
    private List<Integer> imgList;
    // 发布者名称
    private String name;
    // 评论数量
    private String comment;
    // 发布时间
    private String time;
    // 新闻类型(1=单图/置顶,2=三图)
    private int type;

    // get和set方法
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public List<Integer> getImgList() {
        return imgList;
    }

    public void setImgList(List<Integer> imgList) {
        this.imgList = imgList;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }
}
核心字段解析
  1. private int id:新闻唯一标识,用于区分不同新闻;
  2. private String title:新闻标题字符串;
  3. private List<Integer> imgList核心字段,存储新闻图片的资源 ID(int 类型),使用 List 集合支持多张图片;
  4. private String name/comment/time:辅助信息,字符串类型;
  5. private int type多类型核心字段,1 代表单图 / 置顶条目,2 代表三图条目,适配器通过该字段加载不同布局。
实体类规范
  1. 所有字段私有化(private),保证数据安全性;
  2. 提供公共的 get/set 方法,外部类只能通过方法访问数据;
  3. 字段命名见名知意,符合 Java 驼峰命名规范;
  4. 集合类型用于存储多张图片,扩展性强。
核心组件:RecyclerView 适配器(NewsAdapter)深度解析

RecyclerView 的核心工作原理 是:适配器(Adapter)+ 视图持有者(ViewHolder)+ 布局管理器(LayoutManager) 。其中适配器是连接数据和列表的桥梁,负责:

  1. 创建列表条目视图;
  2. 绑定数据到视图控件;
  3. 处理多类型条目布局;
  4. 返回条目数量。

本项目的适配器NewsAdapter实现了多类型条目适配,是整个项目的核心,我们将逐行解析代码逻辑。

RecyclerView 适配器基础概念
  1. ViewHolder:视图持有者,缓存条目布局中的控件,避免重复 findViewById,提升性能;
  2. onCreateViewHolder() :创建条目视图,加载 XML 布局,返回 ViewHolder;
  3. onBindViewHolder() :绑定数据到 ViewHolder 中的控件,刷新界面;
  4. getItemCount() :返回列表总条目数;
  5. getItemViewType() :返回条目类型,实现多布局适配。
NewsAdapter 完整代码
java 复制代码
package cn.edu.headline;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    // 上下文对象
    private Context mContext;
    // 新闻数据源
    private List<NewsBean> NewsList;

    // 构造方法:传入上下文和数据源
    public NewsAdapter(Context context,List<NewsBean> NewsList) {
        this.mContext = context;
        this.NewsList=NewsList;
    }

    // 1. 创建条目视图,根据类型加载不同布局
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView=null;
        RecyclerView.ViewHolder holder=null;
        // 类型1:加载单图/置顶条目布局
        if (viewType == 1){
            itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_one, parent, false);
            holder= new MyViewHolder1(itemView);
        }
        // 类型2:加载三图条目布局
        else if (viewType == 2){
            itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_two, parent, false);
            holder= new MyViewHolder2(itemView);
        }
        return holder;
    }

    // 2. 获取条目类型(根据NewsBean中的type字段)
    @Override
    public int getItemViewType(int position) {
        return NewsList.get(position).getType();
    }

    // 3. 绑定数据到控件
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        // 获取当前位置的新闻数据
        NewsBean bean=NewsList.get(position);
        // 判断ViewHolder类型,对应不同条目
        if (holder instanceof MyViewHolder1){
            MyViewHolder1 holder1 = (MyViewHolder1) holder;
            // 置顶新闻:第一个条目,显示置顶图标,隐藏图片
            if (position==0) {
                holder1.iv_top.setVisibility(View.VISIBLE);
                holder1.iv_img.setVisibility(View.GONE);
            }
            // 单图新闻:隐藏置顶图标,显示图片
            else {
                holder1.iv_top.setVisibility(View.GONE);
                holder1.iv_img.setVisibility(View.VISIBLE);
            }
            // 设置文字数据
            holder1.title.setText(bean.getTitle());
            holder1.name.setText(bean.getName());
            holder1.comment.setText(bean.getComment());
            holder1.time.setText(bean.getTime());
            // 设置图片数据(判断图片集合是否为空)
            if (bean.getImgList().size()==0)return;
            holder1.iv_img.setImageResource(bean.getImgList().get(0));
        }
        // 三图条目
        else if (holder instanceof MyViewHolder2){
            MyViewHolder2 holder2 = (MyViewHolder2) holder;
            // 设置文字数据
            holder2.title.setText(bean.getTitle());
            holder2.name.setText(bean.getName());
            holder2.comment.setText(bean.getComment());
            holder2.time.setText(bean.getTime());
            // 设置三张图片
            holder2.iv_img1.setImageResource(bean.getImgList().get(0));
            holder2.iv_img2.setImageResource(bean.getImgList().get(1));
            holder2.iv_img3.setImageResource(bean.getImgList().get(2));
        }
    }

    // 4. 返回列表总条目数
    @Override
    public int getItemCount() {
        return NewsList.size();
    }

    // 视图持有者1:单图/置顶条目
    class MyViewHolder1 extends RecyclerView.ViewHolder {
        ImageView iv_top,iv_img;
        TextView title,name,comment,time;
        public MyViewHolder1(View view) {
            super(view);
            // 绑定控件
            iv_top = view.findViewById(R.id.iv_top);
            iv_img = view.findViewById(R.id.iv_img);
            title = view.findViewById(R.id.tv_title);
            name = view.findViewById(R.id.tv_name);
            comment = view.findViewById(R.id.tv_comment);
            time = view.findViewById(R.id.tv_time);
        }
    }

    // 视图持有者2:三图条目
    class MyViewHolder2 extends RecyclerView.ViewHolder {
        ImageView iv_img1,iv_img2,iv_img3;
        TextView title,name,comment,time;
        public MyViewHolder2(View view) {
            super(view);
            // 绑定控件
            iv_img1 = view.findViewById(R.id.iv_img1);
            iv_img2 = view.findViewById(R.id.iv_img2);
            iv_img3 = view.findViewById(R.id.iv_img3);
            title = view.findViewById(R.id.tv_title);
            name = view.findViewById(R.id.tv_name);
            comment = view.findViewById(R.id.tv_comment);
            time = view.findViewById(R.id.tv_time);
        }
    }
}
适配器核心方法逐行解析
成员变量与构造方法

java

运行

kotlin 复制代码
private Context mContext;       // 上下文:用于加载布局、获取资源
private List<NewsBean> NewsList; // 数据源:新闻集合

// 构造方法:外部传入上下文和数据,初始化适配器
public NewsAdapter(Context context,List<NewsBean> NewsList) {
    this.mContext = context;
    this.NewsList=NewsList;
}
  • 上下文Context是 Android 核心组件,用于加载 XML 布局;
  • 数据源List<NewsBean>是列表的所有数据,适配器从该集合中获取数据。
获取条目类型:getItemViewType ()

java

运行

arduino 复制代码
@Override
public int getItemViewType(int position) {
    return NewsList.get(position).getType();
}
  • 重写该方法,返回当前位置新闻的type字段;
  • RecyclerView 根据返回值(1/2)判断加载哪种布局;
  • 这是实现多类型条目的核心方法。
创建条目视图:onCreateViewHolder ()
java 复制代码
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View itemView=null;
    RecyclerView.ViewHolder holder=null;
    // 根据viewType加载不同布局
    if (viewType == 1){
        // 加载单图布局
        itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_one, parent, false);
        holder= new MyViewHolder1(itemView);
    }else if (viewType == 2){
        // 加载三图布局
        itemView = LayoutInflater.from(mContext).inflate(R.layout.list_item_two, parent, false);
        holder= new MyViewHolder2(itemView);
    }
    return holder;
}
  • LayoutInflater:布局加载器,将 XML 布局转换为 View 对象;

  • inflate () 参数

    • 第一个参数:布局资源 ID;
    • 第二个参数:父容器;
    • 第三个参数:是否附加到父容器(必须为 false,RecyclerView 自动管理);
  • 根据viewType创建对应的 ViewHolder,返回给 RecyclerView。

绑定数据:onBindViewHolder ()

这是适配器最核心的方法,负责将数据设置到控件上:

  1. 获取数据 :通过 position 获取当前新闻对象NewsBean
  2. 判断 ViewHolder 类型:区分单图 / 三图条目;
  3. 置顶逻辑:第一个条目显示置顶图标,隐藏图片;
  4. 设置文字 :调用 TextView 的setText()方法;
  5. 设置图片 :调用 ImageView 的setImageResource()方法,传入图片资源 ID;
  6. 判空处理:避免图片集合为空导致崩溃。
视图持有者(ViewHolder)

适配器定义了两个内部类 ViewHolder,分别对应两种条目:

  • MyViewHolder1 :绑定list_item_one.xml中的所有控件;
  • MyViewHolder2 :绑定list_item_two.xml中的所有控件;
  • ViewHolder 的作用:缓存控件,只在创建时 findViewById 一次,后续直接使用,避免重复查找控件,大幅提升列表滑动性能。
RecyclerView 多类型实现原理总结
  1. 实体类中定义type字段标记条目类型;
  2. 适配器重写getItemViewType()返回类型;
  3. onCreateViewHolder()根据类型加载不同布局;
  4. onBindViewHolder()根据类型绑定不同数据;
  5. 两个 ViewHolder 分别管理两种布局的控件。

这是 Android 开发中多类型列表的标准实现方案,适用于所有复杂列表场景。

主界面逻辑实现:MainActivity

MainActivity 是项目的主控制器,负责:

  1. 初始化控件;
  2. 构造新闻数据源;
  3. 配置 RecyclerView;
  4. 绑定适配器,显示列表数据。
MainActivity 完整代码
java 复制代码
package cn.edu.headline;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    // 新闻标题数组
    private String[] titles = {"各地餐企齐行动,杜绝餐饮浪费",
            "花菜有人焯水,有人直接炒,都错了,看饭店大厨如何做",
            "睡觉时,双脚突然蹬一下,有踩空感,像从高楼坠落,是咋回事?",
            "实拍外卖小哥砸开小吃店的卷帘门救火,灭火后淡定继续送外卖",
            "还没成熟就被迫提前采摘,8毛一斤却没人要,果农无奈:不摘不行",
            "大会、大展、大赛一起来,北京电竞"好嗨哟""};
    // 发布者数组
    private String[] names = {"央视新闻客户端", "味美食记", "民富康健康", "生活小记",
            "禾木报告", "燕鸣"};
    // 评论数数组
    private String[] comments = {"9884评", "18评", "78评", "678评", "189评",
            "304评"};
    // 发布时间数组
    private String[] times = {"6小时前", "刚刚", "1小时前", "2小时前", "3小时前",
            "4个小时前"};
    // 单图图片数组
    private int[] icons1 = {R.drawable.food, R.drawable.takeout,
            R.drawable.e_sports};
    // 三图图片数组
    private int[] icons2 = {R.drawable.sleep1, R.drawable.sleep2, R.drawable.sleep3,
            R.drawable.fruit1,R.drawable.fruit2, R.drawable.fruit3};
    // 新闻类型数组
    private int[] types = {1, 1, 2, 1, 2, 1};

    // RecyclerView控件
    private RecyclerView mRecyclerView;
    // 适配器
    private NewsAdapter mAdapter;
    // 新闻数据源集合
    private List<NewsBean> NewsList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 加载主界面布局
        setContentView(R.layout.activity_main);
        // 构造新闻数据
        setData();
        // 初始化RecyclerView
        mRecyclerView = findViewById(R.id.rv_list);
        // 设置布局管理器:线性垂直布局
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        // 初始化适配器
        mAdapter = new NewsAdapter(MainActivity.this, NewsList);
        // 绑定适配器到RecyclerView
        mRecyclerView.setAdapter(mAdapter);
    }

    // 构造新闻数据源
    private void setData() {
        NewsList = new ArrayList<NewsBean>();
        NewsBean bean;
        // 循环遍历数组,封装NewsBean对象
        for (int i = 0; i < titles.length; i++) {
            bean = new NewsBean();
            bean.setId(i + 1);
            bean.setTitle(titles[i]);
            bean.setName(names[i]);
            bean.setComment(comments[i]);
            bean.setTime(times[i]);
            bean.setType(types[i]);
            // 为不同位置的新闻设置图片集合
            switch (i) {
                case 0: // 置顶新闻:无图片
                    List<Integer> imgList0 = new ArrayList<>();
                    bean.setImgList(imgList0);
                    break;
                case 1:// 单图新闻
                    List<Integer> imgList1 = new ArrayList<>();
                    imgList1.add(icons1[i - 1]);
                    bean.setImgList(imgList1);
                    break;
                case 2:// 三图新闻
                    List<Integer> imgList2 = new ArrayList<>();
                    imgList2.add(icons2[i - 2]);
                    imgList2.add(icons2[i - 1]);
                    imgList2.add(icons2[i]);
                    bean.setImgList(imgList2);
                    break;
                case 3:// 单图新闻
                    List<Integer> imgList3 = new ArrayList<>();
                    imgList3.add(icons1[i - 2]);
                    bean.setImgList(imgList3);
                    break;
                case 4:// 三图新闻
                    List<Integer> imgList4 = new ArrayList<>();
                    imgList4.add(icons2[i - 1]);
                    imgList4.add(icons2[i]);
                    imgList4.add(icons2[i + 1]);
                    bean.setImgList(imgList4);
                    break;
                case 5:// 单图新闻
                    List<Integer> imgList5 = new ArrayList<>();
                    imgList5.add(icons1[i - 3]);
                    bean.setImgList(imgList5);
                    break;
            }
            // 将Bean对象添加到集合
            NewsList.add(bean);
        }
    }
}
核心逻辑解析
数据定义
  • 使用数组定义所有新闻数据(标题、发布者、评论、时间、图片、类型);
  • 数组长度一致(6 条数据),通过下标一一对应;
  • 图片分为单图数组icons1和三图数组icons2,方便适配不同条目。
onCreate () 生命周期方法

Activity 创建时执行的核心逻辑:

  1. setContentView(R.layout.activity_main):加载主界面布局;
  2. setData():调用方法构造新闻数据源;
  3. findViewById(R.id.rv_list):绑定 RecyclerView 控件;
  4. setLayoutManager()必须设置布局管理器,RecyclerView 才能显示(线性布局);
  5. new NewsAdapter():创建适配器,传入上下文和数据;
  6. setAdapter():将适配器绑定到 RecyclerView,显示列表。
setData () 构造数据源
  • 创建List<NewsBean>集合存储所有新闻;

  • 循环遍历数组,创建NewsBean对象,设置所有属性;

  • 使用switch语句为不同位置的新闻设置图片集合:

    • 第 0 条:置顶新闻,空图片集合;
    • 第 1/3/5 条:单图新闻,添加 1 张图片;
    • 第 2/4 条:三图新闻,添加 3 张图片;
  • 最后将 Bean 对象添加到集合,完成数据源构造。

RecyclerView 配置核心要点
  1. 必须设置布局管理器:RecyclerView 没有默认布局管理器,不设置则列表不显示;

    • LinearLayoutManager:线性垂直 / 水平布局;
    • GridLayoutManager:网格布局;
    • StaggeredGridLayoutManager:瀑布流布局;
  2. 适配器与数据源绑定:适配器必须持有数据源,才能显示数据;

  3. 控件初始化 :通过findViewById绑定 RecyclerView,id 与 XML 一致。

3 自定义 View

核心目标:掌握自定义 View 的 3 个核心方法,能够在界面中绘制一个圆形图案。

3.1 自定义 View 的背景

Android 系统提供的控件多为 View 的子类,实际开发中若系统控件无法满足样式 / 功能需求,可通过自定义 View实现,最简单的方式是创建类继承 View(或其子类),并重写构造方法。

3.2 自定义 View 的基础实现

构造方法重写(两种)

运行

java 复制代码
public class Customview extends View{
    // Java代码中创建对象时使用
    public Customview(Context context) {
        super(context);
    }
    // XML布局中引入自定义控件时使用
    public Customview(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

自定义 View 的 3 个核心方法

方法名称 功能
onMeasure() 用于测量控件尺寸
onDraw() 用于绘制图像 / 样式
onLayout() 用于指定布局中子控件的位置
相关推荐
法欧特斯卡雷特2 小时前
从 Kotlin 编译器 API 的变化开始: 2.3.20
android·后端·开源
词元Max3 小时前
1.5 Harness 架构深度解析:Claude Code 为什么强?
android·架构
yy55273 小时前
Mysql 主从复制与读写分离
android·数据库·mysql
zhenxin01224 小时前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb
做萤石二次开发的哈哈4 小时前
萤石云硬件接入如何完成云对讲套件低代码集成?
android·低代码·rxjava
恋猫de小郭6 小时前
2026 AI 时代下,Flutter 和 Dart 的机遇和未来发展,AI 一体化
android·前端·flutter
缺土的鲨鱼辣椒6 小时前
Android 资源类型概述
android
sp426 小时前
安卓原生 MQTT 通讯 Java 实现
android
PD我是你的真爱粉6 小时前
MySQL 事务与并发控制:从日志底层到 MVCC 哲学
android·mysql·adb