【问题标题】:Android ListView headersAndroid ListView 标头
【发布时间】:2012-11-15 10:50:06
【问题描述】:

我的 ListView 上有一些事件。事件按天排序,我希望每天都有带有日期的标题,然后事件在下面收听。

这是我填充该列表的方式:

ArrayList<TwoText> crs = new ArrayList<TwoText>();

crs.add(new TwoText("This will be header", event.getDate()));

for (Event event : events) {
    crs.add(new TwoText(event.getStartString() + "-" + event.getEndString(), event.getSubject()));
}

arrayAdapter = new TwoTextArrayAdapter(this, R.layout.my_list_item, crs);
lv1.setAdapter(arrayAdapter);

这就是我的类 TwoText 的样子:

public class TwoText {
    public String classID;
    public String state;

    public TwoText(String classID, String state) {
        this.classID = classID;
        this.state = state;
    }
}

这就是我的 TwoTextArrayAdapter 类的外观:

import java.util.ArrayList;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class TwoTextArrayAdapter extends ArrayAdapter<TwoText> {

    private ArrayList<TwoText> classes;
    private Activity con;
    TextView seperator;

    public TwoTextArrayAdapter(Activity context, int textViewResourceId, ArrayList<TwoText> classes) {
        super(context, textViewResourceId, classes);
        this.con = context;
        this.classes = classes;

    }

    @Override

    public View getView(int position, View convertView, ViewGroup parent) {

        View v = convertView;

        if (v == null) {

            LayoutInflater vi = (LayoutInflater) con.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

            v = vi.inflate(R.layout.my_list_item, null);

        }

        TwoText user = classes.get(position);

        if (user != null) {

            TextView content1 = (TextView) v.findViewById(R.id.list_content1);

            TextView content2 = (TextView) v.findViewById(R.id.list_content2);

            if (content1 != null) {

                content1.setText(user.classID);
            }   
            if(content2 != null) {

                content2.setText(user.state);
            }
        }
        return v;
    }
}

这是 my_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        style="?android:attr/listSeparatorTextViewStyle"
        android:id="@+id/separator"
        android:text="Header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#757678"
        android:textColor="#f5c227" />

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/list_content1"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="5dip"
            android:clickable="false"
            android:gravity="center"
            android:longClickable="false"
            android:paddingBottom="1dip"
            android:paddingTop="1dip"
            android:text="sample"
            android:textColor="#ff7f1d"
            android:textSize="17dip"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/list_content2"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="5dip"
            android:clickable="false"
            android:gravity="center"
            android:linksClickable="false"
            android:longClickable="false"
            android:paddingBottom="1dip"
            android:paddingTop="1dip"
            android:text="sample"
            android:textColor="#6d6d6d"
            android:textSize="17dip" />
    </LinearLayout>

</LinearLayout>

我现在所做的是将标题添加为常规列表对象,但我希望它作为标题并且在我的情况下有一个日期。

我的 xml 中有此代码作为标题:

<TextView
        style="?android:attr/listSeparatorTextViewStyle"
        android:id="@+id/separator"
        android:text="Header"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="#757678"
        android:textColor="#f5c227" />

我尝试在不必要时隐藏它并在必要时显示它,但我只是弄乱了我的其余代码。我尝试了更多教程,但它们也有相同的效果。

谁能指导我如何做到这一点?

