Programing

SearchView로 RecyclerView를 필터링하는 방법

lottogame 2020. 3. 16. 08:14
반응형

SearchView로 RecyclerView를 필터링하는 방법


SearchView지원 라이브러리에서 구현하려고합니다 . 사용자가를 사용하여 의 영화 SearchView를 필터링 하기를 원합니다 .ListRecyclerView

지금까지 몇 가지 자습서를 따라하고 난을 추가 한 SearchView받는 사람 ActionBar, 그러나 나는 여기에서 갈 곳 정말 모르겠습니다. 몇 가지 예를 보았지만 입력을 시작할 때 결과가 표시되지 않습니다.

이것은 내 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);
    }
}

그리고 이것은 내 Adapter:

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

소개

정확히 당신이 어떤 문제를 겪고 있는지 당신의 질문에서 명확하지 않기 때문에, 나는이 기능을 구현하는 방법에 대한이 빠른 연습을 작성했습니다. 여전히 궁금한 점이 있으면 언제든지 문의하십시오.

GitHub 리포지토리 에서 내가 말하는 모든 것에 대한 실제 사례가 있습니다 .
예제 프로젝트에 대한 자세한 내용을 보려면 프로젝트 홈페이지를 방문하십시오 .

어쨌든 결과는 다음과 같습니다.

데모 이미지

데모 앱으로 먼저 플레이하고 싶다면 Play 스토어에서 설치할 수 있습니다.

Google Play에서 가져 오기

어쨌든 시작할 수 있습니다.


설정 SearchView

폴더에 res/menu이라는 새 파일을 만듭니다 main_menu.xml. 그것에서 항목을 추가하고 설정 actionViewClassandroid.support.v7.widget.SearchView. 지원 라이브러리를 사용하고 있으므로 지원 라이브러리의 네임 스페이스를 사용하여 actionViewClass속성 을 설정해야 합니다. xml 파일은 다음과 같아야합니다.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/action_search"
          android:title="@string/action_search"
          app:actionViewClass="android.support.v7.widget.SearchView"
          app:showAsAction="always"/>

</menu>

당신에 Fragment또는 Activity평소처럼이 메뉴 XML을 팽창해야합니다, 당신은 찾아보실 수 있습니다 MenuItem포함되는 SearchView및 구현 OnQueryTextListener우리가에 입력 한 텍스트에 대한 변경 사항을 수신하는 데 사용하려고한다 SearchView:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);

    final MenuItem searchItem = menu.findItem(R.id.action_search);
    final SearchView searchView = (SearchView) searchItem.getActionView();
    searchView.setOnQueryTextListener(this);

    return true;
}

@Override
public boolean onQueryTextChange(String query) {
    // Here is where we are going to implement the filter logic
    return false;
}

@Override
public boolean onQueryTextSubmit(String query) {
    return false;
}

이제 SearchView사용할 준비가되었습니다. 구현을 onQueryTextChange()마치면 나중에 필터 로직을 구현할 것 Adapter입니다.


설정 Adapter

가장 먼저 이것은이 예제에 사용할 모델 클래스입니다.

public class ExampleModel {

    private final long mId;
    private final String mText;

    public ExampleModel(long id, String text) {
        mId = id;
        mText = text;
    }

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }
}

에 텍스트를 표시하는 것은 기본 모델 일뿐 RecyclerView입니다. 이것은 텍스트를 표시하는 데 사용할 레이아웃입니다.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="model"
            type="com.github.wrdlbrnft.searchablerecyclerviewdemo.ui.models.ExampleModel"/>

    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
        android:clickable="true">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="8dp"
            android:text="@{model.text}"/>

    </FrameLayout>

</layout>

보시다시피 데이터 바인딩을 사용합니다. 데이터 바인딩 작업을 해 본 적이 없다면 실망하지 마십시오! 매우 간단하고 강력하지만이 답변의 범위에서 어떻게 작동하는지 설명 할 수 없습니다.

이것은 수업을 ViewHolder위한 것 ExampleModel입니다 :

public class ExampleViewHolder extends RecyclerView.ViewHolder {

    private final ItemExampleBinding mBinding;

