适配器视图无处不在,以至于您很难找到一个不使用它们的流行Android应用程序。该名称可能听起来不熟悉,但如果您认为您从未见过适配器视图,则可能是错误的。每次看到 Android 应用以列表、网格或堆栈的形式显示用户界面元素时,您都会看到一个正在运行的适配器视图。
顾名思义,适配器视图是一个对象。这意味着,您可以将其添加到活动中,就像添加任何其他用户界面小部件一样。但是,它无法自行显示任何数据。其内容始终由另一个对象(适配器)确定。在本教程中,我将向您展示如何创建适配器并使用它们来馈送不同类型的适配器视图。
1、什么是适配器?
适配器是实现接口的类的对象。它充当数据集和适配器视图之间的链接,适配器视图是扩展抽象类的类的对象。数据集可以是以结构化方式呈现数据的任何内容。数组、对象和对象是常用的数据集。
适配器负责从数据集中检索数据并基于该数据生成对象。然后,生成的对象用于填充绑定到适配器的任何适配器视图。
您可以从头开始创建自己的适配器类,但大多数开发人员选择使用或扩展 Android SDK 提供的适配器类。
2、适配器视图如何工作?
适配器视图可以非常有效地显示大型数据集。例如,和小部件可以显示数百万个项目,而不会出现任何明显的滞后,同时保持内存和CPU使用率非常低。他们是怎么做到的?不同的适配器视图遵循不同的策略。但是,这是他们中的大多数人通常做的事情。
它们仅呈现那些已经在屏幕上或即将在屏幕上移动的对象。这样,适配器视图消耗的内存可以是恒定的,并且与数据集的大小无关。
它们还允许开发人员最大限度地减少昂贵的布局膨胀操作,并回收已移出屏幕的现有对象。这样可以降低 CPU 消耗。
3、创建ArrayAdapter
要创建适配器,您需要具备以下条件:
- 数据集
- 包含所生成对象的布局的资源文件
此外,由于该类只能处理字符串,因此需要确保生成的对象的布局至少包含一个小部件。
第 1 步:创建数据集
该类可以使用数组和对象作为数据集。现在,让我们使用数组作为数据集。
String[] cheeses = {
"Parmesan",
"Ricotta",
"Fontina",
"Mozzarella",
"Cheddar"
};
步骤 2:创建资源文件
创建一个新的布局 XML 文件,其根元素为 ,并将其命名为项.xml。将“大文本”微件拖放到其中,并将其属性的值设置为cheese_name。布局 XML 文件应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/cheese_name" />
</LinearLayout>
步骤 3:创建适配器
在活动中,使用类的构造函数创建类的新实例。作为其参数,传递资源文件的名称、的标识符和对数组的引用。适配器现在已准备就绪。
ArrayAdapter<String> cheeseAdapter =
new ArrayAdapter<String>(this,
R.layout.item,
R.id.cheese_name,
cheeses
);
4. 创建列表
要显示可垂直滚动的项目列表,可以使用微件。要将小部件添加到活动中,可以将其拖放到活动的布局 XML 文件中,也可以使用 Java 代码中的构造函数创建小组件。
ListView cheeseList = new ListView(this);
setContentView(cheeseList);
若要将其绑定到我们在上一步中创建的适配器,请调用如下所示的方法。
cheeseList.setAdapter(cheeseAdapter);
如果现在运行应用,则应该能够以列表的形式查看数组的内容。
5. 创建网格
要显示可垂直滚动的项目二维网格,可以使用微件。两者都是抽象类的子类,它们有许多相似之处。因此,如果您知道如何使用其中一个,那么您也知道如何使用另一个。
使用类的构造函数创建一个新实例,并将其传递给活动的方法。
GridView cheeseGrid = new GridView(this);
setContentView(cheeseGrid);
若要设置网格中的列数,请调用其方法。我将把它做成一个两列的网格。
cheeseGrid.setNumColumns(2);
通常,您希望使用 、 和 方法来调整列的宽度和之间的间距。请注意,这些方法使用像素作为其单位。setColumnWidth()setVerticalSpacing()setHorizontalSpacing()
cheeseGrid.setColumnWidth(60);
cheeseGrid.setVerticalSpacing(20);
cheeseGrid.setHorizontalSpacing(20);
现在,您可以使用该方法将 绑定到我们之前创建的适配器。
cheeseGrid.setAdapter(cheeseAdapter);`
再次运行应用以查看外观。
6. 添加事件侦听器
可以侦听适配器视图中项目的单击和长单击事件。例如,让我们将单击事件侦听器添加到 GridView
创建实现接口的匿名类的新实例,并将其传递给对象的方法。安卓工作室会为接口的方法自动生成一个存根。您会注意到,该方法的参数包含一个整数,用于指定列表项的位置。您可以使用此整数来找出用户单击的数据集中的哪个项目。AdapterView.OnItemClickListenersetOnItemClickListener()GridViewonItemClick()
下面的代码演示如何在每次单击 中的项时将简单消息显示为小吃栏。
cheeseGrid.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView,
View view, int position, long rowId) {
// Generate a message based on the position
String message = "You clicked on " + cheeses[position];
// Use the message to create a Snackbar
Snackbar.make(adapterView, message, Snackbar.LENGTH_LONG)
.show(); // Show the Snackbar
}
});
如果运行应用并单击网格中的任意项目,屏幕底部将显示一条消息。请注意,您可以使用相同的代码来侦听。
7. 扩展ArrayAdapter
在它生成的对象的布局中只能处理一个小部件。要拓宽它的功能,你必须扩展它。但是,在我们这样做之前,让我们创建一个稍微复杂的数据集。
假设我们的数据集包含以下类的对象,而不是字符串:
static class Cheese {
String name;
String description;
public Cheese(String name, String description) {
this.name = name;
this.description = description;
}
}
这是我们将使用的数据集:
Cheese[] cheeses = {
new Cheese("Parmesan", "Hard, granular cheese"),
new Cheese("Ricotta", "Italian whey cheese"),
new Cheese("Fontina", "Italian cow's milk cheese"),
new Cheese("Mozzarella", "Southern Italian buffalo milk cheese"),
new Cheese("Cheddar", "Firm, cow's milk cheese"),
};
如您所见,该类包含两个字段和 。要在列表或网格中显示这两个字段,项目的布局必须包含两个微件。
创建一个新的布局 XML 文件并将其命名为custom_item.xml。向其添加“大文本”和“小文本”构件。将第一个小部件的属性设置为cheese_name将第二个小部件的属性设置为cheese_description。布局 XML 文件的内容现在应如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/cheese_name" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Small Text"
android:id="@+id/cheese_description" />
</LinearLayout>
还必须能够处理两个小部件。重新访问您的活动,创建一个扩展该类的新匿名类,并重写其方法。确保将数组作为参数传递给其构造函数。
ArrayAdapter<Cheese> cheeseAdapter =
new ArrayAdapter<Cheese>(this, 0, cheeses) {
@Override
public View getView(int position,
View convertView,
ViewGroup parent) {
}
};
在方法内部,必须将参数用作数组的索引,并在该索引处获取项。getView()position
Cheese currentCheese = cheeses[position];
该方法的第二个参数使我们能够重用对象。如果忽略它,适配器视图的性能将很差。首次调用该方法时, 是 。必须通过扩充指定列表项布局的资源文件来初始化它。为此,请使用该方法获取对 的引用并调用其方法。
// Inflate only once
if(convertView == null) {
convertView = getLayoutInflater()
.inflate(R.layout.custom_item, null, false);
}
此时,您可以使用 来获取对布局内小部件的引用,并调用它们的方法以使用数组中的数据对其进行初始化。
TextView cheeseName =
(TextView)convertView.findViewById(R.id.cheese_name);
TextView cheeseDescription =
(TextView)convertView.findViewById(R.id.cheese_description);
cheeseName.setText(currentCheese.name);
cheeseDescription.setText(currentCheese.description);
最后,返回,以便它可用于填充与适配器关联的任何适配器视图。
return convertView;
8. 使用视图支架
适配器视图重复调用该方法以填充自身。因此,您必须尝试尽量减少在其中执行的操作数。getView()
在上一步中,您可能已经注意到,即使我们确保列表项的布局只膨胀一次,每次调用该方法时都会调用该方法,该方法会消耗许多 CPU 周期。
为了避免这种情况并提高适配器视图的性能,我们需要将方法的结果存储在对象中。为此,我们可以使用视图持有者对象,它只不过是一个类的对象,可以存储布局中存在的小部件。
由于布局具有两个构件,因此视图持有者类还必须具有两个构件。我已将类命名为“视图持有者”。
static class ViewHolder{
TextView cheeseName;
TextView cheeseDescription;
}
在该方法中,在放大布局后,现在可以使用该方法初始化视图持有者对象。
ViewHolder viewHolder = new ViewHolder();
viewHolder.cheeseName =
(TextView)convertView.findViewById(R.id.cheese_name);
viewHolder.cheeseDescription =
(TextView)convertView.findViewById(R.id.cheese_description);
要将视图持有者对象存储在 中,请使用其方法。convertViewsetTag()
// Store results of findViewById
convertView.setTag(viewHolder);
现在,每次调用时,您都可以使用该方法检索视图持有者对象,并使用其方法更新其中的小部件。
TextView cheeseName =
((ViewHolder)convertView.getTag()).cheeseName;
TextView cheeseDescription =
((ViewHolder)convertView.getTag()).cheeseDescription;
cheeseName.setText(currentCheese.name);
cheeseDescription.setText(currentCheese.description);
如果现在运行应用,则可以看到每个单元格中显示两行文本。