Как написать собственный ExpandableListAdapter

Я хочу написать свой собственный ExpandableListAdapter, который работает аналогично ArrayAdapter. Моя модель данных такова:

public class Group {

    private String name;   

    private List<Child> children;
}

public class Child {

     private String name;
}

Довольно просто. Как я могу сопоставить это отношение с реализацией ExpandableListAdapter? У меня есть рабочий SimpleExpandableListAdapter, работающий прямо сейчас, но мне нужно больше настраивать элементы управления (отображать значки и т.д.). Что я должен сделать для этого?

Главное, что мне нужен метод add(), чтобы иметь возможность добавлять группы и отменять список, когда дети добавляются и удаляются из адаптера. На самом деле я удивлен, что в SDK нет (даже абстрактной) реализации, которая помогает выполнить это.

Ответы

Ответ 1

Вот реализация, которую я просто взбивал. Я понятия не имею, работает ли это или нет, но мне кажется "умным":) Кстати, как нужно собирать комбинированный идентификатор ребенка или объединенный идентификатор группы, поэтому я просто импровизировал там.

package example;

import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

import android.content.Context;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListAdapter;

public abstract class AbstractExpandableListAdapter<A, B> implements ExpandableListAdapter {

    private final List<Entry<A, List<B>>> objects;

    private final DataSetObservable dataSetObservable = new DataSetObservable();

    private final Context context;

    private final Integer groupClosedView;

    private final Integer groupExpandedView;

    private final Integer childView;

    private final LayoutInflater inflater;

    public AbstractExpandableListAdapter(Context context, int groupClosedView, 
            int groupExpandedView, int childView, List<Entry<A, List<B>>> objects) {
        this.context = context;
        this.objects = objects;
        this.groupClosedView = new Integer(groupClosedView);
        this.groupExpandedView = new Integer(groupExpandedView);
        this.childView = new Integer(childView);

        this.inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public void add(Entry<A, List<B>> group) {
        this.getObjects().add(group);
        this.notifyDataSetChanged();
    }

    public void remove(A group) {
        for (Entry<A, List<B>> entry : this.getObjects()) {
            if (entry != null && entry.getKey().equals(group)) {
                this.getObjects().remove(group);
                this.notifyDataSetChanged();
                break;
            }
        }
    }

    public void remove(Entry<A, List<B>> entry) {
        remove(entry.getKey());
    }

    public void addChild(A group, B child) {
        for (Entry<A, List<B>> entry : this.getObjects()) {
            if (entry != null && entry.getKey().equals(group)) {
                if (entry.getValue() == null) 
                    entry.setValue(new ArrayList<B>());

                entry.getValue().add(child);
                this.notifyDataSetChanged();
                break;
            }
        }
    }

    public void removeChild(A group, B child) {
        for (Entry<A, List<B>> entry : this.getObjects()) {
            if (entry != null && entry.getKey().equals(group)) {
                if (entry.getValue() == null)
                    return;

                entry.getValue().remove(child);
                this.notifyDataSetChanged();
                break;
            }
        }
    }

    public void notifyDataSetChanged() {
        this.getDataSetObservable().notifyChanged();
    }

    public void notifyDataSetInvalidated() {
        this.getDataSetObservable().notifyInvalidated();
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        this.getDataSetObservable().registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        this.getDataSetObservable().unregisterObserver(observer);
    }

    public int getGroupCount() {
        return getObjects().size();
    }

    public int getChildrenCount(int groupPosition) {
        return getObjects().get(groupPosition).getValue().size();
    }

    public Object getGroup(int groupPosition) {
        return getObjects().get(groupPosition).getKey();
    }

    public Object getChild(int groupPosition, int childPosition) {
        return getObjects().get(groupPosition).getValue().get(childPosition);
    }

    public long getGroupId(int groupPosition) {
        return ((Integer)groupPosition).longValue();
    }

    public long getChildId(int groupPosition, int childPosition) {
        return ((Integer)childPosition).longValue();
    }

    public boolean hasStableIds() {
        return true;
    }

    public View getGroupView(int groupPosition, boolean isExpanded,
            View convertView, ViewGroup parent) {

        if (convertView != null && convertView.getId() != 
                (isExpanded ? getGroupExpandedView() : getGroupClosedView())) {
//          do nothing, we're good to go, nothing has changed.
        } else {
//          something has changed, update.
            convertView = inflater.inflate(isExpanded ? getGroupExpandedView() :
                    getGroupClosedView(), parent, false);
            convertView.setTag(getObjects().get(groupPosition));
        }

        return convertView;
    }

    public View getChildView(int groupPosition, int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) {

        if (convertView != null) {
//          do nothing 
        } else {
//          create
            convertView = inflater.inflate(getChildView(), parent, false);
            convertView.setTag(getObjects().get(groupPosition).getValue().get(childPosition));
        }

        return convertView;
    }

    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

    public boolean areAllItemsEnabled() {
        return true;
    }

    public boolean isEmpty() {
        return getObjects().size() == 0;
    }

    public void onGroupExpanded(int groupPosition) {

    }

    public void onGroupCollapsed(int groupPosition) {

    }

    public long getCombinedChildId(long groupId, long childId) {
        return groupId * 10000L + childId;
    }

    public long getCombinedGroupId(long groupId) {
        return groupId * 10000L;
    }

    protected DataSetObservable getDataSetObservable() {
        return dataSetObservable;
    }

    protected List<Entry<A, List<B>>> getObjects() {
        return objects;
    }

    protected Context getContext() {
        return context;
    }

    protected Integer getGroupClosedView() {
        return groupClosedView;
    }

    protected Integer getGroupExpandedView() {
        return groupExpandedView;
    }

    protected Integer getChildView() {
        return childView;
    }
}

Любые комментарии или критика приветствуются.

Ответ 2

Я был довольно удивлен, что я тоже не нашел лучшей документации. Если вы его нашли, напишите здесь. Лучший пример реализации, который я нашел, был в ApiDemos. Существует ExpandableListActivity, который реализует BaseExpandableListAdapter. Класс называется ExpandableList1.java.

Вам нужно будет создать свой собственный метод add(), который добавит ваши классы Group и Child к адаптеру. Я не думаю, что это будет сложно с первого взгляда. Фактически вы можете просто создать ссылки на объекты класса. Когда я реализовал мой, мой набор данных был небольшим и не менялся, поэтому мне нужно было только обратиться к моему файлу array.xml.

Ответ 3

