带图片和文本的自定义Android ListView






4.64/5 (41投票s)
在Android中创建自定义ListView
屏幕截图
默认列表视图项目

ListView 项目选择时

SampleActivity 屏幕在项目选择时

引言
本文将提供一个小型教程,帮助有好奇心的准 Android 开发者实现一个带图片和一些文本的自定义 Android ListView
,类似于标准音乐播放器应用或天气小部件中的那些。
背景
Android SDK 提供的 ListView
控件默认是简单、朴素且绝对乏味的。对于真实的商业移动应用程序,最先给用户留下深刻印象的是移动应用程序的用户界面。Android ListView
的默认外观和感觉并不吸引人,因为它只使用内部的 TextView
控件在每个 ListView
行中渲染一个简单的 string
。对于大多数应用程序,您需要创建一个图形更丰富、视觉上更令人愉悦的界面。
所以我们要做的就是将平淡无奇的 ListView
转换为类似这样的内容:

变成令人惊叹的自定义列表。
现在,在我们尝试在 UI 上显示一些非凡的闪亮内容之前,我们首先需要一个数据源。为了简单起见,让我们采用这个简单的 XML 数据源,其中包含世界一些知名城市的天气信息。
XML 数据源
<?xml version="1.0" encoding="utf-8"?>
<Weather>
<weatherdata>
<id>1</id>
<city>Berlin</city>
<tempc>0°C</tempc>
<tempf>32°F</tempf>
<condition>Snowing</condition>
<windspeed>5 kmph</windspeed>
<icon>snowing</icon>
</weatherdata>
<weatherdata>
<id>2</id>
<city>Bangalore</city>
<tempc>23°C</tempc>
<tempf>73.4°F</tempf>
<condition>Thunderstorms</condition>
<windspeed>10 kmph</windspeed>
<icon>thunder</icon>
</weatherdata>
<weatherdata>
<id>3</id>
<city>London</city>
<tempc>5°C</tempc>
<tempf>41°F</tempf>
<condition>Rainy</condition>
<windspeed>2 kmph</windspeed>
<icon>rainy</icon>
</weatherdata>
<weatherdata>
<id>4</id>
<city>New York</city>
<tempc>18°C</tempc>
<tempf>64.4°F</tempf>
<condition>Cloudy</condition>
<windspeed>7 kmph</windspeed>
<icon>cloudy</icon>
</weatherdata>
<weatherdata>
<id>5</id>
<city>Sydney</city>
<tempc>32°C</tempc>
<tempf>89.6°F</tempf>
<condition>Sunny</condition>
<windspeed>10 kmph</windspeed>
<icon>sunny</icon>
</weatherdata>
</Weather>
假设我们已经创建了一个名为“WeatherDisplay
”的空 Android 项目,默认活动文件为 WeatherActivity.java。
将 XML 文件添加到 Android 项目
- 右键单击项目 > 新建 Android XML 文件。
- 将文件命名为 weatherdata.xml。
- 将 XML 文件放在
WeatherDisplay
Android 项目的 assets 文件夹下。
创建自定义列表视图项目布局
现在我们已经创建了一个 Android 项目和一个空的 UI,我们将用我们自定义的列表(每个 listview
项目都有一个图片和文本)填充它。如前所述,我们想要类似这样的内容:

在我们开始创建布局的基本骨架之前,我们还应该创建几个文件,每个文件定义一个合适的渐变背景(基于选择),以实现那种闪亮的突出显示效果。
常规渐变背景(默认状态样式)
让我们在 res/drawable 文件夹下创建一个新的 XML 文件,并将其命名为 gradient_bg.xml。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#D5DDE0"
android:centerColor="#e7e7e8"
android:endColor="#CFCFCF"
android:angle="270" />
</shape>
高亮渐变背景(鼠标悬停状态样式)
为了实现这一点,让我们在 res/drawable 文件夹下创建一个新的 XML 文件,并将其命名为 gradient_bg_hover.xml。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#78DDFF"
android:centerColor="#16cedb"
android:endColor="#09adb9"
android:angle="270" />
</shape>
因此,既然我们已经定义了不同的选择背景,我们将使用这些样式信息,并将这些信息链接到我们计划创建的 listview
项目上。以便根据用户选择和取消选择,在 listview
上实现突出显示效果。
为了根据用户选择链接 gradient_bg.xml 和 gradient_bg_hover.xml 文件中定义的样式信息,让我们在 drawable 文件夹下创建一个 XML 文件,并将其命名为 list_selector.xml。此文件中定义的配置将作为我们稍后将要定义的 listview
项目的背景。
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_selected="false"
android:state_pressed="false"
android:drawable="@drawable/gradient_bg" />
<item android:state_pressed="true"
android:drawable="@drawable/gradient_bg_hover" />
<item android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/gradient_bg_hover" />
</selector>
list_selector.xml 文件将根据控件(listview
项目)的当前按下状态定义样式信息,即当控件未按下时,它将引用 gradient_bg.xml 中定义的样式信息。
和
当控件处于按下状态时,它将引用 gradient_bg_hover.xml 中定义的样式信息。现在我们已经在文件中定义了样式和背景突出显示信息,我们将创建我们自定义 listview
项目的实际布局。
创建自定义列表视图行项目
让我们自定义设计一个 listview
,它包含左侧的图片(天气图标),右侧的箭头,以及中间的信息(城市名称和天气信息)。我使用了 RelativeLayout
作为父节点,并使用相对定位属性放置了所有其他项。
- 右键单击 ? 新建 ? Android XML 文件
- 将文件命名为 list_row.xml,并将其放在 res/layout 位置下。
- 将以下代码粘贴到文件中
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="@drawable/list_selector" android:orientation="horizontal" android:padding="5dip" > <!-- ListRow Left side Thumbnail image --> <LinearLayout android:id="@+id/thumbnail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_marginRight="5dip" android:padding="3dip" > <ImageView android:id="@+id/list_image" android:contentDescription="@string/app_name" android:layout_width="60dip" android:layout_height="60dip" android:src="@drawable/sunny" /> </LinearLayout> <!-- Rightend Arrow --> <ImageView android:id="@+id/imageView1" android:contentDescription="@string/app_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/arrow" /> <!-- City--> <TextView android:id="@+id/tvCity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toRightOf="@+id/list_image" android:layout_marginLeft="75dip" android:layout_centerVertical="true" android:paddingBottom ="10dip" android:text="@string/strCity" android:textColor="#040404" android:textSize="25dip" android:textStyle="bold" android:typeface="sans" /> <!-- Weather Information--> <TextView android:id="@+id/tvCondition" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/tvCity" android:layout_alignLeft="@+id/tvCity" android:paddingTop="5dip" android:layout_centerHorizontal="true" android:text="@string/strWeather" android:textColor="#343434" android:textSize="15dip" /> </RelativeLayout>
从上面的文件中可以看出,我们在布局文件中定义了以下 UI 控件来实现所需的定制。
ImageView
(list_image) : 存储天气图标TextView
(tvCity) : 显示城市名称TextView
(tvCondition) : 显示天气信息(多云、下雨等)ImageView
(imageView1): 显示右侧的箭头图标。
这将产生我们正在努力实现的结果,即带图片和一些文本的 listview
项目。

