[英]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.