实验目的
掌握ArrayAdapter 的基本用法及自定义Adapter 的方法;
实验要求
实验内容
在本实验项目中,要求实现如图1. Code06最终运行效果 所示的新闻阅读器应用。
步骤一,创建Android工程
打开Android Studio 创建名为Code06 的工程,选择Empty Activity 模板。
步骤二,修改activity_main.xml 布局
在activity_main.xml 中添加一个ListView 作为根容器的唯一子控件,代码如下所示。
Copy <?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 两个字符串数组,代码如下所示。
Copy <?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() 方法设置数据适配器,并绑定每个Item对应的布局。在MainActivity 类中进行此操作。
1. 读取arrays.xml文件中的titles、authors字符串数组资源。
在MainActivity 类中定义了titles 、authors 两个字符串数组,并使用getResources() 得到Resources 对象,并通过该对象的getStringArray 的方法获取arrays.xml 文件中定义的字符串数组资源。
Copy 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运行效果——仅显示新闻标题 所示。
步骤五,设置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 数据与控件的绑定;
Copy public SimpleAdapter(Context context,
List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to);
1. 构造数据源的List对象。
使用List > dataList 替换掉之前定义的titles 、authors 数组。将数据源的构造操作 放入initData() 方法中。
Copy 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 > 对象时所使用到的两个key:NEWS_TITLE 及NEWS_AUTHOR 。
to 数组中则是android.R.layout.simple_list_item_2 资源中的两个TextView 控件标签的id。
Copy 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运行效果——仅显示新闻标题及作者 所示。
步骤六,自定义Item布局
要在ListView 控件中的每一列中显示如图1. Code06最终运行效果 所示新闻图片、标题、作者样式,需要自定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 文件中加入该控件的依赖库,代码如下所示。
Copy 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 布局代码下所示。
Copy <?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 路径字符串数组,代码如下所示。
Copy <?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 方法。
Copy 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项时的布局;
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 对应的布局的加载及数据绑定操作。
Copy 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 的绑定。
Copy 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最终运行效果 所示。
实验小结
通过本次实验,你应该掌握了如下知识内容: