我正在尝试使用自定义适配器过滤自定义列表视图。当搜索参数更改或变为空时,我在复制原始数据并将其放回列表时遇到问题。过滤适用于第一个输入字符,但如果更改,则不会再次搜索整个数据集。我知道这是因为我需要一份原始数据的重复列表,但我无法真正开始工作,因为我不知道如何正确实现它,因为我使用自定义类作为我的数据类型。我只使用它的名称和类别属性,名称是实际项目,它也按类别排序。
我的适配器不在此示例中:https://gist.github.com/fjfish/3024308
这是List Adapter的代码:
class DataListAdapter extends BaseAdapter implements Filterable {
private Context mContext;
private List<Object> originalData = null;
private List<Object> filteredData = null;
private static final int CARRIER = 0;
private static final int HEADER = 1;
private LayoutInflater inflater;
private ItemFilter mFilter = new ItemFilter();
DataListAdapter(Context context, List<Object> input) {
this.mContext = context;
this.originalData = input;
this.filteredData = input;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getItemViewType(int position) {
if (originalData.get(position) instanceof Carrier) {
return CARRIER;
} else {
return HEADER;
}
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getCount() {
return originalData.size();
}
@Override
public Object getItem(int position) {
return originalData.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@SuppressLint("InflateParams")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
switch (getItemViewType(position)) {
case CARRIER:
convertView = inflater.inflate(R.layout.listview_item_data_layout, null);
break;
case HEADER:
convertView = inflater.inflate(R.layout.listview_header_data_layout, null);
break;
}
}
switch (getItemViewType(position)) {
case CARRIER:
TextView name = (TextView) convertView.findViewById(R.id.fragment_data_list_view_carrier_name);
name.setText(((Carrier) originalData.get(position)).get_name());
break;
case HEADER:
TextView category = (TextView) convertView.findViewById(R.id.fragment_data_list_view_category);
category.setText((String) originalData.get(position));
break;
}
return convertView;
}
@Override
public Filter getFilter() {
return mFilter;
}
private class ItemFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, null, 1);
String filterString = constraint.toString().toLowerCase();
FilterResults results = new FilterResults();
final List<Object> list = originalData;
int count = list.size();
final List<Object> nlist = new ArrayList<>(count);
String filterableString = "";
for (int i = 0; i < count; i++) {
switch (getItemViewType(i)) {
case CARRIER:
filterableString = ((Carrier)list.get(i)).get_name();
break;
case HEADER:
filterableString = "";
break;
}
if(filterableString.toLowerCase().contains(filterString)) {
nlist.add(dbHelper.getCarriersWithName(filterableString).get(0));
}
}
results.values = nlist;
results.count = nlist.size();
return results;
}
@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if(results.count == 0) {
notifyDataSetInvalidated();
} else {
originalData = (List<Object>)results.values;
notifyDataSetChanged();
}
}
}
}
我的主要活动显然看起来像这样,应该没问题。问题出现在过滤后的数据列表中,我无法开始工作。
List<Object> combinedCategoryCarrierList = dbHelper.getCombinedCategoryCarrierList();
adapter = new DataListAdapter(mContext, combinedCategoryCarrierList);
listView.setAdapter(adapter);
listView.setTextFilterEnabled(true);
searchEditText = (EditText) view.findViewById(R.id.fragment_data_search);
searchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
adapter.getFilter().filter(searchEditText.getText().toString());
}
});
如果有人能向我展示如何使用自定义数据类型和节标题组合的示例,我将不胜感激。甚至改变我的代码:)我无法找到所有这些都适用的例子。
修改: The screen looks like this, so I want to keep the category headers when filtering.
答案 0 :(得分:2)
我没有找到原始问题的解决方案,但我想出了一个更好的解决方案。我不知道Android中有ExpandableListView
可用。这基本上是一个ListView,但这些项目分为组和它们的Childs,它们是可扩展和可折叠的,所以正是我想要的。
以下是我使用工作过滤器和组实现它的方法:
所以,首先,这是我的主要布局文件。请注意我正在使用Fragments,这就是为什么代码在获取上下文方面有点不同。该组件的功能保持不变。
<?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" >
<EditText
android:id="@+id/fragment_data_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:hint="@string/data_search_hint"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp" />
<ExpandableListView
android:id="@+id/fragment_data_expandable_list_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:groupIndicator="@null" />
</LinearLayout>
您的标题/组项目和子项目还需要两个布局文件。我的标题项有一个显示类别名称的TextView和一个显示+或 - 的ImageView,用于显示类别是折叠还是展开。
这是我的标题布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:descendantFocusability="blocksDescendants" >
<TextView
android:id="@+id/fragment_data_list_view_category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:gravity="start"
android:textStyle="bold"
android:textSize="18sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:textColor="@android:color/primary_text_light"
android:text="@string/placeholder_header_listview"
android:maxLines="1"
android:ellipsize="end" />
<ImageView
android:id="@+id/fragment_data_list_view_category_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:contentDescription="@string/content_description_list_view_header"
android:src="@drawable/ic_remove_black_24dp"
android:tag="maximized"/>
</RelativeLayout>
当我尝试设置onItemClickListener时,属性android:descendantFocusability="blocksDescendants"
修复了一个错误。如果您遇到此问题,请尝试将RelativeLayout用于您的子布局(如果您尚未使用)。它为我修复了它,onClickItemListener没有使用LinearLayout执行。
这是我的子项目的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:descendantFocusability="blocksDescendants" >
<TextView
android:id="@+id/fragment_data_list_view_carrier_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/placeholder_item_listview"
android:textSize="18sp"
android:textStyle="normal"
android:textColor="@android:color/primary_text_light"
android:maxLines="1"
android:ellipsize="end" />
</RelativeLayout>
以下代码来自我的fragment类,它处理ExpandableListView的所有逻辑:
public class Fragment_Data extends Fragment {
private Context mContext;
private ExpandableListView expandableListView;
private List<String> categories_list;
private HashMap<String, List<Carrier>> carriers_list;
private DataExpandableListAdapter adapter;
private DatabaseHelper dbHelper;
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getActivity().setTitle(R.string.nav_item_data);
}
第一部分显示了所需变量的声明和必要的方法onViewCreated
。 Carrier
类是一个自定义对象,具有名称,类别等属性。 DatabaseHelper
也是一个自定义类,它处理我的数据库并为我获取所有数据,这些数据被转换为Carrier对象。您当然可以使用任何您喜欢的数据类型。
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_data_layout, container, false);
mContext = getContext();
expandableListView = (ExpandableListView) view.findViewById(R.id.fragment_data_expandable_list_view);
dbHelper = new DatabaseHelper(mContext, null, null, 1);
adapter = new DataExpandableListAdapter(mContext, categories_list, carriers_list);
displayList();
expandAllGroups();
EditText searchEditText = (EditText) view.findViewById(R.id.fragment_data_search);
searchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.filterData(s.toString());
expandAllGroups();
}
@Override
public void afterTextChanged(Editable s) {
}
});
expandableListView.setOnItemLongClickListener(deleteSelectedItem);
expandableListView.setOnChildClickListener(editSelectedItem);
return view;
}
onCreate
方法处理所有重要的事情,例如设置适配器,为项目充气布局和设置onClick事件以及搜索字段的onTextChange事件。
private void expandAllGroups() {
for(int i = 0; i < adapter.getGroupCount(); i++) {
expandableListView.expandGroup(i);
}
}
private void displayList() {
prepareListData();
adapter = new DataExpandableListAdapter(mContext, categories_list, carriers_list);
expandableListView.setAdapter(adapter);
expandAllGroups();
}
private void prepareListData() {
categories_list = new ArrayList<>();
carriers_list = new HashMap<>();
categories_list = dbHelper.getCategoryList();
for(int i = 0; i < categories_list.size(); i++) {
List<Carrier> carrierList = dbHelper.getCarriersWithCategory(categories_list.get(i));
carriers_list.put(categories_list.get(i), carrierList);
}
}
使用expandAllGroups()
,您只需展开所有群组,因为默认情况下它们会折叠。 displayList()
只为ExpandableListView设置适配器并调用prepareListData()
,它填充类别(组)列表和运营商(子)列表。请注意,子List是一个散列映射,其中键是类别,值本身是载波列表,因此适配器知道哪些子项属于哪个子项。
以下是Adapter
的代码:
class DataExpandableListAdapter extends BaseExpandableListAdapter {
private Context mContext;
private List<String> list_categories = new ArrayList<>();
private List<String> list_categories_original = new ArrayList<>();
private HashMap<String, List<Carrier>> list_carriers = new HashMap<>();
private HashMap<String, List<Carrier>> list_carriers_original = new HashMap<>();
DataExpandableListAdapter(Context context, List<String> categories, HashMap<String, List<Carrier>> carriers) {
this.mContext = context;
this.list_categories = categories;
this.list_categories_original = categories;
this.list_carriers = carriers;
this.list_carriers_original = carriers;
}
如果要使用过滤,则需要拥有两个原始列表的副本。这用于在搜索查询为空或再次或简单地不同时还原所有数据。过滤器将删除与原始列表不匹配的所有项目。
@Override
public int getGroupCount() {
return this.list_categories.size();
}
@Override
public int getChildrenCount(int groupPosition) {
return this.list_carriers.get(this.list_categories.get(groupPosition)).size();
}
@Override
public Object getGroup(int groupPosition) {
return this.list_categories.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return this.list_carriers.get(this.list_categories.get(groupPosition)).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 true;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
展开BaseExpandableListAdapter
时,需要覆盖这些方法。您可以使用类似的内容替换所有return null;
语句,具体取决于您的数据。
@SuppressLint("InflateParams")
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
String headerTitle = (String) getGroup(groupPosition);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.listview_header_data_layout, null);
}
TextView lblListHeader = (TextView) convertView.findViewById(R.id.fragment_data_list_view_category);
lblListHeader.setText(headerTitle);
ImageView expandIcon = (ImageView) convertView.findViewById(R.id.fragment_data_list_view_category_icon);
if(isExpanded) {
expandIcon.setImageResource(R.drawable.ic_remove_black_24dp);
} else {
expandIcon.setImageResource(R.drawable.ic_add_black_24dp);
}
return convertView;
}
此覆盖方法只是为每个标题/组/类别项目的布局增加,并根据组的状态(如果它已折叠或展开)设置文本和图像。
@SuppressLint("InflateParams")
@Override
public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
final String carrierName = ((Carrier)getChild(groupPosition, childPosition)).get_name();
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) this.mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.listview_item_data_layout, null);
}
TextView txtListChild = (TextView) convertView.findViewById(R.id.fragment_data_list_view_carrier_name);
txtListChild.setText(carrierName);
return convertView;
}
与儿童用品相同。
现在终于过滤了: 我使用此自定义方法筛选出我需要与搜索查询匹配的所有项目。请记住,每次EditText的文本更改时都会调用此方法。
void filterData(String query) {
query = query.toLowerCase();
list_categories = new ArrayList<>();
list_carriers = new HashMap<>();
DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, null, 1);
if(query.trim().isEmpty()) {
list_categories = new ArrayList<>(list_categories_original);
list_carriers = new HashMap<>(list_carriers_original);
notifyDataSetInvalidated();
}
else {
//Filter all data with the given search query. Yes, it's complicated
List<String> new_categories_list = new ArrayList<>();
HashMap<String, List<Carrier>> new_carriers_list = new HashMap<>();
List<String> all_categories_list = dbHelper.getCategoryList();
for(int i = 0; i < all_categories_list.size(); i++) {
List<Carrier> carriersWithCategoryList = dbHelper.getCarriersWithCategory(all_categories_list.get(i));
List<Carrier> matchingCarriersInCategory = new ArrayList<>();
for(Carrier carrierInCategory : carriersWithCategoryList) {
if(carrierInCategory.get_name().toLowerCase().contains(query)) {
matchingCarriersInCategory.add(carrierInCategory);
if(!new_categories_list.contains(all_categories_list.get(i))) {
new_categories_list.add(all_categories_list.get(i));
}
}
}
new_carriers_list.put(all_categories_list.get(i), matchingCarriersInCategory);
}
if(new_categories_list.size() > 0 && new_carriers_list.size() > 0) {
list_categories.clear();
list_categories.addAll(new_categories_list);
list_carriers.clear();
list_carriers.putAll(new_carriers_list);
}
notifyDataSetChanged();
}
}`
这可能非常令人困惑,但由于我的数据结构,在我的情况下需要这么复杂。在你的情况下可能会更容易。
这基本上是做的,它首先检查搜索查询是否为空。如果它为空,则将两个列表重置为我在构造函数中指定的“备份”列表。然后我打电话给notifyDataSetInvalidated();
告诉适配器它的内容将被重新填充。它可能与notifyDataSetChanged();
一起使用,我没有测试过,但它应该是因为我们将原始列表设置回原来的状态。
现在,如果搜索查询不为空,我会浏览每个类别,看看该特定类别是否包含与搜索查询匹配的任何项目。如果是这种情况,那么该项目将被添加到新的子列表中,并且它的类别/父级也将被添加到新的父级列表中(如果它尚未存在)。
最后但并非最不重要的是,该方法检查两个列表是否都不为空。如果它们不为空,则清空原始列表并放入新的过滤数据,并通过调用notifyDataSetChanged();
通知适配器
我希望这对任何人都有帮助。