webentwicklung-frage-antwort-db.com.de

So filtern Sie eine RecyclerView mit einer SearchView

Ich versuche, SearchView aus der Support-Bibliothek zu implementieren. Ich möchte, dass der Benutzer das SearchView verwendet, um ein List von Filmen in einem RecyclerView zu filtern.

Ich habe bisher ein paar Tutorials befolgt und das SearchView zum ActionBar hinzugefügt, bin mir aber nicht sicher, wohin ich von hier aus gehen soll. Ich habe einige Beispiele gesehen, aber keines zeigt Ergebnisse, wenn Sie mit dem Tippen beginnen.

Dies ist mein 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);
    }
}

Und das ist mein 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);
        }
    }
}
292
Jacques Krause

Einführung

Da aus Ihrer Frage nicht klar hervorgeht, womit genau Sie Probleme haben, habe ich diese kurze Anleitung zur Implementierung dieser Funktion verfasst. Wenn Sie noch Fragen haben, können Sie diese gerne stellen.

Ich habe ein funktionierendes Beispiel für alles, worüber ich hier spreche. GitHub Repository .
Wenn Sie mehr über das Beispielprojekt erfahren möchten, besuchen Sie die Homepage des Projekts .

In jedem Fall sollte das Ergebnis so aussehen:

demo image

Wenn Sie zuerst mit der Demo-App herumspielen möchten, können Sie sie aus dem Play Store installieren:

Get it on Google Play

Auf jeden Fall können wir loslegen.


Einrichten der SearchView

Erstellen Sie im Ordner res/menu Eine neue Datei mit dem Namen main_menu.xml. Fügen Sie einen Eintrag hinzu und setzen Sie die actionViewClass auf Android.support.v7.widget.SearchView. Da Sie die Unterstützungsbibliothek verwenden, müssen Sie den Namespace der Unterstützungsbibliothek verwenden, um das Attribut actionViewClass festzulegen. Ihre XML-Datei sollte ungefähr so ​​aussehen:

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

In Ihrer Fragment oder Activity müssen Sie diese Menü-XML wie gewohnt aufblasen, dann können Sie die MenuItem suchen, die die SearchView enthält, und die OnQueryTextListener, mit dem wir auf Änderungen des in SearchView eingegebenen Texts warten:

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

Und jetzt kann die SearchView verwendet werden. Wir werden die Filterlogik später in onQueryTextChange() implementieren, sobald wir mit der Implementierung von Adapter fertig sind.


Einrichten der Adapter

In erster Linie ist dies die Modellklasse, die ich für dieses Beispiel verwenden werde:

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

Es ist nur Ihr Basismodell, das einen Text in RecyclerView anzeigt. Dies ist das Layout, mit dem ich den Text anzeigen werde:

<?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>

Wie Sie sehen, verwende ich die Datenbindung. Wenn Sie noch nie mit Datenbindung gearbeitet haben, lassen Sie sich nicht entmutigen! Es ist sehr einfach und leistungsstark, ich kann jedoch nicht erklären, wie es im Rahmen dieser Antwort funktioniert.

Dies ist die ViewHolder für die Klasse 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);
    }
}

Wieder nichts besonderes. Es wird lediglich die Datenbindung verwendet, um die Modellklasse an dieses Layout zu binden, wie wir es in der obigen Layout-XML definiert haben.

Jetzt können wir endlich zu dem wirklich interessanten Teil kommen: Den Adapter schreiben. Ich werde die grundlegende Implementierung von Adapter überspringen und mich stattdessen auf die Teile konzentrieren, die für diese Antwort relevant sind.

Aber zuerst müssen wir über eines sprechen: Die SortedList Klasse.


SortedList

Das SortedList ist ein völlig erstaunliches Werkzeug, das Teil der Bibliothek RecyclerView ist. Es kümmert sich darum, die Adapter über Änderungen am Datensatz zu informieren, und dies auf sehr effiziente Weise. Sie müssen lediglich die Reihenfolge der Elemente festlegen. Dazu müssen Sie eine compare() -Methode implementieren, die zwei Elemente in SortedList wie eine Comparator vergleicht. Anstatt jedoch eine List zu sortieren, werden die Elemente in der RecyclerView sortiert!

