В моем приложении есть ExpandableListView, в котором мне нужно сделать с контекстным меню действий действие над несколькими дочерними элементами нескольких групп.
В своем исследовании я обнаружил, что множественный выбор в расширяемых представлениях списка невозможен или очень сложно реализовать. Поэтому я решил реализовать свое собственное решение следующим образом (я разместил код ниже для пояснения, это черновик кода, он не является окончательным, я только что реализовал/жестко закодировал некоторые вещи, чтобы увидеть, если это работает или нет):
- Я открываю контекстное меню действий по щелчку на дочернем элементе, ставлю галочку в представлении дочернего элемента и меняю фон этого дочернего элемента.
- Я открываю одно и то же контекстное меню действий при каждом щелчке по каждому дочернему элементу.
- закрытие контекстного меню, которое я еще не реализовал (хотел проверить, работает ли это раньше)
- Я бы сделал действие, извлекая дочерние элементы из карты, соответствующей карте, которую я даю адаптеру.
- мой код работает, пока я не нажму на другую группу, чтобы выбрать другого ребенка
- затем, когда я открываю другую группу, выбранные дочерние элементы (галочка и фон) перемещаются к дочерним элементам в этой группе (я только что открыл соответствующую группу, чтобы что-то выбрать), а последние выбранные элементы не были выбраны
- Я выложил скриншоты для ясности
Я не заметил какой-либо модели поведения
Я не знаю, почему это происходит.
адаптер:
public class CoordinateExpandableListAdapter extends BaseExpandableListAdapter {
private Context context;
private Map<String, List<String>> coordinateList;
private List<String> groupList;
public CoordinateExpandableListAdapter(Context context, List<String> groupList, Map<String, List<String>> coordinateList) {
this.context = context;
this.groupList = groupList;
this.coordinateList = coordinateList;
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return coordinateList.get(groupList.get(groupPosition)).get(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
class ChildRowHolder {
TextView childRowTitle;
public ChildRowHolder(View view) {
childRowTitle = (TextView) view.findViewById(R.id.child_item_expandable_list_view_title);
}
}
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
View childRow = convertView;
ChildRowHolder childRowHolder = null;
String childRowTitle = (String) getChild(groupPosition, childPosition);
if (childRow == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
childRow = inflater.inflate(R.layout.child_expandable_list_view_item, null);
childRowHolder = new ChildRowHolder(childRow);
childRow.setTag(childRowHolder);
} else {
childRowHolder = (ChildRowHolder) childRow.getTag();
}
childRowHolder.childRowTitle.setText(childRowTitle);
return childRow;
}
@Override
public int getChildrenCount(int groupPosition) {
return coordinateList.get(groupList.get(groupPosition)).size();
}
@Override
public Object getGroup(int groupPosition) {
return groupList.get(groupPosition);
}
@Override
public int getGroupCount() {
return groupList.size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
class GroupRowHolder {
TextView groupRowTitle;
public GroupRowHolder(View view) {
groupRowTitle = (TextView) view.findViewById(R.id.group_item_expandable_list_view);
}
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
View groupRow = convertView;
GroupRowHolder groupRowHolder = null;
String coodinateCategory = (String) getGroup(groupPosition);
if (groupRow == null) {
LayoutInflater inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
groupRow = inflator.inflate(R.layout.group_expandable_list_view_item, null);
groupRowHolder = new GroupRowHolder(groupRow);
groupRow.setTag(groupRowHolder);
} else {
groupRowHolder = (GroupRowHolder) groupRow.getTag();
}
groupRowHolder.groupRowTitle.setText(coodinateCategory);
groupRowHolder.groupRowTitle.setTypeface(null, Typeface.BOLD);
return groupRow;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
фрагмент из расширяемого представления списка:
expandableListView = (ExpandableListView) getActivity().findViewById(R.id.coordinate_expandable_list_view);
CoordinateExpandableListAdapter expandableListAdapter = new CoordinateExpandableListAdapter(getActivity(), groupList, coordinateList);
expandableListView.setAdapter(expandableListAdapter);
registerForContextMenu(expandableListView);
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View childView, int groupPosition, int childPosition, long id) {
// Start the CAB using the ActionMode.Callback defined above
actionMode = getActivity().startActionMode(actionModeCallback);
ArrayList<Integer> positions = new ArrayList<Integer>();
positions.add(groupPosition);
positions.add(childPosition);
Integer key = -1;
if ((key = isPositionsAdded(positions)) == null) {
selectedMap.put(selectedMapKey++, positions);
ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
tick.setVisibility(View.VISIBLE);
childView.setBackgroundColor(getResources().getColor(R.color.backgroung_expandable_list_view_child));
} else {
selectedMap.remove(key);
ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
tick.setVisibility(View.GONE);
childView.setBackgroundColor(getResources().getColor(R.color.background_color));
}
return true;
}
});
expandableListView.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
@Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
// TODO Auto-generated method stub
return false;
}
});
}
private Integer isPositionsAdded(ArrayList<Integer> positions) {
if (selectedMap.containsValue(positions)) {
for (Map.Entry<Integer, ArrayList<Integer>> entry : selectedMap.entrySet()) {
if (entry.getValue().equals(positions)) {
return entry.getKey();
}
}
}
return null;
}
private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {// Called when the action mode is created; startActionMode() was called
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.coordinate_list_context_menu, menu);
return true;
}
// Called each time the action mode is shown. Always called after onCreateActionMode, but
// may be called multiple times if the mode is invalidated.
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.delete:
deleteCurrentItems();
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
}
};
скрины: - сначала выбрал из группы 2, пункты 1, 2
- во-вторых, я открыл группу 1, и выбор пошел от выбранных элементов к детям группы 1.
И что хуже всего, это поведение непоследовательно, иногда оно работает так, как задумано, но в большинстве случаев это не так, может быть, это происходит чисто случайно.
ИЗМЕНИТЬ
Как я уже упоминал в комментарии ниже, я публикую черновик своего рабочего пользовательского кода для людей, которые ищут ответ в этом выпуске. Это не окончательный код, но он работает и дает представление о решении.
Предложение Джея Сойера о сторонней библиотеке выглядит довольно хорошо и проверено в производстве.
Когда я нажимаю на ребенка, вызывается setOnChildClickListener onClick. Там выполняется первая проверка позиции (она добавлена или нет в пользовательской структуре данных), и я соответственно выбираю/отменяю выбор дочернего элемента.
expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View childView, int groupPosition, int childPosition, long id) {
// check / not check a child
ArrayList<Object> position = new ArrayList<Object>();
position.add(groupPosition);
position.add(childPosition);
position.add(childView);
if (expandableListAdapter.isPositionAleadyAdded(position) == null) {
expandableListAdapter.selectChild(position);
} else {
expandableListAdapter.deSelectChild(position);
}
// Start the CAB using the ActionMode.Callback
// defined above
actionMode = getActivity().startActionMode(actionModeCallback);
// set title of contextual action mode
setContextualMenuTitle((ActionMode) actionMode);
return true;
}
});
Впоследствии, когда я нажимаю на группу, чтобы расширить/свернуть группу, вызываются методы адаптеров getGroupView и getChildView. И в тех методах я снова делаю подобную проверку. Я сохраняю позицию группы, дочернюю позицию и вид в списке (ArrayList).
private List<ArrayList<Object>> selectedChildren = new ArrayList<ArrayList<Object>>();
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
View childView = convertView;
ChildRowHolder childRowHolder = null;
String childRowTitle = (String) getChild(groupPosition, childPosition);
if (childView == null) {
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
childView = inflater.inflate(R.layout.child_expandable_list_view_item, null);
childRowHolder = new ChildRowHolder(childView);
childView.setTag(childRowHolder);
} else {
childRowHolder = (ChildRowHolder) childView.getTag();
}
childRowHolder.childRowTitle.setText(childRowTitle);
// select / deselect child
ArrayList<Object> position = new ArrayList<Object>();
position.add(groupPosition);
position.add(childPosition);
position.add(childView);
if (isPositionAleadyAdded(position) != null) {
selectChild(position);
} else {
deSelectChild(position);
}
return childView;
}
/**
* This method return the key in the selected child map if it exists already. Null otherwise.
*
* @param position the group and child position
* @return the key of the element of the map if it exists already, null otherwise
*/
public ArrayList<Object> isPositionAleadyAdded(ArrayList<Object> position) {
for (ArrayList<Object> entry : selectedChildren) {
if (entry.get(0) == position.get(0)
&& entry.get(1) == position.get(1)) {
return position;
}
}
return null;
}
/**
* This method does the selection of a child.
*
* @param childView view of child
* @param position position of child
*/
public void selectChild(ArrayList<Object> position) {
if (isPositionAleadyAdded(position) == null) {
selectedChildren.add(position);
}
LinearLayout childView = (LinearLayout) position.get(2);
ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
tick.setVisibility(View.VISIBLE);
childView.setBackgroundColor(context.getResources().getColor(R.color.backgroung_expandable_list_view_child));
}
/**
* This method un selects a child.
*
* @param childView view of child
* @param position position of child
*/
public void deSelectChild(ArrayList<Object> position) {
ArrayList<Object> pos = null;
int elemNo = -1;
if ((pos = isPositionAleadyAdded(position)) != null) {
elemNo = getNoElemInSelectedChildred(pos);
selectedChildren.remove(elemNo);
}
LinearLayout childView = (LinearLayout) position.get(2);
ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
tick.setVisibility(View.GONE);
childView.setBackgroundColor(context.getResources().getColor(R.color.background_color));
}
private int getNoElemInSelectedChildred(ArrayList<Object> position) {
int index = 0;
for (ArrayList<Object> entry : selectedChildren) {
if (entry.get(0) == position.get(0) && entry.get(1) == position.get(1)) {
return index;
} else {
index++;
}
}
return -1;
}
/*
* This method clears the map of all children.
*/
public void unSelectAllChildren() {
for (ArrayList<Object> entry : selectedChildren) {
LinearLayout childView = (LinearLayout) entry.get(2);
ImageView tick = (ImageView) childView.findViewById(R.id.child_item_expandable_list_view_selected);
tick.setVisibility(View.GONE);
childView.setBackgroundColor(context.getResources().getColor(R.color.background_color));
}
selectedChildren.clear();
}
public List<ArrayList<Object>> getSelectedChildren() {
return selectedChildren;
}
И мне удалось поймать кнопку галочки контекстного меню на прослушивателе кликов, чтобы вызвать метод unSelectAllChildren
private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
...
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(final ActionMode mode) {
int doneButtonId = Resources.getSystem().getIdentifier("action_mode_close_button", "id", "android");
LinearLayout layout = (LinearLayout) getActivity().findViewById(doneButtonId);
ImageView doneview = (ImageView) layout.getChildAt(0);
doneview.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
expandableListAdapter.unSelectAllChildren();
setContextualMenuTitle(mode);
}
});
}
};