    public ExampleViewHolder(ItemExampleBinding binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    public void bind(ExampleModel item) {
        mBinding.setModel(item);
    }
}

또 특별한 것은 없습니다. 위의 레이아웃 xml에서 정의한대로 데이터 바인딩을 사용하여 모델 클래스를이 레이아웃에 바인딩합니다.

이제 우리는 마침내 어댑터 작성이라는 정말 흥미로운 부분에 도달 할 수 있습니다. 나는 기본 구현을 건너 뛰고 Adapter대신이 답변과 관련된 부분에 집중할 것입니다.

그러나 먼저 우리가 이야기해야 할 것이 있습니다 : SortedList클래스.


정렬 된 목록

SortedList의 일부인 완전히 놀라운 도구입니다 RecyclerView도서관. Adapter데이터 세트의 변경 사항에 대해 알리고 매우 효율적인 방식으로 처리합니다. 당신이해야 할 유일한 것은 요소의 순서를 지정하는 것입니다. compare()두 개의 요소를 SortedLista처럼 비교 하는 메소드를 구현하여이를 수행해야 합니다 Comparator. 그러나를 정렬하는 대신 ! List에서 항목을 정렬하는 데 사용됩니다 RecyclerView.

SortedList와 상호 작용 Adapter하는 관통 Callback당신이 구현해야 클래스 :

private final SortedList.Callback<ExampleModel> mCallback = new SortedList.Callback<ExampleModel>() {

    @Override
    public void onInserted(int position, int count) {
         mAdapter.notifyItemRangeInserted(position, count);
    }

    @Override
    public void onRemoved(int position, int count) {
        mAdapter.notifyItemRangeRemoved(position, count);
    }

    @Override
    public void onMoved(int fromPosition, int toPosition) {
        mAdapter.notifyItemMoved(fromPosition, toPosition);
    }

    @Override
    public void onChanged(int position, int count) {
        mAdapter.notifyItemRangeChanged(position, count);
    }

    @Override
    public int compare(ExampleModel a, ExampleModel b) {
        return mComparator.compare(a, b);
    }

    @Override
    public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
        return oldItem.equals(newItem);
    }

    @Override
    public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
        return item1.getId() == item2.getId();
    }
}

같은 콜백 상단의 방법에서 onMoved, onInserted등 당신은 동등한 당신의 방법을 알려 호출해야 Adapter. 세 하단에있는 방법 compare, areContentsTheSame그리고 areItemsTheSame당신은 이러한 개체가 화면에 나타납니다 주문 어떤 종류의 객체 표시 할 것에과에 따라 구현해야합니다.

이 방법들을 하나씩 살펴 보자.

@Override
public int compare(ExampleModel a, ExampleModel b) {
    return mComparator.compare(a, b);
}

이것이 compare()내가 이전에 이야기했던 방법입니다. 이 예에서는 Comparator두 모델을 비교하는 호출을 전달합니다 . 화면에 항목을 알파벳 순서로 표시하려면. 이 비교기는 다음과 같습니다.

private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
    @Override
    public int compare(ExampleModel a, ExampleModel b) {
        return a.getText().compareTo(b.getText());
    }
};

이제 다음 방법을 살펴 보겠습니다.

@Override
public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
    return oldItem.equals(newItem);
}

이 방법의 목적은 모델의 내용이 변경되었는지 확인하는 것입니다. 이를 SortedList사용하여 변경 이벤트를 호출 RecyclerView해야하는지 여부, 즉 이전 버전과 새 버전을 교차 페이드해야하는지 여부를 결정 합니다. 클래스가 정확 equals()하고 hashCode()구현 된 모델 을 만들 경우 일반적으로 위와 같이 구현할 수 있습니다. 클래스에 equals()and hashCode()구현을 추가하면 ExampleModel다음과 같이 보일 것입니다.

public class ExampleModel implements SortedListAdapter.ViewModel {

    private final long mId;
    private final String mText;

    public ExampleModel(long id, String text) {
        mId = id;
        mText = text;
    }

    public long getId() {
        return mId;
    }

    public String getText() {
        return mText;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ExampleModel model = (ExampleModel) o;

        if (mId != model.mId) return false;
        return mText != null ? mText.equals(model.mText) : model.mText == null;

    }

    @Override
    public int hashCode() {
        int result = (int) (mId ^ (mId >>> 32));
        result = 31 * result + (mText != null ? mText.hashCode() : 0);
        return result;
    }
}

빠른 참고 : Android Studio, IntelliJ 및 Eclipse와 같은 대부분의 IDE 에는 버튼을 한 번만 누르면 생성 equals()hashCode()구현할 수 있는 기능이 있습니다! 따라서 직접 구현할 필요는 없습니다. 인터넷에서 IDE에서 어떻게 작동하는지 찾아보십시오!