【问题讨论】:

    标签: java android listview header android-arrayadapter


    【解决方案1】:

    我是这样做的,键是Adapter 类中的getItemViewType 和getViewTypeCount。 getViewTypeCount 返回列表中有多少类型的项目,在这种情况下,我们有一个标题项目和一个事件项目,所以两个。 getItemViewType 应该返回我们在输入 position 处拥有的 View 的类型。

    Android 会自动在convertView 中为您传递正确类型的View。

    以下代码的结果如下所示:

    首先我们有一个接口,我们的两个列表项类型将实现

    public interface Item {
        public int getViewType();
        public View getView(LayoutInflater inflater, View convertView);
    }
    

    然后我们有一个适配器,它接受Item 的列表

    public class TwoTextArrayAdapter extends ArrayAdapter<Item> {
        private LayoutInflater mInflater;
    
        public enum RowType {
            LIST_ITEM, HEADER_ITEM
        }
    
        public TwoTextArrayAdapter(Context context, List<Item> items) {
            super(context, 0, items);
            mInflater = LayoutInflater.from(context);
        }
    
        @Override
        public int getViewTypeCount() {
            return RowType.values().length;
    
        }
    
        @Override
        public int getItemViewType(int position) {
            return getItem(position).getViewType();
        }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       return getItem(position).getView(mInflater, convertView);
    }
    

    编辑 Better For Performance.. 可以在滚动时注意到

    private static final int TYPE_ITEM = 0; 
    private static final int TYPE_SEPARATOR = 1; 
    
    public View getView(int position, View convertView, ViewGroup parent)  {
        ViewHolder holder = null;
        int rowType = getItemViewType(position);
        View View;
        if (convertView == null) {
            holder = new ViewHolder();
            switch (rowType) {
                case TYPE_ITEM:
                    convertView = mInflater.inflate(R.layout.task_details_row, null);
                    holder.View=getItem(position).getView(mInflater, convertView);
                    break;
                case TYPE_SEPARATOR:
                    convertView = mInflater.inflate(R.layout.task_detail_header, null);
                    holder.View=getItem(position).getView(mInflater, convertView);
                    break;
            }
            convertView.setTag(holder);
        }
        else
        {
            holder = (ViewHolder) convertView.getTag();
        }
        return convertView; 
    } 
    
    public static class ViewHolder {
        public  View View; } 
    }
    

    然后我们有实现Item 的类并膨胀正确的布局。在您的情况下,您将拥有类似 Header 类和 ListItem 类的东西。

       public class Header implements Item {
        private final String         name;
    
        public Header(String name) {
            this.name = name;
        }
    
        @Override
        public int getViewType() {
            return RowType.HEADER_ITEM.ordinal();
        }
    
        @Override
        public View getView(LayoutInflater inflater, View convertView) {
            View view;
            if (convertView == null) {
                view = (View) inflater.inflate(R.layout.header, null);
                // Do some initialization
            } else {
                view = convertView;
            }
    
            TextView text = (TextView) view.findViewById(R.id.separator);
            text.setText(name);
    
            return view;
        }
    
    }
    

    然后是ListItem 类

        public class ListItem implements Item {
        private final String         str1;
        private final String         str2;
    
        public ListItem(String text1, String text2) {
            this.str1 = text1;
            this.str2 = text2;
        }
    
        @Override
        public int getViewType() {
            return RowType.LIST_ITEM.ordinal();
        }
    
        @Override
        public View getView(LayoutInflater inflater, View convertView) {
            View view;
            if (convertView == null) {
                view = (View) inflater.inflate(R.layout.my_list_item, null);
                // Do some initialization
            } else {
                view = convertView;
            }
    
            TextView text1 = (TextView) view.findViewById(R.id.list_content1);
            TextView text2 = (TextView) view.findViewById(R.id.list_content2);
            text1.setText(str1);
            text2.setText(str2);
    
            return view;
        }
    
    }
    

    还有一个简单的Activity 来显示它

    public class MainActivity extends ListActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            List<Item> items = new ArrayList<Item>();
            items.add(new Header("Header 1"));
            items.add(new ListItem("Text 1", "Rabble rabble"));
            items.add(new ListItem("Text 2", "Rabble rabble"));
            items.add(new ListItem("Text 3", "Rabble rabble"));
            items.add(new ListItem("Text 4", "Rabble rabble"));
            items.add(new Header("Header 2"));
            items.add(new ListItem("Text 5", "Rabble rabble"));
            items.add(new ListItem("Text 6", "Rabble rabble"));
            items.add(new ListItem("Text 7", "Rabble rabble"));
            items.add(new ListItem("Text 8", "Rabble rabble"));
    
            TwoTextArrayAdapter adapter = new TwoTextArrayAdapter(this, items);
            setListAdapter(adapter);
        }
    
    }
    

    R.layout.header 的布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >
    
        <TextView
            style="?android:attr/listSeparatorTextViewStyle"
            android:id="@+id/separator"
            android:text="Header"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:background="#757678"
            android:textColor="#f5c227" />
    
    </LinearLayout>
    

    R.layout.my_list_item 的布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal" >
    
        <TextView
            android:id="@+id/list_content1"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="5dip"
            android:clickable="false"
            android:gravity="center"
            android:longClickable="false"
            android:paddingBottom="1dip"
            android:paddingTop="1dip"
            android:text="sample"
            android:textColor="#ff7f1d"
            android:textSize="17dip"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/list_content2"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="5dip"
            android:clickable="false"
            android:gravity="center"
            android:linksClickable="false"
            android:longClickable="false"
            android:paddingBottom="1dip"
            android:paddingTop="1dip"
            android:text="sample"
            android:textColor="#6d6d6d"
            android:textSize="17dip" />
    
    </LinearLayout>
    

    R.layout.activity_main.xml 的布局

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity" >
    
        <ListView
            android:id="@android:id/list"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />
    
    </RelativeLayout>
    

    您还可以更高级地使用ViewHolders、异步加载内容或任何您喜欢的方式。

    【讨论】:

    • 喜欢您的解决方案,但由于您正在扩展 ArrayAdapter,因此您不应跟踪自己的项目列表。只需使用 ArrayAdapter 中的内部跟踪。否则,存储物品的内存量会增加一倍
    • 优秀的解决方案。如果标题不可点击(可以在构造函数中设置),我只会将此添加到适配器中 @Override public boolean isEnabled(int position) { return (getItem(position).getViewType() == RowType.LIST_ITEM .ordinal()); }
    • 滚动列表视图时行会随机化吗?谁能指导一下
    • 为什么 Google 不能只用 3 行代码实现这一目标?
    • 这个答案是我在 SO 上找到的最佳答案之一 - 清晰、简洁且解释清楚。但是,我确实遇到了 ListView 的结果是半随机顺序的问题(第一个标题和项目还可以,后面的项目搞砸了),我设法找出了问题所在。 'EDIT Better For Performance.. 可以在滚动时注意到'下面的代码块让我搞砸了 - 从自定义 ArrayAdapter 类中删除它为我解决了这个问题。我建议每个得到随机结果的人都试试这个。不过,感谢您的出色回答。真的帮了我大忙!
    【解决方案2】:

    您可能正在寻找一个ExpandableListView,它具有用于分隔项目(子项)的标题(组)。

    关于这个主题的好教程:here。

    【讨论】:

    • 我不希望它们可扩展
    • 如果这是唯一的问题,您可以覆盖 onItemClick 方法以防止展开/折叠视图。
    • 但我仍然需要它们可以点击以用于其他目的
    • Err...我实际上是想说onGroupClick 只处理点击“标题”,你不需要禁用点击或类似的东西,只需取消任何折叠操作并设置所有要从头开始扩展的组。
    • 我同意ExpandableListView 在大多数情况下是最好的。但是,我有时想在我的活动中显示一个平面列表,并在其他时间显示一个带有标题的列表。可悲的是,ExpandableListAdapter 接口没有扩展ListAdapter 接口,因此对于多态性,我不得不使用@antew 的解决方案。
    【解决方案3】:

    作为替代方案,有一个很好的3rd party library 专门为此用例设计。因此,您需要根据存储在适配器中的数据生成标头。它们被称为 Rolodex 适配器,与ExpandableListViews 一起使用。它们可以很容易地被定制成一个带有标题的普通列表。

    使用 OP 的 Event 对象并知道标头基于与之关联的 Date...代码如下所示:

    活动

        //There's no need to pre-compute what the headers are. Just pass in your List of objects. 
        EventDateAdapter adapter = new EventDateAdapter(this, mEvents);
        mExpandableListView.setAdapter(adapter);
    

    适配器

    private class EventDateAdapter extends NFRolodexArrayAdapter<Date, Event> {
    
        public EventDateAdapter(Context activity, Collection<Event> items) {
            super(activity, items);
        }
    
        @Override
        public Date createGroupFor(Event childItem) {
            //This is how the adapter determines what the headers are and what child items belong to it
            return (Date) childItem.getDate().clone();
        }
    
        @Override
        public View getChildView(LayoutInflater inflater, int groupPosition, int childPosition,
                                 boolean isLastChild, View convertView, ViewGroup parent) {
            //Inflate your view
    
            //Gets the Event data for this view
            Event event = getChild(groupPosition, childPosition);
    
            //Fill view with event data
        }
    
        @Override
        public View getGroupView(LayoutInflater inflater, int groupPosition, boolean isExpanded,
                                 View convertView, ViewGroup parent) {
            //Inflate your header view
    
            //Gets the Date for this view
            Date date = getGroup(groupPosition);
    
            //Fill view with date data
        }
    
        @Override
        public boolean hasAutoExpandingGroups() {
            //This forces our group views (headers) to always render expanded.
            //Even attempting to programmatically collapse a group will not work.
            return true;
        }
    
        @Override
        public boolean isGroupSelectable(int groupPosition) {
            //This prevents a user from seeing any touch feedback when a group (header) is clicked.
            return false;
        }
    }
    

    【讨论】:

      【解决方案4】:

      我做了什么来将日期(例如 2016 年 12 月 1 日)作为标题。 我使用了 StickyHeaderListView 库

      https://github.com/emilsjolander/StickyListHeaders

      将日期转换为以毫秒为单位的长[不包括时间]并将其作为标题ID。

      @Override
      public long getHeaderId(int position) {
          return <date in millis>;
      }
      

      【讨论】:

        【解决方案5】:

        Here is a sample project,基于 antew 的详细而有用的答案,实现了一个带有多个标题的 ListView,其中包含视图持有者以提高滚动性能。

        在这个项目中,ListView 中表示的对象是类HeaderItem 或类RowItem 的实例,它们都是抽象类Item 的子类。 Item 的每个子类对应于自定义适配器中的不同视图类型ItemAdapter。 ItemAdapter 上的 getView() 方法将每个列表项的视图创建委托给 HeaderItem 或 RowItem 上的个性化 getView() 方法,具体取决于传递给getView() 适配器上的方法。每个Item 子类都提供了自己的视图持有者。

        视图持有者的实现如下。 Item 子类上的 getView() 方法检查传递给 ItemAdapter 上的 getView() 方法的 View 对象是否为空。如果是这样,适当的布局被膨胀,并且视图持有者对象被实例化并通过View.setTag()与膨胀的视图相关联。如果View 对象不为空,则视图持有者对象已经与视图相关联,并且通过View.getTag() 检索视图持有者。视图持有者的使用方式可以在HeaderItem的以下代码sn-p中看到:

        @Override
        View getView(LayoutInflater i, View v) {
            ViewHolder h;
            if (v == null) {
                v = i.inflate(R.layout.header, null);
                h = new ViewHolder(v);
                v.setTag(h);
            } else {
                h = (ViewHolder) v.getTag();
            }
            h.category.setText(text());
            return v;
        }
        
        private class ViewHolder {
            final TextView category;
        
            ViewHolder(View v) {
                category = v.findViewById(R.id.category);
            }
        }
        

        ListView 的完整实现如下。这是Java代码:

        import android.app.ListActivity;
        import android.os.Bundle;
        import android.view.LayoutInflater;
        import android.view.View;
        import android.view.ViewGroup;
        import android.widget.ArrayAdapter;
        import android.widget.TextView;
        
        import java.util.ArrayList;
        import java.util.Collections;
        import java.util.HashSet;
        import java.util.List;
        import java.util.Set;
        
        public class MainActivity extends ListActivity {
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setListAdapter(new ItemAdapter(getItems()));
            }
        
            class ItemAdapter extends ArrayAdapter<Item> {
                final private List<Class<?>> viewTypes;
        
                ItemAdapter(List<Item> items) {
                    super(MainActivity.this, 0, items);
                    if (items.contains(null))
                        throw new IllegalArgumentException("null item");
                    viewTypes = getViewTypes(items);
                }
        
                private List<Class<?>> getViewTypes(List<Item> items) {
                    Set<Class<?>> set = new HashSet<>();
                    for (Item i : items) 
                        set.add(i.getClass());
                    List<Class<?>> list = new ArrayList<>(set);
                    return Collections.unmodifiableList(list);
                }
        
                @Override
                public int getViewTypeCount() {
                    return viewTypes.size();
                }
        
                @Override
                public int getItemViewType(int position) {
                    Item t = getItem(position);
                    return viewTypes.indexOf(t.getClass());
                }
        
                @Override
                public View getView(int position, View v, ViewGroup unused) {
                    return getItem(position).getView(getLayoutInflater(), v);
                }
            }
        
            abstract private class Item {
                final private String text;
        
                Item(String text) {
                    this.text = text;
                }
        
                String text() { return text; }
        
                abstract View getView(LayoutInflater i, View v);
            }
        
            private class HeaderItem extends Item {
                HeaderItem(String text) {
                    super(text);
                }
        
                @Override
                View getView(LayoutInflater i, View v) {
                    ViewHolder h;
                    if (v == null) {
                        v = i.inflate(R.layout.header, null);
                        h = new ViewHolder(v);
                        v.setTag(h);
                    } else {
                        h = (ViewHolder) v.getTag();
                    }
                    h.category.setText(text());
                    return v;
                }
        
                private class ViewHolder {
                    final TextView category;
        
                    ViewHolder(View v) {
                        category = v.findViewById(R.id.category);
                    }
                }
            }
        
            private class RowItem extends Item {
                RowItem(String text) {
                    super(text);
                }
        
                @Override
                View getView(LayoutInflater i, View v) {
                    ViewHolder h;
                    if (v == null) {
                        v = i.inflate(R.layout.row, null);
                        h = new ViewHolder(v);
                        v.setTag(h);
                    } else {
                        h = (ViewHolder) v.getTag();
                    }
                    h.option.setText(text());
                    return v;
                }
        
                private class ViewHolder {
                    final TextView option;
        
                    ViewHolder(View v) {
                        option = v.findViewById(R.id.option);
                    }
                }
            }
        
            private List<Item> getItems() {
                List<Item> t = new ArrayList<>();
                t.add(new HeaderItem("Header 1"));
                t.add(new RowItem("Row 2"));
                t.add(new HeaderItem("Header 3"));
                t.add(new RowItem("Row 4"));
        
                t.add(new HeaderItem("Header 5"));
                t.add(new RowItem("Row 6"));
                t.add(new HeaderItem("Header 7"));
                t.add(new RowItem("Row 8"));
        
                t.add(new HeaderItem("Header 9"));
                t.add(new RowItem("Row 10"));
                t.add(new HeaderItem("Header 11"));
                t.add(new RowItem("Row 12"));
        
                t.add(new HeaderItem("Header 13"));
                t.add(new RowItem("Row 14"));
                t.add(new HeaderItem("Header 15"));
                t.add(new RowItem("Row 16"));
        
                t.add(new HeaderItem("Header 17"));
                t.add(new RowItem("Row 18"));
                t.add(new HeaderItem("Header 19"));
                t.add(new RowItem("Row 20"));
        
                t.add(new HeaderItem("Header 21"));
                t.add(new RowItem("Row 22"));
                t.add(new HeaderItem("Header 23"));
                t.add(new RowItem("Row 24"));
        
                t.add(new HeaderItem("Header 25"));
                t.add(new RowItem("Row 26"));
                t.add(new HeaderItem("Header 27"));
                t.add(new RowItem("Row 28"));
                t.add(new RowItem("Row 29"));
                t.add(new RowItem("Row 30"));
        
                t.add(new HeaderItem("Header 31"));
                t.add(new RowItem("Row 32"));
                t.add(new HeaderItem("Header 33"));
                t.add(new RowItem("Row 34"));
                t.add(new RowItem("Row 35"));
                t.add(new RowItem("Row 36"));
        
                return t;
            }
        
        }
        

        还有两个列表项布局,每个 Item 子类一个。这是 HeaderItem 使用的布局header:

        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#FFAAAAAA"
            >
            <TextView
                android:id="@+id/category"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="4dp"
                android:textColor="#FF000000"
                android:textSize="20sp"
                android:textStyle="bold"
                />
         </LinearLayout>
        

        这是 RowItem 使用的布局 row:

        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?android:attr/listPreferredItemHeight"
            >
            <TextView
                android:id="@+id/option"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="15sp"
                />
        </LinearLayout>
        

        这是生成的 ListView 的一部分的图像:

        【讨论】:

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