Android 在选项卡中的多个片段之间切换

Android Switch Between Multiple Fragments In A Tab

我有一个带有 TabLayout 的应用程序,它有一个处理选项卡的 ViewPager。这两个选项卡实例化了不同的片段。

我想要实现的是在同一个选项卡中切换片段。由于同一选项卡中的每个片段都拥有不同的布局(因为它依赖于特定的用户流程),使用 FrameLayout 并切换其内容,只会在前一个片段之上创建新片段的布局。

我应该创建一个带有容器的通用片段并用每个片段替换它吗?如果是这样,我是否需要在运行时为每个片段构建布局?

我在网上和 SO 中进行了搜索,但未能找到专门针对我的情况的解决方案。

有没有办法实现这个,或者我应该从不同的角度来解决这个问题?

部分代码供参考:

MainActivtiy XML:

<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main2Activity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary" />
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

第一个片段的XML:

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

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:gravity="center"
        ></TextView>

    <ProgressBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleLarge"
        android:id="@+id/progress_bar"
        android:layout_gravity="center">

    </ProgressBar>
    <GridView
        android:rowCount="3"
        android:numColumns="2"
        android:gravity="center"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:layout_marginLeft="40dp"
        >


    </GridView>

</FrameLayout>

第二个片段的XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:layout_gravity="center"
        android:layout_marginBottom="5dp">
    </TextView>

    <RadioGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:id="@+id/myRadioGroup"
        android:orientation="horizontal">

    </RadioGroup>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:inputType="numberDecimal">

    </EditText>

    <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:text="Submit"
        android:id="@+id/submit_btn">

    </androidx.appcompat.widget.AppCompatButton>

</LinearLayout>

-- 编辑--

我用来在片段之间切换的代码(在第一个片段中找到)是这样的(虽然我也尝试了其他各种方法):

getActivity().getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.container,secondFragment, "secondFragment")
                    .addToBackStack(null)
                    .commit();

-- 编辑#2--

下面是 FragmentPagerAdapter 代码:

public class SectionsPagerAdapter extends FragmentPagerAdapter {

    @StringRes
    private static final int[] TAB_TITLES = new int[]{R.string.tab_text_1, R.string.tab_text_2};
    private final Context mContext;

    public SectionsPagerAdapter(Context context, FragmentManager fm) {
        super(fm);
        mContext = context;
    }

    @Override
    public Fragment getItem(int position) {
        // getItem is called to instantiate the fragment for the given page.
        // Return a PlaceholderFragment (defined as a static inner class below).
        if (position == 1) {
            return firstFragment.newInstance("One", "Two");
        }
        return PlaceholderFragment.newInstance(position + 1);
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return mContext.getResources().getString(TAB_TITLES[position]);
    }

    @Override
    public int getCount() {
        // Show 2 total pages.
        return 2;
    }
}

而此代码来自 mainActivity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
    ViewPager viewPager = findViewById(R.id.view_pager);
    viewPager.setAdapter(sectionsPagerAdapter);
    TabLayout tabs = findViewById(R.id.tabs);
    tabs.setupWithViewPager(viewPager);
}

替换代码似乎是正确的,但是:如何实例化片段?参见 this related answer。您不能替换静态放置在 xml 布局中的片段。 例如,将您的第一个片段放在另一个 LinearLayout 中并以编程方式对其进行膨胀。对 secondFragment 做同样的事情。继续使用 FrameLayout 作为容器。

更新(因为您发布了更多代码):

您仍在使用第一个片段作为第二个片段的容器。 那真的是你想要的吗?我猜你更想在 viewpager 中有一个布局作为选项卡,只有一个 "container",以及 2 个额外的 xml 布局(LinearLayout 或其他)用于 2 个片段。

Viewpager 页面作为容器:

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


</FrameLayout>

第一个片段:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:gravity="center" />

    <ProgressBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        style="?android:attr/progressBarStyleLarge"
        android:id="@+id/progress_bar"
        android:layout_gravity="center">

    </ProgressBar>

    <GridView
        android:rowCount="3"
        android:numColumns="2"
        android:gravity="center"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:stretchMode="columnWidth"
        android:layout_marginLeft="40dp">
    </GridView>

</LinearLayout>

第二个片段:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20dp"
        android:layout_gravity="center"
        android:layout_marginBottom="5dp">
    </TextView>

    <RadioGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:id="@+id/myRadioGroup"
        android:orientation="horizontal">

    </RadioGroup>

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:inputType="numberDecimal">

    </EditText>

    <androidx.appcompat.widget.AppCompatButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:text="Submit"
        android:id="@+id/submit_btn"/>
