使用 Xamarin.Android 自定义 ListView 的外观

ListView 的外观由显示的行的布局决定。 若要更改 ListView 的外观,请使用其他行布局。

内置行视图

有 12 种可使用 Android.Resource.Layout 引用的内置视图:

  • TestListItem - 具有最小格式的单行文本。

  • SimpleListItem1 - 单行文本。

  • SimpleListItem2 - 两行文本。

  • SimpleSelectableListItem - 支持单个或多个项目选择的单行文本(在 API 级别 11 中添加)。

  • SimpleListItemActivated1 - 类似于 SimpleListItem1,但背景色指示何时选中行(在 API 级别 11 种添加)。

  • SimpleListItemActivated2 - 类似于 SimpleListItem2,但背景色指示何时选中行(在 API 级别 11 种添加)。

  • SimpleListItemChecked - 显示用于指示所选内容的选中标记。

  • SimpleListItemMultipleChoice - 显示复选框以指示多选选择。

  • SimpleListItemSingleChoice - 显示单选按钮以指示互斥选择。

  • TwoLineListItem - 两行文本。

  • ActivityListItem - 包含图像的单行文本。

  • SimpleExpandableListItem - 按类别对行进行分组,可展开或折叠每个组。

每个内置行视图都有一个与之关联的内置样式。 以下屏幕截图显示了每个视图的显示方式:

TestListItem、SimpleSelectableListItem、SimpleListitem1 和 SimpleListItem2 的屏幕截图

SimpleListItemActivated1、SimpleListItemActivated2、SimpleListItemChecked 和 SimpleListItemMultipleChecked 的屏幕截图

SimpleListItemSingleChoice、TwoLineListItem、ActivityListItem 和 SimpleExpandableListItem 的屏幕截图

BuiltInViews 解决方案中的 BuiltInViews/HomeScreenAdapter.cs 示例文件包含用于生成不可展开的列表项屏幕的代码。 视图在 GetView 方法中设置,如下所示:

view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItem1, null);

然后,可以通过引用 Android.Resource.Id 下的标准控件标识符 Text1Text2Icon 来设置视图的属性(不要设置视图没有包含的视图,否则会引发异常):

view.FindViewById<TextView>(Android.Resource.Id.Text1).Text = item.Heading;
view.FindViewById<TextView>(Android.Resource.Id.Text2).Text = item.SubHeading;
view.FindViewById<ImageView>(Android.Resource.Id.Icon).SetImageResource(item.ImageResourceId); // only use with ActivityListItem

BuiltInViews 解决方案中的 BuiltInExpandableViews/ExpandableScreenAdapter.cs 示例文件包含用于生成 SimpleExpandableListItem 屏幕的代码。 组视图在 GetGroupView 方法中设置,如下所示:

view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleExpandableListItem1, null);

子视图在 GetChildView 方法中设置,如下所示:

view = context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleExpandableListItem2, null);

然后,可以通过引用标准控件标识符 Text1Text2 来设置组视图和子视图,如上所示。 SimpleExpandableListItem 屏幕截图(如上所示)提供了单行组视图 (SimpleExpandableListItem1) 和两行子视图 (SimpleExpandableListItem2) 的示例。 或者,可以为两行 (SimpleExpandableListItem2) 配置组视图,可以为单行 (SimpleExpandableListItem1) 配置子视图,或者组视图和子视图可具有相同的行数。

Accessories

行可在视图右侧添加附件,以指示选择状态:

  • SimpleListItemChecked - 创建勾号作为指示器的单选列表。

  • SimpleListItemSingleChoice - 创建单选按钮类型列表,其中只能选择一项。

  • SimpleListItemMultipleChoice - 创建复选框类型列表,其中可选择多项。

上述附件按各自顺序显示在下面的屏幕上:

包含配件的 SimpleListItemChecked、SimpleListItemSingleChoice 和 SimpleListItemMultipleChoice 的屏幕截图

若要显示其中一个附件,请将所需的布局资源 ID 传递给适配器,然后手动设置所需行的选择状态。 以下代码行显示如何使用其中一种布局创建和分配 Adapter

ListAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleListItemChecked, items);

无论显示的附件如何,ListView 本身都支持不同的选择模式。 为了避免混淆,请使用带有 SingleChoice 附件的 Single 选择模式和带有 MultipleChoice 样式的 CheckedMultiple模式。 选择模式由 ListViewChoiceMode 属性控制。

处理 API 级别

早期版本的 Xamarin.Android 将枚举实现为整数属性。 最新版本引入了适当的 .NET 枚举类型,使发现潜在选项变得更加容易。