이제 마지막 방법을 살펴 보자.

@Override
public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
    return item1.getId() == item2.getId();
}

SortedList두 항목이 같은 일을 참조하는지 확인하기 위해이 방법을 사용합니다. 가장 간단한 용어로 ( SortedList작동 방식을 설명하지 않고 ) 객체가 이미 포함되어 List있는지, 애니메이션을 추가, 이동 또는 변경 해야하는지 결정하는 데 사용 됩니다. 모델에 ID가있는 경우 일반적으로이 방법에서 ID 만 비교합니다. 그들이 그것을 확인하는 다른 방법을 알아낼 필요가 없다면, 그러나 당신은 이것을 구현하는 것은 특정 앱에 달려 있습니다. 일반적으로 모든 모델에 ID를 제공하는 가장 간단한 옵션입니다. 예를 들어 데이터베이스에서 데이터를 쿼리하는 경우 기본 키 필드가 될 수 있습니다.

SortedList.Callback올바르게 구현 하면 다음과 같은 인스턴스를 만들 수 있습니다 SortedList.

final SortedList<ExampleModel> list = new SortedList<>(ExampleModel.class, mCallback);

생성자의 첫 번째 매개 변수로 SortedList모델의 클래스를 전달해야합니다. 다른 매개 변수는 SortedList.Callback위에서 정의한 것입니다.

우리가 구현하는 경우 : 이제 본론하자 AdapterA의를 SortedList다음과 비슷한 모습이 될 것입니다 그것은 :

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
        @Override
        public int compare(ExampleModel a, ExampleModel b) {
            return mComparator.compare(a, b);
        }

        @Override
        public void onInserted(int position, int count) {
            notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count) {
            notifyItemRangeChanged(position, count);
        }

        @Override
        public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
            return oldItem.equals(newItem);
        }

        @Override
        public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
            return item1.getId() == item2.getId();
        }
    });

    private final LayoutInflater mInflater;
    private final Comparator<ExampleModel> mComparator;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    }

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
        return new ExampleViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) {
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    }

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

Comparator우리가 같은 사용할 수 있도록 항목이 생성자를 통해 전달되는 정렬하는 데 사용 Adapter항목이 다른 순서로 표시하기로하는 경우도 있습니다.

이제 거의 다 끝났습니다! 그러나 먼저에 항목을 추가하거나 제거 할 수있는 방법이 필요합니다 Adapter. 이를 위해 다음에 Adapter항목을 추가하고 제거 할 수있는 메소드를 추가 할 수 있습니다 SortedList.

public void add(ExampleModel model) {
    mSortedList.add(model);
}

public void remove(ExampleModel model) {
    mSortedList.remove(model);
}

public void add(List<ExampleModel> models) {
    mSortedList.addAll(models);
}

public void remove(List<ExampleModel> models) {
    mSortedList.beginBatchedUpdates();
    for (ExampleModel model : models) {
        mSortedList.remove(model);
    }
    mSortedList.endBatchedUpdates();
}

SortedList이미 SortedList.Callback! 메소드 를 통해이를 수행 하므로 통지 메소드를 호출 할 필요가 없습니다 . 그 외에도, 이러한 메소드의 구현 List은 모델 을 제거하는 remove 메소드를 제외하고는 매우 간단합니다 . 은 이후 SortedList우리가리스트를 반복 할 필요가 하나의 객체를 제거하고 하나를 사용하여 모델 하나를 제거 할 수 있습니다 하나의 제거 방법이있다. beginBatchedUpdates()처음에 전화 하면 모든 변경 사항을 일괄 처리하고 SortedList성능을 향상시킵니다. 우리가 전화 endBatchedUpdates()하면 RecyclerView모든 변경 사항에 대해 한 번에 알립니다.

또한 이해해야 할 것은 객체를 객체에 추가하고 SortedList이미 객체를 추가하면 SortedList다시 추가되지 않는다는 것입니다. 대신 메소드를 SortedList사용하여 areContentsTheSame()객체가 변경되었는지 확인하고 항목이있는 RecyclerView경우 업데이트됩니다.

어쨌든, 내가 일반적으로 선호하는 것은 한 RecyclerView에 모든 항목을 교체 할 수있는 한 가지 방법입니다 . 에없는 것을 모두 제거 List하고에서 누락 된 모든 항목을 추가하십시오 SortedList.