</LinearLayout>

然后,如 Mohamed 在他的回答中所说,要在第一个片段中切换片段,您需要使用 getChildFragmentManager(),它负责嵌套片段。

void switchFragment(Fragment fragment) {
        getActivity().getChildFragmentManager()
                    .beginTransaction().
                    replace(R.id.container,secondFragment, "secondFragment")
                    .addToBackStack(null)
                    .commit();
}

首先我看不出你是如何填充 ViewPager 的。

我会推荐 PagerAdapter,这可以是 FragmentPagerAdapterFragmentStatePagerAdapter,具体取决于您的需要。

据我了解,您想将 ViewPager 中的一个 Fragment 替换为另一个,对吗?

这变得(几乎)像处理 Adapter 数据的任何其他 RecyclerViewListView 问题。

您可以:

  • 在确定要显示的选项卡的逻辑之后创建 Adapter/ViewPager,或者
  • 显示ViewPager,修改适配器数据并调用notifyDataSetChanged()

如果选择 notifyDataSetChanged() 选项,please read this, about FragmentPagerAdapter & notifyDatasetChanged()

示例,使用 Adapter 数据集更改:

public class SectionsPagerAdapter extends FragmentPagerAdapter {
    private final Context mContext;
    private List<FragmentTabDescriber> fragmentDescribers = new ArrayList();

    public SectionsPagerAdapter(Context context, FragmentManager fm) {
        super(fm);
        mContext = context;
    }

    public void setContent(List<FragmentTabDescriber> fragmentDescribers) {
        this.fragmentDescribers = fragmentDescribers;
        notifyDataSetChanged();
    }

    @Override
    public Fragment getItem(int position) {
        return fragmentDescribers.get(position)
                .instantiateFragment(position);
    }

    @Override
    public int getItemPosition(@NonNull Object object) {
        return POSITION_NONE; //Either this, or use a FragmentStatePagerAdapter
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return fragmentDescribers.get(position).getTitle();
    }

    @Override
    public int getCount() {
        return fragmentDescribers.size();
    }

}

interface FragmentTabDescriber {
    Fragment instantiateFragment(int position);
    String getTitle();
}

用法:

    FragmentTabDescriber fragmentOneDescriber = new FragmentTabDescriber() {
        @Override
        public Fragment instantiateFragment(int position) {
            return TypeOneFragment.newInstance("yey", "uhu");
        }

        @Override
        public String getTitle() {
            return "Fragment one";
        }
    };
    FragmentTabDescriber fragmentXDescriber = new FragmentTabDescriber() {
        @Override
        public Fragment instantiateFragment(int position) {
            return TypeXFragment.newInstance(position);
        }

        @Override
        public String getTitle() {
            return "Fragment X";
        }
    };
    FragmentTabDescriber fragmentYDescriber = new FragmentTabDescriber() {
        @Override
        public Fragment instantiateFragment(int position) {
            return TypeYFragment.newInstance(position);
        }

        @Override
        public String getTitle() {
            return "Fragment Y";
        }
    };

    public void createTabs() {
        List<FragmentTabDescriber> tabDescribers = Arrays.asList(fragmentOneDescriber, fragmentXDescriber);
        sectionsAdapter.setContent(tabDescribers);

        /// /// Some events happen, then change fragment 
        List<FragmentTabDescriber> tabDescribers = Arrays.asList(fragmentOneDescriber, fragmentYDescriber);
        sectionsAdapter.setContent(tabDescribers);
    }

您的流程将是 Inflate Layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
    <com.google.android.material.tabs.TabLayout/>
    <FrameLayout />
</LinearLayout>

框架布局会根据选项卡布局选择动态变化。 TabLayout 将在运行时在 onCreateView 期间填充,您将在其中以编程方式添加选项卡。您将需要为计划充气的每个选项卡创建单独的片段,或动态传递要充气的包裹。

之后您将添加 .addOnTabSelectedListener 知道用户何时单击选项卡。 接下来,您将实现自定义 类 以控制 FrameLayout 的可见布局,它将接收片段作为变量 fragment = new ManualEntryTabsMainFragment(); addCenterFragments(fragment); 添加中心方法将如下所示

