【问题标题】:Filtering RecyclerView's list with LiveData content using SearchView使用 SearchView 过滤带有 LiveData 内容的 RecyclerView 列表
【发布时间】:2018-04-05 08:22:07
【问题描述】:

我创建了 RecyclerView,其中包含简单的单词列表(GroupVc 对象的字符串名称)。由于列表可能很长,我想使用工具栏中的 SearchView 使其可过滤。 我的应用程序的架构基于 Android 架构组件,其中所有 GroupVc 对象都存在于 Room 数据库中,并且通过 ViewModel 和 Repository 对象提供从 UI 对 DataBase 的访问。我正在用 LiveData 中包含的所有 GroupsVc 的列表来归档我的 RecyclerView 以保持更新。 我的问题是我不知道如何使 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:

活动

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;
}
}

视图模型

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;
}
}

【问题讨论】:

  • 好吧,这有点像查询方法,它根据约束参数返回 GroupVc 列表?
  • @Query("SELECT * FROM groupvc WHERE name_group = :nameGroup") List getFilteredGroupVc(String nameGroup);
  • 问题的答案在这里:stackoverflow.com/a/56394003/6884103 using switchMap and MutableLiveData

标签: android android-recyclerview android-architecture-components android-livedata


【解决方案1】:

删除过滤器时数据没有正确恢复的问题是,因为你在发布过滤器结果时覆盖了你的数据列表。

解决办法如下:

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() 中调用过滤器

注意: 如果您使用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(),您将为适配器设置一个新列表。请记住,您总是从过滤列表中获取数据。因此,您必须使用您为适配器设置的新列表(新数据)更新过滤列表(旧数据)。

你有两个选择:

  1. 拨打setGroupsVc()后在外线呼叫过滤器:

喜欢

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

在您的适配器中添加新字段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
}

【讨论】:

  • 我已经根据您的代码gist.github.com/LAHomieJob/a2c43d49f82e6817e107f27ebc9e9fdc 更改了我的适配器,但它最终会得到完整的空回收器视图,直到我没有在 searchView 中输入文本。并且结果并不充分。你能给我解释一下“如果你为适配器设置一个新的数据列表,你必须调用 adapter.getFilter().filter(text); 或者你将最后一个过滤器字符串保存在你的适配器中并在 setGroupsVc 中调用你的过滤器( )"
  • 我已经实施了这两个建议。现在它工作得更好了 - 我检索到正确的结果,但在打开活动时我仍然得到空白屏幕。这是我更新的要点gist.github.com/LAHomieJob/a2c43d49f82e6817e107f27ebc9e9fdc 我还添加了我的 GroupsActivity 的完整版本gist.github.com/LAHomieJob/aaa005eae27c6b2c25b1df1c27c3b89f 明天我将继续在这个线程上工作
  • 我现在不明白你的确切问题...正如你所见,适配器被初始化为一个空列表。因此,在您调用 adapter.setGroupsVc(yourNewList); 之前,您的 recyclerview 中没有任何项目。那么你的屏幕不应该是空的。
  • 但是在我的 GroupsActivity 类中,我添加了这个方法的调用 adapter.setGroupsVc(yourNewList);。查看从 70 到 75 的行。我从 LiveData 对象获取它
  • 嗯,我不明白你目前的问题。你能解释一下吗?那我可以帮你...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-29
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 2020-03-17
  • 2015-03-03
相关资源
最近更新 更多