Die SortedList interagiert mit der Adapter über eine Callback Klasse, die Sie implementieren müssen:

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

In den Methoden am oberen Rand des Rückrufs wie onMoved, onInserted usw. müssen Sie die entsprechende Benachrichtigungsmethode für Ihre Adapter aufrufen. Die drei Methoden am unteren Rand compare, areContentsTheSame und areItemsTheSame müssen Sie implementieren, je nachdem, welche Art von Objekten Sie anzeigen möchten und in welcher Reihenfolge diese Objekte auf dem Bildschirm angezeigt werden sollen Bildschirm.

Gehen wir nacheinander die folgenden Methoden durch:

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

Dies ist die Methode compare(), über die ich zuvor gesprochen habe. In diesem Beispiel übergebe ich den Aufruf nur an eine Comparator, die die beiden Modelle vergleicht. Wenn Sie möchten, dass die Elemente in alphabetischer Reihenfolge auf dem Bildschirm angezeigt werden. Dieser Komparator könnte so aussehen:

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

Schauen wir uns nun die nächste Methode an:

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

Mit dieser Methode können Sie feststellen, ob sich der Inhalt eines Modells geändert hat. Das SortedList verwendet dies, um zu bestimmen, ob ein Änderungsereignis aufgerufen werden muss - mit anderen Worten, ob das RecyclerView die alte und die neue Version überblenden soll. Wenn Sie Modellklassen mit einer korrekten equals()- und hashCode() -Implementierung haben, können Sie diese normalerweise wie oben beschrieben implementieren. Wenn wir der Klasse ExampleModel eine Implementierung von equals() und hashCode() hinzufügen, sollte sie ungefähr so ​​aussehen:

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

Kurze Randnotiz: Die meisten IDEs wie Android Studio, IntelliJ und Eclipse haben Funktionen, um auf Knopfdruck equals() und hashCode()-Implementierungen für Sie zu generieren Sie müssen sie also nicht selbst implementieren. Schauen Sie im Internet nach, wie es in Ihrer IDE funktioniert!

Schauen wir uns nun die letzte Methode an:

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

Mit dieser Methode prüft SortedList, ob sich zwei Elemente auf dasselbe Objekt beziehen. In einfachsten Worten (ohne zu erklären, wie SortedList funktioniert) wird hiermit bestimmt, ob ein Objekt bereits in List enthalten ist und ob eine Animation zum Hinzufügen, Verschieben oder Ändern abgespielt werden muss. Wenn Ihre Modelle eine ID haben, vergleichen Sie normalerweise nur die ID in dieser Methode. Wenn dies nicht der Fall ist, müssen Sie einen anderen Weg finden, um dies zu überprüfen. Die Implementierung hängt jedoch von Ihrer spezifischen App ab. Normalerweise ist es die einfachste Möglichkeit, allen Modellen eine ID zuzuweisen. Dies kann beispielsweise das Primärschlüsselfeld sein, wenn Sie die Daten aus einer Datenbank abfragen.

Mit dem korrekt implementierten SortedList.Callback Können wir eine Instanz des SortedList erstellen:

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

Als ersten Parameter im Konstruktor von SortedList müssen Sie die Klasse Ihrer Modelle übergeben. Der andere Parameter ist nur das oben definierte SortedList.Callback.

Kommen wir nun zur Sache: Wenn wir die Adapter mit einer SortedList implementieren, sollte sie ungefähr so ​​aussehen:

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

Das zum Sortieren des Elements verwendete Comparator wird über den Konstruktor übergeben, sodass dasselbe Adapter verwendet werden kann, auch wenn die Elemente in einer anderen Reihenfolge angezeigt werden sollen.

Jetzt sind wir fast fertig! Aber wir müssen zuerst eine Möglichkeit finden, Elemente zu Adapter hinzuzufügen oder daraus zu entfernen. Zu diesem Zweck können wir der Adapter Methoden hinzufügen, mit denen wir der SortedList Elemente hinzufügen und daraus entfernen können:

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

