我试图实现从支持库的SearchView。我想让用户使用SearchView来过滤一个RecyclerView中的电影列表。

到目前为止,我已经遵循了一些教程,我已经添加了搜索视图到动作栏,但我真的不确定从这里去哪里。我看过一些例子,但没有一个在你开始输入时显示结果。

这是MainActivity:

public class MainActivity extends ActionBarActivity {

    RecyclerView mRecyclerView;
    RecyclerView.LayoutManager mLayoutManager;
    RecyclerView.Adapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mRecyclerView.setHasFixedSize(true);

        mLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(mLayoutManager);

        mAdapter = new CardAdapter() {
            @Override
            public Filter getFilter() {
                return null;
            }
        };
        mRecyclerView.setAdapter(mAdapter);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

这是我的适配器:

public abstract class CardAdapter extends RecyclerView.Adapter<CardAdapter.ViewHolder> implements Filterable {

    List<Movie> mItems;

    public CardAdapter() {
        super();
        mItems = new ArrayList<Movie>();
        Movie movie = new Movie();
        movie.setName("Spiderman");
        movie.setRating("92");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Doom 3");
        movie.setRating("91");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers");
        movie.setRating("88");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers 2");
        movie.setRating("87");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Transformers 3");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Noah");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman 2");
        movie.setRating("86");
        mItems.add(movie);

        movie = new Movie();
        movie.setName("Ironman 3");
        movie.setRating("86");
        mItems.add(movie);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycler_view_card_item, viewGroup, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        Movie movie = mItems.get(i);
        viewHolder.tvMovie.setText(movie.getName());
        viewHolder.tvMovieRating.setText(movie.getRating());
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder{

        public TextView tvMovie;
        public TextView tvMovieRating;

        public ViewHolder(View itemView) {
            super(itemView);
            tvMovie = (TextView)itemView.findViewById(R.id.movieName);
            tvMovieRating = (TextView)itemView.findViewById(R.id.movieRating);
        }
    }
}

当前回答

适配器:

public void setFilter(List<Channel> newList){
        mChannels = new ArrayList<>();
        mChannels.addAll(newList);
        notifyDataSetChanged();
    }

在活动:

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                return false;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                newText = newText.toLowerCase();
                ArrayList<Channel> newList = new ArrayList<>();
                for (Channel channel: channels){
                    String channelName = channel.getmChannelName().toLowerCase();
                    if (channelName.contains(newText)){
                        newList.add(channel);
                    }
                }
                mAdapter.setFilter(newList);
                return true;
            }
        });

其他回答

我建议用以下2件事修改@Xaver Kapeller的解决方案,以避免在您清除搜索文本后(过滤器不再工作),因为适配器的列表后面的大小小于过滤器列表,并且发生了IndexOutOfBoundsException。所以代码需要修改如下

public void addItem(int position, ExampleModel model) {
    if(position >= mModel.size()) {
        mModel.add(model);
        notifyItemInserted(mModel.size()-1);
    } else {
        mModels.add(position, model);
        notifyItemInserted(position);
    }
}

并在moveItem功能中进行修改

public void moveItem(int fromPosition, int toPosition) {
    final ExampleModel model = mModels.remove(fromPosition);
    if(toPosition >= mModels.size()) {
        mModels.add(model);
        notifyItemMoved(fromPosition, mModels.size()-1);
    } else {
        mModels.add(toPosition, model);
        notifyItemMoved(fromPosition, toPosition); 
    }
}

希望能对你有所帮助!

遵循@Shruthi Kamoji的清洁方式,我们可以使用一个过滤器,它的意思是:

public abstract class GenericRecycleAdapter<E> extends RecyclerView.Adapter implements Filterable
{
    protected List<E> list;
    protected List<E> originalList;
    protected Context context;

    public GenericRecycleAdapter(Context context,
    List<E> list)
    {
        this.originalList = list;
        this.list = list;
        this.context = context;
    }

    ...

    @Override
    public Filter getFilter() {
        return new Filter() {
            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(CharSequence constraint, FilterResults results) {
                list = (List<E>) results.values;
                notifyDataSetChanged();
            }

            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                List<E> filteredResults = null;
                if (constraint.length() == 0) {
                    filteredResults = originalList;
                } else {
                    filteredResults = getFilteredResults(constraint.toString().toLowerCase());
                }

                FilterResults results = new FilterResults();
                results.values = filteredResults;

                return results;
            }
        };
    }

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

        for (E item : originalList) {
            if (item.getName().toLowerCase().contains(constraint)) {
                results.add(item);
            }
        }
        return results;
    }
} 

这里的E是一个泛型类型,你可以使用你的类扩展它:

public class customerAdapter extends GenericRecycleAdapter<CustomerModel>

或者只是将E更改为您想要的类型(例如<CustomerModel>)

然后从searchView(你可以放在menu.xml上的小部件):

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    @Override
    public boolean onQueryTextSubmit(String text) {
        return false;
    }

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

