- 下载source code - 21.7 KB
你想知道如何把多个ListView控件放到一个布局中,但是让它们在显示时表现正确吗 多个列表项?你对它们正确滚动有问题吗?这个例子将向你展示如何组合单独的ListView控件 到一个单一的ListView,并将其分割为子节,每个子节使用自己的ListAdapter。我应该澄清一下,我们实际上没有嵌套 列表视图控件,我们将在一个列表视图中使用子节,并动态填充每个列表项。 背景:这个例子假设你已经熟悉Android和Mono c#编码。我基于Wrox书中的一个例子, 专业的Android编程与Mono为Android和。net / c#。这个例子对书中的例子做了一些修改。这种方法将允许滚动行为 正常工作。最佳实践是不要在一个布局中有一个以上的ListView。这样做会导致 ListView默认显示每个列表项, 强制单独滚动每个ListView。这是非常恼人的行为,而期望的行为将是每个ListView 显示它的所有列表项,并允许父布局处理滚动。此方法将允许您实现此行为。我还扩展了图书示例,向您展示了如何处理ListView。ItemClicked事件来正确处理正确的项类型,因为我们的示例将组合多个列表项类型,每个列表项类型使用代码从它们自己的适配器中萌发。我们的布局将显示一个列表视图,为我们定义的每种不同类型的食物进行分区。首先,让我们定义我们的数据模型: 隐藏,复制Code
public class MeatType { private double _pricePerPound; public MeatType(String name, String description, double pricePerPound) { _name = name; _description = description; _pricePerPound = pricePerPound; } public String Name { get { return _name; } set { _name = value; } } public String Description { get { return _description; } set { _description = value; } } public double PricePerPound { get { return _pricePerPound; } set { _pricePerPound = value; } } }
为了简单起见,我们还有蔬菜类和水果类,它们的结构都和肉类一样, 但是我不会在这里列出它们,因为它们的结构是相同的。接下来,我们需要一个模板来描述我们的食物类型列表项的布局。尽管我们将为每种食物类型编写单独的ListAdapter,但是在本例中适配器都可以使用相同的列表项模板,因此,我们只需要一个,FoodTypeListItem.xml。这个模板将是线性布局, 与水平方向,和三个TextView控件持有 我们的三个财产价值。收缩,复制Code
xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:id="@+id/rootLayout"> <TextViewandroid:id="@+id/nameLabel"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="10px"android:width="100px"android:textAppearance="?android:attr/textAppearanceSmall"/> <TextViewandroid:id="@+id/descriptionLabel"android:layout_width="wrap_content"android:layout_height="wrap_content"android:width="150px"android:textAppearance="?android:attr/textAppearanceSmall"/> <TextViewandroid:id="@+id/priceLabel"android:layout_width="wrap_content"android:layout_height="wrap_content"android:width="50px"android:textAppearance="?android:attr/textAppearanceSmall"/> </LinearLayout>
此模板将用于膨胀我们的列表项。每个适配器都可以调用这个模板,并以不同的方式填充它,但是外观将是相同的,从而允许接口表示的一致性。接下来,我们需要编写列表适配器,每个适配器都将扩展baseadapter>同样,为了简洁起见,我将只显示MeatTypeListAdapter, VegetableTypeListAdapter和FruitTypeListAdapter除了Type>强制类型转换。 隐藏,收缩,适配器的骨架是GetView方法。它接受给定位置的项,并创建已定义类型的视图 ,并使用项属性值填充其控件。您必须编写此方法,以正确处理计划使用的模板和数据来自的项类型。LinearLayout类型在我们的例子中,我们的观点是,如果你使用一个不同的根布局类型,甚至只是一个基本控制类型,那么您的代码应该反映这类通过改变! (LinearLayout视图),不管你的控制类型,这样你提供适当的视图类型。如果条款允许回收的代码在运行时,如果一个LinearLayout已经通过,即相同的视图类型GetView连续调用,那么通货膨胀没有必要只接下来,我们需要创建一个SectionedListAdapter处理多个列表我们想要包含在列表视图控件。在编写此适配器之前,我们需要 ListSection类来描述单独的列表子节。的 ListSection类将保存该节的文本标题、该节的列标题名和 ListAdapter,与此部分一起使用。隐藏,同样,在创建分段适配器之前,我们需要一个xml模板来描述分段标题或分隔符。 您可以简单地使用TextView来实现这一点。设置分隔符视图样式标签为"?android:attr/listSeparatorTextViewStyle" 将在分隔符视图的底部边框上放置分隔符边框线。对于这个示例,我希望分隔符也包含列标题,因此这个模板将比简单的TextView更复杂一些。 xml看起来是这样的:收缩,复制Code
xmlversion="1.0"encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/rootLayout"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="vertical"> <TextViewandroid:id="@+id/caption"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_marginTop="10px"android:textAppearance="?android:attr/textAppearanceSmall"/> <LinearLayoutandroid:id="@+id/columnHeaderLayout"android:layout_width="fill_parent"android:layout_height="wrap_content"android:orientation="horizontal"style="?android:attr/listSeparatorTextViewStyle"> <TextViewandroid:id="@+id/columnHeader1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:marginLeft="10px"android:width="100px"android:textAppearance="?android:attr/textAppearanceSmall"/> <TextViewandroid:id="@+id/columnHeader2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:width="150px"android:textAppearance="?android:attr/textAppearanceSmall"/> <TextViewandroid:id="@+id/columnHeader3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:width="50px"android:textAppearance="?android:attr/textAppearanceSmall"/> </LinearLayout> </LinearLayout>
您可以看到,我们已经设置了内部LinearLayout,它保存我们的列标题,作为listSeperatorTextViewStyle,因此整个LinearLayout得到边界线在它下面。现在我们准备创建我们的分段适配器,SectionedListAdapterHide,收缩,复制code
using Android.App; using Android.Widget; using Android.Views; public class SectionedListAdapter { private const int TYPE_SECTION_HEADER = 0; private Context _context; private LayoutInflater _inflater; private List_sections; public SectionedListAdapter(Context context) { _context = context; _inflater = LayoutInflater.From(_context); _sections = new List (); } public List Sections { get { return _sections; } set { _sections = value; } } // Each section has x list items + 1 list item for the caption. This is the reason for the +1 in the tally public override int Count { get { int count = 0; foreach (ListSection s in _sections) count += s.Adapter.Count + 1; return count; } } // We know there will be at least 1 type, the seperator, plus each // type for each section, that is why we start with 1 public override int ViewTypeCount { get { int viewTypeCount = 1; foreach (ListSection s in _sections) viewTypeCount += s.Adapter.ViewTypeCount; return viewTypeCount; } } public override ListSection this[int index] { get { return _sections[index]; } } // Since we dont want the captions selectable or clickable returning a hard false here achieves this public override bool AreAllItemsEnabled() { return false; } public override int GetItemViewType(int position) { int typeOffset = TYPE_SECTION_HEADER + 1; foreach (ListSection s in _sections) { if (position == 0) return TYPE_SECTION_HEADER; int size = s.Adapter.Count + 1; if (position < size) return (typeOffset + s.Adapter.GetItemViewType(position - 1)); position -+ size; typeOffset += s.Adapter.ViewTypeCount; } return -1; } public override long GetItemId(int position) { return position; } public void AddSection(String caption, String columnHeader1, String columnHeader2, String columnHeader3, BaseAdapter adapter) { _sections.Add(new ListSection(caption, columnHeader1, columnHeader2, columnHeader3, adapter)); } public override View GetView(int position, View convertView, ViewGroup parent) { View view = convertView; foreach (ListSection s in _sections) { // postion == 0 means we have to inflate the section separator if (position == 0) { if (view == null || !(view is LinearLayout)) { view = _inflater.Inflate(Resource.Layout.ListSeparator, parent, false); } TextView caption = view.FindViewById (Resource.Id.caption); caption.Text = s.Caption; TextView columnHeader1 = view.FindViewById (Resource.Id.columnHeader1); columnHeader1.Text = s.ColumnHeader1; TextView columnHeader2 = view.FindViewById (Resource.Id.columnHeader2); columnHeader2.Text = s.ColumnHeader2; TextView columnHeader3 = view.FindViewById (Resource.Id.columnHeader3); columnHeader3.Text = s.ColumnHeader3; } int size = s.Adapter.Count + 1; // postion < size means we are at an item, so we just pass through its View from its adapter if (position < size) return s.Adapter.GetView(position - 1, convertView, parent); position -= size; } return null; } public override Java.Lang.Object GetItem(int position) { foreach (ListSection s in _sections) { if (position == 0) return null; // this is a separator item, dont want it instantiated int size = s.Adapter.Count + 1; if (position < size) return s.Adapter.GetItem(position); position -= size; } return null; } }
正如你看到的,在这个例子中我们需要覆盖GetItem。通常不需要这样做,因为默认情况下它会返回 这(int)。但是,对于我们的分段适配器,这个[int]返回一个ListSection对象,当试图从ListView检索列表项时,它不起作用。如上所述重写此方法将迫使该方法深入到适当的子列表并返回适当的对象 需要的是填充需要容纳所有这些信息的ListView。下面的代码片段来自App. Hide的OnCreate方法。复制Code
// Fist lets create and populate the List<> instances that hold our food items Listmeats = new List (); meats.Add(new MeatType("Hamburger", "Ground chuck beef", 2.76)); meats.Add(new MeatType("Sirloin", "Sliced sirloin steaks", 4.56)); List veggies = new List new VegetableType("Brocolli", "Cut brocolli floretes", 1.76)); veggies.Add(new VegetableType("Carrots", "Cut peeled baby carrots", 2.18)); List fruits = new List (); fruits.Add(new FruitType("Apple", "Granny smith apples", 0.87)); fruits.Add(new FruitType("Peach", "South Carolina peaches", 1.12)); // Now we create our adapters for the item types MeatTypeListAdapter madptr = new MeatTypeListAdapter(this, Resource.Layout.FoodTypeListItem, meats); VegetableTypeListAdapter vadptr = new VegetableTypeListAdapter(this, Resource.Layout.FoodTypeListItem, veggies); FruitTypeListAdapter fadptr = new FruitTypeListAdapter(this, Resource.Layout.FoodTypeListItem, fruits); // Now we create our sectioned adapter and add its sections SectionedListAdapter sadptr = new SectionedListAdapter(this); sadptr.AddSection("Available Meats", "Name", "Description", "Price (lb.)", madptr); sadptr.AddSection("Available Vegetables", "Name", "Description", "Price (lb.)", vadptr); sadptr.AddSection("Available Fruits", "Name", "Description", "Price (lb.)", fadptr); // Now fetch the ListView and set its adapter to the sectioned adapter ListView foodList = FindViewById (Resource.Id.foodList); foodList.SetAdapter(sadptr); foodList.ItemClick += new EventHandler (foodList_ItemClick);
现在进入最后一步,正确处理ItemClick事件。我们必须确保在触发ItemClick事件时查询正确的子列表。在本例中,我们将显示一个带有所单击条目细节的新布局。下面是ItemClick事件处理程序的核心。也许有更好的方法来实现这一点,我当然愿意接受任何建议,但是我没有看到明确的方法来强制转换Java.Lang。对象转换为。net对象。出于这个原因,我比较了两个ToString方法的输出。如果你的数据对象碰巧有一个自定义的ToString方法,你可能需要稍微调整一下。 隐藏,复制Code
private void foodList_ItemClick(object sender, AdapterView.ListItemClickEventArgs e) { SectionedListAdapter adptr = (sender as ListView).Adapter as SectionedListAdapter; if (adptr.GetItem(e.Position != null) { if (adptr.GetItem(e.Position).ToString() == typeof(MeatType).ToString()) { // Handle your code however you like here for when a meat is clicked } else if (adptr.GetItem(e.Position).ToString() == typeof(VegetableType).ToString()) { // Handle your code however you like here for when a vegetable is clicked } else if (adptr.GetItem(e.Position).ToString() == typeof(FruitType).ToString()) { // Handler your code however you like here for when a fruit is clicked } } }
Update——我意识到,只有当您只想以某种通用的方式对单击作出反应时,这个方法才真正有用。如果你需要处理被点击的对象,事情就会变得有点棘手,不过还不算太糟。为此,首先需要创建一个包装器类来处理Java.Lang的通道。对象实例,在覆盖适配器的GetItem(int)方法时传递。首先,包装器类JavaObjectHandlerHide复制Code
using Java.Lang; using System; public class JavaObjectHandler : Java.Lang.Object { private System.Object _instance; public JavaObjectHandler(System.Object instance) { _instance = instance; } public System.Object Instance { get { return _instance; } } }
Now,当我们覆盖适配器中的GetItem(int)方法时,我们将传递一个JavaObjectHandler实例,而不是默认的Java.Lang。对象实例,我们的JavaObjectHandler将包含隐藏在适配器中的数据对象。回到任何你的baseadapter>类并添加以下方法复制code
public override Java.Lang.Object GetItem(int position) { if (position < _items.Count) { return new JavaObjectHandler(_items[position]); } return null; }
,因为我们已经在SectionedListAdapter中覆盖了这个方法,现在如果单击分隔符,它将传递null,或者包含我们的数据对象的底层JavaObjectHandler。现在您可以修改ItemClick事件来使用这个新的JavaObjectHandler实例,例如hide复制code
private void foodList_ItemClick(object sender, AdapterView.ItemClickEventArgs e) { SectionedListAdapter adapter = (sender as ListView).Adapter as SectionedListAdapter; JavaObjectHandler item = adapter.GetItem(e.Position); if (item != null) { if (item.Instance is MeatType) { Toast.MakeText(this, "You clicked a meat: " + (item.Instance as MeatType).Name, ToastLength.Short).Show(); } else if (item.Instance is VegetableType) { Toast.MakeText(this, "You clicked a vegetable: " + (item.Instance as VegetableType).Name, ToastLength.Short).Show(); } else if (item.Instance is FruitType) { Toast.MakeText(this, "You clicked a fruit: " + (item.Instance as FruitType).Name, ToastLenght.Short).Show(); } } }
这个分段适配器可以用于ListView或ListActivity类似。, History 1.0 - 2012年12月7日-一般操作发布。稍后将介绍源代码和示例项目。1.1—12/17/2012—更新以反映更高级的JavaObjectHandler的使用 ItemClick事件处理。添加示例项目到文章。Sample是为Android 4.0.3构建的。 本文转载于:http://www.diyabc.com/frontweb/news30838.html