Wir brauchen hier keine Benachrichtigungsmethoden aufzurufen, da die SortedList dies bereits durch SortedList.Callback Erledigt! Abgesehen davon ist die Implementierung dieser Methoden ziemlich einfach, mit einer Ausnahme: der remove-Methode, mit der eine List von Modellen entfernt wird. Da SortedList nur eine Entfernungsmethode hat, mit der ein einzelnes Objekt entfernt werden kann, müssen wir die Liste durchlaufen und die Modelle nacheinander entfernen. Wenn Sie zu Beginn beginBatchedUpdates() aufrufen, werden alle Änderungen, die Sie an SortedList vornehmen, stapelweise ausgeführt und die Leistung verbessert. Wenn wir endBatchedUpdates() aufrufen, wird der RecyclerView über alle Änderungen auf einmal benachrichtigt.

Außerdem müssen Sie verstehen, dass ein Objekt, das Sie zu SortedList hinzufügen und das sich bereits in SortedList befindet, nicht erneut hinzugefügt wird. Stattdessen verwendet SortedList die Methode areContentsTheSame(), um herauszufinden, ob sich das Objekt geändert hat - und ob das Element in RecyclerView aktualisiert wird.

Normalerweise bevorzuge ich jedoch eine Methode, mit der ich alle Elemente in der RecyclerView auf einmal ersetzen kann. Entfernen Sie alles, was nicht in der List enthalten ist, und fügen Sie alle Elemente hinzu, die in der SortedList fehlen:

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

Diese Methode fasst alle Aktualisierungen erneut zusammen, um die Leistung zu steigern. Die erste Schleife ist umgekehrt, da durch das Entfernen eines Elements zu Beginn die Indizes aller darauf folgenden Elemente durcheinander gebracht werden und dies in einigen Fällen zu Problemen wie Dateninkonsistenzen führen kann. Danach fügen wir einfach die List zu der SortedList hinzu, indem wir addAll() verwenden, um alle Elemente hinzuzufügen, die nicht bereits in der SortedList und - genau wie ich beschrieben - enthalten sind Oben - Aktualisieren Sie alle Elemente, die sich bereits in der SortedList befinden, aber geändert wurden.

Und damit ist die Adapter abgeschlossen. Das Ganze sollte ungefähr so ​​aussehen:

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

Jetzt fehlt nur noch die Filterung!


Implementierung der Filterlogik

Um die Filterlogik zu implementieren, müssen wir zuerst eine List aller möglichen Modelle definieren. In diesem Beispiel erstelle ich eine List von ExampleModel Instanzen aus einem Array von Filmen:

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

Hier ist nichts Besonderes los, wir instanziieren nur die Adapter und setzen sie auf die RecyclerView. Danach erstellen wir aus den Filmenamen im Array List eine MOVIES von Modellen. Dann fügen wir alle Modelle zu SortedList hinzu.

Jetzt können wir zu onQueryTextChange() zurückkehren, das wir zuvor definiert haben, und mit der Implementierung der Filterlogik beginnen:

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

Das ist wieder ziemlich einfach. Wir rufen die Methode filter() auf und übergeben die List von ExampleModel s sowie die Abfragezeichenfolge. Wir rufen dann replaceAll() auf dem Adapter auf und übergeben das gefilterte List, das von filter() zurückgegeben wurde. Wir müssen auch scrollToPosition(0) für die RecyclerView aufrufen, um sicherzustellen, dass der Benutzer bei der Suche nach etwas immer alle Elemente sehen kann. Andernfalls bleibt RecyclerView beim Filtern möglicherweise in einer nach unten gerollten Position und blendet anschließend einige Elemente aus. Wenn Sie nach oben scrollen, wird die Benutzererfahrung beim Suchen verbessert.

Jetzt müssen Sie nur noch filter() selbst implementieren:

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

Als erstes rufen wir toLowerCase() für die Abfragezeichenfolge auf. Wir möchten nicht, dass bei unserer Suchfunktion zwischen Groß- und Kleinschreibung unterschieden wird, und indem wir toLowerCase() für alle Zeichenfolgen aufrufen, die wir vergleichen, können wir sicherstellen, dass wir unabhängig von der Groß- und Kleinschreibung dieselben Ergebnisse zurückgeben. Es durchläuft dann nur alle Modelle in der List, die wir übergeben haben, und prüft, ob die Abfragezeichenfolge im Text des Modells enthalten ist. Wenn dies der Fall ist, wird das Modell zu dem gefilterten Wert List hinzugefügt.