ChoiceMode 是整数或枚举,具体取决于你面向的 API 级别。 若要以 Gingerbread API 为目标,示例文件 AccessoryViews/HomeScreen.cs 有一个注释掉的块:

// For targeting Gingerbread the ChoiceMode is an int, otherwise it is an
// enumeration.

lv.ChoiceMode = Android.Widget.ChoiceMode.Single; // 1
//lv.ChoiceMode = Android.Widget.ChoiceMode.Multiple; // 2
//lv.ChoiceMode = Android.Widget.ChoiceMode.None; // 0

// Use this block if targeting Gingerbread or lower
/*
lv.ChoiceMode = 1; // Single
//lv.ChoiceMode = 0; // none
//lv.ChoiceMode = 2; // Multiple
//lv.ChoiceMode = 3; // MultipleModal
*/

以编程方式选择项

使用 SetItemChecked 手动设置“选择”哪些项(可调用多次来进行多选):

// Set the initially checked row ("Fruits")
lv.SetItemChecked(1, true);

该代码还需要检测与多选不同的单选。 若要确定在 Single 模式下选择了哪个行,请使用 CheckedItemPosition 整数属性:

FindViewById<ListView>(Android.Resource.Id.List).CheckedItemPosition

若要确定在 Multiple 模式下选择了哪些行,需要循环访问 CheckedItemPositionsSparseBooleanArray。 稀疏数组就像一个字典,只包含值已更改的条目,因此必须遍历整个数组来查找 true 值,从而了解已在列表中选择了哪些内容,如以下代码片段所示:

var sparseArray = FindViewById<ListView>(Android.Resource.Id.List).CheckedItemPositions;
for (var i = 0; i < sparseArray.Size(); i++ )
{
   Console.Write(sparseArray.KeyAt(i) + "=" + sparseArray.ValueAt(i) + ",");
}
Console.WriteLine();

创建自定义行布局

4 个内置行视图非常简单。 若要显示更复杂的布局(例如电子邮件列表、推文或联系人信息),需要自定义视图。 自定义视图通常声明为 Resources/Layout 目录中的 AXML 文件,然后由自定义适配器使用其资源 ID 加载。 视图可以包含任意数量的显示类(例如 TextView 和 ImageView 等控件),还可包含自定义颜色、字体和布局。

此示例在很多方面与上述示例不同:

  • 继承自 Activity,而不是 ListActivity。 可以自定义任何 ListView 的行,但是其他控件也可包含在 Activity 布局中(例如标题、按钮或其他用户界面元素)。 此示例在 ListView 上方添加一个标题进行说明。

  • 需要屏幕的 AXML 布局文件;在前面的示例中,ListActivity 不需要布局文件。 此 AXML 包含 ListView 控件声明。

  • 需要一个 AXML 布局文件来呈现每一行。 此 AXML 文件包含具有自定义字体和颜色设置的文本和图像控件。

  • 使用可选的自定义选择器 XML 文件在选定行时设置该行的外观。

  • Adapter 实现从 GetView 重写中返回自定义布局。

  • ItemClick 必须以不同的方式声明(事件处理程序附加到 ListView.ItemClick,而不是 ListActivity 中的重写 OnListItemClick)。

下面详细介绍了这些更改,首先是创建活动的视图和自定义行视图,然后介绍为呈现它们而对适配器和活动进行的修改。

将 ListView 添加到活动布局

由于 HomeScreen 不再继承自 ListActivity,它没有默认视图,因此必须为主屏幕视图创建布局 AXML 文件。 对于此示例,视图将具有标题(使用 TextView)和 ListView 来显示数据。 布局在 Resources/Layout/HomeScreen.axml 文件中定义,如下所示:

<?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:id="@+id/Heading"
        android:text="Vegetable Groups"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#00000000"
        android:textSize="30dp"
        android:textColor="#FF267F00"
        android:textStyle="bold"
        android:padding="5dp"
    />
    <ListView android:id="@+id/List"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:cacheColorHint="#FFDAFF7F"
    />
</LinearLayout>

Activity 和自定义布局(而不是 ListActivity)结合使用的优势在于能够向屏幕添加其他控件,例如此示例中的标题 TextView

创建自定义行布局

需要另一个 AXML 布局文件,才能包含将在列表视图中显示的每一行的自定义布局。 在此示例中,行将具有绿色背景、棕色文本和右对齐图像。 有关用于声明此布局的 Android XML 标记,请参阅 Resources/Layout/CustomView.axml:

<?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="#FFDAFF7F"
   android:padding="8dp">
    <LinearLayout android:id="@+id/Text"
       android:orientation="vertical"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:paddingLeft="10dip">
        <TextView
         android:id="@+id/Text1"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textColor="#FF7F3300"
         android:textSize="20dip"
         android:textStyle="italic"
         />
        <TextView
         android:id="@+id/Text2"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textSize="14dip"
         android:textColor="#FF267F00"
         android:paddingLeft="100dip"
         />
    </LinearLayout>
    <ImageView
        android:id="@+id/Image"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:padding="5dp"
        android:src="@drawable/icon"
        android:layout_alignParentRight="true" />
</RelativeLayout >

虽然自定义行布局可以包含许多不同的控件,但滚动性能可能会受到复杂设计和图像使用的影响(尤其是在必须通过网络加载它们时)。 有关解决滚动性能问题的详细信息,请参阅 Google 的文章。

引用自定义行视图

自定义适配器示例的实现位于 HomeScreenAdapter.cs。 关键方法是 GetView,其中它使用资源 ID Resource.Layout.CustomView 加载自定义 AXML,然后在返回之前设置视图中每个控件的属性。 会显示完整的适配器类:

public class HomeScreenAdapter : BaseAdapter<TableItem> {
   List<TableItem> items;
   Activity context;
   public HomeScreenAdapter(Activity context, List<TableItem> items)
       : base()
   {
       this.context = context;
       this.items = items;
   }
   public override long GetItemId(int position)
   {
       return position;
   }
   public override TableItem this[int position]
   {
       get { return items[position]; }
   }
   public override int Count
   {
       get { return items.Count; }
   }
   public override View GetView(int position, View convertView, ViewGroup parent)
   {
       var item = items[position];
       View view = convertView;
       if (view == null) // no view to re-use, create new
           view = context.LayoutInflater.Inflate(Resource.Layout.CustomView, null);
       view.FindViewById<TextView>(Resource.Id.Text1).Text = item.Heading;
       view.FindViewById<TextView>(Resource.Id.Text2).Text = item.SubHeading;
       view.FindViewById<ImageView>(Resource.Id.Image).SetImageResource(item.ImageResourceId);
       return view;
   }
}

在活动中引用自定义 ListView

由于 HomeScreen 类现在继承自 Activity,因此在类中声明一个 ListView 字段,用于保存对 AXML 中声明的控件的引用:

ListView listView;

然后,该类必须使用 SetContentView 方法加载活动的自定义布局 AXML。 之后,它可以在布局中找到 ListView控件,然后创建和分配适配器并分配单击处理程序。 OnCreate 方法的代码如下所示:

SetContentView(Resource.Layout.HomeScreen); // loads the HomeScreen.axml as this activity's view
listView = FindViewById<ListView>(Resource.Id.List); // get reference to the ListView in the layout

// populate the listview with data
listView.Adapter = new HomeScreenAdapter(this, tableItems);
listView.ItemClick += OnListItemClick;  // to be defined

最后,必须定义 ItemClick 处理程序;在本例中,它只显示一条 Toast 消息:

void OnListItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
   var listView = sender as ListView;
   var t = tableItems[e.Position];
   Android.Widget.Toast.MakeText(this, t.Heading, Android.Widget.ToastLength.Short).Show();
}

生成的屏幕如下所示:

生成的 CustomRowView 的屏幕截图

自定义行选择器颜色

当某一行被触摸时,应突出显示该行以供用户反馈。 当自定义视图指定为背景色时(就像 CustomView.axml 那样),它还会替代所选内容突出显示效果。 CustomView.axml 中的此代码行将背景设置为浅绿色,但这也意味着在触摸行时没有可视指示器:

android:background="#FFDAFF7F"

若要重新启用突出显示行为,并自定义使用的颜色,请改为将背景属性设置为自定义选择器。 该选择器将同时声明默认背景色和突出显示颜色。 Resources/Drawable/CustomSelector.xml 文件包含以下声明:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false"
  android:state_selected="false"
  android:drawable="@color/cellback" />
<item android:state_pressed="true" >
  <shape>
     <gradient
      android:startColor="#E77A26"
        android:endColor="#E77A26"
        android:angle="270" />
  </shape>
</item>
<item android:state_selected="true"
  android:state_pressed="false"
  android:drawable="@color/cellback" />
</selector>

若要引用自定义选择器,请将 CustomView.axml 中的背景属性更改为:

android:background="@drawable/CustomSelector"

所选行和相应的 Toast 消息现在如下所示:

所选行为橙色,Toast 消息显示了所选行的名称

防止自定义布局上的闪烁

Android 尝试通过缓存布局信息来提高 ListView 滚动的性能。 如果你有很长的数据滚动列表,还应在活动的 AXML 定义中设置 ListView 声明的 android:cacheColorHint 属性(与自定义行布局的背景相同的颜色值)。 如果不包含此提示,那么在用户滚动浏览具有自定义行背景色的列表时可能会出现“闪烁”。