如何获取viewpager中特定标签页显示的当前片段?

21

我想要获取在选项卡b_1中后退栈中的最后一个片段,或者当前显示的片段对我来说是一样的。如下图所示,我有一个ViewPager,还有内部的另一个选项卡b。 因此现在有四个当前显示的碎片。

问题:如何获取Fragment 2实例?

我已经看到了其他解决方案,但是没有一种适用于这种情况。

注释:返回的片段不一定是托管在ViewPager中的片段。 我可以在选项卡中打开两个以上的片段。

scenario

使用此方法,我可以获取所有当前可见的片段,但无法获取特定的片段。

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);
    }
}

fragment_host.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"/>

HostFragment.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;
    }
}

fragment2_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;
    }
}
4个回答

46

首先在您的ViewPagers适配器中定义一个SparseArray,如下所示。 在这个数组中,我们将保存片段的实例。

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

覆写你的 Adapter 的 instantiateItem 方法。

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

还需重写您的 ViewPagerdestroyItem 方法

@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) {

    }
});

通过这种方式,您可以保留一个实例和顶部片段。


好的,我已经实现了,但是它没有按预期工作。我只保存了一个“片段”;这意味着主VP和内部VP的“onPageSelected”将“片段”保存到全局属性中。当显示“Fragment2”时,“currentFragment”是“RootFragment2”。 - Santiago Gil
好的,这是因为上一个选择的页面是要进入 tab_b。然而,它显示的是 tab_b1,尽管它并没有被选择(默认情况下,viewpager 中始终有一个打开的页面)。 - Santiago Gil
@SantiGil,最近怎么样?我的解决方案对你有用吗? - savepopulation
你的解决方案有效,但仅适用于主VP。当你有内部VP时,它会失败,因为当选择tab_b时,currentFragmentRootFragment2(这不考虑内部ViewPager)。尽管我第一次手动使用正确的值进行了初始化,但这种情况仍然会发生。我用另一种方式解决了这个问题,让我发布答案,这样你就可以看到它。 - Santiago Gil
我们在 onDestroyItem 方法中从稀疏数组中移除了片段引用,因此我认为不需要使用弱引用数组。 - savepopulation
显示剩余7条评论

3

在与DEADMC的一些评论后,我决定使用额外的内存来实现它,将打开的片段保存在列表中。

我解释一下我的解决方案。我有两种特殊片段,hostroot。 Host Fragments是标签的init。Root Fragments则拥有一个ViewPager。在这种情况下,tab_b具有带有内部ViewPager的Root Fragment。

Hierarchy

对于每个选项卡,也就是每个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 的 Fragment 实现的接口。

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 不是 Fragment,但我实现了 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();
        }
    }
}

and 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();

该方法可以获取当前活动的片段(Fragment)。

0
你可以创建一个类似于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 ;
}

我有一个解决方案,但正如我所评论的,“要返回的片段不一定是托管在ViewPager中的。我可以在选项卡中打开两个以上的片段。”我的意思是,我不知道要搜索哪个“fragmentName”,因为堆栈中可能有5-6个片段,而我不知道当前显示的是哪一个。 - Santiago Gil
我也有一个类似的解决方案,但我认为应该有更好的方法,因为我不认为它很聪明。解决方案是:每个可以在选项卡中使用的片段都实现了一个接口 Tab_x_Fragment,其中 x 是它可以显示的选项卡的编号。因此,我只需要从所有当前显示的片段中获取 instanceof Tab_b1_Fragment 的片段即可。 - Santiago Gil
几乎可以将其保留在MainActivity中的另一个列表中,并通过特殊方法传递它。因此,您已经在主要活动中拥有每个选项卡的当前状态,例如,将它们保存在哈希映射中,键为选项卡名称。 - Andrey Danilov
是的,我也考虑过这个。但我需要特殊的内存,我相信我可以利用Android的功能来完成这个任务。 - Santiago Gil
你为你的碎片只花费了特定的内存指针。每个碎片仅需要4个字节。我认为它不会真正地使你的应用程序变慢。 - Andrey Danilov
哈哈,我知道!但是我的意思是,它不仅是特殊的内存,而且是复制的。这些信息都在后退栈中。 - Santiago Gil

0
我会这样做:
1. 创建类似于这样的一个接口。
public interface ReturnMyself {
    Fragment returnMyself();
}

2. 所有 ViewPagers 内的片段都应该实现这个。

3. 在您的主 VP 中添加 OnPageChangeListener。这样,您就可以始终知道当前位置。

4. 在您的内部 VP 中添加 OnPageChangeListener,这样您就可以知道哪一个在屏幕上。

5. 向您的适配器(主适配器和内部适配器)添加方法,该方法从传递给它的列表中返回您的片段。

public ReturnMyself getReturnMyselfAtPosition()

6. 所有碎片都应该在returnMyself()中返回这个。

7. 有内部碎片的碎片应该在returnMyself中返回类似的东西。

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

8。从主片段/活动中,您只需调用。

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

你已经获得了当前的片段。


getReturnMyselfAtPosition()方法到底是做什么的?您是指FragmentStatePagerAdaptergetItem(int position)方法吗? - Santiago Gil
可能是这样。但是getRetunMyselfAtPosition应该返回ReturnMyself而不是fragment,所以您不需要进行强制转换 :-) - wojciech_maciejewski
但是这个解决方案会返回在ViewPager选项卡中托管的Fragment。正如我所说,“要返回的片段不一定是托管在ViewPager中的。我可以在一个选项卡中打开两个以上的片段。” - Santiago Gil
是的,但是这样只能返回内部ViewPager中托管的片段,而不是RootFragment2。例如,如果Fragment2tab_b1内打开Fragment5,则会返回Fragment2,因为它是托管在内部ViewPager中的。 - Santiago Gil
不,它不会。很抱歉你不理解这个。 - wojciech_maciejewski
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接