2

我有一个我正在尝试解决的应用程序。在应用程序的一种布局中,我有多个 ListView 组件。大纲如下所示:

<ScrollView>
   <RelativeLayout>
      <TextView />
      <TextView />
      <ListView />
      <ListView />
      <ListView />
      <Button />
   </RelativeLayout>
</ScrollView>

三个独立的 ListView 组件列出了 3 种不同类型的项目。我的问题来了,当用 ScrollView 封装时,ListViews 决定每个只显示 1 个项目,然后下降到它们的滚动行为。我更喜欢他们显示所有项目,并让 ScrollView 进行滚动。这可能吗?

我在这里阅读了其他一些问题,似乎惯例是每个布局不使用多个 ListView。如果可能的话,我宁愿做这项工作,因为列出的 3 个单独的项目是相关的,并且一起显示是有意义的。

4

2 回答 2

1

ListViews 主要是为滚动而设计的。ListView 使用回收机制,在项目数量很大(相对于屏幕大小)时使滚动平滑。如果您希望您的 ListView 一次显示所有项目,您实际上并不需要 ListView。改用线性布局。我猜您正在使用列表适配器来填写您的列表。因此,您可以使用自定义 setAdapter() 方法扩展 LinearLayout 并利用您的适配器。这是我为不可滚动列表创建的类。

public class NonScrollableListView extends LinearLayout {
    private BaseAdapter mAdapter;
    private AdapterDataSetObserver mDataSetObserver;

    public NonScrollableListView(Context context) {
        super(context);
    }

    public NonScrollableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NonScrollableListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if(mAdapter != null && mDataSetObserver != null){
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if(mAdapter != null && mDataSetObserver != null){
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
    }

    public void setAdapter(BaseAdapter adapter) {
        this.mAdapter = adapter;

        if(mAdapter != null && mDataSetObserver != null){
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        mDataSetObserver = new AdapterDataSetObserver();
        mAdapter.registerDataSetObserver(mDataSetObserver);

        mDataSetObserver.onChanged();
    }

    private void fillChildViews(){
        if(mAdapter != null){
           int requiredChilrenCount = mAdapter.getCount();
           int currentChildrenCount = getChildCount();

            for(int i = 0; i < requiredChilrenCount; i++){
                View nextChild = getChildAt(i);
                View nextChildToAdd = mAdapter.getView(i, nextChild, this);
                nextChildToAdd.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));

                if(nextChild == null){
                    addView(nextChildToAdd);
                }
            }

           //Remove remaining child views if any
           for(int i = requiredChilrenCount; i < currentChildrenCount; i++){
               //The length of the children list changes so need to get it at each iteration
               removeViewAt(getChildCount() - 1);
           }
        }
        else{
            removeAllViews();
        }
    }

    private class AdapterDataSetObserver extends DataSetObserver{
        @Override
        public void onChanged() {
            fillChildViews();
        }
    }
}

您可以像使用列表视图一样使用它。请注意,预计项目数量会相对较少。否则,您将遇到性能问题。

于 2012-11-30T05:42:33.020 回答
0

我最终采用的解决方案是分段列表适配器,用于在我的布局中填充单个 ListView 控件。分段适配器使 ListView 与 Preferences List 非常相似。但是分段适配器更加通用,因为您可以自定义分段分隔项,并包含多个列表项布局。假设您已经了解 Mono for Android 的基础知识,下面将详细介绍如何实现这一点。

首先,您需要一个 Section 对象,它将描述每个单独的列表部分。

public class ListSection
{
   private String _caption;
   private String _columnHeader1, _columnHeader2, _columnHeader3;
   private BaseAdapter _adapter;
   public ListSection(String caption, String columnHeader1, String columnHeader2, String columnHeader3, BaseAdapter adapter)
   {
      _caption = caption;
      _columnHeader1 = columnHeader1;
      _columnHeader2 = columnHeader2;
      _columnHeader3 = columnHeader3;
      _adapter = adapter;
   }
   public String Caption { get { return _caption; } set { _caption = value; } }
   public String ColumnHeader1 { get { return _columnHeader1; } set { _columnHeader1 = value; } }
   public String ColumnHeader2 { get { return _columnHeader2; } set { _columnHeader2 = value; } }
   public String ColumnHeader3 { get { return _columnHeader3; } set { _columnHeader3 = value; } }
   public BaseAdapter Adapter { get { return _adapter; } set { _adapter = value; } }
}

该对象存储列表中每个部分的所有信息、作为该部分标题的标题以及我希望列表具有的 3 列中的每一列的列标题。此外,我们存储了一个唯一的列表适配器,它将为列表的这一部分提供视图。这允许您为每个部分提供不同的适配器。如果需要,您可以扩展此部分对象以进一步描述分隔部分,从而为您提供更大的灵活性,并有机会更改每个部分的基本结构。

接下来,您需要一个 XML 模板来描述列表的分隔符。由于我的每个部分都将具有相同的基本结构,因此我可以每次都重复使用相同的模板,而不会使它进一步复杂化。

