简体   繁体   English

同一RelativeLayout中的多个ListView

[英]Multiple ListView in same RelativeLayout

I have an app that I am trying to work out. 我有一个正在尝试的应用程序。 In one layout of the app, I have multiple ListView components. 在应用程序的一种布局中,我有多个ListView组件。 The outline looks like this: 大纲如下所示:

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

The three separate ListView components are listing 3 different types of items. 三个单独的ListView组件列出了3种不同类型的项目。 My problem comes in, that when encapsulated withing the ScrollView, the ListViews decide to only display 1 item each, and then fall to their scroll behavior. 我的问题来了,当与ScrollView封装在一起时,ListViews决定每个仅显示1个项目,然后归因于其滚动行为。 What I prefer is for them to show all their items, and let the ScrollView do the scrolling. 我更希望他们显示所有项目,并让ScrollView进行滚动。 Is this possible? 这可能吗?

I've read a few other questions on here, and it seems maybe the convention is to NOT use multiple ListView's per layout. 我在这里阅读了其他一些问题,看来惯例是不对每个布局使用多个ListView。 I would rather make this work if possible, as the 3 separate items listed are related, and make sense to be shown together. 如果可能的话,我宁愿进行这项工作,因为列出的3个单独的项目是相关的,并且可以一起显示。

ListViews are mainly designed for scrolling. ListView主要用于滚动。 ListView uses recycling mechanism to make the scrolling smooth when the number of items is large(relatively to screen size). 当项目数很大时(相对于屏幕尺寸),ListView使用回收机制使滚动平滑。 If you want your ListView to display all items at once you do not really need a ListView. 如果您希望ListView一次显示所有项目,则实际上并不需要ListView。 Use LinearLayout instead. 请改用LinearLayout。 I guess you are using list adapter to fill your lists. 我想您正在使用列表适配器填充列表。 So you can extend LinearLayout and utilize your adapters by using custom setAdapter() method. 因此,您可以使用自定义setAdapter()方法扩展LinearLayout并利用您的适配器。 Here is the class I created for non scrollable list. 这是我为不可滚动列表创建的类。

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();
        }
    }
}

You can use it similar as you use your list views. 您可以像使用列表视图一样使用它。 Note that number of items is expected to be relatively small. 注意,预计项目数量会相对较少。 Otherwise you will experience performance issues. 否则,您将遇到性能问题。

The solution I ended up going with was a Sectioned List Adapter, used to populate a single ListView control in my layout. 我最终使用的解决方案是一个分段列表适配器,用于在我的布局中填充单个ListView控件。 The Sectioned Adapter causes a ListView to closely resemble a Preferences List. 分段适配器使ListView与首选项列表非常相似。 But the Sectioned Adapter is more versatile because you can customize the section seperator items, and include multiple list item layouts. 但是,分段适配器更具通用性,因为您可以自定义节分隔符项,并包括多个列表项布局。 Here's the breakdown on how to achieve this, assuming you already know the basics of Mono for Android. 假设您已经了解Android版Mono的基础知识,这是如何实现此目的的细分。

First you need a Section Object, which will describe each individual list section. 首先,您需要一个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; } }
}

This object stores all the information for each section of the list, a caption which will be the title for the section, as well as a column header for each of the 3 columns I want my list to have. 该对象存储列表的每个部分的所有信息,标题(将作为该部分的标题)以及我希望列表具有的3列中的每列的列标题。 Additionally we store a unique list adapter that will provide the Views for this section of the list. 此外,我们存储了一个唯一的列表适配器,该适配器将提供列表此部分的视图。 This allows you to provide a different adapter for each section. 这使您可以为每个部分提供不同的适配器。 You could extend this section object to further describe the separator section if you wanted, giving you more flexibility, and the chance to change what each section's basic structure looks like. 如果需要,可以扩展此部分对象以进一步描述分隔器部分,从而为您提供更大的灵活性,并有机会更改每个部分的基本结构。

Next you need an XML template to describe the separator for the list. 接下来,您需要一个XML模板来描述列表的分隔符。 Since each of my sections will have the same basic structure, I can recycle the same template each time rather than complicate it further. 由于我的每个部分都将具有相同的基本结构,因此我可以每次都回收相同的模板,而不必进一步使其复杂化。

<?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>

You'll notice, the Inner LinearLayout control I gave a style="?android:attr/listSeparatorTextViewStyle" tag. 您会注意到,内部的LinearLayout控件给了我一个style =“?android:attr / listSeparatorTextViewStyle”标签。 This tells android to give that view a bottom border. 这告诉android给该视图底部边框。 If you just wanted a simple TextView separator, you could do this, and just give it this same style tag. 如果只需要一个简单的TextView分隔符,则可以这样做,并为其赋予相同的样式标签。

Now my ListAdapters are all basically the same, they just extend a different data object. 现在我的ListAdapter基本上都相同,只是扩展了一个不同的数据对象。 All three populate 3 columns, all of which fit their data in the same size columns, they are just different logical objects. 所有这三个填充3列,所有这些列都将它们的数据容纳在相同大小的列中,它们只是不同的逻辑对象。 Each adapter is a BaseAdapter extension that populates a view with the 3 columns of data. 每个适配器都是一个BaseAdapter扩展,它使用3列数据填充视图。 I've made the assumption here that you know how to create a standard extension of BaseAdapter. 我在这里假设您知道如何创建BaseAdapter的标准扩展。 What I will show is how to create the ListSectionAdapter. 我将展示的是如何创建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;
    }
 }

Now all you have to do is in your code, when you are populating the Activity or Layout containing the ListView, create your separate adapters, then create your sectioned adapter and add a section for each separate list type you want in it. 现在,您要做的就是在代码中,当您填充包含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);

For the ItemClick event, there may be a better way to do this, but I used the following method, which compares the ToString of the returned Object Types from the GetItem(int) method of the sectioned list, which we've extended to return the base list adapter object type. 对于ItemClick事件,可能有一种更好的方法,但是我使用了以下方法,该方法比较了分段列表的GetItem(int)方法中返回的对象类型的ToString,我们已经对其进行了扩展以返回基本列表适配器对象类型。

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

My click event just populates and opens a new Layout describing the item clicked. 我的click事件只是填充并打开一个新的Layout,用于描述单击的项目。 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. 我从Wrox的书《使用Mono C#/。Net进行专业Android编程》中收集了此示例,并对其进行了修改以满足我的需求,希望您能看到它的工作原理,以便您可以对其进行修改以满足自己的需求。

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

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