private FragmentTransaction fragmentTransaction;
private List<Fragment> activeCenterFragments = new ArrayList<Fragment>();

private void addCenterFragments(Fragment fragment) {
    removeActiveCenterFragments();
    FragmentManager fm = getActivity().getSupportFragmentManager();
    fragmentTransaction = fm.beginTransaction();
    fragmentTransaction.add(R.id.{YOUR FRAMELAYOUT}, fragment);
    fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
    activeCenterFragments.add(fragment);
    fragmentTransaction.commit();
}

private void removeActiveCenterFragments() {
    if (activeCenterFragments.size() > 0) {
        FragmentManager fm = getActivity().getSupportFragmentManager();
        fragmentTransaction = fm.beginTransaction();
        for (Fragment activeFragment : activeCenterFragments) {
            fragmentTransaction.remove(activeFragment);
        }
        activeCenterFragments.clear();
        fragmentTransaction.commit();
    }
}

有错误的完整样本: https://gist.github.com/skryshtafovych/dd3abead65692690c6b0de5524da2af9

我已经根据您的要求创建了项目。我创建了 a repo for you.

我动态制作了片段寻呼机适配器。

据我了解,您需要在另一个片段(例如选项卡 A)中的片段之间切换。我尝试了以下代码: 第一个选项卡仅包含容器:

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

对应第一个标签java代码:

public class FirstTabFragment extends Fragment implements TypeOneFragment.OnButtonClick {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View rootView = inflater.inflate(R.layout.fragment_first_tab, container, false);
    switchFragment(new TypeOneFragment());        
    return rootView;
}


void switchFragment(Fragment fragment) {
    getChildFragmentManager().beginTransaction().
            replace(R.id.container, fragment).
            commit();
}

您需要使用getChildFragmentManager()将片段替换为片段

编辑: 我的 ViewPagerAdapter 看起来像:

public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
    SectionsPagerAdapter(FragmentManager fm) {
        super(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        if (position == 0)
            return FirstTabFragment.newInstance("One","Two");
        else {
            return new SecondTabFragment();
        } 
    }

    @Override
    public int getCount() {
       return 2;
    }

    @Override
    public CharSequence getPageTitle(int position) {          
        if (position == 0)
           return "First Tab";
        else 
           return "Second Tab"; 
    }
}

编辑 2:

根据您的评论“想象一下包含片段 A 的第一个选项卡,有一个编辑文本和一个按钮。当用户按下按钮时,片段 A 切换到片段 B。”。我在 TypeOneFragment(即 Fragment A)中添加了接口。因此在 Fragment A 中单击按钮将通过接口调用其父片段方法,从那里你可以切换片段。

TypeOneFragment java:

public class TypeOneFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_type_one, container, false);
    onAttachToParentFragment(getParentFragment()); //Added this line to initialize the interface
    Button btnClick = rootView.findViewById(R.id.btnSwitch);
    btnClick.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (mListener != null) {
                mListener.btnClicked();
            }
        }
    });
    return rootView;
}

private OnButtonClick mListener;
interface OnButtonClick {
    void btnClicked();
}

private void onAttachToParentFragment(Fragment parentFragment) {
    try {
        mListener = (OnButtonClick) parentFragment;
    } catch (ClassCastException e) {
        throw new ClassCastException(parentFragment.toString() + " must implement OnButtonClick");
    }
}
}

需要在其父片段(FirstTabFragment)中实现接口

public class FirstTabFragment extends Fragment implements TypeOneFragment.OnButtonClick {
///.....

@Override
public void btnClicked() {
    if (something) {
        switchFragment(new TypeOneFragment());
    } else {
        switchFragment(new TypeTwoFragment());
    }
    something = !something;
}
}

也许我能理解你想要什么

试试这个,首先在 XML fragment_second.xml 更新:

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Type Button to Add Fragment"
            android:textSize="20dp"
            android:layout_gravity="center"
            android:layout_marginBottom="5dp">
        </TextView>

        <androidx.appcompat.widget.AppCompatButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="10dp"
            android:text="Submit"
            android:id="@+id/submit_btn"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <LinearLayout
            android:id="@+id/linear_dynamic_fragments_containers"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</androidx.core.widget.NestedScrollView>

PlaceholderFragment.java 更新的下一步:

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class PlaceholderFragment extends Fragment {

public static PlaceholderFragment newInstance(int i) {

    Bundle args = new Bundle();

    PlaceholderFragment fragment = new PlaceholderFragment();
    fragment.setArguments(args);
    return fragment;
}

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_second, container, false);
}