现在我们有了 listview
项目,是时候将这些项目显示为一个列表,即在应用程序的 res/layout 文件夹(主屏幕)内的 ListView
中了。为此,我们将定义一个 ListView
。
主布局文件 (main.xml)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#b5b5b5"
android:dividerHeight="1dp"
android:listSelector="@drawable/list_selector">
</ListView>
</LinearLayout>
注意
在上面的文件中,我们通过设置 listSelector
的属性来引用 list_selector.xml 中定义的样式信息,从而定义每个 listview
项目的外观。
现在我们已经完成了自定义 ListView
项目的设计,我们的下一个任务是解析数据源 weatherdata.xml 并将数据更新到 listview
。我们将使用 SAX 解析器来解析天气数据 XML 文件。这将在下一节中解释。
Using the Code
i. WeatherActivity 类
这是启动天气信息应用程序时调用的主活动。该活动主要执行两项操作:
- 解析 XML 数据并将适当的列表填充到稍后将绑定到
ListView
的列表中DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); Document doc = docBuilder.parse (getAssets().open("weatherdata.xml")); List<HashMap<String,String>> weatherDataCollection = new ArrayList<HashMap<String,String>>(); // normalize text representation doc.getDocumentElement ().normalize (); NodeList weatherList = doc.getElementsByTagName("weatherdata"); HashMap<String,String> map = null; for (int i = 0; i < weatherList.getLength(); i++) { map = new HashMap<String,String>(); Node firstWeatherNode = weatherList.item(i); if(firstWeatherNode.getNodeType() == Node.ELEMENT_NODE){ Element firstWeatherElement = (Element)firstWeatherNode; //------- NodeList idList = firstWeatherElement.getElementsByTagName(KEY_ID); Element firstIdElement = (Element)idList.item(0); NodeList textIdList = firstIdElement.getChildNodes(); //--id map.put(KEY_ID, ((Node)textIdList.item(0)).getNodeValue().trim()); //2.------- NodeList cityList = firstWeatherElement.getElementsByTagName(KEY_CITY); Element firstCityElement = (Element)cityList.item(0); NodeList textCityList = firstCityElement.getChildNodes(); //--city map.put(KEY_CITY, ((Node)textCityList.item(0)).getNodeValue().trim()); //3.------- NodeList tempList = firstWeatherElement.getElementsByTagName(KEY_TEMP_C); Element firstTempElement = (Element)tempList.item(0); NodeList textTempList = firstTempElement.getChildNodes(); //--temperature map.put(KEY_TEMP_C, ((Node)textTempList.item(0)).getNodeValue().trim()); //4.------- NodeList condList = firstWeatherElement.getElementsByTagName(KEY_CONDN); Element firstCondElement = (Element)condList.item(0); NodeList textCondList = firstCondElement.getChildNodes(); //--Weather condition map.put(KEY_CONDN, ((Node)textCondList.item(0)).getNodeValue().trim()); //5.------- NodeList speedList = firstWeatherElement.getElementsByTagName(KEY_SPEED); Element firstSpeedElement = (Element)speedList.item(0); NodeList textSpeedList = firstSpeedElement.getChildNodes(); //--WindSpeed map.put(KEY_SPEED, ((Node)textSpeedList.item(0)).getNodeValue().trim()); //6.------- NodeList iconList = firstWeatherElement.getElementsByTagName(KEY_ICON); Element firstIconElement = (Element)iconList.item(0); NodeList textIconList = firstIconElement.getChildNodes(); //--Weather icon map.put(KEY_ICON, ((Node)textIconList.item(0)).getNodeValue().trim()); //Add to the Arraylist holding Weather related data of all cities weatherDataCollection.add(map); } }
- 通过
BinderData
类绑定解析器读取的数据,并将其更新到相应的 UI 元素(即TextView
和ImageView
元素)。BinderData bindingData = new BinderData(this, weatherDataCollection);
- 将此适配的列表数据设置为
Listview
的数据源。list.setAdapter(bindingData);
- 最后,通过将所需参数打包到
Intent
中,在选择列表中的特定项目后调用一个新的示例活动。Intent i = new Intent(); i.setClass(WeatherActivity.this, SampleActivity.class); // parameters i.putExtra("position", String.valueOf(position + 1)); /* selected item parameters * 1. City name * 2. Weather * 3. Wind speed * 4. Temperature * 5. Weather icon */ i.putExtra("city", weatherDataCollection.get(position).get(KEY_CITY)); i.putExtra("weather", weatherDataCollection.get(position).get(KEY_CONDN)); i.putExtra("windspeed", weatherDataCollection.get(position).get(KEY_SPEED)); i.putExtra("temperature", weatherDataCollection.get(position).get(KEY_TEMP_C)); i.putExtra("icon", weatherDataCollection.get(position).get(KEY_ICON)); // start the sample activity startActivity(i);
ii. SampleActivity 类
此类给出所选城市(从 WeatherData
活动中选择)的天气信息的详细概述。通过 Intent
从 WeatherActivity
类传递的参数被接收,并设置到相应的控件(即 TextView
和 ImageButton
)。
注意:此活动应在 AndroidManifest.xml 文件中注册。
// Get position to display
Intent i = getIntent();
this.position = i.getStringExtra("position");
this.city = i.getStringExtra("city");
this.weather= i.getStringExtra("weather");
this.temperature = i.getStringExtra("temperature");
this.windSpeed = i.getStringExtra("windspeed");
this.iconfile = i.getStringExtra("icon");
iii. BinderData 类
此类负责为每个 listview
项目绑定数据内容,即它将为 list_row.xml 中找到的每个控件设置文本和图片。此类将采用 ViewHolder
模式来优化 ListViews
。
View vi=convertView;
if(convertView==null){
vi = inflater.inflate(R.layout.list_row, null);
holder = new ViewHolder();
holder.tvCity = (TextView)vi.findViewById(R.id.tvCity); // city name
holder.tvWeather = (TextView)vi.findViewById(R.id.tvCondition); // weather
holder.tvTemperature = (TextView)vi.findViewById(R.id.tvTemp); // city temperature
holder.tvWeatherImage =(ImageView)vi.findViewById(R.id.list_image); // thumb image
vi.setTag(holder);
}
else{
holder = (ViewHolder)vi.getTag();
}
// Setting all values in listview
holder.tvCity.setText(weatherDataCollection.get(position).get(KEY_CITY));
holder.tvWeather.setText(weatherDataCollection.get(position).get(KEY_CONDN));
holder.tvTemperature.setText(weatherDataCollection.get(position).get(KEY_TEMP_C));
//Setting an image
String uri = "drawable/"+ weatherDataCollection.get(position).get(KEY_ICON);
int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(
uri, null, vi.getContext().getApplicationContext().getPackageName());
Drawable image = vi.getContext().getResources().getDrawable(imageResource);
holder.tvWeatherImage.setImageDrawable(image);
其中 ViewHolder
是一个 static
类,它将作为 list_row.xml 中定义的所有 Android UI 元素的占位符。
static class ViewHolder{
TextView tvCity;
TextView tvTemperature;
TextView tvWeather;
ImageView tvWeatherImage;
}
就这样!!我们已经准备就绪。在 Android 模拟器上运行应用程序,或者最好将其部署到您的智能手机上,以查看根据您的需求定制的所需 listview
。您可以尝试在列表行项目中使用不同的内容。
关注点
- https://developer.android.com.cn/guide/topics/ui/layout/listview.html
- https://developer.android.com.cn/guide/topics/ui/layout/relative.html
- https://developer.android.com.cn/reference/javax/xml/parsers/SAXParser.html
- http://www.jondev.net/articles/Android_XML_SAX_Parser_Example
历史
修改版
- 为优化的
listview
实现了ViewHolder
模式 - 修改了 XML 数据解析方法
- 添加了显示所选城市天气详情的代码片段