使用 SearchView 过滤包含 LiveData 内容的 RecyclerView 列表

Filtering RecyclerView's list with LiveData content using SearchView

我创建了包含简单单词列表(GroupVc 对象的字符串名称)的 RecyclerView。由于列表可能很长,我想使用工具栏中的 SearchView 使其可过滤。 我的应用程序的架构基于 Android 架构组件,其中所有 GroupVc 对象都存在于 Room 数据库中,并且通过 ViewModel 和 Repository 对象提供从 UI 访问数据库。我将我的 RecyclerView 归档为包含在 LiveData 中的所有 GroupsVc 的列表,以保持更新。 我的问题是我不知道如何使 RecyclerView 可过滤。我试图通过在适配器中实现可过滤接口来做到这一点:

public class GroupsVcAdapter extends
    RecyclerView.Adapter<GroupsViewHolder> implements Filterable{

private LayoutInflater mInflater;
private List<GroupVc> mGroupsVc;
private List<GroupVc> filteredGroupsVc;
private OnItemClicked onClick;
public GroupsVcAdapter(Context context, OnItemClicked onClick) {
    mInflater = LayoutInflater.from(context);
    this.onClick = onClick;
}

public List<GroupVc> getmGroupsVc() {
    return mGroupsVc;
}

@Override
public GroupsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View itemView = mInflater.inflate(R.layout.layout_list_of_groups, parent, false);
    return new GroupsViewHolder(itemView, onClick);
}

@Override
public void onBindViewHolder(final GroupsViewHolder holder, int position) {
    if (mGroupsVc != null) {
        GroupVc current = mGroupsVc.get(position);
        holder.getNameView().setText(current.getNameGroup());
    } else {
        holder.getNameView().setText(R.string.nogroups);
    }
}

public void setGroupsVc(List<GroupVc> mGroupsVc) {
    this.mGroupsVc = mGroupsVc;
    notifyDataSetChanged();
}

@Override
public int getItemCount() {
    if (mGroupsVc != null)
        return mGroupsVc.size();
    else return 0;
}

@Override
public Filter getFilter() {
    return new Filter() {
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
                mGroupsVc = (List<GroupVc>) results.values;
                notifyDataSetChanged();
        }

        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            filteredGroupsVc = null;
            if (constraint.length() == 0) {
                filteredGroupsVc = mGroupsVc;
            } else {
                filteredGroupsVc = getFilteredResults(constraint.toString().toLowerCase());
            }

            FilterResults results = new FilterResults();
            results.values = filteredGroupsVc;
            return results;
        }
    };
}

protected List<GroupVc> getFilteredResults(String constraint) {
    List<GroupVc> results = new ArrayList<>();

    for (GroupVc item : mGroupsVc) {
        if (item.getNameGroup().toLowerCase().contains(constraint)) {
            results.add(item);
        }
    }
    return results;
}
}

然后在Activity我写了一个方法:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu_activity_words, menu);
    // Associate searchable configuration with the SearchView
    SearchManager searchManager =
            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView =
            (SearchView) menu.findItem(R.id.action_search).getActionView();
    searchView.setSearchableInfo(
            searchManager.getSearchableInfo(getComponentName()));
    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String text) {
            return false;
        }

        @Override
        public boolean onQueryTextChange(String text) {
            adapter.getFilter().filter(text);
            return true;
        }
    });
    return true;
}

The result was that my RecyclerView got filtered correctly but it wasn't restored after I closed SearchView. The only way to do it is to reload activity. **So I'm interested in How can I restore RecyclerView's list and can I use for filtering any LiveData's capacities?** Below I post the complete code of Activity, ViewModel and Repository:

Activity

public class GroupsActivity extends AppCompatActivity {

private static final String DELETE_DIALOG = "Delete dialog";
private static final String EDIT_DIALOG = "Edit dialog";
private RecyclerView mRecyclerView;
private GroupsVcAdapter adapter;
private GroupsViewModel mGroupsViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_groups);
    //Creating of toolbar with title Groups
    Toolbar myToolbar = findViewById(R.id.toolbar_groups);
    setSupportActionBar(myToolbar);
    //Enable Up Button
    ActionBar ab = getSupportActionBar();
    ab.setDisplayHomeAsUpEnabled(true);
    //RecyclerView containing the list of groups with sound icons
    mRecyclerView = initRecyclerView();
    //Using ViewModel to observe GroupVc data
    mGroupsViewModel = ViewModelProviders.of(this).get(GroupsViewModel.class);
    mGroupsViewModel.getAllGroups().observe(this, new Observer<List<GroupVc>>() {
        @Override
        public void onChanged(@Nullable List<GroupVc> groupVcs) {
            adapter.setGroupsVc(groupVcs);
        }
    });
}