public void replaceAll(List<ExampleModel> models) {
    mSortedList.beginBatchedUpdates();
    for (int i = mSortedList.size() - 1; i >= 0; i--) {
        final ExampleModel model = mSortedList.get(i);
        if (!models.contains(model)) {
            mSortedList.remove(model);
        }
    }
    mSortedList.addAll(models);
    mSortedList.endBatchedUpdates();
}

이 방법은 다시 모든 업데이트를 일괄 처리하여 성능을 향상시킵니다. 첫 번째 루프는 시작시 항목을 제거하면 그 뒤에 나오는 모든 항목의 색인이 엉망이되어 일부 경우 데이터 불일치와 같은 문제가 발생할 수 있으므로 반대입니다. 그 후 우리 ListSortedListusing addAll()에 추가 하여 이미 존재하지 않는 모든 항목을 추가합니다 SortedList-위에서 설명한 것처럼 이미 SortedList있지만 변경 된 모든 항목을 업데이트하십시오 .

그리고 이것으로 Adapter완성되었습니다. 모든 것이 다음과 같이 보일 것입니다 :

public class ExampleAdapter extends RecyclerView.Adapter<ExampleViewHolder> {

    private final SortedList<ExampleModel> mSortedList = new SortedList<>(ExampleModel.class, new SortedList.Callback<ExampleModel>() {
        @Override
        public int compare(ExampleModel a, ExampleModel b) {
            return mComparator.compare(a, b);
        }

        @Override
        public void onInserted(int position, int count) {
            notifyItemRangeInserted(position, count);
        }

        @Override
        public void onRemoved(int position, int count) {
            notifyItemRangeRemoved(position, count);
        }

        @Override
        public void onMoved(int fromPosition, int toPosition) {
            notifyItemMoved(fromPosition, toPosition);
        }

        @Override
        public void onChanged(int position, int count) {
            notifyItemRangeChanged(position, count);
        }

        @Override
        public boolean areContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
            return oldItem.equals(newItem);
        }

        @Override
        public boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
            return item1 == item2;
        }
    });

    private final Comparator<ExampleModel> mComparator;
    private final LayoutInflater mInflater;

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        mInflater = LayoutInflater.from(context);
        mComparator = comparator;
    }

    @Override
    public ExampleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(mInflater, parent, false);
        return new ExampleViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(ExampleViewHolder holder, int position) {
        final ExampleModel model = mSortedList.get(position);
        holder.bind(model);
    }

    public void add(ExampleModel model) {
        mSortedList.add(model);
    }

    public void remove(ExampleModel model) {
        mSortedList.remove(model);
    }

    public void add(List<ExampleModel> models) {
        mSortedList.addAll(models);
    }

    public void remove(List<ExampleModel> models) {
        mSortedList.beginBatchedUpdates();
        for (ExampleModel model : models) {
            mSortedList.remove(model);
        }
        mSortedList.endBatchedUpdates();
    }

    public void replaceAll(List<ExampleModel> models) {
        mSortedList.beginBatchedUpdates();
        for (int i = mSortedList.size() - 1; i >= 0; i--) {
            final ExampleModel model = mSortedList.get(i);
            if (!models.contains(model)) {
                mSortedList.remove(model);
            }
        }
        mSortedList.addAll(models);
        mSortedList.endBatchedUpdates();
    }

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

이제 누락 된 것은 필터링을 구현하는 것입니다!


필터 로직 구현

필터 로직을 구현하려면 먼저 List가능한 모든 모델 을 정의 해야합니다. 예를 들어 내가 작성 ListExampleModel영화의 배열에서 인스턴스를 :

private static final String[] MOVIES = new String[]{
        ...
};

private static final Comparator<ExampleModel> ALPHABETICAL_COMPARATOR = new Comparator<ExampleModel>() {
    @Override
    public int compare(ExampleModel a, ExampleModel b) {
        return a.getText().compareTo(b.getText());
    }
};

private ExampleAdapter mAdapter;
private List<ExampleModel> mModels;
private RecyclerView mRecyclerView;

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    mAdapter = new ExampleAdapter(this, ALPHABETICAL_COMPARATOR);

    mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
    mBinding.recyclerView.setAdapter(mAdapter);

    mModels = new ArrayList<>();
    for (String movie : MOVIES) {
        mModels.add(new ExampleModel(movie));
    }
    mAdapter.add(mModels);
}

