Установка RecyclerViews itemAnimator в ноль не удаляет анимацию - PullRequest
0 голосов
/ 25 апреля 2018

Мой внешний RecyclerView аварийно завершает работу с

IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true...

или

IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

Как видно из названия, у меня есть RecyclerView в макете элемента списка первого RecyclerView.Этот макет используется для отображения сообщений, а внутренний RecyclerView для отображения вложений, которые идут с сообщением.Внутренняя RecyclerViews видимость установлена ​​на GONE или VISIBLE в зависимости от того, есть ли в сообщении какие-либо вложения или нет.Упрощенная внешняя компоновка элементов списка выглядит следующим образом

ConstraintLayout
    TextView
    TextView
    TextView
    RecyclerView

А часть адаптера, которая обрабатывает внутренний RecyclerView, выглядит следующим образом

private fun bindFiles(message: Message?) = with(itemView) {
      if (message != null && message.attachments.isNotEmpty())
      {
            sent_message_attachments.setAsVisible()
            sent_message_attachments.layoutManager = GridLayoutManager(this.context,Math.min(message.attachments.size,3))
            sent_message_attachments.adapter = AttachmentAdapter(message.attachments)
            sent_message_attachments.itemAnimator = null
            sent_message_attachments.setHasFixedSize(true)
      }
      else{
            sent_message_attachments.setAsGone()
            sent_message_attachments.adapter = null
            sent_message_attachments.layoutManager = null
      }
    }

Ошибка связана сспособ, которым я получаю вложения во внутреннем адаптере, поскольку, как только я отключаю часть, которая запускает процесс загрузки, все в порядке.При загрузке изображений с устройства проблем нет, но как только я запускаю процесс загрузки, все идет к черту.Это та часть, которая обрабатывает изображения и запускает процесс загрузки во внутреннем адаптере.У меня есть функции для видео и для других типов файлов, которые в значительной степени совпадают, но используют немного другой макет.

private fun bindImage(item: HFile?) = with(itemView) {
      if (item != null)
      {
        if (item.isOnDevice && !item.path.isNullOrEmpty())
        {
          if (item.isGif)
          {
            attachment_image.displayGif(File(item.path))
          }
          else
          {
            attachment_image.displayImage(File(item.path))
          }
        }
        else
        {
          //TODO: Add option to load images manually
          FileHandler(item.id).downloadFileAsObservable(false)
              .subscribeOn(Schedulers.io())
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe(
                  { progress ->
                    //TODO: Show download process
                  },
                  { error -> 
                  error.printStackTrace()
                  //TODO: Enable manual retry 
                  },
                  { notifyItemChanged(adapterPosition)} //onComplete
              )
        }
      }
  }

Я использую ту же структуру, что и выше, в моих DiscussionListAdapter для загрузки портретов для обсуждения (изображения профиля и т. д.) и у него нет той же проблемы.

Это функции расширений, используемые для надувания viewHolders и для отображения изображений

fun ViewGroup.inflate(layoutRes: Int): View
{
  return LayoutInflater.from(context).inflate(layoutRes, this, false)
}

fun ImageView.displayGif(file:File){
  GlideApp.with(context).asGif().load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}

fun ImageView.displayImage(file:File){
  GlideApp.with(context).load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}

Я был на этомв течение прошлых нескольких дней и просто не могу обдумать это.Любая помощь в любом направлении очень ценится.Я знаю, что мои объяснения могут быть немного повсюду, поэтому просто попросите разъяснений, когда это необходимо:)

ОБНОВЛЕНИЕ

Теперь я смог произвести это с GridLayout, а такжес RecyclerView.Можно с уверенностью предположить, что вложенные RecyclerViews не были виновником здесь.Я даже пытался отказаться от Rx-компонента, который обрабатывал загрузку изображений и создал IntentService для процесса, но такие же сбои все еще происходят.

С GridLayout Я имею в виду, что вместо того, чтобы иметь другой адаптер длязаполнение вложенного RecyclerView Я использую только один адаптер для заполнения сообщения, а также для надувания и заполнения представлений для вложений, а также для присоединения этих представлений к вложенному GridLayout.

Сбой происходит при запускечтобы загрузить файл и затем прокрутить представление, которое должно показать загруженный файл, из экрана.Это представление должно быть переработано, но по какой-то причине процесс загрузки (который в моих тестовых примерах занимает всего около 100-400 мс) заставляет приложение выдавать одну из двух ошибок, упомянутых в исходном вопросе.Возможно, стоит отметить, что я использую Realm, а адаптер получает список RealmResults<Message> в качестве набора данных.Мой докладчик ищет изменения в списке, а затем уведомляет адаптер при необходимости (измененный из-за реализации IntentService).

Вот так я могу воспроизводить это снова и снова:

  1. Открыть дискуссию, в которой есть сообщения с вложениями
  2. Начать прокрутку вверх для просмотра дополнительных сообщений
  3. Передать сообщение с вложением и вывести его из экрана, пока он еще загружается
  4. Crash