private RecyclerView initRecyclerView() {
    RecyclerView recyclerView = findViewById(R.id.groups_recycler_view);
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(layoutManager);
    OnItemClicked listener = (v, position) -> {
        switch (v.getId()) {
            case R.id.delete_group:
                Log.i(DELETE_DIALOG, "Delete button");
                break;
            case R.id.edit_group:
                Log.i(EDIT_DIALOG, "Edit button");
                break;
            case R.id.surface:
                Log.i(SURFACE, "Surface button");
                break;
        }
    };
    adapter = new GroupsVcAdapter(this, listener);
    recyclerView.setAdapter(adapter);
    return recyclerView;
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu_activity_words, menu);
    // Associate searchable configuration with the SearchView
    SearchManager searchManager =
            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView =
            (SearchView) menu.findItem(R.id.action_search).getActionView();
    searchView.setSearchableInfo(
            searchManager.getSearchableInfo(getComponentName()));
    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String text) {
            return false;
        }

        @Override
        public boolean onQueryTextChange(String text) {
            adapter.getFilter().filter(text);
            return true;
        }
    });
    return true;
}
}

ViewModel

public class GroupsViewModel extends AndroidViewModel {

private LiveData<List<GroupVc>> mAllGroups;
private GroupRepository mRepository;

public GroupsViewModel(@NonNull Application application) {
    super(application);
    mRepository = new GroupRepository(application);
    mAllGroups = mRepository.getAllGroupVc();
}

public LiveData<List<GroupVc>> getAllGroups() {
    return mAllGroups;
}
}

存储库

public class GroupRepository {
private LiveData<List<GroupVc>> mAllGroups;
private GroupVcDao mGroupVcDao;

public GroupRepository(Application application) {
    AppDatabase db = AppDatabase.getInstance(application);
    mGroupVcDao = db.groupVcDao();
    mAllGroups = mGroupVcDao.getAllGroupVc();
}

public LiveData<List<GroupVc>> getAllGroupVc() {
    return mAllGroups;
}
}

删除筛选后数据没有正确恢复的问题是因为发布筛选结果时覆盖了数据列表。

解决方法如下:

public GroupsVcAdapter(Context context, OnItemClicked onClick) {
  mInflater = LayoutInflater.from(context);
  this.onClick = onClick;

  // init the lists
  mGroupsVc = new ArrayList<>();
  filteredGroupsVc = new ArrayList<>();
}

public List<GroupVc> getFilteredGroupsVc() {
  return filteredGroupsVc;
}

@Override
public int getItemCount() {
  return filteredGroupsVc != null ? filteredGroupsVc.size() : 0;
}

@Override
public Filter getFilter() {
  return new Filter() {
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
      filteredGroupsVc = (List<GroupVc>) results.values;
      notifyDataSetChanged();
    }

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
      filteredGroupsVc.clear();
      if (constraint.length() == 0) {
        filteredGroupsVc.addAll(mGroupsVc);
      else {
        filteredGroupsVc = getFilteredResults(constraint.toString().toLowerCase());
      }

      FilterResults results = new FilterResults();
      results.values = filteredGroupsVc;
      results.count = filteredGroupsVc.size();
      return results;
    }
  };
}

如果为适配器设置新的数据列表,则必须调用 adapter.getFilter().filter(text); 或将最后一个过滤器字符串保存在适配器中并在 setGroupsVc()[=27= 中调用过滤器]

注意: 如果使用 notifyDataSetChanged();,则没有任何动画。如果你想有动画,使用其他通知方法。

希望对您有所帮助。

编辑

同时更新 onBindViewHolder() 以从过滤列表中获取数据。

@Override
public void onBindViewHolder(final GroupsViewHolder holder, int position) {
  if (filteredGroupsVc != null) {
    GroupVc current = filteredGroupsVc.get(position);
    holder.getNameView().setText(current.getNameGroup());
  } else {
    holder.getNameView().setText(R.string.nogroups);
  }
}

现在您总是从筛选列表中获取数据。初始状态是您的列表为空。这就是您在 RecyclerView 中看到的内容。如果您调用 setGroupsVc(),您会为适配器设置一个新列表。请记住,您总是从过滤列表中获取数据。因此,您必须使用设置给适配器(新数据)的新列表更新过滤列表(旧数据)。

您有 2 个选择:

  1. 调用后在外面调用过滤器 setGroupsVc():

喜欢

...
setGroupsVc(yourNewList);
adapter.getFilter().filter(searchview.getQuery());
...
  1. 保存适配器中的最后一个过滤器并在 setGroupsVc():
  2. 中调用过滤器

在您的适配器中添加新字段 lastFilter

private String lastFilter = "";

里面performFiltering()保存过滤字符串

lastFilter = contraint.toString();

至少在 setGroupsVc()

中调用过滤器
public void setGroupsVc(List<GroupVc> mGroupsVc) {
  this.mGroupsVc = mGroupsVc;
  getFilter().filter(lastFilter);
  // notifyDataSetChanged(); -> not necessary anymore because we already call this inside our filter
}