   public class CustomExpandableAdapter extends BaseExpandableListAdapter {
        private Context mContext;
        private List<Group> mData;
        private int mSelectedPosition = -1;

        public CustomExpandableAdapter(Context context, List<Group> data ) {
            mData = data;
            mContext = context;

        }

        @Override
        public int getGroupCount() {
            return mData.size();
        }

        @Override
        public int getChildrenCount(int groupPosition) {
            return mData.get(groupPosition).children.size();
        }

        @Override
        public Object getGroup(int groupPosition) {
            return mData.get(groupPosition);
        }

        @Override
        public Object getChild(int groupPosition, int childPosition) {
            return mData.get(groupPosition).children.get(childPosition);
        }

        @Override
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            HeaderViewHolder headerViewHolder = null;
            if (convertView == null) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.faq_header_text_layout, null);
                headerViewHolder = new HeaderViewHolder(convertView);
                convertView.setTag(headerViewHolder);
            }
            headerViewHolder = (HeaderViewHolder) convertView.getTag();

            headerViewHolder.mGroupHeader.setText(mData.get(groupPosition).name);
            return convertView;
        }

        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
            ChildViewHolder childViewHolder = null;
            if (convertView == null) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.faq_textview_layout, null);
                childViewHolder = new ChildViewHolder(convertView);
                convertView.setTag(childViewHolder);
            }
            childViewHolder = (ChildViewHolder) convertView.getTag();

                        childViewHolder.mChildTitle.setText(mData.get(groupPosition).children.get(childPosition));
            return convertView;
        }

        @Override
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            return false;
        }

        private static class HeaderViewHolder {
            final TextView mGroupHeader;

            private HeaderViewHolder(View group) {
                mGroupHeader = (TextView) group.findViewById(R.id.txv_faq_header_text_layout);
            }
        }

        private static class ChildViewHolder {
            final TextView mChildTitle;

            private ChildViewHolder(View group) {
                mChildTitle = (TextView) group.findViewById(R.id.txv_faq_textview_layout);
            }
        }

        @Override
        public void unregisterDataSetObserver(DataSetObserver observer) {
            if (observer != null) {
                super.unregisterDataSetObserver(observer);
            }
        }

        public void setSelectedPosition(int selectedPosition) {
            mSelectedPosition = selectedPosition;
        }
    }

Ответ 4

Увидев, сколько лет этот пост и ответы, я думал, что хочу отметить, что есть очень хорошая сторонняя библиотека, которая sorta заполняет эту отсутствует пробел. Хотя опубликованные пользовательские решения хороши, они все еще не хватает некоторых вещей и следуют громоздкому дизайну, требуя от программиста создания структуры данных структур данных. Иногда вы просто хотите организовать один Список в симпатичные маленькие группы без хлопот делать это самостоятельно.

Он называется RolodexArrayAdapter и может быть легко использован при создании пользовательских ExpandableListAdapters... без необходимости беспокоиться обо всех проблемах и функциях управления данными. Он поддерживает такие методы, как add, addAll, remove, removeAll, saveAll, содержит, сортировку и т.д. Он также поддерживает более сложные функции, такие как ChoiceMode, Filtering и автоматически расширяющиеся группы.

Пример:

class MovieAdapter extends RolodexArrayAdapter<Integer, MovieItem> {
    public MovieAdapter(Context activity, List<MovieItem> movies) {
        super(activity, movies);
    }

    @Override
    public Integer createGroupFor(MovieItem childItem) {
        //Lets organize our movies by their release year
        return childItem.year;
    }

    @Override
    public View getChildView(LayoutInflater inflater, int groupPosition, int childPosition,
                             boolean isLastChild, View convertView, ViewGroup parent) {
        if (convertView == null) {
            //Inflate your view
        }
        //Fill view with data
        return convertView;
    }

    @Override
    public View getGroupView(LayoutInflater inflater, int groupPosition, boolean isExpanded,
                             View convertView, ViewGroup parent) {
        if (convertView == null) {
            //Inflate your view
        }
        //Fill view with data
        return convertView;
    }

    @Override
    public boolean hasAutoExpandingGroups() {
        return true;
    }

    @Override
    protected boolean isChildFilteredOut(MovieItem movie, CharSequence constraint) {
        //Lets filter by movie title
        return !movie.title.toLowerCase(Locale.US).contains(
                constraint.toString().toLowerCase(Locale.US));
    }

    @Override
    protected boolean isGroupFilteredOut(Integer year, CharSequence constraint) {
        //Lets filter out everything whose year does not match the numeric values in the constraint.
        return TextUtils.isDigitsOnly(constraint) && !year.toString().contains(constraint);
    }
}