Android提供了DiffUtil. callback()和DiffUtil. callback()。ItemCallback<T>它们帮助我们很好地过滤回收视图

DiffUtil是一个计算两者之差的实用程序类 列出并输出转换第一个更新操作的列表 列表到第二个。 https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil

DiffUtil.Callback()与RecyclerView一起使用。适配器

and

DiffUtil。ItemCallback与ListAdapter一起使用

使用RecyclerView过滤

创建你的RecyclerView,就像你通常会覆盖

onCreateViewHolder

onBindViewHolder

getItemCount

和扩展RecyclerView。ViewHolder类

就像您所做的那样(这是代码片段的Kotlin版本)

override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder? {
    val v: View = LayoutInflater.from(viewGroup.context)
        .inflate(R.layout.recycler_view_card_item, viewGroup, false)
    return ViewHolder(v)
}

fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
    val movie: Movie = mItems.get(i)
    viewHolder.tvMovie.setText(movie.getName())
    viewHolder.tvMovieRating.setText(movie.getRating())
}

override fun getItemCount(): Int {
    return mItems.size()
}

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    var tvMovie: TextView
    var tvMovieRating: TextView

    init {
        tvMovie = itemView.findViewById<View>(R.id.movieName) as TextView
        tvMovieRating = itemView.findViewById<View>(R.id.movieRating) as TextView
    }
}

现在创建另一个类来实现DiffUtil.Callback()

这个类将帮助将recyclerviews currentlist转换为过滤后的列表

class MoviesDiffUtilCallback(private val oldList: List<Movies>, private val newList: List<Movies>) : DiffUtil.Callback() {

override fun getOldListSize() = oldList.size

override fun getNewListSize() = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = oldList[oldItemPosition].aUniqueId == newList[newItemPosition]. aUniqueId

//aUniqueId-> a field that is unique to each item in your listItems

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = oldList[oldItemPosition] == newList[newItemPosition]

}

在你的活动或片段类设置你的适配器和过滤器

private fun setupAdapter() {

//mItems is the list you will pass to the adapter
        adapter = CardAdapter(mItems)

        recyclerView.adapter = adapter

}

fun filter(searchText : String){

    val newFilter = mItems.filter {

        it.name.lowercase().contains(text.lowercase()) //filterlogic

    }

//Calculate the list of update operations that can covert one list into the other one 
    val diffResult = DiffUtil.calculateDiff(PostsDiffUtilCallback(mItems,newFilter))

    mItems.clear()


    mItems.addAll(newFilter)

//dispatch all updates to the RecyclerView
    diffResult.dispatchUpdatesTo(adapter)

}

使用ListAdapter进行筛选

我们将使用可过滤接口来帮助我们过滤(仍然在思考为什么我不应该直接使用过滤器函数来获取filteredLists和submitList(filteredLists))

创建你的ListAdapter类

class CardAdapter (
private val mItems : List<Movies>) : ListAdapter<Movies, CardAdapter.BillsPackageViewHolder>(MoviesDiffCallback()),
Filterable {

override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder? {
    val v: View = LayoutInflater.from(viewGroup.context)
        .inflate(R.layout.recycler_view_card_item, viewGroup, false)
    return ViewHolder(v)
}

fun onBindViewHolder(viewHolder: ViewHolder, i: Int) {
    val movie: Movie = mItems.get(i)
    viewHolder.tvMovie.setText(movie.getName())
    viewHolder.tvMovieRating.setText(movie.getRating())
}


override fun getFilter(): Filter {

    return object : Filter() {

        override fun performFiltering(constraint: CharSequence?): FilterResults {

            return FilterResults().apply {

                values = if (constraint.isNullOrEmpty())
                    mItems
                else
                    onFilter(mItems, constraint.toString())
            }
        }

        @Suppress("UNCHECKED_CAST")
        override fun publishResults(constraint: CharSequence?, results: FilterResults?) {

            submitList(results?.values as? List<Movies>)

        }
    }

}

fun onFilter(list: List<Movies>, constraint: String) : List<Movies>{

    val filteredList = list.filter {

        it.name.lowercase().contains(constraint.lowercase())

    }

    return filteredList

}

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    var tvMovie: TextView
    var tvMovieRating: TextView

    init {
        tvMovie = itemView.findViewById<View>(R.id.movieName) as TextView
        tvMovieRating = itemView.findViewById<View>(R.id.movieRating) as TextView
    }
}
}

现在创建另一个实现DiffUtil的类。ItemCallback

class MoviesDiffCallback : DiffUtil.ItemCallback<Movies>() {

override fun areItemsTheSame(oldItem: Movies, newItem: Movies): Boolean {
    return oldItem.someUniqueid == newItem.someUniqueid
}

override fun areContentsTheSame(oldItem: Movies, newItem: Movies): Boolean {
    return oldItem == newItem
}
}

在MainActivity或Fragment中设置适配器和过滤器