Und das ist es! Der obige Code wird auf API-Level 7 und höher ausgeführt und ab API-Level 11 erhalten Sie Artikelanimationen kostenlos!

Mir ist klar, dass dies eine sehr detaillierte Beschreibung ist, die das Ganze wahrscheinlich komplizierter erscheinen lässt als es wirklich ist, aber es gibt eine Möglichkeit, dieses ganze Problem zu verallgemeinern und die Implementierung einer Adapter basierend auf einer SortedList viel einfacher.


Verallgemeinerung des Problems und Vereinfachung des Adapters

In diesem Abschnitt werde ich nicht weiter ins Detail gehen - zum Teil, weil ich auf die Zeichenbeschränkung für Antworten auf Stapelüberlauf stoße, aber auch, weil das meiste davon bereits oben erklärt wurde -, aber um die Änderungen zusammenzufassen: Wir können eine Basis implementieren Adapter -Klasse, die sich bereits um die Bearbeitung der SortedList - und Bindungsmodelle für ViewHolder -Instanzen kümmert und eine bequeme Möglichkeit zum Implementieren einer Adapter basierend auf a bietet SortedList. Dafür müssen wir zwei Dinge tun:

  • Wir müssen eine Schnittstelle ViewModel erstellen, die alle Modellklassen implementieren müssen
  • Wir müssen eine Unterklasse ViewHolder erstellen, die eine bind() -Methode definiert, mit der Adapter Modelle automatisch binden kann.

Dadurch können wir uns auf den Inhalt konzentrieren, der in der RecyclerView angezeigt werden soll, indem wir nur die Modelle und die entsprechenden ViewHolder - Implementierungen implementieren. Mit dieser Basisklasse müssen wir uns nicht um die komplizierten Details der Adapter und ihrer SortedList kümmern.

SortedListAdapter

Aufgrund der Zeichenbeschränkung für Antworten in StackOverflow kann ich nicht jeden Schritt der Implementierung dieser Basisklasse ausführen oder den vollständigen Quellcode hinzufügen, aber Sie finden den vollständigen Quellcode dieser Basisklasse - ich habe ihn SortedListAdapter - in diesem GitHub Gist .

Um Ihnen das Leben zu vereinfachen, habe ich auf jCenter eine Bibliothek veröffentlicht, die die SortedListAdapter enthält! Wenn Sie es verwenden möchten, müssen Sie nur die folgende Abhängigkeit zur build.gradle-Datei Ihrer App hinzufügen:

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

Weitere Informationen zu dieser Bibliothek finden Sie auf der Homepage der Bibliothek .

Verwenden des SortedListAdapter

Um die SortedListAdapter zu verwenden, müssen wir zwei Änderungen vornehmen:

  • Ändern Sie die ViewHolder so, dass sie SortedListAdapter.ViewHolder Erweitert. Der Typparameter sollte das Modell sein, das an dieses ViewHolder gebunden werden soll - in diesem Fall ExampleModel. Sie müssen Daten in performBind() anstelle von bind() an Ihre Modelle binden.

    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);
        }
    }
    
  • Stellen Sie sicher, dass alle Ihre Modelle die Schnittstelle ViewModel implementieren:

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

Danach müssen wir nur noch die ExampleAdapter aktualisieren, um SortedListAdapter zu erweitern und alles zu entfernen, was wir nicht mehr brauchen. Der Typparameter sollte der Typ des Modells sein, mit dem Sie arbeiten - in diesem Fall ExampleModel. Wenn Sie jedoch mit verschiedenen Modelltypen arbeiten, setzen Sie den Typparameter auf 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);
    }
}

Danach sind wir fertig! Als letztes noch zu erwähnen: Die SortedListAdapter hat nicht die gleichen add(), remove() oder replaceAll() Methoden wie unsere ursprüngliche ExampleAdapter hätten. Es verwendet ein separates Objekt Editor, um die Elemente in der Liste zu ändern, auf die über die Methode edit() zugegriffen werden kann. Wenn Sie also Elemente entfernen oder hinzufügen möchten, müssen Sie edit() aufrufen. Fügen Sie dann die Elemente zu dieser Instanz Editor hinzu und entfernen Sie sie. Rufen Sie anschließend commit() auf es, um die Änderungen auf die SortedList anzuwenden:

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

