2017-05-27 49 views
1

我想用自定义适配器筛选自定义列表视图。当搜索参数更改或变为空时,我遇到了复制原始数据并将其放回列表中的问题。过滤对第一个输入字符起作用,但如果更改了它,它将不会再次搜索整个数据集。我知道这是因为我需要一个重复的原始数据列表,但我无法真正实现它的功能,因为我不知道如何正确实现它,因为我使用自定义类作为我的数据类型。我只使用它的名称和类别属性,名称是实际的项目,它也按类别排序。使用包含节标题的自定义列表适配器自定义筛选ListView

我根据我的适配器关闭这个例子:https://gist.github.com/fjfish/3024308

这里是我的名单适配器代码:

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

move adapter.getFilter()。filter(s.toString());里面onTextChanged() –

+0

我改变了一点,以测试它是否有所作为,但它不 –

+0

使用s.toString()而不是searchEditText.getText() –

回答

1

我没有找到一个解决方案,我原来的问题,但我想出了一个更好的方法来整体情况。我不知道Android中有可用的ExpandableListView。这基本上是一个ListView,但是这些项目被划分为可扩展和可折叠的Groups和它们的Childs,正是我想要的。

这里是我一起工作的过滤器和组实现了它:

因此,要开始,这是我的主要布局文件。请注意,我正在使用片段,这就是为什么代码在获取上下文方面有点不同。虽然组件的功能保持不变。

<?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> 

android:descendantFocusability="blocksDescendants"修正了一个错误,当我试图设置一个onItemClickListener属性。如果你有这个问题,请尝试使用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> 

下面的代码是从我的片段类,它处理所有的逻辑为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); 
    } 

这第一部分显示所需变量的声明和必要的方法onViewCreatedCarrier类是一个具有名称,类别等属性的自定义对象。 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; 
} 

与所有喜欢设置转接器,膨胀的布局和设置的项目和搜索领域的onTextChange事件的onclick事件的重要的东西onCreate方法处理。

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知道哪些子项属于哪个父项。

下面是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();

来通知适配器。我希望这能帮助任何人。

+0

太糟糕了我只有一票 - 很好的答案! – 0X0nosugar

+0

哈哈谢谢:) –