读书频道 > 软件开发 > C# > C#开发Android应用实战——使用Mono for Android和.NET/C#
6.3.3 使用自定义列表适配器来自定义ListView
2012-12-26 11:00:22     我来说两句 
收藏    我要投稿   
《C#开发Android应用实战——使用Mono for Android和.NET/C# 全面透彻地讲解Android应用编程知识,分析如何结合使用C#和Mono来编写在Android设备系列上运行的应用程序。在这本由多位专家联袂撰写的必备精品书...  立即去当当网订购

到目前为止,你已经看到了一些Android SDK内置的针对ListView项的布局、用于创建列表适配器的类以及用于显示ListView的ListActivity。使用这些功能并没有什么错。然而,有时你可能需要在ListView项中以一种区别于默认布局所允许的特殊方式来显示数据。或者你发现使用SimpleListAdapter来显示数据对象集合并不像想象中的那样简单和优雅。针对这些情况,更好的办法是使用自定义的列表适配器(BaseAdapter类的子类)为ListView项创建自己的布局。其实该方法比听起来更容易,它可能会成为创建自定义列表显示的首选方法。

下面的示例将Animal对象列表用作数据源。你需要显示动物的名称和描述信息,并在ListView中显示每个动物的一张图像。程序清单6-18显示了一个包含动物名称、描述信息和图像的简单类。在该示例中,Image引用了图像的资源ID,而不是一个实际的Image对象,因此可以将实际的图像放在本示例之外。

程序清单6-18  Animal数据对象
public class Animal
{
   public string Name
   {
       get;
       set;
   }
   public string Description
   {
       get;
       set;
   }
   public int Image
   {
       get;
       set;
   }
}
Lists02\Animal.cs

接下来,每个ListView项都需要一个布局。一个ListView项通常显示来自于数据源的单一对象的信息。而该示例也是这么做的。单个ListView项负责显示单个动物的相关信息,其中包括名称、描述信息以及图像。

然而,相比于Android内置的SimpleListItem1,更多的事情发生在程序清单6-19所示的XML布局中。水平的LinearLayout拥有用于显示动物图像的ImageView,而它的子垂直LinearLayout则拥有两个TextViews:一个用于显示动物的名称,另一个用于显示动物的相关描述信息。该布局将图像显示在左边,右边显示动物名称,而名称下面则显示相关的描述信息。

程序清单6-19  针对Animal的ListView项布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/widget28"
   android:layout_width="fill_parent"
   android:layout_height="80px">
   <ImageView
   android:id="@+id/imageItem"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_gravity="center_vertical">
   </ImageView>
   <LinearLayout
   android:id="@+id/linearText"
   android:layout_width="wrap_content"
   android:layout_height="fill_parent"
   android:orientation="vertical"
   android:layout_marginLeft="10px"
   android:layout_marginTop="10px">
       <TextView
       android:id="@+id/textTop"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="TextView">
       </TextView>
       <TextView
       android:id="@+id/textBottom"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="TextView">
       </TextView>
   </LinearLayout>
</LinearLayout>
Lists02\Resources\layout\AnimalItem.axml

需要重点注意的是布局内不同视图的ID,后面将通过编程的方式为这些视图设置数据。而此时应该注意的是ImageView和两个TextViews的ID:imageItem、textTop和textBottom。通过Mono for Android可以自动利用这些ID,以便在Resource.Id类中引用它们。在Visual Studio中,Resource.Designer.cs会根据Resources文件夹中的文件自动生成,而这些文件的生成操作被设置为AndroidResource。需要记住的是,每当生成项目时都会重新生成Resource.Id类成员,也只有在生成之后这些资源才会出现在智能感知中。

假设你所使用的数据源是一些可枚举的列表、数组或者其他Animal对象集合,那么构建一个可供ListView使用的自定义ListAdapter是非常容易的。其中一种方法是创建一个继承了BaseAdapter<T>的新类AnimalListAdapter。请参见程序清单6-20。

