#android #android-fragments #android-recyclerview #android-viewpager2
#Android #android-фрагменты #android-recyclerview #android-viewpager2
Вопрос:
У меня есть основное действие, в котором размещены 5 фрагментов с view pager 2 (не сдвинутый) и нижней навигацией. Имя первого фрагмента — home fragment. Фрагмент Home содержит
1 просмотр пейджера: где показаны некоторые фотографии из firebase (используется адаптер пейджера)
2 просмотра в режиме рециркуляции: где некоторые элементы отображаются также из firebase (при нажатии на другое действие)
Проблема: Когда я просматриваю фрагменты с нижней навигацией, это работает нормально. Но когда я перехожу от 5-го фрагмента (самого последнего) к 1-му фрагменту (домашний фрагмент), иногда элементы в view pager, recycler view удваиваются с мерцанием (кажется, что это повторно добавленные элементы). То же самое происходит, когда я перехожу к другому действию и возвращаюсь к нему через некоторое время.
Адаптер просмотра Recycler
public class CourseListAdapter extends RecyclerView.Adapter<CourseListAdapter.CourseListViewHolder> {
public static final int HOME_PAGE = 1;
public static final int DISPLAY_COURSE = 2;
private Picasso mPicasso;
private List<DisplayCourse> courseList;
private static final String TAG = "CourseListAdapter";
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(int position, View view);
}
private String mSender;
public void setOnItemClickListener(OnItemClickListener listener) {
mListener = listener;
}
public CourseListAdapter(List<DisplayCourse> courseList, String sender) {
this.courseList = courseList;
this.mSender = sender;
mPicasso = Picasso.get();
}
@NonNull
@Override
public CourseListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case 1:
View view1 = inflater.inflate(R.layout.recom_course_home_layout, parent, false);
return new CourseListViewHolder(view1, mListener);
case 2:
View view2 = inflater.inflate(R.layout.display_course_layout, parent, false);
return new CourseListViewHolder(view2, mListener);
default:
View view3 = inflater.inflate(R.layout.section_video_item_layout, parent, false);
return new CourseListViewHolder(view3, mListener);
}
}
@Override
public void onBindViewHolder(@NonNull CourseListViewHolder holder, int position) {
holder.title.setText(courseList.get(position).getCourseTitle());
mPicasso.load(courseList.get(position).getThumbnailURL())
.networkPolicy(NetworkPolicy.OFFLINE)
.into(holder.thumbnail, new Callback() {
@Override
public void onSuccess() {
}
@Override
public void onError(Exception e) {
mPicasso.load(courseList.get(position).getThumbnailURL())
.error(R.drawable.ofklogo)
.into(holder.thumbnail, new Callback() {
@Override
public void onSuccess() {
}
@Override
public void onError(Exception e) {
}
});
}
});
}
@Override
public int getItemCount() {
return courseList.size();
}
public static class CourseListViewHolder extends RecyclerView.ViewHolder {
ImageView thumbnail;
TextView title;
public CourseListViewHolder(@NonNull View itemView, final OnItemClickListener listener) {
super(itemView);
title = itemView.findViewById(R.id.courseTitle);
thumbnail = itemView.findViewById(R.id.courseThumbNailImageView);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = getAdapterPosition();
if (listener != null) {
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position, view);
}
}
}
});
}
}
@Override
public int getItemViewType(int position) {
if (mSender.equals("home_page")) {
return HOME_PAGE;
} else if (mSender.equals("displayCourse")) {
return DISPLAY_COURSE;
}
return -1;
}
}
В домашнем фрагменте onCreateView() Я использую этот адаптер
mainActivityViewModel.getRandomCourseLiveData_2().observe(this, new Observer<DataSnapshot>() {
@Override
public void onChanged(DataSnapshot dataSnapshot) {
DisplayCourse course = dataSnapshot.getValue(DisplayCourse.class);
randomCourse_2.add(course);
recom_course_2 = new CourseListAdapter(randomCourse_2, "home_page");
binding.randomCourseRecyclerView2.setAdapter(recom_course_2);
recom_course_2.setOnItemClickListener(new CourseListAdapter.OnItemClickListener() {
@Override
public void onItemClick(int position, View view) {
Intent intent = new Intent(getActivity(), CourseActivity.class);
intent.putExtra("section_name", "Arts");
intent.putExtra("course_name", randomCourse_2.get(position).getCourseTitle());
intent.putExtra("course_name_english", randomCourse_2.get(position).getCourseTitleEnglish());
intent.putExtra("from", "home");
startActivity(intent);
Log.d(TAG, "onItemClick: " randomCourse_2.get(position).getCourseTitle());
Log.d(TAG, "onItemClick: " randomCourse_2.get(position).getCourseTitleEnglish());
}
});
}
});
Комментарии:
1. Пожалуйста, предоставьте свои примеры кода, возможно, RecyclerView. Реализация адаптера и то, как вы с ними работаете.
2. Я добавил код адаптера. пожалуйста, проверьте. Я пытаюсь решить это в течение 2 дней, но безуспешно
3. Похоже, с вашим адаптером все в порядке. Пожалуйста, покажите, где и как вы используете адаптер
4. @Link182 Я использую view model для извлечения данных из firebase, используя запрос firebase live data, подобный этому примеру ссылка
Ответ №1:
Вы создаете и заполняете представление recycler, наблюдая за LiveData
объектом. LiveData
в некоторых случаях старые события запускаются снова при воссоздании компонента Android, даже если модель представления не воссоздана. Это обычная практика для Google, использующих полурабочие решения, половина библиотек jetpack — блестящее дерьмо.
Чтобы решить эту проблему, вам нужно переключиться на лучшую библиотеку observer, такую как RxJava, или выполнить некоторые обходные пути, например, обернуть полезную нагрузку LiveData в объект, подобный этому https://github.com/Link184/ArchitectureMVVM/blob/master/library/src/main/java/com/link184/architecture/mvvm/common/Event.kt и наблюдать за этим, как здесь https://github.com/Link184/ArchitectureMVVM/blob/master/library/src/main/java/com/link184/architecture/mvvm/base/MvvmContext.kt
myLiveData.postValue(Event(MyPaload()))
myLiveData.observe(myLifecycleOwner) { event: Event<MyPayload> ->
event.getContentIfNotHandled()?.let {
handleEvent(it)
}
}
Другая возможная проблема, возможно, вы случайно запускаете событие дважды, трудно сказать, не видя общей картины.
Также быстрым обходным путем является удаление дубликатов из адаптера, для получения которого вам нужно только изменить свой ArrayList
на HashSet
private Set<DisplayCourse> courseList
Просто передайте элементы в конструкторе адаптера как HashSet
Комментарии:
1. спасибо за ваш ответ. не могли бы вы, пожалуйста, подсказать мне или указать направление, где я могу узнать об использовании set в классе адаптера? У меня нет никакого опыта работы с наборами.
2. Я пробовал с наборами. Я получаю данные в виде наборов и отправляю их на адаптер. там я преобразую набор в список массивов, чтобы получить содержимое с указанием позиции. список работает нормально. но он ведет себя так же, как и раньше. Я не понимаю. set хранит только уникальные данные. тогда как это происходит? Я так потерян @link182
3.
dataSnapshot.getValue(DisplayCourse.class)
возвращает правильный список объектов? Я имею в виду, без дубликатов? Попробуйте использовать debugger, чтобы увидеть, где / когда добавляются дубликаты? Также я вижу, что вы звонитеrandomCourse_2.add(course)
пожалуйста, дважды проверьте, где / как используется адаптер