Реализация интерфейса Fiterable для пользовательского ArrayAdapter

Моя цель состоит в том, чтобы использовать ArrayAdapter для загрузки ListView с 20 пользовательскими объектами песен и иметь адаптер, чтобы иметь возможность возвращать соответствующие объекты песен, имя альбома которых содержит строку, которую пользователь вводит в представлении поиска.

Пока 20 песен отображаются, как и ожидалось, однако фильтр просто не работает: изменение в searchView не влияет на приложение. Я попытался установить «ограничение» фильтра на жестко закодированную строку, фильтр и на это не реагирует.

файл класса для объектов песни:

public class Track {
private String mSongName;
private String mArtistName;
private String mAlbumName;
private int mAlbumRelease;
private int mAlbumArt;

public Track (String artistName, String albumName, String songName, int releaseDate, int imageID) {
    mArtistName = artistName;
    mAlbumName = albumName;
    mSongName = songName;
    mAlbumRelease = releaseDate;
    mAlbumArt = imageID;
}
public String getArtistName(){
    return mArtistName;
}
public String getAlbumName(){
    return mAlbumName;
}
public String getSongName(){
    return mSongName;
}
public String getReleaseDate(){
    return Integer.toString(mAlbumRelease);
}
public int getAlbumArt(){
    return mAlbumArt;
}
}

class, используемый для загрузки ArrayList с 20 песнями:

public class RecordMixTape {
ArrayList<Track> mixTape = new ArrayList<>();
public void record (){
    mixTape.add(new Track("Lana del Rey", "Ultraviolence", "Black Beauty", 2014, R.drawable.ultraviolence));
    mixTape.add(new Track("Lana del Rey", "Young and Beautiful", "Young and Beautiful", 2013, R.drawable.young_and_beautiful));
    mixTape.add(new Track("Childish Gambino", "Redbone", "Redbone", 2016, R.drawable.redbone));
    mixTape.add(new Track("Coldplay", "A Head Full Of Dreams", "Fun", 2015, R.drawable.a_head_full_of_dreams));
    mixTape.add(new Track("Eminem", "Revival", "Tragic Endings", 2017, R.drawable.revival));
    mixTape.add(new Track("Phil Collins", "Face Value", "In The Air Tonight", 1981, R.drawable.face_value));
    mixTape.add(new Track("The Beatles", "A Hard Day's Night", "And I Love Her", 1964, R.drawable.a_head_full_of_dreams));
    mixTape.add(new Track("Shayne Ward", "Shayne Ward", "That's My Goal", 2006, R.drawable.shayne_ward));
    mixTape.add(new Track("Kanye West", "808s & Heartbreak", "Heartless", 2008, R.drawable.kanye_808s_heartbreak));
    mixTape.add(new Track("Jay-z & Eminem", "The Blueprint", "Renegade", 2001, R.drawable.the_blueprint));
    mixTape.add(new Track("Frank Ocean", "Novacane", "Novacane", 2011, R.drawable.novacane));
    mixTape.add(new Track("Pink Floyd", "The Dark Side Of The Moon", "Time", 1973, R.drawable.the_dark_side_of_the_moon));
    mixTape.add(new Track("The Antlers", "Hospice", "Kettering", 2009, R.drawable.hospice));
    mixTape.add(new Track("Justin Bieber", "Believe", "As Long As You Love Me", 2012, R.drawable.believe));
    mixTape.add(new Track("Robbie Williams", "Escapology", "Feel", 2002, R.drawable.escapology));
    mixTape.add(new Track("Elton John", "Goodbye Yellow Brick Road", "Candle In The Wind", 1973, R.drawable.goodbye_yellow_brick_road));
    mixTape.add(new Track("Michael Jackson", "Off The Wall", "She's Out Of My Life", 1979, R.drawable.off_the_wall));
    mixTape.add(new Track("Passenger", "All The Little Lights", "Let Her Go", 2012, R.drawable.all_the_little_lights));
    mixTape.add(new Track("Johnny Cash", "Unearthed", "Hurt", 2003, R.drawable.unearthed));
    mixTape.add(new Track("Sinead O'Connor", "I Do Not Want What I Haven't Got", "Nothing Compares 2 U", 1990, R.drawable.i_do_not_want_what_i_havent_got));
}
public ArrayList<Track> getList(){
    return mixTape;
}
}