@Override
public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    view.findViewById(R.id.submit_btn).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            addDynamicFragment(view);
        }
    });
}

private void addDynamicFragment(View view) {
    if(view == null){
        return;
    }
    LinearLayout container = view.findViewById(R.id.linear_dynamic_fragments_containers);
    FrameLayout childFragmentContainer = getChildFragmentContainer();
    addFragmentToContainer(container.getChildCount(), childFragmentContainer);
    container.addView(childFragmentContainer);
}

private void addFragmentToContainer(int position, FrameLayout childFragmentContainer) {
    Fragment childFragment = ChildFragment.newInstance(position);
    getChildFragmentManager()
            .beginTransaction().
                    replace(childFragmentContainer.getId(), childFragment,"ChildFragment")
            .addToBackStack(null)
            .commit();
}

private FrameLayout getChildFragmentContainer() {
    FrameLayout frameLayout = new FrameLayout(getActivity());
    frameLayout.setId(View.generateViewId());
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    frameLayout.setLayoutParams(params);
    return frameLayout;
}
}

并添加这个新布局fragment_child.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="350dp"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:layout_margin="4dp">

<TextView
    android:id="@+id/text_child_fragment"
    style="@style/TextAppearance.AppCompat.Title"
    android:layout_gravity="center"
    android:textColor="@android:color/white"
    android:text="ChildFragment Count: "
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
</FrameLayout>

并添加这个新的 class ChildFragment.java

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

public class ChildFragment extends Fragment {

public static ChildFragment newInstance(int position) {

    Bundle args = new Bundle();
    args.putInt("KEY_INT", position);
    ChildFragment fragment = new ChildFragment();
    fragment.setArguments(args);
    return fragment;
}



@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_child, container, false);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    TextView textView = view.findViewById(R.id.text_child_fragment);
    textView.setText("ChildFragment Count: " + getArguments().getInt("KEY_INT"));
}
}

你应该展示这样的东西:

已经为您提供了您需要的工具,但我想补充几点。

假设用户流程如下:

      Tab 1         Tab 2
     /  |  \       /  |  \
  1.1  1.2  1.3  2.1 2.2  2.3
  /                   |
1.1.1               2.2.2

Tab 1Tab 2 由您的 ViewPager 处理。 他们每个人都将自己处理到 1.1, 1.2, 1.3(模拟 2.1, 2.2, 2.3)的特定用户流。

要保持​​ ViewPager-Scroll-Abilities,您必须确保这些 child-fragments 的寿命在 parentFragment 之内(Tab 1Tab 2)。此外,他们的 root-layout 需要属性 android:clickable="true".

您可以使用这些选项卡的 childFragmentManager 将其存档,将(在 1.1.12.2.2 时替换)添加到 R.id.container 中,这应该在布局中Tab 1Tab 2!您的 FirstFragment.xml 已经在其 FrameLayout 上声明了此属性。第二个缺少 id-attribute。

现在,child 可以另外使用它自己的 childFragmentManager 来向自己添加另一个片段,但同样的限制将适用。 另外,我想没有必要同时让所有 children 保持活动状态,因此我只需要替换选项卡 (1.1, 1.2, etc.) 的 "first layer"。请注意,您需要用相同的 childFragmentManager.

替换片段

即使您的 FragmentPagerAdapter 只包含两个片段,这不应导致在应用程序中丢失引用,您也应该将片段声明为 SectionsPagerAdapter.

的字段

幸存的方向变化需要恢复之前显示的片段,这会自动恢复它们的 children。

public class SectionsPagerAdapter extends FragmentPagerAdapter {

@StringRes
private static final int[] TAB_TITLES = new int[]{R.string.tab_text_1, R.string.tab_text_2};
private final Context mContext;
private final FirstFragment first;
private final PlaceholderFragment second;

public SectionsPagerAdapter(Context context, FragmentManager fm) {
    super(fm);
    FirstFragment firstFrag = fm.findFragmentByTag("first");
    if (firstFrag == null) {
        first = FirstFragment.newInstance("one", "two");
    } else {
        first = firstFrag;
    }
    PlaceholderFragment placeFrag = fm.findFragmentByTag("second");
    if (placeFrag == null) {
        second = PlaceholderFragment.newInstance(1);     // your logic provided
    } else {
        second = placeFrag;
    }
    mContext = context;
}

@Override
public Fragment getItem(int position) {
    // getItem is called to instantiate the fragment for the given page.
    // Return a PlaceholderFragment (defined as a static inner class below).
    if (position == 1) {
        return first;
    }
    second.updateState(1);              // in case this is really required
    return second;
}

}

