2017-05-27 72 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

哈哈謝謝:) –

相關問題