<?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="wrap_content">
              <TextView
                        android:id="@+id/caption"
                        android:layout_marginTop="10px"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:textAppearance="?android:attr/textAppearanceSmall" />
              <LinearLayout
                        android:orientation="horizontal"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        style="?android:attr/listSeparatorTextViewStyle">
                        <TextView
                                  android:id="@+id/columnHeader1"
                                  android:layout_marginLeft="10px"
                                  android:layout_width="wrap_content"
                                  android:layout_height="wrap_content"
                                  android:width="100px"
                                  android:textAppearance="?android:attr/textAppearanceSmall" />
                        <TextView
                                  android:id="@+id/columnHeader2"
                                  android:layout_marginLeft="10px"
                                  android:layout_width="wrap_content"
                                  android:layout_height="wrap_content"
                                  android:width="100px"
                                  android:textAppearance="?android:attr/textAppearanceSmall" />
                        <TextView
                                  android:id="@+id/columnHeader3"
                                  android:layout_marginLeft="10px"
                                  android:layout_width="wrap_content"
                                  android:layout_height="wrap_content"
                                  android:width="100px"
                                  android:textAppearance="?android:attr/textAppearanceSmall" />
                        </LinearLayout>
</LinearLayout>

你会注意到,Inner LinearLayout 控件我给了一个 style="?android:attr/listSeparatorTextViewStyle" 标签。这告诉android给那个视图一个底部边框。如果你只想要一个简单的 TextView 分隔符,你可以这样做,并给它同样的样式标签。

现在我的 ListAdapter 基本上都是一样的,它们只是扩展了一个不同的数据对象。这三个都填充了 3 列,所有这些列都适合它们的数据在相同大小的列中,它们只是不同的逻辑对象。每个适配器都是一个 BaseAdapter 扩展,它使用 3 列数据填充视图。我在这里假设您知道如何创建 BaseAdapter 的标准扩展。我将展示如何创建 ListSectionAdapter。

public class ListSectionAdapter : BaseAdapter<ListSection>
{
   private const int TYPE_SECTION_HEADER = 0;
   private Context _context;
   private List<ListSection> _sections;
   private LayoutInflater _inflater;
   public ListSectionAdapter(Context context)
   {
      _context = context;
      _inflater = Inflater.From(_context);
      _sections = new List<ListSection>();
   }
   public List<ListSection> Sections { get { return _sections; } set { _sections = value; } }
   public override int Count
   {
      get
      {
         int count = 0;
         foreach(ListSection s in _sections) count += s.Adapter.Count + 1;
         return count;
      }
   }
   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]; } }
   public override bool AreAllItemsEnable() { 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)
      {
         if(position == 0)
         {
             if(view == null || !(view is LinearLayout)) view = _inflater.Inflate(Resource.Layout.SectionSeparator, parent, false);
             TextView caption = view.FindViewById<TextView>(Resource.Id.caption);
             caption.Text = s.Caption;
             TextView columnHeader1 = view.FindViewById<TextView>(Resource.Id.columnHeader1);
             columnHeader1.Text = s.ColumnHeader1;
             TextView columnHeader2 = view.FindViewById<TextView>(Resource.Id.columnHeader2);
             columnHeader2.Text = s.ColumnHeader2;
             TextView columnHeader3 = view.FindViewById<TextView>(Resource.Id.columnHeader3);
             columnHeader3.Text = s.ColumnHeader3;
             return view;
          }
          int size = s.Adapter.Count + 1;
          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;
          int size = s.Adapter.Count + 1;
          if(position < size) return s.Adapter.GetItem(position);
          position -= size;
       }
       return null;
    }
 }

现在您所要做的就是在您的代码中,当您填充包含 ListView 的 Activity 或 Layout 时,创建单独的适配器,然后创建分段适配器并为您想要的每个单独的列表类型添加一个部分。

ListAdapterType1 adapter1 = new ListAdapterType1();
ListAdapterType2 adapter2 = new ListAdapterType2();
ListAdapterType3 adapter3 = new ListAdapterType3();

ListSectionAdapter sectionAdapter = new ListSectionAdapter(this);
sectionAdapter.AddSection("Section 1", "Column 1", "Column 2", "Column 3", adapter1);
sectionAdapter.AddSection("Section 2", "Column 1", "Column 2", "Column 3", adapter2);
sectionAdapter.AddSection("Section 3", "Column 1", "Column 2", "Column 3", adapter3);

ListView myList = FindViewById<ListView>(Resource.Id.MyList);
myList.SetAdapter(sectionAdapter);

对于 ItemClick 事件,可能有更好的方法来做到这一点,但我使用了以下方法,它比较了分段列表的 GetItem(int) 方法返回的 Object Types 的 ToString,我们已将其扩展为 return基本列表适配器对象类型。

private void MyList_ItemClick(object sender, AdapterView.ItemClickEventArgs e) { ListSectionAdapter adapter = (sender as ListView).Adapter as ListSectionAdapter; if(adapter.GetItem(e.Position).ToString() == typeof(ObjectA).ToString()) { // respond accordingly to object type A being clicked } // so on and so forth on each different object type contained in the sectioned list }

My click event just populates and opens a new Layout describing the item clicked. The differentiation of object type is necessary because I use a different layout based on the object type clicked, because static information on the layout differs per object type.

Hope this helps. I gleaned this example from Wrox's book, Professional Android Programming with Mono C#/.Net, and modified it to meet my needs, hopefully you can see how it works so you can modify it to meet your own needs.

于 2012-12-11T14:52:25.287 回答