здесь идет основной java-файл активности:

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ArrayList<Track> newMixTape = new ArrayList<>();
    //instantiate custom RecordMixTape object, and load an ArrayList with 20 Track objects
    RecordMixTape myMixTape = new RecordMixTape();
    myMixTape.record();
    newMixTape = myMixTape.getList();
    final TrackAdapter newTrackAdapter = new TrackAdapter(this, newMixTape);
    ListView listView = (ListView) findViewById(R.id.mixTapeList);
    final SearchView searchMsg = (SearchView) findViewById(R.id.search_msg);
    listView.setAdapter(newTrackAdapter);
    //have the searchView execute the filter method in the TrackAdapter
    searchMsg.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String query) {
            newTrackAdapter.getFilter().filter(query);
            return false;
        }

        @Override
        public boolean onQueryTextChange(String query) {
            newTrackAdapter.getFilter().filter(query);
            return false;
        }
    });
}
}

и это пользовательский ArrayAdapter:

public class TrackAdapter extends ArrayAdapter<Track> implements Filterable {
private ArrayList<Track> filteredList = null;
private ArrayList<Track> data = null;

public TrackAdapter(Activity context, ArrayList<Track> trackList) {
    super(context, 0, trackList);
    data = trackList;
}

@NonNull
@Override
public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    View listItemView = convertView;
    if (listItemView == null) {
        listItemView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false);
    }
    final Track currentTrack = getItem(position);
    //clone song information to the current convertView
    final TextView artistNameField = listItemView.findViewById(R.id.artistName);
    artistNameField.setText(currentTrack.getArtistName());
    final TextView albumNameField = listItemView.findViewById(R.id.albumName);
    albumNameField.setText(currentTrack.getAlbumName());
    final TextView songNameField = listItemView.findViewById(R.id.songName);
    songNameField.setText(currentTrack.getSongName());
    TextView albumReleaseDate = listItemView.findViewById(R.id.releaseDate);
    albumReleaseDate.setText(currentTrack.getReleaseDate());
    ImageView albumArtView = listItemView.findViewById(R.id.albumArt);
    albumArtView.setImageResource(currentTrack.getAlbumArt());
    albumArtView.setOnClickListener(new View.OnClickListener() {
        //intent which triggers another activity
        @Override
        public void onClick(View view) {
            Intent playThisTrack = new Intent(getContext(), NowPlayingActivity.class);
            playThisTrack.putExtra("index", position);
            getContext().startActivity(playThisTrack);
        }
    });

    return listItemView;
}

Filter myFilter = new Filter() {
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        String filterString = constraint.toString().toLowerCase();
        FilterResults results = new FilterResults();
        ArrayList<Track> resultList = new ArrayList<Track>();
        for (int i = 0; i < data.size(); i++) {
            Track item = data.get(i);
            if (item.getAlbumName().toLowerCase().contains(filterString)) {
                resultList.add(item);
            }
        }
        results.values = resultList;
        results.count = resultList.size();
        return results;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        filteredList = (ArrayList<Track>) results.values;
        notifyDataSetChanged();
    }
};

@Override
public Filter getFilter() {
    return myFilter;
}
}

и на всякий случай, это основная активность xml, которая загружает другой файл пользовательского макета для отображения песен:

<LinearLayout
android:id="@+id/parent_view"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.yunfrankzhang.mixtapeofsadness.NowPlayingActivity">
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <SearchView
        android:id="@+id/search_msg"
        android:layout_marginTop="8dp"
        android:layout_marginRight="8dp"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Search here"
        android:textSize="16dp"
        android:layout_weight="1"
        android:layout_marginLeft="8dp"/>
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mixTapeList">
</ListView>
</LinearLayout>