继承自BaseAdapter<T>后需要实现一个方法和两个属性:

Count属性

T类型的Indexer this属性

GetItemId方法

Count属性的作用是显而易见的。它返回应该在列表中显示的项目数量。大多数情况下,所返回的数量就是数据源中的项目数量。

Indexer属性应该返回数据源中给定索引所对应的数据对象。此时,该属性应该返回传递到索引器的位置所对应的Animal对象类型。

GetItemId方法接受一个int位置值并返回一个long值类型。但就Mono的目的而言,任何重要的基础Android代码都不使用返回的实际数字。如果想要在代码的任何位置使用ItemId值,务必要关注所返回的值。然而最佳做法是确保为每个传入到方法的不同位置返回一个唯一值。而处理该方法更简单的做法是直接返回position参数。同时还需要注意,ItemId值被传递到一些ListView事件中,比如ItemClick事件,所以如果所传入的值可以提供更多的信息从而确定给定位置的实际数据源对象,那么将是非常有用的。

程序清单6-20  AnimalListAdapter
using System;
using System.Collections.Generic;
using Android.App;
using Android.Views;
using Android.Widget;
namespace Lists02
{
   public class AnimalListAdapter : BaseAdapter<Animal>
   {
       Activity context;
       public List<Animal> Animals;

       public AnimalListAdapter(Activity context, List<Animal> animals)
           : base()
       {
           this.context = context;
           this.Animals = animals;
   }
       public override int Count
       {
           get { return this.Animals.Count; }
       }
       public override Animal this[int position]
       {
           get { return this.Animals[position]; }
       }
       public override View GetView(int position, View convertView, ViewGroup
         parent)
       {
           //Get our object for this position
           var item = this.Animals[position];

           //Try to reuse convertView if it's not null,otherwise inflate it from
             our item layout
           // This gives us some performance gains by not always inflating a new
              view
           // This will sound familiar to MonoTouch developers with
              UITableViewCell.DequeueReusableCell()
           var view = convertView;

           if (convertView == null || !(convertView is LinearLayout))
               view=context.LayoutInflater.Inflate(Resource.Layout.AnimalItem,
                 parent, false);

           //Find references to each subview in the list item's view
           var imageItem=view.FindViewById(Resource.Id.imageItem)as ImageView;
           var textTop=view.FindViewById(Resource.Id.textTop)as TextView;
           var textBottom=view.FindViewById(Resource.Id.textBottom)as TextView;

           //Assign this item's values to the various subviews
           imageItem.SetImageResource(item.Image);
           textTop.SetText(item.Name, TextView.BufferType.Normal);
           textBottom.SetText(item.Description, TextView.BufferType.Normal);

           //Finally return the view
           return view;
       }

       public long GetItemId(int position)
       {
           return position;
       }
   }
}
Lists02\AnimalListAdapter.cs

你可能注意到适配器的构造函数期望传入一个Activity。适配器还需要一个上下文引用,以便为ListView项创建布局以及所有视图。无论什么时候创建View对象,都需要使用上下文,这次也不例外。一般来说,应该向构造函数传入对正在创建自定义ListAdapter的Activity的引用。

列表适配器中的GetView方法负责返回一个View,而该View是给定位置所对应的ListView项的布局。上述示例所做的第一件事是从数据源(List<Animal>Animals属性)中获取给定位置的Animal对象。

提示:如果你曾经从事过任何针对iPhone的开发,那么可能对下面的概念会比较熟悉:在对表适配器的调用中重用窗体元格,以节省内存资源并提高运行速度。这个概念也适用于Android。

即使列表中只有500个项目,但在任何给定时间里只有一部分项目(例如10个项目)可以显示在屏幕上。这样的话,相比于为列表中所有的500个项目创建ListView项目布局实例,只为屏幕所显示的项目生成10个实例可能更有效率。然后当某些项目从视图上消失并显示另外的项目时,将对应的实例回收利用(如图6-10所示)。


 

