Nano Hash - криптовалюты, майнинг, программирование

Получить текущий фрагмент с помощью ViewPager2

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

viewPager.getCurrentItem() дает текущий отображаемый индекс, а adapter.getItem(index) обычно создает новый Fragment для текущего индекса. Если не хранить ссылку на все созданные фрагменты в getItem(), я не знаю, как получить доступ к текущему отображаемому фрагменту.

В старом ViewPager одним из решений было вызвать adapter.instantiateItem(index), который вернет фрагмент по желаемому индексу.

Я что-то упустил с ViewPager2?


  • Получили ли вы какое-нибудь решение по вашему запросу? 23.05.2019
  • Нет, решения пока нет. 24.05.2019
  • fragmentManager.findFragmentById(getItemId(index).toInt()) изнутри FragmentStateAdapter, похоже, работает, но только после добавления фрагмента. Из документов это может не сработать, если фрагменты перемещены. Default implementation works for collections that don't add, move, remove items 18.12.2019
  • Просто общее замечание для будущих читателей (я знаю, что это не ваш вопрос). Если вы ищете способ уведомить фрагмент о том, что он отображен, вы можете использовать ViewModel и заставить каждый фрагмент соблюдать одну и ту же ViewModel. Тогда каждый фрагмент ответит, если их javaClass.name совпадает с переданным. 18.12.2019

Ответы:


1

В ViewPager2 FragmentManager по умолчанию присвоил фрагментам теги следующим образом:

Фрагмент в 1-й позиции имеет тег "f0"

Фрагмент во 2-й позиции имеет тег "f1"

Фрагмент в 3-й позиции имеет тег "f2" и так далее ... так что вы можете получить свой тег фрагмента и объединив f с позицией вашего фрагмента. Чтобы получить текущий фрагмент, вы можете получить текущую позицию из позиции viewPager2 и сделать свой тег следующим образом (для Kotlin):

val myFragment = supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)

Для фрагмента в определенной позиции

val myFragment = supportFragmentManager.findFragmentByTag("f" + position)

Вы можете использовать фрагмент и всегда проверять, не является ли он нулевым, если вы используете эту технику.

Если вы размещаете ViewPager2 во фрагменте, используйте вместо этого childFragmentManager.

ПОМНИТЕ

Если у вас overriden getItemId(position: Int) в вашем адаптере. Тогда ваш случай другой. Должен быть:

val myFragment = supportFragmentManager.findFragmentByTag("f" + your_id_at_that_position)

ИЛИ ПРОСТО:

val myFragment = supportFragmentManager.findFragmentByTag("f" + adapter.getItemId(position))

Если вы размещаете ViewPager2 во фрагменте, используйте childFragmentManager вместо supportFragmentManager.

