【问题标题】:Listview with custom adapter shows repeating grid items if adapter used from Fragment如果从 Fragment 使用适配器,则带有自定义适配器的 Listview 会显示重复的网格项目
【发布时间】:2017-03-16 01:15:13
【问题描述】:

它是通过 C# 实现的 Xamarin.Android。

如果从 Fragment 代码中使用适配器,则问题是自定义适配器在 ListView 中显示的项目过多。如果我从 MainActivity 中使用它,它会正常工作。

我需要在位于 Tab (Fragment) 中的 ListView 元素中显示动态自定义信息。 ListView 的每一行都应该包含动态数量的文本框,一排一排。为此,自定义 adapter 生成带有 TextView 元素的 GridLayout。行数也是动态的,这就是使用 List 的原因。我试图尽可能地简化我的代码。现实生活中Data类比较复杂,使用了很多Tab等。

输入: 1. 一些数据的简单类

public class MyData
{
    internal int ID;
    internal string Nick;

    public MyData(int _id, string _nick)
    {
        ID = _id;
        Nick = _nick;
    }
}     

2。 Tab1.axml 是我们需要显示的 Tab 的布局。它只包含 Adapter 应该放置数据的 ListView 元素

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
    android:minWidth="25px"
    android:minHeight="25px"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/listView1" />

3. Grid.axml 是一个布局,它将代表 ListView 的每一行。它将在 GridView 内包含动态数量的 TextView 元素

<GridLayout xmlns:p1="http://schemas.android.com/apk/res/android"
p1:minWidth="25px"
p1:minHeight="25px"
p1:layout_width="match_parent"
p1:layout_height="match_parent"
p1:id="@+id/gridLayout1"
p1:columnCount="2" />

4。 Main.axml 是包含 Tabs 的主布局(本例中只有 1 个)。 fragmentContainer 是我们的 Tab 容器。 listViewMain 是检查Adapter是否正常工作的listview。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearLayout1">
<FrameLayout
    android:minWidth="25px"
    android:minHeight="25px"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/fragmentContainer" />
<ListView
    android:minWidth="25px"
    android:minHeight="25px"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/listViewMain" />

5. MainActivity.cs 包含所有代码

 protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        SetContentView(Resource.Layout.Main);

        //creating one main tab
        ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
        AddTab("Tab 1", new SampleTabFragment1());

        /* //to test if Adapter works properly From MainActivtity instead of Fragment
        List<MyData> listOfData = new List<MyData>();
        MyData item = new MyData(1, "Nick#1");
        listOfData.Add(item);

        item = new MyData(2, "Nick#2");
        listOfData.Add(item);

        ListView table = this.FindViewById<ListView>(Resource.Id.listViewMain);
        table.Adapter = new MyDataAdapter(this, listOfData);*/
    }

创建Tab的标准函数:

void AddTab(string tabText, Fragment view)
    {
        var tab = this.ActionBar.NewTab();
        tab.SetText(tabText);

        // must set event handler before adding Tab
        tab.TabSelected += delegate (object sender, ActionBar.TabEventArgs e)
        {
            var fragment = this.FragmentManager.FindFragmentById(Resource.Id.fragmentContainer);
            if (fragment != null)
                e.FragmentTransaction.Remove(fragment);
            e.FragmentTransaction.Add(Resource.Id.fragmentContainer, view);
        };
        tab.TabUnselected += delegate (object sender, ActionBar.TabEventArgs e)
        {
            e.FragmentTransaction.Remove(view);
        };

        this.ActionBar.AddTab(tab);
    }

我们将自定义适配器绑定到列表视图的 Tab 片段:

class SampleTabFragment1 : Fragment
    {
        public override void OnDestroyView()
        {
            base.OnDestroyView();
        }            
        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            base.OnCreateView(inflater, container, savedInstanceState);
            var view = inflater.Inflate(Resource.Layout.Tab1, container, false);

            //Creating our data
            List <MyData> listOfData = new List<MyData>();                
            MyData item = new MyData(1, "Nick#1");
            listOfData.Add(item);

            item = new MyData(2, "Nick#2");
            listOfData.Add(item);

            //Find listview from Tab1.axml which is actually our Tab Fragment
            ListView table = view.FindViewById<ListView>(Resource.Id.listView1);
            //Binding listview with custom adapter
            table.Adapter = new MyDataAdapter(this.Activity, listOfData);

            return view;
        }
    }

最后,我们自定义数据的适配器:

public class MyDataAdapter : BaseAdapter<MyData>
    {
        Activity context;
        List<MyData> list;
        public MyDataAdapter(Activity _context, List<MyData> _list)
        {
            context = _context;
            list = _list;
        }
        public override View GetView(int position, View convertView, ViewGroup parent)
        {
            View view = convertView;

            // re-use an existing view, if one is available
            // otherwise create a new one
            if (view == null)
                view = context.LayoutInflater.Inflate(Resource.Layout.Grid, parent, false);

            //find GridView from Grid.axml and set columns count to 2 (only ID and Nick)
            GridLayout tableView = view.FindViewById<GridLayout>(Resource.Id.gridLayout1);
            tableView.ColumnCount = 2;

            //Creating TextView to show ID, place position is 0 - first
            TextView textCaption = new TextView(view.Context);
            textCaption.SetText(list[position].ID.ToString(), TextView.BufferType.Normal);
            tableView.AddView(textCaption, 0);

            //Creating TextView to show ID, place position is 1 - second in a row of Grid
            textCaption = new TextView(view.Context);
            textCaption.SetText(list[position].Nick, TextView.BufferType.Normal);
            tableView.AddView(textCaption, 1);

            return view;
        }
        public override int Count
        {
            get { return list.Count; }
        }
        public override long GetItemId(int position)
        {
            return position;
        }

        public override MyData this[int index]
        {
            get { return list[index]; }
        }            
    }

所有这些都应该向最终用户显示两行 ListView。第一行包含 GridView,其中一行中有两个 TextView 元素“1”和“Nick#1”。第二行应包含 GridView,其中一行包含“2”和“Nick#2”,如下所示: calling from MainActivity

...但是 2 个 ListView 行中的每一行都包含大量带有数据的 GridView 行: calling from Fragment

如果您将通过评论禁用 Tab

        //ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
        //AddTab("Tab 1", new SampleTabFragment1());

并取消注释 MainActivity.cs 中的代码区域

//to test if Adapter works properly without Fragment

它将使用与片段 SampleTabFragment1() 调用中使用的完全相同的自定义适配器和布局结构显示正确的结果。 看起来有一些关于使用带有自定义适配器和/或布局的片段的技巧,你怎么看?

【问题讨论】:

  • 我认为您可能想再看一下片段的设计理念 - developer.android.com/guide/components/fragments.html 看起来您可能正在“嵌套”布局,其中您的预期列表被多次添加。简化您的布局,以确保您有一个简单的FrameLayout,您可以将片段插入其中。然后,您应该在 list.Count 上设置断点并确保它只有 2 个项目。然后检查是否也填充了其他控件。
  • 但是已经尽可能的简化了:Main Layout 包含 FragmentContainer for Fragment。 Fragment 的布局非常简单 - 只是 ListView。而已。网格布局仅用于自定义列表适配器,效果很好。问题是 list.Count 属性没问题,==2。但由于某种原因,它从 Fragment 调用了几次,这是不正确的。如果使用 MainActivity 而不是 Fragment,则调用正确。
  • 我更喜欢使用FragmentTransaction.Replace(),因为添加和删除片段有时很容易出错,除非您了解生命周期。尝试将该代码重构为 Replace()
  • 看起来问题是ListView 回收机制是如何工作的。这里some info 但在我的情况下,我什至不能将match_parent 用于layout_heightListView。相反应该使用固定值。无论如何,使用固定大小不是解决方案,因为我所有的数据都是动态的,而且使用固定大小根本不是一个好主意。看起来需要看看 ArrayAdapter Android 实现,并尝试理解“神奇”为什么它适用于 Fragment(如果大小不固定)以及为什么所有自定义适配器都不能...

标签: c# android android-fragments gridview xamarin


【解决方案1】:

在遇到这个问题后,我决定改用 Java Android 而不是使用 C#。在学习 Java(和 Android 开发)几周后,我尝试重写我的程序并面临同样的问题!这次我尝试通过ListView 的自定义适配器动态创建TableRowsTextViews

第一行仍然有重复的元素!但解决方案非常简单。您应该在运行时在当前view 未创建适配器的情况下创建新视图(等于 null)。 (当然,如果您不动态创建任何视图,而是从 XML 布局中使用它已经可以正常工作)此解决方案适用于我的 Java 程序,对于我的旧 C# 应该是这样的代码:

public override View GetView(int position, View convertView, ViewGroup parent)
{
    View view = convertView;

    // re-use an existing view, if one is available
    // otherwise create a new one
    if (view == null)
    {
        view = context.LayoutInflater.Inflate(Resource.Layout.Grid, parent, false);
        //find GridView from Grid.axml and set columns count to 2 (only ID and Nick)
        GridLayout tableView = view.FindViewById<GridLayout>(Resource.Id.gridLayout1);
        tableView.ColumnCount = 2;

        //Creating TextView to show ID, place position is 0 - first
        TextView textCaption = new TextView(view.Context);
        textCaption.SetText(list[position].ID.ToString(), TextView.BufferType.Normal);
        tableView.AddView(textCaption, 0);

        //Creating TextView to show ID, place position is 1 - second in a row of Grid
        textCaption = new TextView(view.Context);
        textCaption.SetText(list[position].Nick, TextView.BufferType.Normal);
        tableView.AddView(textCaption, 1);
    }

    return view;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多