此时就是convertView参数的用武之地了。如果当前没有被回收利用的ListView项布局的实例,那么该参数为NULL。事实上,在最初10次调用GetView时,convertView都为NULL。然而,当第11次(此时再次假设屏幕一次只能显示10个项目)调用GetUiew时,convertView将包含从屏幕上消失的ListView项的回收实例。此时需要重点注意的是,任何TextViews、ImageViews或者其他convertView子视图中的相关值都会保持最后一次所设置的值不变。

如果convertView恰好为NULL,那么程序清单6-20所示的代码使用了LayoutInflater从前面所看到的布局来膨胀视图。否则,可以重用convertView,从而不必膨胀任何布局。

如果需要为不同的列表项使用多种类型的布局或者视图,那么应该重写适配器中的GetTypeCount属性以及GetItemViewType(int position)方法。ListView为每个列表项调用GetItemViewType方法,以确保为GetView方法中的contentView参数传递正确类型的回收的View。这也意味着可以在GetView方法中使用GetItemViewType方法,从而知道正在使用哪种类型的视图。

该示例使用了View对象来查找当Animal实例被显示时需要设置相应值的所有子视图。视图的FindViewById方法可以很快地找到ImageView和两个TextViews的引用。一旦拥有了这些引用,就可以使用要显示的行的Animal实例为这些子视图设置值。当完成了这些工作后,就会返回行需要显示的视图。可将FindViewById方法视为ASP.NET的Control.FindControl方法的Android版本。

最后一个需要的是用于显示ListView的实际Activity。程序清单6-21显示了ListActivity通过使用自定义列表适配器显示ListView的简单示例。

程序清单6-21  显示ListView的Activity
using System.Collections.Generic;
using Android.App;
using Android.OS;
namespace Lists02
{
   [Activity(Label = "Animal List", MainLauncher = true)]
   public class AnimalListActivity : ListActivity
   {
       protected override void OnCreate(Bundle savedInstanceState)
       {
           base.OnCreate(savedInstanceState);

           this.ListAdapter = new AnimalListAdapter(this,
               new List<Animal>() {
                   new Animal() { Name = "Elephant",
                       Description = "Big and Gray, but what the hey",
                       Image = Resource.Drawable.Elephant },
                   new Animal() { Name = "Chinchilla",
                       Description = "Little people of the andes",
                       Image = Resource.Drawable.Chinchilla },
                   new Animal() { Name = "Lion",
                       Description = "Cowardly lion, anyone?",
                       Image = Resource.Drawable.Lion },
                   new Animal() { Name = "Skunk",
                       Description = "Ello, baby. I am ze locksmith...",
                       Image = Resource.Drawable.Skunk },
                   new Animal() { Name = "Rhino",
                       Description = "Most live to about 60!",
                       Image = Resource.Drawable.Rhino },
                   new Animal() { Name = "Zebra",
                       Description = "Stripes, not so great for hiding"
                       Image = Resource.Drawable.Zebra }

           });
       }
   }
}
Lists02\AnimalListActivity.cs

现在,你已经拥有了显示自定义列表需要的所有组件。首先通过使用多个视图创建列表项的自定义布局,然后通过重写BaseAdapter类创建一个自定义列表适配器,最后创建带有数据源的自定义ListActivity以便显示连接到自定义适配器的ListView。此时的列表应该如图6-11所示。


 

现在你已经看到了一个通过使用非默认列表项布局创建自定义列表的有用示例。该示例还说明了合并自定义数据对象的重要实际使用案例。目前通过继承BaseAdapter实现了列表适配器的最基本形式。此后其用法没有限制!

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:6.3.2 使用Android的ListAdapter
下一篇:6.3.4 处理ListView事件
相关文章
图文推荐
2.2.3 对程序进行调
2.2.2 编译和运行程
2.2.1 创建控制台项
2.1 开发环境的搭建
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站