private fun setupAdapter() {

    adapter = CardAdapter(mItems)

    recyclerView.adapter = adapter

}

fun filter(searchString : String){

    adapter.filter.filter(searchString)

}

我不知道为什么每个人都用两个相同的列表来解决这个问题。这使用太多内存…

为什么不只是隐藏未找到的元素,并简单地将它们的索引存储在一个Set中,以便以后能够恢复它们呢?这样内存就少多了,特别是当你的对象很大的时候。

public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.SampleViewHolders>{
    private List<MyObject> myObjectsList; //holds the items of type MyObject
    private Set<Integer> foundObjects; //holds the indices of the found items

    public MyRecyclerViewAdapter(Context context, List<MyObject> myObjectsList)
    {
        this.myObjectsList = myObjectsList;
        this.foundObjects = new HashSet<>();
        //first, add all indices to the indices set
        for(int i = 0; i < this.myObjectsList.size(); i++)
        {
            this.foundObjects.add(i);
        }
    }

    @NonNull
    @Override
    public SampleViewHolders onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View layoutView = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.my_layout_for_staggered_grid, null);
        MyRecyclerViewAdapter.SampleViewHolders rcv = new MyRecyclerViewAdapter.SampleViewHolders(layoutView);
        return rcv;
    }

    @Override
    public void onBindViewHolder(@NonNull SampleViewHolders holder, int position)
    {
        //look for object in O(1) in the indices set
        if(!foundObjects.contains(position))
        {
            //object not found => hide it.
            holder.hideLayout();
            return;
        }
        else
        {
            //object found => show it.
            holder.showLayout();
        }

        //holder.imgImageView.setImageResource(...)
        //holder.nameTextView.setText(...)
    }

    @Override
    public int getItemCount() {
        return myObjectsList.size();
    }

    public void findObject(String text)
    {
        //look for "text" in the objects list
        for(int i = 0; i < myObjectsList.size(); i++)
        {
            //if it's empty text, we want all objects, so just add it to the set.
            if(text.length() == 0)
            {
                foundObjects.add(i);
            }
            else
            {
                //otherwise check if it meets your search criteria and add it or remove it accordingly
                if (myObjectsList.get(i).getName().toLowerCase().contains(text.toLowerCase()))
                {
                    foundObjects.add(i);
                }
                else
                {
                    foundObjects.remove(i);
                }
            }
        }
        notifyDataSetChanged();
    }

    public class SampleViewHolders extends RecyclerView.ViewHolder implements View.OnClickListener
    {
        public ImageView imgImageView;
        public TextView nameTextView;

        private final CardView layout;
        private final CardView.LayoutParams hiddenLayoutParams;
        private final CardView.LayoutParams shownLayoutParams;

        
        public SampleViewHolders(View itemView)
        {
            super(itemView);
            itemView.setOnClickListener(this);
            imgImageView = (ImageView) itemView.findViewById(R.id.some_image_view);
            nameTextView = (TextView) itemView.findViewById(R.id.display_name_textview);

            layout = itemView.findViewById(R.id.card_view); //card_view is the id of my androidx.cardview.widget.CardView in my xml layout
            //prepare hidden layout params with height = 0, and visible layout params for later - see hideLayout() and showLayout()
            hiddenLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
            hiddenLayoutParams.height = 0;
            shownLayoutParams = new CardView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        @Override
        public void onClick(View view)
        {
            //implement...
        }

        private void hideLayout() {
            //hide the layout
            layout.setLayoutParams(hiddenLayoutParams);
        }

        private void showLayout() {
            //show the layout
            layout.setLayoutParams(shownLayoutParams);
        }
    }
}

我只是有一个EditText作为我的搜索框:

cardsSearchTextView.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                myViewAdapter.findObject(editable.toString().toLowerCase());
            }
        });

结果:

这是我对扩展@klimat答案不丢失过滤动画的看法。

public void filter(String query){
    int completeListIndex = 0;
    int filteredListIndex = 0;
    while (completeListIndex < completeList.size()){
        Movie item = completeList.get(completeListIndex);
        if(item.getName().toLowerCase().contains(query)){
            if(filteredListIndex < filteredList.size()) {
                Movie filter = filteredList.get(filteredListIndex);
                if (!item.getName().equals(filter.getName())) {
                    filteredList.add(filteredListIndex, item);
                    notifyItemInserted(filteredListIndex);
                }
            }else{
                filteredList.add(filteredListIndex, item);
                notifyItemInserted(filteredListIndex);
            }
            filteredListIndex++;
        }
        else if(filteredListIndex < filteredList.size()){
            Movie filter = filteredList.get(filteredListIndex);
            if (item.getName().equals(filter.getName())) {
                filteredList.remove(filteredListIndex);
                notifyItemRemoved(filteredListIndex);
            }
        }
        completeListIndex++;
    }
}

基本上,它所做的就是查看一个完整的列表,并逐个向过滤后的列表添加/删除项。