Alle Änderungen, die Sie auf diese Weise vornehmen, werden stapelweise ausgeführt, um die Leistung zu steigern. Die replaceAll() -Methode, die wir in den obigen Kapiteln implementiert haben, ist auch in diesem Editor -Objekt vorhanden:

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

Wenn Sie vergessen, commit() aufzurufen, werden Ihre Änderungen nicht übernommen!

863
Xaver Kapeller

Alles, was Sie tun müssen, ist, filter Methode in RecyclerView.Adapter hinzuzufügen:

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 wird im Adapterkonstruktor wie itemsCopy.addAll(items) initialisiert.

Rufen Sie in diesem Fall einfach filter von OnQueryTextListener aus an:

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

Es ist ein Beispiel für das Filtern meines Telefonbuchs nach Name und Telefonnummer.

170
klimat

Wenn wir @Shruthi Kamoji auf eine sauberere Art und Weise folgen, können wir einfach ein filterbares verwenden, das dafür gedacht ist:

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

Das E hier ist ein generischer Typ, den Sie mit Ihrer Klasse erweitern können:

public class customerAdapter extends GenericRecycleAdapter<CustomerModel>

Oder ändern Sie einfach das E in den gewünschten Typ (z. B. <CustomerModel>).

Dann über searchView (das Widget, das Sie in menu.xml einfügen können):

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

erstellen Sie einfach zwei Listen in Adapter ein orignal und ein temp und implementiert 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();
            }
        };
    }

wo

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

Mit Android Architecture Components durch die Verwendung von LiveData kann dies problemlos mit jeder Art von Adapter implementiert werden. Sie müssen nur die folgenden Schritte ausführen:

1. Richten Sie Ihre Daten so ein, dass sie aus dem RaumDatenbank als LiveData wie im folgenden Beispiel zurückkehren:

@Dao
public interface CustomDAO{

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

2. Erstellen Sie ein ViewModel -Objekt, um Ihre Daten live mit einer Methode zu aktualisieren, die Ihr DAO und Ihr UI VERBINDET

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

}

. Rufen Sie Ihre Daten im laufenden Betrieb aus dem ViewModel auf, indem Sie die Abfrage wie folgt über onQueryTextListener übergeben:

In onCreateOptionsMenu stellen Sie Ihren Hörer wie folgt ein

searchView.setOnQueryTextListener(onQueryTextListener);

Richten Sie den Abfrage-Listener wie folgt an einer beliebigen Stelle in Ihrer SearchActivity-Klasse ein

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

Hinweis: Die Schritte (1.) und (2.) sind Standard AAC ViewModel und DAO Implementierung, die einzige echte "Magie" Hier ist im OnQueryTextListener, das die Ergebnisse Ihrer Liste dynamisch aktualisiert, wenn sich der Abfragetext ändert.

Wenn Sie weitere Informationen benötigen, zögern Sie bitte nicht zu fragen. Ich hoffe das hat geholfen :).

2
Panos Gr

Ich empfehle, die Lösung von @Xaver Kapeller mit den folgenden zwei Punkten zu ändern, um ein Problem zu vermeiden, nachdem Sie den gesuchten Text gelöscht haben (der Filter hat nicht mehr funktioniert), da die Liste auf der Rückseite des Adapters kleiner als die Filterliste ist und die IndexOutOfBoundsException aufgetreten ist. Der Code muss also wie folgt geändert werden

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

Und ändern Sie auch in moveItem Funktionalität

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

Hoffe, dass es dir helfen könnte!

0
toidv

Dies ist meine Einstellung zur Erweiterung von @klimat, um sicherzustellen, dass die Filteranimation nicht verloren geht.

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

Im Grunde geht es darum, eine vollständige Liste zu durchsuchen und einer gefilterten Liste nach der anderen Elemente hinzuzufügen/daraus zu entfernen.

0
AhmadF

Im Adapter:

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

In Aktivität:

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;
            }
        });
0
Firoz Ahmed