> For the complete documentation index, see [llms.txt](https://xxgqin.gitbook.io/android/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xxgqin.gitbook.io/android/ch02/ch02-4.md).

# 第七个项目——新闻列表

## 实验目的

* 掌握**ListView**控件的基本用法；
* 掌握**ListView**自定义**Item**布局的方法；
* 掌握**ArrayAdapter**的基本用法及自定义**Adapter**的方法；
* 掌握**CardView**控件的基本用法；

## 实验要求

* 创建布局文件并对根据要求对布局进行设置；

## 实验内容

在本实验项目中，要求实现如[图1. Code06最终运行效果](/android/ch02/ch02-4.md#code06_screenshot)所示的新闻阅读器应用。

![图1. Code06最终运行效果](http://www.funnycode.net/guet/img/ch02/Code06_running_screenshot.png)

### 步骤一，创建Android工程

打开**Android Studio**创建名为**Code06**的工程，选择**Empty Activity**模板。

### 步骤二，修改**activity\_main.xml**布局

在**activity\_main.xml**中添加一个**ListView**作为根容器的唯一子控件，代码如下所示。

```markup
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/lv_news_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
```

### 步骤三，准备ListView的数据源

简便起见，我们将所有的新闻数据作为字符串资源放入**arrays.xml**文件中。 在工程窗口中右击**app**模块文件夹，选择【New】-> 【Android Resource File】菜单，弹出**New Resource File**窗口，设置文件名为**arrays**。 在**array.xml**文件中加入*titles*及*authors*两个字符串数组，代码如下所示。

```markup
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="titles">
        <item>Is Tomato a Vegetable or a Fruit?</item>
        <item>Why do Clocks Run Clockwise?</item>
        <item>Who Invented The Pen?</item>
        <item>Abdul Kareem’s Forest</item>
        <item>Venice is Sinking </item>
        <item>Martin Luther King, Jr. </item>
        <item>Charlie Chaplin </item>
        <item>How do Satellites Stay Up? </item>
        <item>Programmed to learn </item>
        <item>How does a Submarine Work? </item>
        <item>Where Did Numerals Originate? </item>
        <item>Why’s the Sun Red during Sunrise and Sunset? </item>
        <item>Why can’t the Sun melt Snow? </item>
        <item>How are Stars Named?</item>
        <item>How Far Away are the Stars? </item>
        <item>An Organism that is visible from Space</item>
        <item>Why Do We Have Wrinkly Fingers After Swimming? </item>
        <item>Why doesn’t our stomach get digested? </item>
        <item>What Is The Origin Of Silk Fabric? </item>
    </string-array>
    <string-array name="authors">
        <item>Chitra Padmanabhan</item>
        <item>Rama Kumaraswamy Thoopal</item>
        <item>Gargi Pant</item>
        <item>Brishti Bandyopadhyay</item>
        <item>Rama Kumaraswamy Thoopal</item>
        <item>Team Pitara</item>
        <item>Team Pitara</item>
        <item>Jim Nicholls</item>
        <item>Chitra Padmanabhan</item>
        <item>Ajay Dasgupta</item>
        <item>Bhaswati Ghosh</item>
        <item>Rama Kumaraswamy Thoopal</item>
        <item>Woodpecker</item>
        <item>Saakshi Khanna</item>
        <item>Ajay Dasgupta</item>
        <item>Gargi Pant</item>
        <item>Ajay Dasgupta</item>
        <item>Gargi Pant</item>
        <item>Gargi Pant</item>
    </string-array>
</resources>
```

### 步骤四，构造**ListView**所需的**Adapter**对象

**ListView**控件需要通过**setAdapter()**&#x65B9;法设置数据适配器，并绑定每个Item对应的布局。在**MainActivity**类中进行此操作。

**1. 读取arrays.xml文件中的titles、authors字符串数组资源。**

在**MainActivity**类中定义了*titles*、*authors*两个字符串数组，并使用**getResources()**&#x5F97;到**Resources**对象，并通过该对象的**getStringArray**的方法获取**arrays.xml**文件中定义的字符串数组资源。

```java
public class MainActivity extends AppCompatActivity {

    private String[] titles = null;
    private String[] authors = null;

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

        titles = getResources().getStringArray(R.array.titles);
        authors = getResources().getStringArray(R.array.authors);

        ArrayAdapter<String> adapter = new ArrayAdapter<String>(
            MainActivity.this, android.R.layout.simple_list_item_1, titles);

        ListView lvNewsList = findViewById(R.id.lv_news_list);
        lvNewsList.setAdapter(adapter);
    }
}
```

**2. 设置ArrayAdapter，指定Item布局。**

在本步骤中使用**ArrayAdapter**绑定*titles*字符串数组作为**ListView**控件的数据源。并指定**Item**的布局为系统预定义的 **android.R.layout.simple\_list\_item\_1**，该布局指包含1个**TextView**控件。

**3. 编译并部署APK。**

编译本项目，并部署运行，其运行效果如[图2. Code06运行效果——仅显示新闻标题](/android/ch02/ch02-4.md#code06_screenshot2)所示。

![图2. Code06运行效果——仅显示新闻标题](http://www.funnycode.net/guet/img/ch02/Code06_running_screenshot2.png)

### 步骤五，设置SimpleAdapter，在ListView中显示新闻标题及作者

**ArrayAdapter**作为**ListView**的适配器使用时，只能用于Item中包含1个**TextView**的情况。如果要在列表中显示新闻的标题及作者，则需要使用**SimpleAdapter**作为适配器。

首先了解**SimpleAdapter**适配器构造函数如何使用，代码如下所示，其中：

* **Context context**，表示上下文对象，可直接传递**MainActivity.this**对象；
* **List\<? extends Map > data**，表示绑定的数据**List**列表，其类型需为**Map**；
* **int resource**，**ListView**的**Item**布局资源，可以为系统预定义或用户自定义的布局资源；
* **String\[] from**，Map中的String类型的Key，与**to**结合一起将Map中的该*key*对应的*value*绑定到**to**数组中的资源上；
* **int\[] to**，**resource**布局资源中对应的控件id，与**from**结合完成Map中*value*数据与控件的绑定；

```java
    public SimpleAdapter(Context context, 
                        List<? extends Map<String, ?>> data, 
                        int resource, String[] from, int[] to);
```

**1. 构造数据源的List对象。**

使用**List > dataList** 替换掉之前定义的*titles*、*authors*数组。将数据源的构造操作 放入**initData()**&#x65B9;法中。

```java
public class MainActivity extends AppCompatActivity {

    private static final String NEWS_TITLE = "news_title";
    private static final String NEWS_AUTHOR = "news_author";

    private List<Map<String, String> > dataList = new ArrayList<>();

    ...

    private void initData() {
        int length;
        titles = getResources().getStringArray(R.array.titles);
        authors = getResources().getStringArray(R.array.authors);

        if (titles.length > authors.length) {
            length = authors.length;
        } else {
            length = titles.length;
        }

        for (int i = 0; i < length; i++) {
            Map map = new HashMap();
            map.put(NEWS_TITLE, titles[i]);
            map.put(NEWS_AUTHOR, authors[i]);

            dataList.add(map);
        }
    }
 }
```

**2. 构造SimpleAdapter适配器。**

**SimpleAdapter**对象所需的*context*上下文参数、数据源对象均已经构造好。**Item**布局则使用 系统预定义的**android.R.layout.simple\_list\_item\_2**资源，该资源包含两个**TextView**控件标签，具体可在**Android Studio**中查看。

**from**数组，则是构造**List >**&#x5BF9;象时所使用到的两个key：**NEWS\_TITLE**及**NEWS\_AUTHOR**。

**to**数组中则是**android.R.layout.simple\_list\_item\_2**资源中的两个**TextView**控件标签的id。

```java
public class MainActivity extends AppCompatActivity {
    private static final String NEWS_TITLE = "news_title";
    private static final String NEWS_AUTHOR = "news_author";    
    ...

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

        initData();

        SimpleAdapter simpleAdapter = new SimpleAdapter(MainActivity.this,
                dataList, android.R.layout.simple_list_item_2,
                new String[]{NEWS_TITLE, NEWS_AUTHOR},
                new int[]{android.R.id.text1, android.R.id.text2});

        ListView lvNewsList = findViewById(R.id.lv_news_list);
        lvNewsList.setAdapter(simpleAdapter);         
    }

    ...
}
```

**3. 编译并部署APK。**

编译本项目，并部署运行，其运行效果如[图3. Code06运行效果——仅显示新闻标题及作者](/android/ch02/ch02-4.md#code06_screenshot3)所示。

![图3. Code06运行效果——仅显示新闻标题及作者](http://www.funnycode.net/guet/img/ch02/Code06_running_screenshot3.png)

### 步骤六，自定义Item布局

要在**ListView**控件中的每一列中显示如[图1. Code06最终运行效果](/android/ch02/ch02-4.md#code06_screenshot)所示新闻图片、标题、作者样式，需要自定**ListView**的**Item**布局。

**1. 自定义list\_item.xml布局。** 在工程窗口中右击**app**模块文件夹，选择【New】-> 【Android Resource File】菜单， 弹出**New Resource File**窗口，将**File Name**设为**list\_item**， **Resource Type**设为**Layout**。

**list\_item.xml**布局中的根元素使用**CardView**卡片容器控件。要使用卡片容器控件，需要在**app**模块的**build.gradle**文件中加入该控件的依赖库，代码如下所示。

```java
apply plugin: 'com.android.application'

...

dependencies {
    ...
    implementation 'com.android.support:cardview-v7:28.0.0'
    ...
}
```

在**CardView**中使用**RelatvieLayout**进行布局管理，其中引入**ImageView**显示新闻图片。 设定其图片拉伸属性**scaleType**为*centerCrop*。

新闻标题及作者对应的**TextView**控件则使用分别使用**layout\_below**属性位于新闻图片及新闻标题下方，并设置合理的**padding**属性，**list\_item.xml**布局代码下所示。

```markup
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.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:padding="8dp"
    app:cardCornerRadius="8dp"
    >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/iv_image"
            android:layout_width="match_parent"
            android:layout_height="128dp"
            android:scaleType="centerCrop"
            />

        <TextView
            android:id="@+id/tv_title"
            style="@style/TextAppearance.AppCompat.Subhead"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/iv_image"
            android:paddingTop="8dp"
            android:paddingStart="8dp"
            android:paddingBottom="4dp"
            android:textColor="@color/colorPrimary"
            android:textSize="18sp"
            android:textStyle="bold" />
        <TextView
            android:id="@+id/tv_subtitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_title"
            android:paddingStart="8dp"
            android:paddingBottom="4dp"
            android:textColor="@android:color/secondary_text_light"
            style="@style/TextAppearance.AppCompat.Subhead"/>
    </RelativeLayout>
</android.support.v7.widget.CardView>
```

**2. 加入图片资源**

将准备好的图片资源放入工程的**res/drawable**文件夹中，并且在**arrays.xml**文件中加入这些图片资源的**drawable**路径字符串数组，代码如下所示。

```markup
<?xml version="1.0" encoding="utf-8"?>
<resources>
    ...
    <string-array name="images">
        <item>@drawable/img_01</item>
        <item>@drawable/img_02</item>
        <item>@drawable/img_03</item>
        <item>@drawable/img_04</item>
        <item>@drawable/img_05</item>
        <item>@drawable/img_06</item>
        <item>@drawable/img_07</item>
        <item>@drawable/img_09</item>
        <item>@drawable/img_08</item>
        <item>@drawable/img_010</item>
        <item>@drawable/img_011</item>
        <item>@drawable/img_012</item>
        <item>@drawable/img_013</item>
        <item>@drawable/img_014</item>
        <item>@drawable/img_015</item>
        <item>@drawable/img_016</item>
        <item>@drawable/img_017</item>
        <item>@drawable/img_018</item>
        <item>@drawable/img_019</item>
    </string-array>
</resources>
```

### 步骤七，构造**NewsAdapter**适配器类

**1. 构造News类。**

**News**类用于存储新闻的标题、作者、新闻正文、对应的标题图，代码如下所示。 需要为**News**类中的每个成员变量设置**set/get**方法，可通过【Refactor】->【Encapsulate Fields...】 窗口选择所需封装的成员变量自动生成**set/get**方法。

```java
public class News {
    private String mTitle;
    private String mAuthor;
    private String mContent;
    private int mImageId;

    public String getTitle() {
        return mTitle;
    }

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

**2. 构造NewsAdapter适配器类。**

右击工程窗口中的**app/java**文件夹，选择【New】-> 【Java Class】，将类名命名为**NewsAdapter**。 继承的父类为**ArrayAdapter**。

对于继承至**ArrayAdapter**类的**NewsAdapter**子类，需要至少实现两个方法：构造器方法以及**getView**方法。

**NewsAdapter**类的构造器需要三个参数，分别为：

* **Context context**，context上下文用于**NewsAdapter**类中**LayoutInflater**加载布局时使用；
* **int resourceId**，用于设置**ListView**每个Item项时的布局；
* **List data**，用于传递**News**对象列表；

**NewsAdapter**构造函数中首先调用父类的构造方法，再将这三个参数保存以便在**getView**方法中使用。

**getView**方法是用于**ListView**显示某一位置**Item**时进行回调的方法。该方法返回值为**Item**所需要加载的**View**控件。 而该方法的的三个参数含义为：

* **int position**，当前**Item**对应的位置；
* **View convertView**，针对**ListView**中缓存的不可见的**Item**的**View**对象；
* **ViewGroup parent**，需要加载的**View**的父容器对象；

通过**position**参数，可以使用**getItem**方法获取对应的**News**对象。

针对第二个参数**convertView**，需要结合**ViewHolder**使用以减少每一次**getView**方法调用 都需要重新构造**Item**项**View**布局对象，从而提升**ListView**效率，在此先不做介绍，详见第\ref{sec:listviewdemo}章内容。

在**getView**方法中调用**LayoutInflater**加载指定的**resourceId**布局，第二个参数则为加载的布局对应的父容器控件对象， 第三个参数表示是否将加载的布局加入父容器控件中，在此情况下要选择**false**。

使用**LayoutInflater**加载完成布局后，可分别绑定新闻标题、新闻作者、新闻标题图控件。并根据**position**取到的**News**对象设置这三个控件的属性，从而完成当前**Item**对应的布局的加载及数据绑定操作。

```java
package com.glriverside.xgqin.listviewdemo;

import android.content.Context;
import android.view.LayoutInflater;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class NewsAdapter extends ArrayAdapter<News> {

    private List<News> mNewsData;
    private Context mContext;
    private int resourceId;

    public NewsAdapter(Context context, int resourceId, List<News> data) {
        super(context, resourceId, data);
        this.mContext = context;
        this.mNewsData = data;
        this.resourceId = resourceId;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        News news = getItem(position);
        View view ;

        view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);

        TextView tvTitle  = view.findViewById(R.id.tv_title);
        TextView tvAuthor = view.findViewById(R.id.tv_subtitle);
        ImageView ivImage = view.findViewById(R.id.iv_image);

        tvTitle.setText(news.getTitle());
        tvAuthor.setText(news.getAuthor());
        ivImage.setImageResource(news.getImageId());
        return view;
    }
}
```

**3. 修改数据源的构造方式。**

在**MainActivity**中修改从**arrays.xml**中加载新闻标题、作者、新闻标题图的方式。

首先使用**List newsList**作为存储加载数据的对象。并在**initData**方法中使用**Resources** 的**obtainTypedArray**方法获取在**arrays.xml**文件定义的新闻标题图**drawable**资源。

在**onCreate**方法中新建**NewsAdapter**，并把自定义的**list\_item**布局，以及在**initData**中构造的**newsList** 对象作为其构造函数参数。

最后将通过**ListView**的**setAdapter**设置构造好的**NewsAdapter**适配器，完成数据源与**ListView**的绑定。

```java
public class MainActivity extends AppCompatActivity {

    public static final String NEWS_ID = "news_id";
    private List<News> newsList = new ArrayList<>();

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

        initData();

        NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this,
                R.layout.list_item, newsList);

        ListView lvNewsList = findViewById(R.id.lv_news_list);

        lvNewsList.setAdapter(newsAdapter);
    }

    private void initData() {
        int length;

        titles = getResources().getStringArray(R.array.titles);
        authors = getResources().getStringArray(R.array.authors);
        TypedArray images = getResources().obtainTypedArray(R.array.images);

        if (titles.length > authors.length) {
            length = authors.length;
        } else {
            length = titles.length;
        }

        for (int i = 0; i < length; i++) {
            News news = new News();
            news.setTitle(titles[i]);
            news.setAuthor(authors[i]);
            news.setImageId(images.getResourceId(i, 0));

            newsList.add(news);
        }
    }
}
```

**4. 编译并部署APK。**

编译本项目，并部署运行，其运行效果如[图1. Code06最终运行效果](/android/ch02/ch02-4.md#code06_screenshot)所示。

## 实验小结

通过本次实验，你应该掌握了如下知识内容：

* 使用**ListView**进行数据显示；
* 自定义**ListView**的**Item**布局；
* 自定义**ArrayAdapter**构造适配器；
* 使用**CardView**卡片布局展示数据；


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xxgqin.gitbook.io/android/ch02/ch02-4.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
