【问题标题】:Android Switch Between Multiple Fragments In A TabAndroid 在选项卡中的多个片段之间切换
【发布时间】:2019-11-16 13:06:43
【问题描述】:

我有一个带有 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);
}

【问题讨论】:

  • 请同时贴出你用来切换片段的代码
  • 帮助我理解切换背后的意图。您想通过单击按钮从 FirstFragment 导航到 SecondFragment 吗?或者你想显示 SecondFragment 直接跳过 FirstFragment 吗?无论如何,您是否希望保留片段的后台堆栈?
  • @JavaGhost - 没有提到跳过任何片段。流程非常基本,我在问题中对其进行了概述。您有一个选项卡布局。它有两个选项卡。每个选项卡都是一个片段。为简单起见,假设第一个包含片段 A 的选项卡有一个编辑文本和一个按钮。当用户按下按钮时,片段 A 切换到片段 B。选项卡不会更改或正在加载任何其他选项卡。
  • 我根据你的评论更新了我的答案
  • @tomerpacific 为您提供了多种解决方案。请说明为什么建议的解决方案不适合您。

标签: android android-fragments android-tablayout


【解决方案1】:

您的流程将是 Inflate Layout

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

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

之后您将添加 .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

【讨论】:

  • 我不是要添加更多选项卡,而是要根据用户的操作更改选项卡的视图。
  • 所以你有你的选项卡的点击监听器只需调用addCenterFragments(fragment)添加传递它一个有效的片段
【解决方案2】:

据我了解,您需要在另一个片段中的片段之间切换(例如 Tab 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>

对应的First Tab 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 中的按钮单击将通过接口调用其父 Fragment 方法,您可以从那里切换 Fragment。

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

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

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

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

【讨论】:

  • 我已尝试实施您的解决方案,但它不起作用。我得到了与我发布的代码相同的体验,其中第二个片段的布局与第一个片段的布局重叠。
  • 您必须为一个选项卡使用三个片段 - 第一个片段仅包含容器(框架布局),在该容器中您将在第二个片段和第三个片段之间切换。因此,即使第一个片段和第二个片段重叠,UI 也不会发生变化(因为第一个片段除了容器之外不包含任何视图)
  • 另外我用 viewpager 适配器类更新了答案,因为我不明白你的 SectionsPagerAdapter 类的 getItem() 方法
【解决方案3】:

一个片段不能直接切换另一个片段(不推荐),这只能通过包含所有片段的活动来完成。将以下代码放入您的活动中

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

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

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

就是这样。

【讨论】:

  • 这个答案达到了最低限度的理解场景并解释片段无法相互通信。其余所有答案都无法描述这一点。
  • 了解清楚。 Fragment 可以直接与其他 Fragment 进行通信,但不建议这样做,因为它会导致很多复杂性。
【解决方案4】:

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

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

您仍在使用您的第一个片段作为第二个片段的容器。 这真的是你想要的吗?我猜你宁愿在 viewpager 中有一个布局作为选项卡,只有一个“容器”,以及 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();
}

【讨论】:

  • 我没有在任何呈现的布局中静态放置片段,而是在运行时添加片段。您发布的链接没有解决我的情况。
  • 好的,你能把你实例化片段的代码贴出来吗?
【解决方案5】:

我一开始看不到你是如何填写ViewPager的。

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

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

这变得(几乎)像任何其他 RecyclerView 或 ListView 问题一样,您在其中处理 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);
    }

【讨论】:

  • 我正在使用 FragmentPageAdapter 填充 ViewPager(当您创建 TabbedActivity 时,它会开箱即用)。当您在标签之间切换时,用另一个片段替换一个片段是毫不费力的,这不是我的意思。
  • 我不是在谈论标签之间的切换。专注于“与任何其他 RecyclerView 或 ListView 一样”。更改适配器内容和 notifyDataSetChanged()。
  • 我添加了一个实现示例,说明如何更改一个选项卡的片段
【解决方案6】:

也许我能理解你想要什么。

试试这个,首先在 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>

并添加这个新类 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"));
}
}

您应该显示如下内容:

【讨论】:

  • 感谢您的详细回答,但我没有在我的问题中提到对滚动片段的任何支持。事实并非如此。
【解决方案7】:

Mohamed Ashik

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

假设如下用户流:

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

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

要维护 ViewPager-Scroll-Abilities,您必须确保这些子片段存在于它们的 parentFragments(Tab 1、Tab 2)内。此外,它们的根布局需要属性android:clickable="true"。

您可以使用这些选项卡的childFragmentManager 归档此内容,将R.id.container 添加(在1.1.1 或2.2.2 时替换)应该在Tab 1 和Tab 2 的布局内!您的 FirstFragment.xml 已在其 FrameLayout 上声明了此属性。第二个缺少 id 属性。

现在,孩子可以另外使用自己的 childFragmentManager 向自己添加另一个片段,但同样的限制将适用。 另外,我想没有必要同时让所有孩子都活着,因此我只需替换标签的“第一层”(1.1, 1.2, etc.)。请注意,您需要将片段替换为相同的 childFragmentManager。

即使您的 FragmentPagerAdapter 仅包含两个 Fragment,这不会导致在应用程序中丢失引用,您可能应该将您的 Fragment 声明为 SectionsPagerAdapter 的字段。

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

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

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

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

当使用com.google.android.material.tabs 或

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

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

【讨论】:

    【解决方案8】:

    回答您面临的替换问题:R.id.container 是附加到您的活动的片段的根布局。如果您尝试从任何地方(活动或片段)用FragmentB 替换此R.id.container,这只会替换布局并将FragmentB 作为子片段添加到原始片段。请记住,R.id.container 是FragmentA 的一部分,活动在使用时不能替换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 类应如下所示:

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

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

    【讨论】:

      【解决方案9】:

      定义一个接口 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();
          }
      
      }
      

      然后在你的活动中

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

      你应该得到这样的东西:

      【讨论】:

        【解决方案10】:

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

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

        【讨论】:

        • 我已经克隆了您的存储库并看到了您的解决方案,但这不适用于我正在尝试做的事情。您正在处理从主要活动而不是从片段本身切换片段。这要求每个片段都知道适配器。
        • @tomerpacific 请克隆我根据您的要求更新的最新版本
        【解决方案11】:

        您可以使用 ViewPager:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-02-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多