Пожалуйста, просветите меня, где я накосячил, троллите мой код, сколько хотите. Я благодарю вас от всего сердца.


person Frank Zhang    schedule 07.02.2018    source источник
comment
Можете ли вы создать минимальный пример, демонстрирующий проблему?   -  person BBB    schedule 07.02.2018
comment
ArrayAdapter - плохой класс для такой настройки ... он уже реализует фильтрацию ... Я бы попытался вызвать oldFilter.publishResults из вашего publishResults, где oldFilter берется внутри адаптера, вызвав super.getFilter() (но я бы не рекомендовал это) ... в TrackAdapter добавьте поле private Filter oldFilter = getFilter();, затем в publishResults просто вызовите `oldFilter.publishResults(ограничение, результаты)`   -  person Selvin    schedule 07.02.2018
comment
Вы хотели сделать data = (ArrayList<Track>) results.values?   -  person OneCricketeer    schedule 07.02.2018
comment
Проблема @BrunoCarvalhal в том, что filteredList никогда не используется ...   -  person Selvin    schedule 07.02.2018
comment
@cricket_007 это не поможет ... ArrayAdapter внутри использует собственную ссылку mData ... ArrayAdapter.getCount() => mData.size() ... было бы лучше, если бы он написал адаптер на основе BaseAdapter, а не ArrayAdapter   -  person Selvin    schedule 07.02.2018
comment
@Selvin Да, но в конструкторе супервызов mData = trackList, так что этот список нужно где-то обновить, и, как вы сказали, filteredData не используется   -  person OneCricketeer    schedule 07.02.2018
comment
@cricket_007 только что попытался установить для results.values ​​данные, приложение по-прежнему показывает тот же старый список. фильтр по-прежнему не работал.   -  person Frank Zhang    schedule 07.02.2018
comment
@Selvin Я думаю, вопрос в том, как я могу заставить адаптер обновить listView из обновленного массива данных?   -  person Frank Zhang    schedule 07.02.2018
comment
data = (ArrayList<Track>) results.values не изменит super.mData (который недоступен)   -  person Selvin    schedule 07.02.2018
comment
Если имя альбома возвращается из объекта в методе toString, ни одно из этих определений фильтра действительно не требуется stackoverflow.com/a/30416831/ 2308683   -  person OneCricketeer    schedule 07.02.2018
comment
data.clear(); data.addAll( (ArrayList<Track>) results.values); обновил бы список   -  person OneCricketeer    schedule 07.02.2018
comment
data тоже никогда не используется :) ... в любом случае ... переопределение toString кажется хорошим выбором для этого   -  person Selvin    schedule 07.02.2018
comment
@cricket_007 ага. Отличное исправление. сделали то, что хотели, я просто поработаю над решением для повторного заполнения списка данных на случай, если пользователь захочет выполнить другой запрос. Спасибо крикет.   -  person Frank Zhang    schedule 07.02.2018
comment
@cricket_007 Эй, крикет, так получилось, что я воспользовался твоей рекомендацией. Я переопределил метод .toString() в своем объекте Track и удалил весь код фильтра BS в своем ArrayAdapter. Угадайте, это прекрасно работает. Здесь все время есть этот элегантный фильтр по умолчанию, и я выцарапываю глаза, пытаясь заново изобрести чертово колесо. Большое спасибо.   -  person Frank Zhang    schedule 08.02.2018


Ответы (1)


иметь адаптер, чтобы иметь возможность возвращать соответствующие объекты песни, название альбома которых содержит строку, которую пользователь вводит в представлении поиска

Если это все, что вы пытаетесь отфильтровать, есть гораздо более простой способ.

Добавьте метод toString() к вашему объекту Track.

public String getAlbumName(){
    return mAlbumName;
}

@Override
public String toString() {
    return getAlbumName();
}

Тогда это просто сделает то, что вам нужно

newTrackAdapter.getFilter().filter("your input");
person OneCricketeer    schedule 08.02.2018