通过您的 Activity 您可以随时访问您的 viewPager 和 childFragmentManager: (在片段内):

((YourActivity) getActivity()).getSectionsViewPager().getFirst().getChildFragmentManager()

//编辑: 我刚在这里看到你的评论: 您只想以编程方式切换选项卡? 在这种情况下,您可以调用

tabLayout.setScrollPosition(position, 0f, true);

使用时 com.google.android.material.tabs

tabLayout.selectTab(tabLayout.getTabAt(position));

使用非 androidx 时 android.support.design.widget

定义一个接口SwitchFragmentInterface。

public interface SwitchFragmentInterface {

    void switchFragment(int id);
}

并定义一个PlaceholderFragment(其中包含一个id为container的FrameLayout)并在此片段中实现上述接口。

public class PlacholderFragment extends Fragment implements SwitchFragmentInterface{

    private static String ARG_POSITION = "position";

    private int fragmentId = 0;

    public PlacholderFragment() {
        // Required empty public constructor
    }

    public static PlacholderFragment newInstance(int position) {
        PlacholderFragment fragment = new PlacholderFragment();
        Bundle bundle = new Bundle();
        bundle.putInt(ARG_POSITION,position);
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        fragmentId = getArguments().getInt(ARG_POSITION,0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_placeholder, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        loadFragment(fragmentId);
    }

    void loadFragment(int id) {
        Fragment fragment ;
        if(id == FirstFragment.ID){
            fragment = FirstFragment.newInstance();
        }else if(id == SecondFragment.ID){
            fragment = SecondFragment.newInstance();
        }else {
            // unindentified fragment
            return;
        }
        getChildFragmentManager().beginTransaction().
                replace(R.id.container, fragment).
                commit();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    //all children fragments call this method when a switch to a different fragment is needed.

    @Override
    public void switchFragment(int id) {
        loadFragment(id);
    }
}

在您的 FirstFragment 中,将父片段 (PlaceholderFragment) 转换为 SwitchFragmentInterface 并使用它来切换片段。

public class FirstFragment extends Fragment {

    SwitchFragmentInterface mCallback;
    public static int ID = 0;

    public FirstFragment() {
        // Required empty public constructor
    }

    public static FirstFragment newInstance() {
        FirstFragment fragment = new FirstFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Button switchButton = view.findViewById(R.id.switch_frag_button);
        switchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                mCallback.switchFragment(SecondFragment.ID);
            }
        });

    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        try {
            mCallback = (SwitchFragmentInterface ) getParentFragment();
        } catch (ClassCastException e) {

            throw new ClassCastException(getParentFragment().toString()
                    + " must implement MyInterface ");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallback = null;
    }

}

对你的第二个片段做同样的事情。

public class SecondFragment extends Fragment {

    SwitchFragmentInterface mCallback;

    public static int ID = 1;

    public SecondFragment() {
        // Required empty public constructor
    }

    public static SecondFragment newInstance() {
        SecondFragment fragment = new SecondFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_second, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        Button switchButton = view.findViewById(R.id.switch_frag_button);
        switchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                mCallback.switchFragment(FirstFragment.ID);
            }
        });

    }


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        try {
            mCallback = (SwitchFragmentInterface ) getParentFragment();
        } catch (ClassCastException e) {
            throw new ClassCastException(getParentFragment().toString()
                    + " must implement MyInterface ");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mCallback = null;
    }

}

我将 SectionsAdapter 更改为此,以便 activity 更好地控制将哪些片段加载到 viewpager 中。

public class SectionsPagerAdapter extends FragmentPagerAdapter {

    @StringRes
    private static final int[] TAB_TITLES = new int[]{R.string.tab_text_1, R.string.tab_text_2};
    private final Context mContext;
    private ArrayList<Fragment> frags = new ArrayList<>();

    public SectionsPagerAdapter(Context context, FragmentManager fm) {
        super(fm);
        mContext = context;
    }

    @Override
    public Fragment getItem(int position) {
        // getItem is called to instantiate the fragment for the given page.
        // Return a PlaceholderFragment (defined as a static inner class below).
        return frags.get(position);
    }