여기서는 특별한 일이 없습니다. 인스턴스화하고 Adapter로 설정했습니다 RecyclerView. 그런 List다음 MOVIES배열 의 영화 이름에서 모델을 만듭니다 . 그런 다음 모든 모델을에 추가합니다 SortedList.

이제 onQueryTextChange()이전에 정의한 내용 으로 돌아가서 필터 로직 구현을 시작할 수 있습니다.

@Override
public boolean onQueryTextChange(String query) {
    final List<ExampleModel> filteredModelList = filter(mModels, query);
    mAdapter.replaceAll(filteredModelList);
    mBinding.recyclerView.scrollToPosition(0);
    return true;
}

이것은 다시 매우 직설적입니다. 우리는 메소드를 호출 filter()하고, 전달 ListExampleModel잘 쿼리 문자열 등으로의. 그런 다음를 호출하고 replaceAll()의해 반환 된 Adapter필터링 된 항목을 전달 List합니다 filter(). 우리는 또한 전화가 scrollToPosition(0)RecyclerView무언가를 검색 할 때 사용자가 항상 모든 항목을 볼 수 있도록 할 수 있습니다. 그렇지 않으면, RecyclerView필터링하는 동안 아래로 스크롤 된 위치에 남아서 몇 개의 항목을 숨길 수 있습니다. 맨 위로 스크롤하면 검색하는 동안 사용자 경험이 향상됩니다.

이제 남은 것은 filter()스스로 구현하는 것입니다.

private static List<ExampleModel> filter(List<ExampleModel> models, String query) {
    final String lowerCaseQuery = query.toLowerCase();

    final List<ExampleModel> filteredModelList = new ArrayList<>();
    for (ExampleModel model : models) {
        final String text = model.getText().toLowerCase();
        if (text.contains(lowerCaseQuery)) {
            filteredModelList.add(model);
        }
    }
    return filteredModelList;
}

여기서 가장 먼저하는 일은 toLowerCase()쿼리 문자열을 호출 하는 것입니다. 검색 기능이 대소 문자를 구분하는 것을 원하지 않으며 toLowerCase()비교할 모든 문자열 을 호출 하여 대소 문자와 상관없이 동일한 결과를 반환 할 수 있습니다. 그런 다음 List전달 된 모든 모델을 반복 하고 쿼리 문자열이 모델의 텍스트에 포함되어 있는지 확인합니다. 이 경우 모델이 필터링 된 항목에 추가됩니다 List.

그리고 그게 다야! 위의 코드는 API 레벨 7 이상에서 실행되며 API 레벨 11부터 무료로 항목 애니메이션을 얻을 수 있습니다!

나는 이것이이 전체를 실제보다 더 복잡하게 보이게하는 매우 상세한 설명이라는 것을 알고 있지만, 우리가이 전체 문제를 일반화 Adapter하고 SortedList훨씬 더 간단한 기반을 구현할 수있는 방법이 있습니다 .


문제점 일반화 및 어댑터 단순화

이 섹션에서는 스택 오버플로에 대한 답변에 대한 문자 제한에 대해 실행 중이지만 대부분 위에서 이미 설명 했으므로 변경 사항을 요약하기 위해 많은 부분을 다루지 않을 것입니다. 우리는 기본 Adapter클래스를 구현할 수 있습니다 인스턴스 SortedList를 바인딩하고 모델을 처리하고 이미 ViewHolderAdapter기반 을 구현하는 편리한 방법을 제공 합니다 SortedList. 이를 위해 우리는 두 가지 일을해야합니다.

  • ViewModel모든 모델 클래스가 구현해야하는 인터페이스 를 만들어야합니다
  • 모델을 자동으로 바인딩하는 데 사용할 수 ViewHolder있는 bind()메서드 를 정의 하는 하위 클래스 를 만들어야합니다 Adapter.

이를 통해 RecyclerView모델을 구현하고 해당 ViewHolder구현 을 통해 표시 할 내용에 집중할 수 있습니다 . 이 기본 클래스를 사용하면 Adapter의 복잡한 세부 사항에 대해 걱정할 필요가 없습니다 SortedList.

정렬 된 목록 어댑터

