繁体   English   中英

同一RelativeLayout中的多个ListView

[英]Multiple ListView in same RelativeLayout

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

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

三个单独的ListView组件列出了3种不同类型的项目。 我的问题来了,当与ScrollView封装在一起时,ListViews决定每个仅显示1个项目,然后归因于其滚动行为。 我更希望他们显示所有项目,并让ScrollView进行滚动。 这可能吗?

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

ListView主要用于滚动。 当项目数很大时(相对于屏幕尺寸),ListView使用回收机制使滚动平滑。 如果您希望ListView一次显示所有项目,则实际上并不需要ListView。 请改用LinearLayout。 我想您正在使用列表适配器填充列表。 因此,您可以使用自定义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();
        }
    }
}

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

我最终使用的解决方案是一个分段列表适配器,用于在我的布局中填充单个ListView控件。 分段适配器使ListView与首选项列表非常相似。 但是,分段适配器更具通用性,因为您可以自定义节分隔符项,并包括多个列表项布局。 假设您已经了解Android版Mono的基础知识,这是如何实现此目的的细分。

首先,您需要一个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>

您会注意到,内部的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)方法中返回的对象类型的ToString,我们已经对其进行了扩展以返回基本列表适配器对象类型。

私有无效MyList_ItemClick(对象发送者,AdapterView.ItemClickEventArgs e){ListSectionAdapter adapter =(发送者为ListView).Adapter为ListSectionAdapter; if(adapter.GetItem(e.Position).ToString()== typeof(ObjectA).ToString()){//对被单击的对象类型A做出相应的响应} //依此类推,对包含的每个不同对象类型在分区列表中}

我的click事件只是填充并打开一个新的Layout,用于描述单击的项目。 必须区分对象类型,因为我会根据所单击的对象类型使用不同的布局,因为布局上的静态信息因对象类型而异。

希望这可以帮助。 我从Wrox的书《使用Mono C#/。Net进行专业Android编程》中收集了此示例,并对其进行了修改以满足我的需求,希望您能看到它的工作原理,以便您可以对其进行修改以满足自己的需求。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM