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

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

Вопрос. Как получить экземпляр Fragment 2?

Я видел другие решения, но ни одно из них не работает для этого сценария.

Аннотация: возвращаемый фрагмент не обязательно должен быть размещен в ViewPager. Я мог бы открыть еще два фрагмента во вкладке.

сценарий

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

public ArrayList<Fragment> getVisibleFragment() {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    ArrayList<Fragment> visibleFragments = new ArrayList<>();
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (fragment != null && fragment.isVisible())
                visibleFragments.add(fragment);
        }
    }
    return visibleFragments;
}

Несколько интересных кодов

Activity_main.xml

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="fixed"
            app:tabGravity="fill"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private static ViewPagerAdapter adapter;
    private static ViewPager viewPager;
    private TabLayout tabLayout;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewPager = (ViewPager) findViewById(R.id.viewpager);
        setupViewPager();

        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(viewPager);
        setupTabIcons();
    }

    private void setupViewPager() {
        adapter = new ViewPagerAdapter(getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment1()), null);
        adapter.addFrag(HostFragment.newInstance(new RootFragment2()), null);
        adapter.addFrag(HostFragment.newInstance(new Fragment4()), null);
        viewPager.setAdapter(adapter);
        viewPager.setOffscreenPageLimit(2);
    }

    public void openNewFragment(Fragment fragment) {
        HostFragment hostFragment = (HostFragment) adapter.getItem(viewPager.getCurrentItem());
        hostFragment.replaceFragment(fragment, true);
    }
}

фрагмент_хост.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/hosted_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Хостфрагмент.java

/**
 * This class implements separate navigation for a tabbed viewpager.
 *
 * Based on https://medium.com/@nilan/separate-back-navigation-for-
 * a-tabbed-view-pager-in-android-459859f607e4#.u96of4m4x
 */
public class HostFragment extends BackStackFragment {

    private Fragment fragment;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.fragment_host, container, false);
        if (fragment != null) {
            replaceFragment(fragment, false);
        }
        return view;
    }

    public void replaceFragment(Fragment fragment, boolean addToBackstack) {
        if (addToBackstack) {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
        } else {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
        }
    }

    public static HostFragment newInstance(Fragment fragment) {
        HostFragment hostFragment = new HostFragment();
        hostFragment.fragment = fragment;
        return hostFragment;
    }

    public Fragment getFragment() {
        return fragment;
    }
}

фрагмент2_root.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/fragment2_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.TabLayout
        android:id="@+id/tab2_tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabMode="fixed"
        app:tabGravity="fill"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/tab2_viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</LinearLayout>

RootFragment2.java

public class RootFragment2 extends Fragment {

    private ViewPagerAdapter adapter;
    private ViewPager viewPager;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Inflate the layout for this fragment.
        View root = inflater.inflate(R.layout.fragment2_root, container, false);

        viewPager = (ViewPager) root.findViewById(R.id.tab2_viewpager);
        setupViewPager(viewPager);

        TabLayout tabLayout = (TabLayout) root.findViewById(R.id.tab2_tabs);
        tabLayout.setupWithViewPager(viewPager);

        return root;
    }

    private void setupViewPager(ViewPager viewPager) {
        adapter = new ViewPagerAdapter(getActivity().getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment2()), null);
        adapter.addFrag(HostFragment.newInstance(new Fragment3()), null);
        viewPager.setAdapter(adapter);
        viewPager.setOffscreenPageLimit(1);
    }

    public ViewPagerAdapter getAdapter() {
        return adapter;
    }

    public ViewPager getViewPager() {
        return viewPager;
    }
}

person Santiago Gil    schedule 25.08.2016    source источник


Ответы (4)


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

SparseArray<Fragment> registeredFragments = new SparseArray<>();

И переопределите метод instantiateItem ваших адаптеров.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment fragment = (Fragment) super.instantiateItem(container, position);
    registeredFragments.put(position, fragment);
    return fragment;
}

Также переопределите метод destroyItem вашего ViewPagers

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    registeredFragments.remove(position);
    super.destroyItem(container, position, object);
}

И определите новый метод для получения экземпляра ViewPager Fragments.

public Fragment getRegisteredFragment(int position) {
    return registeredFragments.get(position);
}

И, наконец, добавьте PageChangeListener к вашему ViewPagers:

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        // Here's your instance
        YourFragment fragment =(YourFragment)yourPagerAdapter.getRegisteredFragment(position);

    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

Я надеюсь, что это поможет вам. Удачи.

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

public void setCurrentFragment(Fragment fragment){
      this.currentFragment = fragment;
}

и в адаптере пейджера вашего подвида вы можете вызвать этот метод, как показано ниже:

subViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        // Here's your instance
        YourFragment fragment =(YourFragment)yourSubPagerAdapter.getRegisteredFragment(position);
        ((MyActivity)getActivity).setCurrentFragment(fragment);
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

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

person savepopulation    schedule 01.09.2016
comment
1. Можете ли вы объяснить, что я делаю (и какие последствия), если я переопределяю instantiateItem и destroyItem? 2. Кроме того, должен ли я сохранить YourFragment fragment из onPageSelected в глобальный атрибут currentFragment, чтобы иметь доступ к нему, когда захочу? 3. И, поскольку у меня есть внутренний Viewpager, нужно ли мне два currentFragment? Я имею в виду, один RootFragment2 в MainActivity и один Fragment2 в RootFragment2. - person Santiago Gil; 01.09.2016
comment
1- Вы просто сохраните экземпляры своих фрагментов, которые будете использовать в своих пейджерах. Когда вы создаете новый фрагмент в своем адаптере ViewPager, вы добавляете его ссылку на разреженный массив и получаете его, когда вам нужно. 2- Да, вы можете сделать это. 3- Да, вы можете использовать 2 текущих фрагмента или оставить один экземпляр, это зависит от вас. - person savepopulation; 01.09.2016
comment
Хорошо, я реализовал это, но не работает так, как хотелось бы. Я сохраняю только fragment; это означает, что onPageSelected основного и внутреннего VP сохраняет fragment в глобальном атрибуте. Когда отображается Fragment2, currentFragment равно RootFragment2. - person Santiago Gil; 01.09.2016
comment
Хорошо, потому что последняя выбранная страница должна была перейти на tab_b. Однако она отображается tab_b1 хотя и не выбрана (по умолчанию в вьюпейджере всегда есть открытая страница). - person Santiago Gil; 01.09.2016
comment
@SantiGil как дела? мое решение работает для вас? - person savepopulation; 02.09.2016
comment
Ваше решение работает, но только для основного вице-президента. Когда у вас есть внутренние VP, это терпит неудачу, потому что когда выбрано tab_b, currentFragment равно RootFragment2 (это не учитывает внутренний ViewPager). Это происходит, несмотря на то, что я впервые запускаю руководство с правильным значением. Я решил это по-другому, позвольте мне опубликовать ответ, чтобы вы могли его увидеть. - person Santiago Gil; 02.09.2016
comment
Да, это то, что я сделал. Два VP, основной и внутренний, устанавливают это значение при выборе страницы. Однако, когда выбран tab_b, tab_b1 и tab_b2 не выбраны, но отображается один из них. Таким образом, currentFragment является корнем, который содержит внутренний просмотрщик, потому что последней выбранной страницей была tab_b. - person Santiago Gil; 02.09.2016
comment
я думаю, что что-то не так с вашей реализацией. - person savepopulation; 02.09.2016
comment
Как насчет хранения ссылок на фрагменты? возможно, вам следует использовать массив WeakReferences - person Lester; 23.10.2016
comment
мы удаляем ссылку на фрагмент из разреженного массива методом DestroyItem. поэтому я думаю, что нет необходимости использовать массив wekreferences. - person savepopulation; 09.04.2018
comment
Есть ли здесь вероятность утечки памяти? Сколько ссылок на фрагменты будет храниться в SparseArray одновременно? - person Rishab Jaiswal; 30.11.2018
comment
здесь нет возможных утечек памяти, потому что, когда мы уничтожаем элемент, мы удаляем его из разреженного массива. количество фрагментов будет храниться в SparseArray за один раз, зависит от offScreenPageLimit вашего viewpager. - person savepopulation; 30.11.2018

После некоторых комментариев с DEADMC я решил реализовать его с дополнительной памятью, сохранив в списке открытые фрагменты.

Я объясняю свое решение. У меня есть два типа специальных фрагментов: host и root. Фрагменты хоста — это те, которые являются инициализацией вкладки. Корневые фрагменты — это те, которые содержат ViewPager. В этом сценарии tab_b имеет корневой фрагмент с внутренним ViewPager.

Иерархия

Для каждой вкладки, то есть для каждого HostFragment, я добавил список fragments для сохранения фрагментов, открытых на этой вкладке. Также метод getCurrentFragment для отображения последнего на этой вкладке.

public class HostFragment extends BackStackFragment {

    private ArrayList<Fragment> fragments = new ArrayList<>();

    public Fragment getCurrentFragment() {
        return fragments.get(fragments.size() - 1);
    }

Также необходим способ удаления последнего фрагмента, отображаемого на этой вкладке, для сохранения истинного состояния. Кнопка «Назад» должна быть перенаправлена ​​на этот метод.

    public void removeCurrentFragment() {
        getChildFragmentManager().popBackStack();
        fragments.remove(fragments.size() - 1);
    }

Также необходимо изменить предыдущие методы, чтобы вставить в список новые открытые фрагменты.

    public void replaceFragment(Fragment fragment, boolean addToBackstack) {
        // NEW: Add new fragment to the list.
        fragments.add(fragment);
        if (addToBackstack) {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
        } else {
            getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
        }
    }

    public static HostFragment newInstance(Fragment fragment) {
        HostFragment hostFragment = new HostFragment();
        // NEW: Add first fragment to the list.
        hostFragment.fragments.add(fragment);
        return hostFragment;
    }
} // HostFragment.

RootFragment — это интерфейс, реализуемый теми фрагментами, которые содержат ViewPager.

public interface RootFragment {

    /**
     * Opens a new Fragment in the current page of the ViewPager held by this Fragment.
     *
     * @param fragment - new Fragment to be opened.
     */
    void openNewFragment(Fragment fragment);

    /**
     * Returns the fragment displayed in the current tab of the ViewPager held by this Fragment.
     */
    Fragment getCurrentFragment();
}

И тогда реализация будет такой:

MainActivity не является фрагментом, но я реализую интерфейс RootFragment по смыслу.

public class MainActivity extends AppCompatActivity implements RootFragment {

    private void setupViewPager() {
        adapter = new ViewPagerAdapter(getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment1()), null);
        adapter.addFrag(new RootFragment2(), null); // This is a Root, not a Host.
        adapter.addFrag(HostFragment.newInstance(new Fragment4()), null);
        viewPager.setAdapter(adapter);
        // 2 because TabLayout has 3 tabs.
        viewPager.setOffscreenPageLimit(2);
    }

    @Override
    public void openNewFragment(Fragment fragment) {
        Fragment hosted = adapter.getItem(viewPager.getCurrentItem());
        // Replace the fragment of current tab to [fragment].
        if (hosted instanceof HostFragment) {
            ((HostFragment) hosted).replaceFragment(fragment, true);
        // Spread action to next ViewPager.
        } else {
            ((RootFragment) hosted).openNewFragment(fragment);
        }
    }

    @Override
    public Fragment getCurrentFragment() {
        Fragment hosted = adapter.getItem(viewPager.getCurrentItem());
        // Return current tab's fragment.
        if (hosted instanceof HostFragment) {
            return ((HostFragment) hosted).getCurrentFragment();
        // Spread action to next ViewPager.
        } else {
            return ((RootFragment) hosted).getCurrentFragment();
        }
    }
}

и RootFragment2

public class RootFragment2 extends Fragment implements RootFragment {

    private void setupViewPager(ViewPager viewPager) {
        adapter = new ViewPagerAdapter(getActivity().getSupportFragmentManager());
        // Wrap with HostFragment to get separate tabbed nagivation.
        adapter.addFrag(HostFragment.newInstance(new Fragment2()), null);
        adapter.addFrag(HostFragment.newInstance(new Fragment3()), null);
        viewPager.setAdapter(adapter);
        // 1 because TabLayout has 2 tabs.
        viewPager.setOffscreenPageLimit(1);
    }

    @Override
    public void openNewFragment(Fragment fragment) {
        ((HostFragment) adapter.getItem(viewPager.getCurrentItem())).replaceFragment(fragment, true);
    }

    @Override
    public Fragment getCurrentFragment() {
        return ((HostFragment) adapter.getItem(viewPager.getCurrentItem())).getCurrentFragment();
    }
}

И тогда мне просто нужно позвонить:

((MainActivity) getActivity()).getCurrentFragment();

person Santiago Gil    schedule 02.09.2016

Вы можете создать что-то вроде класса CustomFragment, который содержит метод getClassName и расширяется от класса Fragment. Затем расширьте все свои фрагменты, такие как RootFragment2, из класса CustomFragment, а не только Fragment.

Таким образом, вы можете получить такой фрагмент:

public CustomFragment getNeededFragment(String fragmentName) {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    CustomFragment result = null;
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (fragment != null && fragment.isVisible()) {
                try {
                    CustomFragment customFragment = (CustomFragment) customFragment;
                    if (customFragment.getClassName().equals(fragmentName)))
                    result = customFragment;
                } catch (ClassCastException e) {
                }
            }
        }
    }
    return result ;
}
person Andrey Danilov    schedule 01.09.2016
comment
Я имел в виду это решение, но как я уже прокомментировал The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab. то есть я не знаю fragmentName его искать, потому что в стеке может быть 5-6 фрагментов, и я не знаю какой из них самый отображается текущий. - person Santiago Gil; 01.09.2016
comment
У меня также есть похожее решение, но я думаю, что должно быть лучшее, потому что я не считаю его умным. Решение такое: каждый фрагмент, который можно использовать во вкладке, реализует интерфейс Tab_x_Fragment, где x — номер вкладки, на которой он может отображаться. Таким образом, мне просто нужно получить фрагмент, который равен instanceof Tab_b1_Fragment из всех отображаемых в данный момент фрагментов. - person Santiago Gil; 01.09.2016
comment
Практически, вы можете хранить его в других списках в MainActivity и передавать его специальным методом. Таким образом, у вас есть текущее состояние каждой вкладки уже в основной деятельности, например, сохраните их в hashMap с ключом как tabName - person Andrey Danilov; 01.09.2016
comment
Да, я тоже думал об этом. Но тогда мне нужна специальная память, и я уверен, что смогу сделать это с помощью функций Android. - person Santiago Gil; 01.09.2016
comment
Вы тратите специальную память только на указатель на свои фрагменты. Для каждого фрагмента требуется всего 4 байта. Я не думаю, что это действительно замедлит ваше приложение. - person Andrey Danilov; 01.09.2016
comment
Ха ха я знаю! Но я имею в виду, что это не только специальная память, но и дублированная. Вся эта информация у меня в бэкстеке. - person Santiago Gil; 01.09.2016

Я бы сделал что-то вроде этого:

1. Создайте такой интерфейс.

public interface ReturnMyself {
    Fragment returnMyself();
}

2. Все фрагменты внутри ViewPagers должны реализовывать это.

3. Добавьте OnPageChangeListener к своему основному ПО. Таким образом, Вы всегда будете знать текущую позицию.

4. Добавьте OnPageChangeListener своих внутренних ПО, чтобы знать, какой из них находится на экране.

5. Добавьте метод в свой адаптер (как основной, так и внутренний), который возвращает ваш фрагмент из переданного ему списка.

public ReturnMyself getReturnMyselfAtPosition()

6. Все фрагменты должны возвращать это в returnMyself()

7. Фрагмент, который имеет внутренние фрагменты, должен возвращать returnMyself что-то вроде.

Fragment returnMyself() {
    return this.myInnerFragmentAdapter.getReturnMyselfAtPosition().returnMyself(); 
}

8. Из основного фрагмента/активности вы просто звоните.

this.adapter.getReturnMyselfAtPosition().returnMyself();

И вы получили свой текущий фрагмент.

person wojciech_maciejewski    schedule 01.09.2016
comment
Что именно делает getReturnMyselfAtPosition()? Вы имеете в виду getItem(int position) метод FragmentStatePagerAdapter? - person Santiago Gil; 01.09.2016
comment
Возможно. Но getRetunMyselfAtPosition должен возвращать ReturnMyself вместо фрагмента, поэтому вам не нужно его приводить :-) - person wojciech_maciejewski; 01.09.2016
comment
Но это решение возвращает мне фрагмент, размещенный на вкладке ViewPager. Как я уже сказал, The fragment to return is not necessary the hosted in the ViewPager. I can have opened two more fragments in a tab. - person Santiago Gil; 01.09.2016
comment
поэтому, как я писал в пункте 7, фрагмент, имеющий внутренние фрагменты, должен возвращать не себя, а один из своих дочерних фрагментов. - person wojciech_maciejewski; 01.09.2016
comment
Да, но при этом вам удается вернуть только размещенный фрагмент во внутреннем ViewPager вместо RootFragment2. Например, если Fragment2 открывает Fragment5 внутри tab_b1, это вернет Fragment2 в качестве текущего отображаемого, потому что он размещен во внутреннем ViewPager. - person Santiago Gil; 01.09.2016
comment
Нет, не будет. Извините, что вы этого не понимаете - person wojciech_maciejewski; 02.09.2016
comment
Он вернул мне HostFragment, на котором размещена вкладка (см. мой ответ), а не фрагмент в контейнере hosted_fragment этого HostFragment. - person Santiago Gil; 02.09.2016