이 때문에 기본 클래스를 구현하거나 여기에 전체 소스 코드를 추가 할 수 있지만이 기본 클래스의 전체 소스 코드를 찾을 수의 각 단계를 통해 갈 수 StackOverflow의 I에 대한 답변 문자 제한의 - 나는 그것이라고 SortedListAdapter-이에 GitHub 요지 .

당신의 인생을 간단하게하기 위해 나는 jCenter에 라이브러리를 게시했습니다 SortedListAdapter! 그것을 사용하려면 앱의 build.gradle 파일 에이 종속성을 추가하기 만하면됩니다.

compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'

이 라이브러리 대한 자세한 정보 는 라이브러리 홈페이지에서 찾을 수 있습니다 .

SortedListAdapter 사용

를 사용하려면 SortedListAdapter두 가지 사항을 변경해야합니다.

  • ViewHolder연장되도록 변경하십시오 SortedListAdapter.ViewHolder. type 매개 변수는 ViewHolder이 경우에 바인딩되어야하는 모델이어야합니다 ExampleModel. performBind()대신 데이터를 모델에 바인딩해야합니다 bind().

    public class ExampleViewHolder extends SortedListAdapter.ViewHolder<ExampleModel> {
    
        private final ItemExampleBinding mBinding;
    
        public ExampleViewHolder(ItemExampleBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }
    
        @Override
        protected void performBind(ExampleModel item) {
            mBinding.setModel(item);
        }
    }
    
  • 모든 모델이 ViewModel인터페이스를 구현해야합니다 .

    public class ExampleModel implements SortedListAdapter.ViewModel {
        ...
    }
    

그 후에 우리는 더 이상 필요없는 모든 것을 ExampleAdapter확장 SortedListAdapter하고 제거 하기 위해 업데이트 해야합니다. type 매개 변수는 작업중인 모델의 유형이어야합니다 (이 경우) ExampleModel. 그러나 다른 유형의 모델로 작업하는 경우 type 매개 변수를로 설정하십시오 ViewModel.

public class ExampleAdapter extends SortedListAdapter<ExampleModel> {

    public ExampleAdapter(Context context, Comparator<ExampleModel> comparator) {
        super(context, ExampleModel.class, comparator);
    }

    @Override
    protected ViewHolder<? extends ExampleModel> onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType) {
        final ItemExampleBinding binding = ItemExampleBinding.inflate(inflater, parent, false);
        return new ExampleViewHolder(binding);
    }

    @Override
    protected boolean areItemsTheSame(ExampleModel item1, ExampleModel item2) {
        return item1.getId() == item2.getId();
    }

    @Override
    protected boolean areItemContentsTheSame(ExampleModel oldItem, ExampleModel newItem) {
        return oldItem.equals(newItem);
    }
}

그 후 우리는 끝났습니다! 언급하지만 마지막으로 남은 하나는 SortedListAdapter동일하지 않습니다 add(), remove()또는 replaceAll()우리의 원래 방법을 ExampleAdapter했다합니다. 별도의 Editor객체를 사용 하여 edit()메소드를 통해 액세스 할 수있는 목록의 항목을 수정합니다 . 따라서 호출하거나 호출해야 할 항목 edit()을 추가하려면이 Editor인스턴스 에서 항목을 추가 및 제거해야합니다. 완료 한 후에는 commit()변경 사항을 다음에 적용하십시오 SortedList.

mAdapter.edit()
        .remove(modelToRemove)
        .add(listOfModelsToAdd)
        .commit();

이 방법으로 수행 한 모든 변경 사항은 함께 배치되어 성능을 향상시킵니다. replaceAll()위의 장에서 구현 방법은이 Editor객체에도 있습니다.

mAdapter.edit()
        .replaceAll(mModels)
        .commit();

전화 commit()잊어 버린 경우 변경 사항이 적용되지 않습니다!


filter메소드 를 추가하기 만하면됩니다 RecyclerView.Adapter.

public void filter(String text) {
    items.clear();
    if(text.isEmpty()){
        items.addAll(itemsCopy);
    } else{
        text = text.toLowerCase();
        for(PhoneBookItem item: itemsCopy){
            if(item.name.toLowerCase().contains(text) || item.phone.toLowerCase().contains(text)){
                items.add(item);
            }
        }
    }
    notifyDataSetChanged();
}

itemsCopy와 같은 어댑터의 생성자에서 초기화됩니다 itemsCopy.addAll(items).

그렇게하면 다음 filter에서 전화하십시오 OnQueryTextListener.

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

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