Не произойдет сбоя, если я остановлюсь и буду ждать завершения загрузки, и все будет работать, как задумано.Изображение / видео / файл обновляются с соответствующим уменьшенным изображением, и приложение не будет зависать, если я прокручиваю его из поля зрения.

ОБНОВЛЕНИЕ 2

Я попытался заменить вложенный ViewGroup на одинImageView просто чтобы увидеть, есть проблема во вложенности.И вот!Это все еще падает.Теперь я действительно сбит с толку, так как DiscussionListAdapter, о котором я упоминал ранее, содержит в себе ту же самую вещь, и она работает как шарм ... Мой поиск продолжается.Надеюсь, кто-нибудь когда-нибудь получит пользу от моей агонии.

ОБНОВЛЕНИЕ 3

Я начал регистрировать родителя каждого ViewHolder в функции onBindViewHolder().Как и ожидалось, я получил nulls после nulls после nulls, до того, как приложение вылетело и извергло это.

04-26 21:54:50.718 27075-27075/com.hailer.hailer.dev D/MsgAdapter: Parent of ViewHolder: android.view.ViewOverlay$OverlayViewGroup{82a9fbc V.E...... .......D 0,0-1440,2168}

В конце концов, в моем безумии есть метод! Но это только ставит больше вопросов. Почему ViewOverlay используется здесь? Как часть RecyclerView или как часть темных магов планирует лишить меня моего здравомыслия?

Sidenote

Я начал копаться в RecyclerViews коде, чтобы проверить, могу ли я найти причину для ViewOverlay тайны. Я обнаружил, что RecyclerView вызывает функцию адаптеров onCreateViewHolder() только дважды. Оба раза предоставили себя в качестве аргумента parent для функции. Так что не повезло ... Что, черт возьми, может заставить представление элемента иметь ViewOverlay в качестве родителя? Родитель является неизменным значением, поэтому единственный способ для ViewOverlay быть установленным в качестве родителя - это создать что-то новое ViewHolder и предоставить ViewOverlay в качестве родительского объекта.

ОБНОВЛЕНИЕ 4

Иногда я поражаюсь своей собственной глупости. ViewOverlay используется, потому что элементы анимируются. Я даже не рассматривал это как вариант, поскольку я установил itemAnimator для RecyclerView как null, но по какой-то странной причине это не работает. Предметы все еще оживляются, и это вызывает всю эту шараду. Так что может быть причиной этого? (Как я решил игнорировать движущиеся элементы, я не знаю, но анимация стала очень ясной, когда я заставлял приложение загружать одно и то же изображение снова и снова, и весь список становился беспорядочным.)

My DiscussionInstanceFragment содержит рассматриваемый RecyclerView и вложенный ConstraintLayout, который, в свою очередь, содержит EditText для ввода пользователя и кнопку отправки.

    val v = inflater.inflate(R.layout.fragment_discussion_instance, container, false)
    val lm = LinearLayoutManager(context)
    lm.reverseLayout = true
    v.disc_instance_messages_list.layoutManager = lm
    v.disc_instance_messages_list.itemAnimator = null
    v.disc_instance_messages_list.adapter = mPresenter.messageAdapter

Это часть, которая обрабатывает инициализацию RecyclerView. Я определенно устанавливаю itemAnimator как null, но анимация просто не остановится! Я попытался установить атрибут animateLayoutChanges xml в корне ConstraintLayout и RecyclerView, но ни один из них не сработал. Стоит отметить, что я также проверил, имел ли RecyclerView itemAnimator в разных состояниях программы, и каждый раз, когда я проверял аниматор, он был нулевым. Так что же оживляет мою RecyclerView?!

Ответы [ 2 ]

0 голосов
/ 27 апреля 2018

Я наконец понял, что это вызвало.В моем DiscussionInstanceView у меня есть маленький view, который анимируется в и из поля зрения с ConstraintLayout анимациями ключевых кадров.Это представление показывает только ход загрузки истории чата и используется только один раз, когда дискуссия открывается впервые.НО, поскольку у меня был призыв скрывать это представление каждый раз, когда мой набор данных обновлялся, я заставлял ConstraintLayout запускать последовательность анимации, таким образом, все обновлялось во время обновления набора данных.Я просто добавил простую проверку, загружал ли я историю или нет, и эта проблема была исправлена.

0 голосов
/ 25 апреля 2018

Я столкнулся с той же проблемой

Попробуй это у своего ребенка RecyclerView у меня это работает

RecyclerView childRC = itemView.findViewById(R.id.cmol_childRC);
layoutManager = new LinearLayoutManager(context);
childRC.setItemAnimator(null);
childRC.setLayoutManager(layoutManager);
childRC.setNestedScrollingEnabled(false);
childRC.setHasFixedSize(true);

теперь установите Adapter вот так

ArrayList<Model> childArryList = new ArrayList<>();
childArryList.addAll(arrayList.get(position).getArrayList());
ChildOrderAdapter adapter = new ChildOrderAdapter(context, childArryList);
holder.childRC.swapAdapter(adapter, true);

надеюсь, это поможет

...