如何为从 Firestore 填充的 RecyclerView 实现过滤器?
Posted
技术标签:
【中文标题】如何为从 Firestore 填充的 RecyclerView 实现过滤器?【英文标题】:How to implement a filter for RecyclerView populated from Firestore? 【发布时间】:2019-06-19 13:32:23 【问题描述】:通过用更新的查询替换适配器来实时过滤从 Firestore 填充的 RecyclingView 显示性能确实很差。
要在我的应用程序中实时过滤RecyclerView
(onQueryTextChange
)我正在使用扩展FirestoreRecyclerAdapter
的自定义适配器,每次我需要过滤数据时,我都会创建一个新的Query
对象,创建新的@987654325 @对象然后创建我的自定义适配器对象,我将FirestoreRecyclerOptions
对象传递给FirestoreRecyclerAdapter
构造函数,最后我在RecyclerView
上调用swapAdapter
。这种方法的问题在于它会导致性能不佳。每次更新查询时,我都可以看到视图闪烁。
DocumentSearchActivity.java
package com.example.testapp;
import android.app.Activity;
import android.app.SearchManager;
import android.arch.lifecycle.LifecycleOwner;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import android.widget.TextView;
import com.example.testapp.adapter.ItemAdapter;
import com.example.testapp.model.Item;
import com.firebase.ui.firestore.FirestoreRecyclerOptions;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
public class DocumentSearchActivity extends AppCompatActivity
private static final String TAG = DocumentSearchActivity.class.getSimpleName();
private RecyclerView mRecyclerView;
private ItemAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private TextView mNoResultsTextView;
private SearchView mSearchView;
private FirestoreQuery mQuery;
private Toolbar mToolbar;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_document_search);
mToolbar = findViewById(R.id.app_bar);
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
mRecyclerView = findViewById(R.id.recycler_view);
mSearchView = findViewById(R.id.search_bar);
mNoResultsTextView = findViewById(R.id.result);
mNoResultsTextView.setVisibility(View.GONE);
if(getIntent().hasExtra(MainActivity.QUERY_EXTRA))
mSearchView.setQuery(getIntent().getStringExtra(MainActivity.QUERY_EXTRA), true);
mSearchView.requestFocus();
mQuery = new FirestoreQuery(this, "");
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Intent intent = getIntent();
if(Intent.ACTION_SEARCH.equals(intent.getAction()))
mQuery.setQueryString(intent.getStringExtra(SearchManager.QUERY));
mAdapter = new ItemAdapter(mQuery.getOptions());
mRecyclerView.setAdapter(mAdapter);
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
@Override
public boolean onQueryTextSubmit(String query)
return true;
@Override
public boolean onQueryTextChange(String newText)
mAdapter = new ItemAdapter(mQuery.setQueryString(newText).getOptions());
mRecyclerView.swapAdapter(mAdapter, true);
mAdapter.notifyDataSetChanged();
mAdapter.startListening();
return true;
);
private void runLayoutAnimation(final RecyclerView recyclerView)
final Context context = recyclerView.getContext();
final LayoutAnimationController controller =
AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down);
recyclerView.setLayoutAnimation(controller);
recyclerView.getAdapter().notifyDataSetChanged();
recyclerView.scheduleLayoutAnimation();
@Override
public boolean onCreateOptionsMenu(Menu menu)
getMenuInflater().inflate(R.menu.actions_document_search_activity, menu);
return super.onCreateOptionsMenu(menu);
private class FirestoreQuery
private LifecycleOwner mLifecycleOwner;
private String mQueryString;
private Query mQuery;
private FirestoreRecyclerOptions<Item> mOptions;
public FirestoreQuery(LifecycleOwner lifecycleOwner, String query)
mLifecycleOwner = lifecycleOwner;
setQueryString(query);
public FirestoreQuery setQueryString(String queryString)
if(queryString.length() != 0)
this.mQueryString = queryString.substring(0,1).toUpperCase() + queryString.substring(1).toLowerCase();
else
this.mQueryString = queryString;
updateQuery();
updateOptions();
return this;
private void updateOptions()
mOptions = new FirestoreRecyclerOptions.Builder<Item>()
.setQuery(mQuery, Item.class)
.setLifecycleOwner(mLifecycleOwner)
.build();
private void updateQuery()
if(mQueryString.isEmpty())
this.mQuery = FirebaseFirestore.getInstance()
.collection("tubes_test")
.orderBy("number");
else
StringBuilder query_lower = new StringBuilder(mQueryString.length());
query_lower.append(mQueryString);
query_lower.setCharAt(mQueryString.length() - 1, (char) (query_lower.charAt(mQueryString.length() - 1) + 1));
if (mQueryString.matches("^[0-9]1,3[A-Z]*$"))
this.mQuery = FirebaseFirestore.getInstance()
.collection("tubes_test")
.whereGreaterThanOrEqualTo("number", mQueryString)
.whereLessThan("number", query_lower.toString());
else
this.mQuery = FirebaseFirestore.getInstance()
.collection("tubes_test")
.whereGreaterThanOrEqualTo("name", mQueryString)
.whereLessThan("name", query_lower.toString());
public FirestoreRecyclerOptions<Item> getOptions()
return mOptions;
ItemAdapter.java
package com.example.testapp.adapter;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import com.example.testapp.DocumentViewerActivity;
import com.example.testapp.R;
import com.example.testapp.model.Item;
import com.firebase.ui.firestore.FirestoreRecyclerAdapter;
import com.firebase.ui.firestore.FirestoreRecyclerOptions;
public class ItemAdapter extends FirestoreRecyclerAdapter<Item, ItemAdapter.ItemHolder>
public ItemAdapter(@NonNull FirestoreRecyclerOptions<Item> options)
super(options);
@Override
protected void onBindViewHolder(@NonNull ItemHolder holder, int position, @NonNull Item item)
final String tubeNumber = item.getNumber();
holder.mNumberTextView.setText(item.getNumber());
holder.mNameTextView.setText(item.getName());
holder.mFormulaTextView.setText(item.getFormula());
holder.mRangeTextView.setText(item.getRange());
holder.mInfoButton.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
Intent intent = new Intent(v.getContext(), DocumentViewerActivity.class);
intent.putExtra("number", tubeNumber);
intent.putExtra("doctype", "info");
v.getContext().startActivity(intent);
);
holder.mManualButton.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
Intent intent = new Intent(v.getContext(), DocumentViewerActivity.class);
intent.putExtra("number", tubeNumber);
intent.putExtra("doctype", "manual");
v.getContext().startActivity(intent);
);
@NonNull
@Override
public ItemHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i)
Context context = viewGroup.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View tubeView = inflater.inflate(R.layout.item_tube, viewGroup, false);
return new ItemHolder(tubeView);
public static class ItemHolder extends RecyclerView.ViewHolder
public TextView mNumberTextView;
public TextView mNameTextView;
public TextView mFormulaTextView;
public TextView mRangeTextView;
public Button mInfoButton;
public Button mManualButton;
public ItemHolder(View itemView)
super(itemView);
mNumberTextView = itemView.findViewById(R.id.number);
mNameTextView = itemView.findViewById(R.id.name);
mFormulaTextView = itemView.findViewById(R.id.formula);
mRangeTextView = itemView.findViewById(R.id.range);
mInfoButton = itemView.findViewById(R.id.info_btn);
mManualButton = itemView.findViewById(R.id.manual_btn);
我想知道是否有更好的方法来解决这个问题。此外,我不想失去甜蜜的 Firestore 功能,例如实时数据更新和对数据库的离线访问。我正在考虑在我的适配器中使用可过滤列表,问题是我如何使其与 Firestore 保持同步。请分享您的见解和指导方针。
【问题讨论】:
您要过滤多少条记录? 您的查询返回多少个对象?请回复@AlexMamo @AlexMamo 总的来说,我在数据库中有大约 400 条记录,具体取决于查询,它可能介于 400 和 0 之间 @JoaquinAlvarez 请见上文 返回10条记录效果好,返回400条慢是真的吗? 【参考方案1】:这是对我有用的解决方案。基本上,我创建了一个自定义可过滤适配器,我只用 Firestore 中的数据填充一次。
CustomItemAdapter.java
package com.example.testapp.adapter;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import com.example.testapp.DocumentViewerActivity;
import com.example.testapp.R;
import com.example.testapp.model.Item;
import java.util.ArrayList;
import java.util.List;
public class CustomItemAdapter extends RecyclerView.Adapter<CustomItemAdapter.ItemHolder> implements Filterable
private List<Item> mTubeList;
private List<Item> mTubeListFiltered;
public CustomItemAdapter(List<Item> tubeList)
mTubeList = tubeList;
mTubeListFiltered = tubeList;
@NonNull
@Override
public ItemHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i)
View tubeView = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_tube, viewGroup, false);
return new ItemHolder(tubeView);
@Override
public void onBindViewHolder(@NonNull ItemHolder holder, int i)
final Item item = mTubeListFiltered.get(i);
//Setting up view
@Override
public int getItemCount()
return mTubeListFiltered.size();
@Override
public Filter getFilter()
return new Filter()
@Override
protected FilterResults performFiltering(CharSequence constraint)
String pattern = constraint.toString().toLowerCase();
if(pattern.isEmpty())
mTubeListFiltered = mTubeList;
else
List<Item> filteredList = new ArrayList<>();
for(Item tube: mTubeList)
if(tube.getNumber().toLowerCase().contains(pattern) || tube.getName().toLowerCase().contains(pattern))
filteredList.add(tube);
mTubeListFiltered = filteredList;
FilterResults filterResults = new FilterResults();
filterResults.values = mTubeListFiltered;
return filterResults;
@Override
protected void publishResults(CharSequence constraint, FilterResults results)
mTubeListFiltered = (ArrayList<Item>) results.values;
notifyDataSetChanged();
;
public static class ItemHolder extends RecyclerView.ViewHolder
//ViewHolder's code
活动片段:
Query query = FirebaseFirestore.getInstance()
.collection("test")
.orderBy("number");
query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>()
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task)
mAdapter = new CustomItemAdapter(task.getResult().toObjects(Item.class));
mRecyclerView.setAdapter(mAdapter);
);
mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener()
@Override
public boolean onQueryTextSubmit(String query)
return true;
@Override
public boolean onQueryTextChange(String newText)
mAdapter.getFilter().filter(newText);
return true;
);
【讨论】:
出于兴趣,您是否已经解决了分页数据的这个问题? (我很好奇如何解决非常大的数据集,例如数百万个对象;我无法将它们全部下载到客户端上进行过滤) 最好的方法是将数据保存在单独的数据库中,例如带有文档 ID 的 mongo db 并在那里进行过滤或使用第三方工具(例如 Algolia)来搜索您的文档。复杂的查询不可能在 firestore 上运行。 @AjahnCharles以上是关于如何为从 Firestore 填充的 RecyclerView 实现过滤器?的主要内容,如果未能解决你的问题,请参考以下文章
如何为 Instagram 克隆构建 Cloud Firestore 数据?