전화 번호부를 이름과 전화 번호로 필터링 한 예입니다.


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

어댑터에 하나의 orignal과 하나의 temp를 두 개의 목록으로 만들고 Filterable을 구현하기 만하면 됩니다.

    @Override
    public Filter getFilter() {
        return new Filter() {
            @Override
            protected FilterResults performFiltering(CharSequence constraint) {
                final FilterResults oReturn = new FilterResults();
                final ArrayList<T> results = new ArrayList<>();
                if (origList == null)
                    origList = new ArrayList<>(itemList);
                if (constraint != null && constraint.length() > 0) {
                    if (origList != null && origList.size() > 0) {
                        for (final T cd : origList) {
                            if (cd.getAttributeToSearch().toLowerCase()
                                    .contains(constraint.toString().toLowerCase()))
                                results.add(cd);
                        }
                    }
                    oReturn.values = results;
                    oReturn.count = results.size();//newly Aded by ZA
                } else {
                    oReturn.values = origList;
                    oReturn.count = origList.size();//newly added by ZA
                }
                return oReturn;
            }

            @SuppressWarnings("unchecked")
            @Override
            protected void publishResults(final CharSequence constraint,
                                          FilterResults results) {
                itemList = new ArrayList<>((ArrayList<T>) results.values);
                // FIXME: 8/16/2017 implement Comparable with sort below
                ///Collections.sort(itemList);
                notifyDataSetChanged();
            }
        };
    }

어디

public GenericBaseAdapter(Context mContext, List<T> itemList) {
        this.mContext = mContext;
        this.itemList = itemList;
        this.origList = itemList;
    }

안드로이드 아키텍처 구성 요소 의 사용을 통해 LiveData 이 쉽게 모든 유형의 구현 될 수 어댑터 . 다음 단계를 수행하면됩니다.

1. 아래 예와 같이 데이터 데이터베이스 에서 LiveData반환하도록 설정하십시오 .

@Dao
public interface CustomDAO{

@Query("SELECT * FROM words_table WHERE column LIKE :searchquery")
    public LiveData<List<Word>> searchFor(String searchquery);
}

2. DAOUI를 연결하는 메소드를 통해 데이터를 실시간으로 업데이트 하는 ViewModel 객체를 만듭니다.

public class CustomViewModel extends AndroidViewModel {

    private final AppDatabase mAppDatabase;

    public WordListViewModel(@NonNull Application application) {
        super(application);
        this.mAppDatabase = AppDatabase.getInstance(application.getApplicationContext());
    }

    public LiveData<List<Word>> searchQuery(String query) {
        return mAppDatabase.mWordDAO().searchFor(query);
    }

}

3. 아래와 같이 onQueryTextListener를 통해 쿼리를 전달 하여 ViewModel 에서 데이터를 즉시 호출합니다 .

내부 onCreateOptionsMenu는 다음과 같이 청취자를 설정합니다

searchView.setOnQueryTextListener(onQueryTextListener);

다음과 같이 SearchActivity 클래스 어딘가에 쿼리 리스너를 설정하십시오.

private android.support.v7.widget.SearchView.OnQueryTextListener onQueryTextListener =
            new android.support.v7.widget.SearchView.OnQueryTextListener() {
                @Override
                public boolean onQueryTextSubmit(String query) {
                    getResults(query);
                    return true;
                }

                @Override
                public boolean onQueryTextChange(String newText) {
                    getResults(newText);
                    return true;
                }

                private void getResults(String newText) {
                    String queryText = "%" + newText + "%";
                    mCustomViewModel.searchQuery(queryText).observe(
                            SearchResultsActivity.this, new Observer<List<Word>>() {
                                @Override
                                public void onChanged(@Nullable List<Word> words) {
                                    if (words == null) return;
                                    searchAdapter.submitList(words);
                                }
                            });
                }
            };

참고 : 단계 (1.) 및 (2.)는 표준 AAC ViewModelDAO 구현이며, 여기서 실제로 진행되는 유일한 "마법"은 OnQueryTextListener에 있으며 쿼리 텍스트가 변경 될 때 목록의 결과를 동적으로 업데이트합니다.

문제에 대한 자세한 설명이 필요하면 언제든지 문의하십시오. 나는 이것이 도움이되기를 바랍니다 :).


