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
,这可以是 FragmentPagerAdapter
或 FragmentStatePagerAdapter
,具体取决于您的需要。
据我了解,您想将 ViewPager
中的一个 Fragment
替换为另一个,对吗?
这变得(几乎)像处理 Adapter
数据的任何其他 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);
}
您的流程将是 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 1
和 Tab 2
由您的 ViewPager 处理。
他们每个人都将自己处理到 1.1, 1.2, 1.3
(模拟 2.1, 2.2, 2.3
)的特定用户流。
要保持 ViewPager-Scroll-Abilities,您必须确保这些 child-fragments 的寿命在 parentFragment
之内(Tab 1
、Tab 2
)。此外,他们的 root-layout 需要属性 android:clickable="true"
.
您可以使用这些选项卡的 childFragmentManager
将其存档,将(在 1.1.1
或 2.2.2
时替换)添加到 R.id.container
中,这应该在布局中Tab 1
和 Tab 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.container
是 FragmentA
的一部分,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)
我有一个带有 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
,这可以是 FragmentPagerAdapter
或 FragmentStatePagerAdapter
,具体取决于您的需要。
据我了解,您想将 ViewPager
中的一个 Fragment
替换为另一个,对吗?
这变得(几乎)像处理 Adapter
数据的任何其他 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);
}
您的流程将是 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 1
和 Tab 2
由您的 ViewPager 处理。
他们每个人都将自己处理到 1.1, 1.2, 1.3
(模拟 2.1, 2.2, 2.3
)的特定用户流。
要保持 ViewPager-Scroll-Abilities,您必须确保这些 child-fragments 的寿命在 parentFragment
之内(Tab 1
、Tab 2
)。此外,他们的 root-layout 需要属性 android:clickable="true"
.
您可以使用这些选项卡的 childFragmentManager
将其存档,将(在 1.1.1
或 2.2.2
时替换)添加到 R.id.container
中,这应该在布局中Tab 1
和 Tab 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.container
是 FragmentA
的一部分,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)