    public void addFragment(Fragment fragment){
        frags.add(fragment);
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        return mContext.getResources().getString(TAB_TITLES[position]);
    }

    @Override
    public int getCount() {
        // Show 2 total pages.
        // returns two since only two fragments will be added
        return frags.size();
    }

}

然后在你的Activity

public class FragmentSwitchActivity extends AppCompatActivity {

    TabLayout tabs;
    ViewPager viewPager;
    SectionsPagerAdapter adapter;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment_switch);

        tabs = findViewById(R.id.tabs);
        viewPager = findViewById(R.id.view_pager);

        adapter = new SectionsPagerAdapter(this,getSupportFragmentManager());
        adapter.addFragment(PlacholderFragment.newInstance(FirstFragment.ID));
        adapter.addFragment(PlacholderFragment.newInstance(SecondFragment.ID));

        viewPager.setAdapter(adapter);
        tabs.setupWithViewPager(viewPager);
    }


}

你应该得到这样的东西:

您面临的替换问题的答案:R.id.container 是附加到您的 activity 的片段的根布局。如果您尝试从任何地方(activity 或片段)将此 R.id.container 替换为 FragmentB,这只会替换布局并将 FragmentB 作为子片段添加到原始片段。请记住,R.id.containerFragmentA 的一部分,activity 在使用时不能替换 FragmentA,而是会将其添加为该片段本身的一部分。

关于如何以更好的方式实现此目的的答案:

              FragmentA (Instantiated by ViewPagerAdapter)
       ___________|____________
      |           |            |
  FragStep1   FragStep2    FragStep3
  (Initial)   (On Action)  (On Action)

在您的 FragmentA 布局中,只有将用于保存片段的容器。让我们这样说:

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

创建步骤界面:

public interface Step {
    void switchStep(int step);
}

您的 FragmentA class 应该如下所示:

public class FragmentA extends Fragment implements Step {
    public FragmentA newInstance(Bundle bundle) {
        FragmentA fragment = new FragmentA();
        fragment.setArguments(bundle);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
                             container, @Nullable Bundle savedInstanceState) {
       View view = inflater.inflate(R.layout.fragmentA, container, false);
       switchStep(0);
       return view;
    }

    @Override
    public void switchStep(int step) {
        Fragment fragment = null;
        switch (step) {
            case 0:
                fragment = FragStep1.newInstance(this);
                break;
            case 1:
                fragment = FragStep2.newInstance(this);
                break;
            case 2:
                fragment = FragStep3.newInstance(this);
                break;
        }
        if (fragment != null) {
            FragmentManager manager = getChildFragmentManager();
            FragmentTransaction transaction = manager.beginTransaction();
            transaction.replace(R.id.container, fragment);
            transaction.commit();
        }
    }
}

FragStep1/FragStep2/FragStep3 将包含对 Step 的引用,以便它们可以调用开关函数。

public class FragStep1 extends Fragment {
    private Step stepInitiator;

    public FragStep1 newInstance(Step step) {
        FragStep1 fragment = new FragStep1();
        fragStep1.stepInitiator = step;
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
                         container, @Nullable Bundle savedInstanceState) {
       View view = inflater.inflate(R.layout.fragment_step1, container, false);
       return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        Button nextButton = view.findViewById(R.id.next_step);
        nextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (stepInitiator != null) {
                    stepInitiator.switchStep(1);
                }
            }
        }
    }
}

注意:我没有对此进行测试,但如果您修复编译问题(如果有的话),这会起作用。

一个fragment不能直接切换另一个fragment(不推荐),只能通过activity持有所有fragment来实现。将以下代码放入您的 activity

ViewPager viewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, 
    getSupportFragmentManager());
    viewPager = findViewById(R.id.view_pager);
    viewPager.setAdapter(sectionsPagerAdapter);
    TabLayout tabs = findViewById(R.id.tabs);
    tabs.setupWithViewPager(viewPager);
}


public void onClickFirstFragButton(int index){
    viewPager.setCurrentItem(index);
}

在你的片段中,点击按钮后不要直接提交另一个片段,而是像这样将点击操作导航到 activity。

(MainActivity getActivity()).onClickFirstFragButton(index);

就是这样。

您可以使用 ViewPager:

    viewPager = view.findViewById(R.id.viewPager)
    tab = view.findViewById(R.id.tab1)
    viewPager.adapter = getSupportFragmentManager()?.let { ViewPagerAdapter(it) }
    tab.setupWithViewPager(viewPager)