12.04.2020
  • Обратите внимание, что если вы хотите использовать этот подход в хост-фрагменте, вы должны использовать childFragmentManager. 27.05.2020
  • Вы можете отредактировать ответ, чтобы добавить эти данные @Micer 27.05.2020
  • Важное примечание: если вы переопределите getItemId () в своем адаптере, этот шаблон не будет работать. Поскольку FragmentStateAdapter использует f + holder.getItemId () при добавлении фрагмента. По умолчанию getItemId возвращает позицию, но если вы переопределите, то оно будет иметь другое значение. 23.07.2020
  • Или это отличная работа, я думаю, поскольку вы переопределили getItemID, вы также знаете идентификатор каждого фрагмента, поэтому теги будут "f"+yourID.toString() @RuslanMyhal 23.07.2020
  • Что, если завтра они решат поменять метку? 06.10.2020
  • Есть и другие решения проблемы, такие как сохранение ссылки на фрагмент и т. Д. Но я не могу беспокоиться из-за чего-то с вероятностью появления 0,1%. @ Dr.aNdRO 06.10.2020
  • Как можно быть настолько уверенным в этом числе на 0,1%? 13.10.2020
  • Потому что в изменениях нет необходимости. Что могло быть причиной этого изменения? Также ViewPager2 - это библиотека с открытым исходным кодом, не привязанная к платформе Android. В принципе, я не вижу причин для беспокойства @Dr. АНДРО 14.10.2020
  • @ Dr.aNdRO прав, внутренняя схема именования не является частью API, поэтому она может измениться в любой момент, по любой причине, без какого-либо предупреждения. Это решение, которое работает, но оно хрупкое, и на него нельзя полагаться, поэтому, если это важно для вас, вам нужно знать о рисках. 20.10.2020
  • Что делать, если у вас есть несколько пейджеров просмотра? Мне совсем не нравится это решение 18.03.2021

  • 2

    У меня была аналогичная проблема при переходе на ViewPager2.

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

    Итак, в моем основном фрагменте (HostFragment), содержащем ViewPager2 представление, я создал следующее свойство:

    private var _currentPage: WeakReference<MyPageFragment>? = null
    val currentPage
        get() = _currentPage?.get()
    
    fun setCurrentPage(page: MyPageFragment) {
        _currentPage = WeakReference(page)
    }
    

    Я решил использовать WeakReference, чтобы не пропускать неактивные экземпляры фрагментов

    И каждый из моих фрагментов, которые я показываю внутри ViewPager2, наследуется от общего суперкласса MyPageFragment. Этот класс отвечает за регистрацию своего экземпляра во фрагменте хоста в onResume:

    override fun onResume() {
        super.onResume()
        (parentFragment as HostFragment).setCurrentPage(this)
    }
    

    Я также использовал этот класс для определения общего интерфейса выгружаемых фрагментов:

    abstract fun someOperation1()
    
    abstract fun someOperation2()
    

    И тогда я могу вызвать их из HostFragment вот так:

    currentPage?.someOperation1()
    

    Я не говорю, что это хорошее решение, но я думаю, что это более элегантно, чем полагаться на внутреннее устройство ViewPager's адаптера с instantiateItem методом, который нам приходилось использовать раньше.

    14.08.2019
  • Еще один действительно хороший ответ - переопределить функцию onPageSelected, показанную на этом сайте proandroiddev.com/look- deep-into-viewpager2-13eb8e06e419 30.03.2020
  • @BenAkin Это действительно хорошая статья, но я не смог найти там, как получить фрагмент в текущей позиции, вы можете поделиться ею здесь? 25.05.2020
  • @Micer, вам нужно создать такой класс ViewPager2PageChangeCallback (частный слушатель val: (Int) - ›Unit): ViewPager2.OnPageChangeCallback () {переопределить fun onPageSelected (position: Int) {super.onPageSelected (position) Log.d ( Util.DEBUG_LOG, в настоящее время это позиция: + позиция)}} затем .... в классе / действии, использующем фрагменты окна просмотра, добавьте это в onCreate: mPager.registerOnPageChangeCallback (viewPager2PageChangeCallback !!) 26.05.2020
  • @BenAkin этот обратный вызов дает вам position: Int, но у вас все еще нет объекта Fragment. Или я что-то упускаю? 28.05.2020

  • 3

    Решение найти текущий фрагмент по его тегу кажется мне наиболее подходящим. Для этого я создал следующие функции расширения:

    fun ViewPager2.findCurrentFragment(fragmentManager: FragmentManager): Fragment? {
        return fragmentManager.findFragmentByTag("f$currentItem")
    }
    
    fun ViewPager2.findFragmentAtPosition(
        fragmentManager: FragmentManager,
        position: Int
    ): Fragment? {
        return fragmentManager.findFragmentByTag("f$position")
    }
    
    • Если ваш хост ViewPager2 - Activity, используйте supportFragmentManager или fragmentManager.
    • Если ваш хост ViewPager2 Fragment, используйте childFragmentManager

    Обратите внимание, что:

    • findFragmentAtPosition будет работать только для фрагментов, которые были инициализированы в ViewPager2 RecyclerView. Таким образом, вы можете получить только те позиции, которые видны + 1.
    • Lint предложит вам удалить ViewPager2. из fun ViewPager2.findFragmentAtPosition, потому что вы не используете ничего из класса ViewPager2. Я думаю, что это должно остаться там, потому что это обходное решение применимо только к ViewPager2.
    28.05.2020

    4

    Мне удалось получить доступ к текущему фрагменту в FragmentStateAdapter, используя отражение.

    Функция расширения в Котлине:

    fun FragmentStateAdapter.getItem(position: Int): Fragment? {
        return this::class.superclasses.find { it == FragmentStateAdapter::class }
            ?.java?.getDeclaredField("mFragments")
            ?.let { field ->
                field.isAccessible = true
                val mFragments = field.get(this) as LongSparseArray<Fragment>
                return@let mFragments[getItemId(position)]
            }
    }
    

    При необходимости добавьте зависимость отражения Kotlin:

    implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
    

    Пример вызова:

    val tabsAdapter = viewpager.adapter as FragmentStateAdapter
    val currentFragment = tabsAdapter.getItem(viewpager.currentItem)
    
    07.12.2019
  • Спасибо, что указали на это. Добавлено примечание о зависимости отражения Kotlin. 07.12.2019
  • отражение дорого, посмотрите на мое решение. @AndrazP 28.03.2020
  • спасибо, это работает для меня @AndrazP 28.04.2021

  • 5
    supportFragmentManager.findFragmentByTag("f" + viewpager.currentItem)
    

    с FragmentStateAdapter во фрагменте placeFragmentInViewHolder(@NonNull final FragmentViewHolder holder)add

    mFragmentManager.beginTransaction()
                        .add(fragment, "f" + holder.getItemId())
                        .setMaxLifecycle(fragment, STARTED)
                        .commitNow()
    
    11.12.2019
  • Не могли бы вы добавить немного информации о почему и как эта строка кода дает ответ на вопрос? Спасибо... 11.12.2019
  • При отладке я вижу, что тег для добавленных фрагментов действительно равен f0, f1 и т. Д., Но мне интересно, насколько это надежно. 18.12.2019
  • да, как использовать этот фрагмент с адаптером состояния фрагмента? 31.01.2020
  • о, я понял это! используйте что-то вроде этого mSomeButton.setOnClickListener (v - ›{OneFragment oneFragment = (OneFragment) getSupportFragmentManager (). findFragmentByTag (f + 0); if (oneFragment! = null) oneFragment.setText (Hello);}); 31.01.2020

  • 6

    Если вы хотите, чтобы текущий Fragment выполнял только какое-то действие в нем, вы можете использовать SharedViewModel, который используется совместно контейнером ViewPager и его Fragments, и передать Identifier каждому фрагменту и наблюдать за LiveData в SharedViewModel. Установите значение этого LiveData на объект, который состоит из Identifier фрагмента, который вы хотите обновить (т.е. Pair<String, MyData>, который String является типом Identifier). Затем внутри ваших наблюдателей проверяют, совпадает ли текущий излучаемый Identifer с Identifier фрагмента или нет, и потребляют данные, если они равны.
    Это не так просто, как использовать теги фрагментов для их поиска. Но, по крайней мере, вам не нужно беспокоиться об изменениях в том, как ViewPager2 создавать теги для каждого фрагмента.

    26.01.2021

    7

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

    //Unclear how reliable this is
    fun getFragmentAtIndex(index: Int): Fragment? {
        return fm.findFragmentByTag("f${getItemId(index)}")
            ?: fm.findFragmentByTag("f$index")
            ?: fm.findFragmentById(getItemId(index).toInt())
            ?: fm.findFragmentById(index)
    }
    

    fm - это supportFragmentManager

    05.10.2020

    8

    Могу также опубликовать свое решение по этому поводу - это тот же базовый подход, что и у @Almighty, за исключением того, что я сохраняю Fragment слабые ссылки в таблице поиска в PagerAdapter:

    private class PagerAdapter(fm: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) {
    
        // only store as weak references, so you're not holding discarded fragments in memory
        private val fragmentCache = mutableMapOf<Int, WeakReference<Fragment>>()
    
        override fun getItemCount(): Int = tabList.size
        
        override fun createFragment(position: Int): Fragment {
            // return the cached fragment if there is one
            fragmentCache[position]?.get()?.let { return it }
    
            // no fragment found, time to make one - instantiate one however you
            // like and add it to the cache
            return tabList[position].fragment.newInstance()
                .also { fragmentCache[position] = WeakReference(it) }
                .also { Timber.d("Created a fragment! $it") }
        }
    
        // not necessary, but I think the code reads better if you
        // can use a getter when you want to... try to get an existing thing
        fun getFragment(position: Int) = createFragment(position)
    }
    

    а затем вы можете позвонить getFragment, указав соответствующий номер страницы, например adapter.currentPage или что-то еще.

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


    Если хотите, вы можете заставить геттер просто возвращать результат поиска (допускающий значение NULL). Эта версия, очевидно, создает фрагмент, если он еще не существует, что полезно, если вы ожидаете, что он там будет. Это может быть удобно, если вы используете ViewPager2.OnPageChangeCallback, который будет запускаться с новым номером страницы до, когда пейджер представления создаст фрагмент - вы можете получить страницу, которая создаст и кэширует ее, и когда пейджер вызывает createFragment, он все еще должен быть в кеше, и его нельзя воссоздавать заново.

    Тем не менее, не гарантируется, что слабая ссылка не будет собрана мусором между этими двумя моментами, поэтому, если вы устанавливаете что-то в этом экземпляре фрагмента (а не просто читаете что-то из него, например заголовок, вы хочу отобразить) имейте это в виду!

    21.10.2020

    9

    ViewPagerAdapter предназначен для скрытия всех этих деталей реализации, поэтому прямого способа сделать это не существует.

    Вы можете попробовать установить и идентификатор или тег для фрагмента при его создании в getItem(), а затем использовать fragmentManager.findFragmentById() или fragmentManager.findFragmentByTag() для извлечения.

    Однако делать это так - это немного похоже на запах кода. Это подсказывает мне, что что-то делается в действии, тогда как это должно быть сделано во фрагменте (или где-то еще).

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

    17.04.2019

    10

    У меня такая же проблема. Я преобразовал ViewPager в ViewPager2, используя FragmentStateAdapter. В моем случае у меня есть класс DetailActivity (расширяет AppCompatActivity), в котором находится ViewPager2, который используется для постраничного просмотра списков данных (контакты, мультимедиа и т. Д.) На устройствах меньшего форм-фактора.

    Мне нужно знать показанный в данный момент фрагмент (это мой собственный класс DetailFragment, который расширяет androidx.fragment.app.Fragment), потому что этот класс содержит строку, которую я использую для обновления заголовка на панели инструментов DetailActivity.

    Сначала я начал регистрировать прослушиватель onPageChangeCallback, как предлагали некоторые, но быстро столкнулся с проблемами:

    1. Сначала я создал теги во время вызова adapter.createFragment() ViewPager2, как это было предложено некоторыми, с идеей добавить вновь созданный фрагмент в объект Bundle (используя FragmentManager.put()) с этим тегом. Таким образом, я мог затем сохранить их при изменении конфигурации. Проблема здесь в том, что во время createFragment() фрагмент фактически еще не является частью FragmentManager, поэтому вызовы put () терпят неудачу.
    2. Другая проблема заключается в том, что если основная идея состоит в том, чтобы использовать метод onPageSelected () OnPageChangeCallback для поиска фрагмента с использованием внутренне сгенерированных имен тегов позиции f + - у нас снова возникает та же проблема синхронизации: самый первый раз в onPageSelected ( ) вызывается ДО вызова createFragment() на адаптере, поэтому еще нет фрагментов, созданных и добавленных в FragmentManager, поэтому я не могу получить ссылку на этот первый фрагмент с помощью тега f0.
    3. Затем я попытался найти способ, в котором я мог бы сохранить позицию, переданную в onPageSelected, а затем попытаться ссылаться на это где-нибудь еще, чтобы получить фрагмент после того, как адаптер сделал вызовы createFragment(), но я не смог идентифицировать какой-либо тип обработчика внутри адаптер, связанный с ним recyclerview, viewpager и т. д., который позволяет мне отображать список фрагментов, на которые я мог бы затем ссылаться на позицию, идентифицированную в этом слушателе. Как ни странно, например, один метод адаптера, который выглядел очень многообещающим, был onViewAttachedToWindow() - однако он помечен как окончательный, поэтому его нельзя переопределить (хотя JavaDoc явно предполагает, что он будет использоваться таким образом).

    Итак, что я сделал, что сработало для меня, было следующее:

    1. В моем классе DetailFragment я создал интерфейс, который может быть реализован хостингом:
        public interface DetailFragmentShownListener {
            // Allows classes that extend this to update visual items after shown
            void onDetailFragmentShown(DetailFragment me);
        }
    
    1. Затем я добавил код в onResume () в DetailFragment, чтобы увидеть, реализовало ли связанное действие интерфейс DetailFragmentShownListener в этом классе, и если да, то выполняю обратный вызов:
        public void onResume() {
            super.onResume();
            View v = getView();
            if (v!=null && v.getViewTreeObserver().isAlive()) {
                v.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        v.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                        // Let our parent know we are laid out
                        if ( getActivity() instanceof DetailFragmentShownListener ) {
                            ((DetailFragmentShownListener) getActivity()).onDetailFragmentShown(DetailFragment.this);
                        }
                    }
                });
            }
        }
    
    1. Затем, когда мне нужно знать, когда отображается этот фрагмент (например, в DetailActivity), я реализую интерфейс, и когда он получает этот обратный вызов, я знаю, что это текущий фрагмент:
        @Override
        public void onDetailFragmentShown(DetailFragment me) {
            mCurrentFragment = me;
            updateToolbarTitle();
        }
    

    mCurrentFragment - свойство этого класса, так как оно используется в других местах.

    06.11.2020

    11

    Решение для получения фрагмента по позиции:

    class ClubPhotoAdapter(
        private val dataList: List<MyData>,
        fm: FragmentManager,
        lifecycle: Lifecycle
    ) : FragmentStateAdapter(fm, lifecycle) {
    
        private val fragmentMap = mutableMapOf<Int, WeakReference<MyFragment>>()
    
        override fun getItemCount(): Int = dataList.size
    
        override fun createFragment(position: Int): Fragment {
            val fragment = MyFragment[dataList.getOrNull(position)]
    
            fragmentMap[position] = WeakReference(fragment)
    
            return fragment
        }
    
        fun getItem(position: Int): MyFragment? = fragmentMap[position]?.get()
    }
    
    27.05.2021

    12

    столкнулся с той же проблемой, теперь она решена путем добавления одного объекта в адаптер

    class MyViewPager2Adapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
    
    
        private val FRAGMENTS_SIZE = 2
    
        var currentFragmentWeakReference: WeakReference<Fragment>? = null
    
        override fun getItemCount(): Int {
            return this.FRAGMENTS_SIZE
        }
    
        override fun createFragment(position: Int): Fragment {
    
            when (position) {
                0 -> {
                    currentFragmentWeakReference= MyFirstFragment()
                    return MyFirstFragment()
                }
                1 -> {
                    currentFragmentWeakReference= MySecondFragment()
                    return MySecondFragment()
                }
            }
    
            return MyFirstFragment() /for default view
    
        }
    
    }
    

    после создания адаптера я зарегистрировал свой Viewpager 2 с ViewPager2.OnPageChangeCallback() и переопределил его метод onPageSelected

    now simple сделал этот трюк, чтобы получить текущий фрагмент

     private fun getCurrentFragment() :Fragment?{
    
            val fragment = (binding!!.pager.adapter as MyViewPager2Adapter).currentFragmentWeakReference?.get()
    
            retrun fragment
        }
    

    Я тестировал это только с двумя фрагментами в ViewPager2.

    Ура, ребята, надеюсь, это может вам помочь.

    28.03.2020
  • Это решение не обрабатывает изменения конфигурации процесса (т.е. телефон убивает приложение в фоновом режиме) - createFragment не вызывается, когда это происходит, поскольку он просто восстанавливает предыдущий статус фрагмента. Ваш weakReference будет нулевым. 14.05.2020
  • тогда вы протечете 21.06.2020
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..