自定义列表项布局






4.85/5 (11投票s)
描述如何使用自定义定义的布局资源创建项目列表
引言
在这篇短文中,我将介绍一种使用单独资源文件快速轻松地自定义列表项视图的方法。
背景
当我们使用特定的框架时,有时会发现内置功能不够用。其中一种情况就是呈现列表项。如果不想仅显示简单的文本列表怎么办?
本文中的示例是用于功能定制和扩展的基线代码。我尽量使其简单化,以便仅展示如何使用资源文件定义项布局,以及如何填充项数据。
操作方法
我使用的开发环境是带有ADT插件的Eclipse环境。
为了使示例尽可能简单,只关注自定义列表项视图,所有代码都基于干净的标准Android项目。
项布局
首先,我们在res/layout文件夹上右键单击,选择New > Android XML File,创建一个新的布局文件。在对话框中,选择Resource Type: Layout,Root Element: Linear Layout,然后输入File item.xml
。
作为示例,我们将在列表中显示每个项的三个值:
- position - 元素的索引。
- id - 元素的自定义标识符。
- item - 项的值。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":position" />
<TextView
android:id="@+id/id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":id" />
<TextView
android:id="@+id/item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":item" />
</LinearLayout>
为了在图形设计器中预览布局,我在android:text
标签中提供了初始值。这样,我们的项看起来会是这样的:
我们可以为该布局创建不同的资源文件,具体取决于屏幕尺寸、设备方向和分辨率。我们继承了Android资源系统的所有优点。现在,我们将保持尽可能简单。
自定义列表适配器
当我们知道项的外观后,现在需要实现适配器。此类用于根据集合创建项视图。
在主应用程序代码包(例如:net.origami.android.examples
)上右键单击,选择New > Class。将其Name设为CustomAdapter
,并在Superclass中输入android.widget.BaseAdapter
。
首先,我们将创建一个构造函数。
public CustomAdapter(Context context, String... items) {
_inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
_items = items;
}
作为参数,我们获取context
,它使我们能够访问应用程序和系统资源,以及我们的items
集合。在此示例中,我选择了字符串,但它可以是任何适合您的类型的集合,也许是某些自定义域对象。
我的适配器将记住以下内容:
_inflater
- 读取资源并将View
实例化的系统服务。_items
- 我的要显示的集合。
接下来,我们覆盖简单的方法:
@Override
public int getCount() {
return _items.length;
}
@Override
public Object getItem(int position) {
return _items[position];
}
@Override
public long getItemId(int position) {
return position + 1;
}
- 集合中的项数。getCount
getItem
- 允许获取指定位置的项。getItemId
- 允许获取项的自定义标识符。这可以是,但不必是,一个位置。在此示例中,我们将0基索引更改为从1到n开始。
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 1.
View view = _inflater.inflate(R.layout.item, null);
// 2.
TextView positionText = (TextView) convertView
.findViewById(R.id.position);
TextView idText = (TextView) convertView.findViewById(R.id.id);
TextView itemText = (TextView) convertView.findViewById(R.id.item);
// 3.
positionText.setText("position: " + position);
idText.setText("id: " + getItemId(position));
itemText.setText("item: " + getItem(position));
return view;
}
我们通过执行三个通用步骤来实现该方法:
- 基于资源布局创建项视图。
我们使用构造函数中记住的_inflater
。 - 查找所有自定义元素。
所有可识别的文本、图像。 - 根据
position
自定义项视图。
分配文本,加载图像,以及显示、隐藏某些元素(如果需要)。
优化
该示例并非完全可行,因为我们还没有使用适配器。无论如何,移动世界的执行优化是一个关键因素,我现在将进行一个题外话。
对getView调用进行的性能调查显示,在列表项首次显示之前,每个可见列表项会执行3次。用户可能在屏幕上看不到太大差异,但可能会注意到我们的应用程序很快“耗尽电池”,因为视图之间切换频繁。
这是由于Android中的布局逻辑。measure
、layout
和measure
是这三个过程。这就是为什么适配器(实际上此优化已在ListView
中实现)具有内置的convertView
优化。
convertView
是一个参数,它允许我们重用先前实例化的项视图对象来为其他项进行自定义。我们不必关心它是如何发生的,只需知道**如果convertView
不是null
,我们就可以重用它**。因此,优化后,我们的getView
实现将如下所示:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// 1.
if (convertView == null) {
convertView = _inflater.inflate(R.layout.item, null);
}
// 2.
TextView positionText = (TextView) convertView
.findViewById(R.id.position);
TextView idText = (TextView) convertView.findViewById(R.id.id);
TextView itemText = (TextView) convertView.findViewById(R.id.item);
// 3.
positionText.setText("position: " + position);
idText.setText("id: " + getItemId(position));
itemText.setText("item: " + getItem(position));
return convertView;
}
您可以看到,只有第一步发生了巨大变化。由于使用了参数,因此还删除了view变量。
它是如何工作的? 如我所提到的,
getView ,并且即将消失的视图将被重用于即将出现的项。不再需要加载资源。有关详细信息,请参阅I/O 2010会议的参考资料。 |
我们还可以进行另一项优化。搜索自定义元素也可能消耗大量资源。也许它不像convertView
参数那么引人注目,但我们应该尽力提供高质量和优化的软件。看看代码:
static class ViewHolder {
TextView positionText;
TextView idText;
TextView itemText;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
// 1.
convertView = _inflater.inflate(R.layout.item, null);
// 2.
holder = new ViewHolder();
holder.positionText = (TextView) convertView
.findViewById(R.id.position);
holder.idText = (TextView) convertView.findViewById(R.id.id);
holder.itemText = (TextView) convertView.findViewById(R.id.item);
convertView.setTag(holder);
} else {
// 2.
holder = (ViewHolder) convertView.getTag();
}
// 3.
holder.positionText.setText("position: " + position);
holder.idText.setText("id: " + getItemId(position));
holder.itemText.setText("item: " + getItem(position));
return convertView;
}
创建了一个新的内部类ViewHolder
。它是用于缓存用于自定义的搜索元素结果的容器。我们每个创建的项视图只搜索一次,并将其作为标签存储在项视图本身中。当重用convertView
时,我们只需从标签中检索ViewHolder
,而无需再次搜索项视图中的自定义元素。
使用适配器
现在是时候显示我们的列表项了。
首先,我们需要将ListView添加到主活动视图资源(main.xml
)中:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/description"
/>
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
并在活动创建时(MainActivity.java)将其与集合一起分配给我们的适配器:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(new CustomAdapter(this,
"Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6",
"Item 7", "Item 8", "Item 9", "Item 10", "Item 11", "Item 12",
"Item 13", "Item 14", "Item 15", "Item 16", "Item 17",
"Item 18", "Item 19", "Item 20"));
}
就是这样了!
选择操作
另一件可能很有用的事情是访问列表中的选定项。通常,它会显示项的详细信息。在此示例中,我将显示一个简单的Toast弹出窗口,其中包含与项视图中显示的数据相同的数据。
为此,只需在设置适配器后添加以下几行即可。
list.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapter, View view, int position, long id) { Toast toast = Toast.makeText( getApplicationContext(), "position: " + position + ", id: " + id + ", value: " + adapter.getItemAtPosition(position), Toast.LENGTH_LONG); toast.show(); } });
功能
这当然是处理ListView
的一种方法,其他可能性包括:
ListActivity
- 使用它作为您的活动的基类,并将ListView
元素标记为id属性设置为@android:id/list
。当我们要显示整个屏幕的列表时,这很有用。在这种情况下,我们不需要在代码中搜索ListView
,只需使用setListAdapter
方法将适配器绑定到活动。ListFragment
(API 11起)- 与ListActivity相同,但采用Fragment的方式。SimpleCursorAdapter
- 内置适配器,用于处理可以从数据库和Content Provider检索的Cursor
对象。在构造函数中,我们传入Cursor
以及项布局、要映射的字段以及项布局元素的相应ID。
ListView
和适配器还有更多功能,例如显示具有不同自定义视图的各种项类型。当然,也包括视图重用。标题、页脚等。但这又是另一回事了……如果您好奇,请参阅参考部分列出的Google I/O会议。参考文献
- ListView的世界 - Google I/O 2010会议,提供视频和幻灯片(pdf格式)。
历史
- 2012-01-21 - 文章的初始版本。