notifyDataSetChanged не работает на RecyclerView
Я получаю данные с сервера, затем анализирую их и сохраняю в списке. Я использую этот список для адаптера RecyclerView. Я использую фрагменты.
Я использую Nexus 5 с KitKat. Я использую библиотеку поддержки для этого. Будет ли это иметь значение?
Вот мой код: (используя фиктивные данные для вопроса)
Переменные-члены:
List<Business> mBusinesses = new ArrayList<Business>();
RecyclerView recyclerView;
RecyclerView.LayoutManager mLayoutManager;
BusinessAdapter mBusinessAdapter;
мой onCreateView()
:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Getting data from server
getBusinessesDataFromServer();
View view = inflater.inflate(R.layout.fragment_business_list,
container, false);
recyclerView = (RecyclerView) view
.findViewById(R.id.business_recycler_view);
recyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(mLayoutManager);
mBusinessAdapter = new BusinessAdapter(mBusinesses);
recyclerView.setAdapter(mBusinessAdapter);
return view;
}
После получения данных с сервера, parseResponse()
называется.
protected void parseResponse(JSONArray response, String url) {
// insert dummy data for demo
mBusinesses.clear();
Business business;
business = new Business();
business.setName("Google");
business.setDescription("Google HeadQuaters");
mBusinesses.add(business);
business = new Business();
business.setName("Yahoo");
business.setDescription("Yahoo HeadQuaters");
mBusinesses.add(business);
business = new Business();
business.setName("Microsoft");
business.setDescription("Microsoft HeadQuaters");
mBusinesses.add(business);
Log.d(Const.DEBUG, "Dummy Data Inserted\nBusinesses Length: "
+ mBusinesses.size());
mBusinessAdapter = new BusinessAdapter(mBusinesses);
mBusinessAdapter.notifyDataSetChanged();
}
Мой BusinessAdapter:
public class BusinessAdapter extends
RecyclerView.Adapter<BusinessAdapter.ViewHolder> {
private List<Business> mBusinesses = new ArrayList<Business>();
// Provide a reference to the type of views that you are using
// (custom viewholder)
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView mTextViewName;
public TextView mTextViewDescription;
public ImageView mImageViewLogo;
public ViewHolder(View v) {
super(v);
mTextViewName = (TextView) v
.findViewById(R.id.textView_company_name);
mTextViewDescription = (TextView) v
.findViewById(R.id.textView_company_description);
mImageViewLogo = (ImageView) v
.findViewById(R.id.imageView_company_logo);
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public BusinessAdapter(List<Business> myBusinesses) {
Log.d(Const.DEBUG, "BusinessAdapter -> constructor");
mBusinesses = myBusinesses;
}
// Create new views (invoked by the layout manager)
@Override
public BusinessAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
Log.d(Const.DEBUG, "BusinessAdapter -> onCreateViewHolder()");
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.item_business_list, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
Log.d(Const.DEBUG, "BusinessAdapter -> onBindViewHolder()");
Business item = mBusinesses.get(position);
holder.mTextViewName.setText(item.getName());
holder.mTextViewDescription.setText(item.getDescription());
holder.mImageViewLogo.setImageResource(R.drawable.ic_launcher);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
Log.d(Const.DEBUG, "BusinessAdapter -> getItemCount()");
if (mBusinesses != null) {
Log.d(Const.DEBUG, "mBusinesses Count: " + mBusinesses.size());
return mBusinesses.size();
}
return 0;
}
}
Но я не получаю данные, отображаемые в представлении. Что я делаю неправильно?
Вот мой журнал,
07-14 21:15:35.669: D/xxx(2259): Dummy Data Inserted
07-14 21:15:35.669: D/xxx(2259): Businesses Length: 3
07-14 21:26:26.969: D/xxx(2732): BusinessAdapter -> constructor
Я не получаю никаких журналов после этого. не должны getItemCount()
в адаптер надо вызывать снова?
9 ответов
В вашем parseResponse()
вы создаете новый экземпляр BusinessAdapter
класс, но вы на самом деле не используете его нигде, так что ваш RecyclerView
не знает, что новый экземпляр существует.
Вам либо нужно:
- Вызов
recyclerView.setAdapter(mBusinessAdapter)
снова, чтобы обновить ссылку на адаптер RecyclerView, чтобы указать на ваш новый - Или просто удалить
mBusinessAdapter = new BusinessAdapter(mBusinesses);
продолжить использовать существующий адаптер. Так как вы не изменилиmBusinesses
ссылка, адаптер будет по-прежнему использовать этот список массивов и должен корректно обновляться при вызовеnotifyDataSetChanged()
,
Попробуйте этот метод:
List<Business> mBusinesses2 = mBusinesses;
mBusinesses.clear();
mBusinesses.addAll(mBusinesses2);
//and do the notification
немного времени, но это должно работать.
Просто чтобы дополнить другие ответы, так как я не думаю, что кто-то упомянул это здесь: notifyDataSetChanged()
должен выполняться в главном потоке (другой notify<Something>
методы RecyclerView.Adapter
ну и конечно)
Из того, что я понимаю, так как у вас есть процедуры синтаксического анализа и призыв к notifyDataSetChanged()
в том же блоке вы либо вызываете его из рабочего потока, либо выполняете синтаксический анализ JSON в основном потоке (что, как я уверен, вы также знаете как нет-нет). Таким образом, правильный путь будет:
protected void parseResponse(JSONArray response, String url) {
// insert dummy data for demo
// <yadda yadda yadda>
mBusinessAdapter = new BusinessAdapter(mBusinesses);
// or just use recyclerView.post() or [Fragment]getView().post()
// instead, but make sure views haven't been destroyed while you were
// parsing
new Handler(Looper.getMainLooper()).post(new Runnable() {
public void run() {
mBusinessAdapter.notifyDataSetChanged();
}
});
}
PS Странная вещь, я не думаю, что вы получаете какие-либо указания на основной поток из IDE или журналов времени выполнения. Это только из моих личных наблюдений: если я позвоню notifyDataSetChanged()
из рабочего потока я не получаю обязательного. Только исходный поток, создавший иерархию представлений, может касаться своего сообщения представлений или чего-либо подобного - он просто молча завершается неудачей (и в моем случае один вызов вне основного потока может даже предотвратить успешное выполнение вызовов основного потока, возможно, из-за какого-либо состояния гонки)
Более того, ни в справочнике по API RecyclerView.Adapter, ни в соответствующем официальном руководстве разработчика явно не упоминаются основные требования к потокам на данный момент (на данный момент 2017), и ни одно из правил проверки линтов в Android Studio, похоже, также не касается этой проблемы.
Но вот объяснение этого самого автора
У меня была такая же проблема. Я просто решил это с объявлением adapter
перед публикой onCreate
класса.
PostAdapter postAdapter;
после этого
postAdapter = new PostAdapter(getActivity(), posts);
recList.setAdapter(postAdapter);
наконец я позвонил:
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// Display the size of your ArrayList
Log.i("TAG", "Size : " + posts.size());
progressBar.setVisibility(View.GONE);
postAdapter.notifyDataSetChanged();
}
Пусть это поможет вам.
Хотя это немного странно, но notifyDataSetChanged
не работает без установки новых значений для адаптера. Итак, вам следует сделать:
array = getNewItems();
((MyAdapter) mAdapter).setValues(array); // pass the new list to adapter !!!
mAdapter.notifyDataSetChanged();
Это сработало для меня.
В моем случае принудительный запуск #notifyDataSetChanged в основном потоке пользовательского интерфейса исправит
public void refresh() {
clearSelection();
// notifyDataSetChanged must run in main ui thread, if run in not ui thread, it will not update until manually scroll recyclerview
((Activity) ctx).runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
}
});
}
Удалите старую модель представления и установите новые данные для адаптера и вызовите notifyDataSetChanged()
У меня всегда есть эта проблема: я забываю, что RecyclerView ожидает новый экземпляр List каждый раз, когда вы загружаете адаптер.
List<X> deReferenced = new ArrayList(myList);
adapter.submitList(deReferenced);
Наличие «того же» списка (ссылки) означает не объявлять «новый», даже если размер списка изменяется, потому что изменения, внесенные в список, также распространяются на другие списки (когда они просто объявляются как
this.localOtherList = myList
) акцент на ключевом слове " = ", обычно компоненты, сравнивающие коллекции, делают копию результата постфактум и сохраняют его как "старый", но не Android DiffUtil.
Итак, если ваш компонент выдает один и тот же список каждый раз, когда вы его отправляете, RecyclerView не запускает новый проход макета. Причина в том, что ... AFAIR, прежде чем DiffUtil даже попытается применить алгоритм Майерса, есть строка, выполняющая:
if (newList == mList)) {return;}
Я не уверен, насколько «хорошая практика» разыменования в той же системе фактически определяется как «хорошая» ... Тем более, что ожидается, что алгоритм сравнения будет иметь новый (пересмотренный) и старый (исходный) компонент, который ДОЛЖЕН теоретически разыменовать коллекцию после завершения процесса, но ... кто знает ...?
Но подождите, есть еще кое-что ...
выполнение new ArrayList() разыменовывает список, НО по какой-то причине Oracle решила, что они должны создать второй «ArrayList» с тем же именем, но с другой функциональностью.
Этот ArrayList находится в классе Arrays.
/**
* Returns a fixed-size list backed by the specified array. (Changes to
* the returned list "write through" to the array.) This method acts
* as bridge between array-based and collection-based APIs, in
* combination with {@link Collection#toArray}. The returned list is
* serializable and implements {@link RandomAccess}.
*
* <p>This method also provides a convenient way to create a fixed-size
* list initialized to contain several elements:
* <pre>
* List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
* </pre>
*
* @param <T> the class of the objects in the array
* @param a the array by which the list will be backed
* @return a list view of the specified array
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a); //Here
}
Эта запись забавна, потому что если вы:
Integer[] localInts = new Integer[]{1, 2, 8};
Consumer<List<Integer>> intObserver;
public void getInts(Consumer<List<Integer>> intObserver) {
this.intObserver = intObserver;
dispatch();
}
private void dispatch() {
List<Integer> myIntegers = Arrays.asList(localInts);
intObserver.accept(myIntegers);
}
... потом:
getInts(
myInts -> {
adapter.submitList(myInts); //myInts = [1, 2, 8]
}
);
Мало того, что отправленный список подчиняется разыменованию при каждой отправке, но и когда
localInts
переменная изменена,
public void set(int index, Integer value) {
localInts[index] = value;
dispatch(); // dispatch again
}
...
myModel.set(1, 4) // localInts = [1, 4, 8]
это изменение также передается в список ВНУТРИ RecyclerView, это означает, что при следующей отправке
(newList == mList)
возвратит «ложь» , разрешающее Diffutils для запуска алгоритма Майера, НОareContentsTheSame(@NonNull T oldItem, @NonNull T newItem)
обратный звонок от
ItemCallback<T>
interface выдает значение «true» при достижении индекса 1. в основном, говоря, что «индекс 1 внутри RecyclerView(который должен был быть 2 в предыдущей версии) всегда был 4», и проход макета по-прежнему не выполняется.
Итак, путь в этом случае следующий:
List<Integer> trulyDereferenced = new ArrayList<>(Arrays.asList(localInts));
adapter.submitList(trulyDereferenced);
Решение для тех пользователей, которые используют обратную раскладку, например список чатов.
добавьте эту функцию в свой класс адаптера
fun updateChatList(msgData: DataDetails) { // DataDetails is custom model class
var tmpdata = ArrayList<DataDetails>()
tmpdata.add(msgData)
tmpdata.addAll(data) //data is your data define in adapter
data.clear()
data.addAll(tmpdata)
this.notifyDataSetChanged()
}