어댑터의 목록 뒷면이 필터 목록보다 크기가 작고 IndexOutOfBoundsException이 발생하여 검색 된 텍스트 (필터가 더 이상 작동하지 않음)를 지운 후 문제를 피하기 위해 @Xaver Kapeller의 솔루션을 아래 2 가지로 수정하는 것이 좋습니다. 따라서 코드는 아래와 같이 수정해야합니다

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

그것이 당신을 도울 수 있기를 바랍니다!


어댑터에서 :

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

이것은 필터링 애니메이션을 잃지 않기 위해 @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++;
    }
}

기본적으로 전체 목록을 살펴보고 필터링 된 목록에 항목을 하나씩 추가 / 제거합니다.


링크를 수정하여 동일한 문제를 해결했습니다. 카드가있는 RecyclerView의 검색 필터. 가능합니까? (도움이 되었기를 바랍니다).

여기 내 어댑터 클래스가 있습니다

public class ContactListRecyclerAdapter extends RecyclerView.Adapter<ContactListRecyclerAdapter.ContactViewHolder> implements Filterable {

Context mContext;
ArrayList<Contact> customerList;
ArrayList<Contact> parentCustomerList;


public ContactListRecyclerAdapter(Context context,ArrayList<Contact> customerList)
{
    this.mContext=context;
    this.customerList=customerList;
    if(customerList!=null)
    parentCustomerList=new ArrayList<>(customerList);
}

   // other overrided methods

@Override
public Filter getFilter() {
    return new FilterCustomerSearch(this,parentCustomerList);
}
}

// 필터 클래스

import android.widget.Filter;
import java.util.ArrayList;


public class FilterCustomerSearch extends Filter
{
private final ContactListRecyclerAdapter mAdapter;
ArrayList<Contact> contactList;
ArrayList<Contact> filteredList;

public FilterCustomerSearch(ContactListRecyclerAdapter mAdapter,ArrayList<Contact> contactList) {
    this.mAdapter = mAdapter;
    this.contactList=contactList;
    filteredList=new ArrayList<>();
}

@Override
protected FilterResults performFiltering(CharSequence constraint) {
    filteredList.clear();
    final FilterResults results = new FilterResults();

    if (constraint.length() == 0) {
        filteredList.addAll(contactList);
    } else {
        final String filterPattern = constraint.toString().toLowerCase().trim();

        for (final Contact contact : contactList) {
            if (contact.customerName.contains(constraint)) {
                filteredList.add(contact);
            }
            else if (contact.emailId.contains(constraint))
            {
                filteredList.add(contact);

            }
            else if(contact.phoneNumber.contains(constraint))
                filteredList.add(contact);
        }
    }
    results.values = filteredList;
    results.count = filteredList.size();
    return results;
}

@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
    mAdapter.customerList.clear();
    mAdapter.customerList.addAll((ArrayList<Contact>) results.values);
    mAdapter.notifyDataSetChanged();
}

}

// 활동 클래스

public class HomeCrossFadeActivity extends AppCompatActivity implements View.OnClickListener,OnFragmentInteractionListener,OnTaskCompletedListner
{
Fragment fragment;
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_homecrossfadeslidingpane2);CardView mCard;
   setContentView(R.layout.your_main_xml);}
   //other overrided methods
  @Override
   public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.

    MenuInflater inflater = getMenuInflater();
    // Inflate menu to add items to action bar if it is present.
    inflater.inflate(R.menu.menu_customer_view_and_search, menu);
    // Associate searchable configuration with the SearchView
    SearchManager searchManager =
            (SearchManager) getSystemService(Context.SEARCH_SERVICE);
    SearchView searchView =
            (SearchView) menu.findItem(R.id.menu_search).getActionView();
    searchView.setQueryHint("Search Customer");
    searchView.setSearchableInfo(
            searchManager.getSearchableInfo(getComponentName()));

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

        @Override
        public boolean onQueryTextChange(String newText) {
            if(fragment instanceof CustomerDetailsViewWithModifyAndSearch)
                ((CustomerDetailsViewWithModifyAndSearch)fragment).adapter.getFilter().filter(newText);
            return false;
        }
    });



    return true;
}
}

OnQueryTextChangeListener () 메소드에서 어댑터를 사용하십시오. 내 adpter가 조각난 상태에서 조각으로 캐스트했습니다. 활동 클래스에있는 경우 어댑터를 직접 사용할 수 있습니다.

참고 URL : https://stackoverflow.com/questions/30398247/how-to-filter-a-recyclerview-with-a-searchview

반응형