65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (41投票s)

2012 年 12 月 13 日

CPOL

6分钟阅读

viewsIcon

407154

downloadIcon

26023

在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.xmlgradient_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 作为父节点,并使用相对定位属性放置了所有其他项。

  1. 右键单击 ? 新建 ? Android XML 文件
  2. 将文件命名为 list_row.xml,并将其放在 res/layout 位置下。
  3. 将以下代码粘贴到文件中
    <?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 类

这是启动天气信息应用程序时调用的主活动。该活动主要执行两项操作:

  1. 解析 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);
        }        
    } 
  2. 通过 BinderData 类绑定解析器读取的数据,并将其更新到相应的 UI 元素(即 TextViewImageView 元素)。
    BinderData bindingData = new BinderData(this, weatherDataCollection);
  3. 将此适配的列表数据设置为 Listview 的数据源。
    list.setAdapter(bindingData); 
  4. 最后,通过将所需参数打包到 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 活动中选择)的天气信息的详细概述。通过 IntentWeatherActivity 类传递的参数被接收,并设置到相应的控件(即 TextViewImageButton)。

注意:此活动应在 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。您可以尝试在列表行项目中使用不同的内容。

关注点

历史

修改版

  • 为优化的 listview 实现了 ViewHolder 模式
  • 修改了 XML 数据解析方法
  • 添加了显示所选城市天气详情的代码片段
© . All rights reserved.