gpt4 book ai didi

android - 使用 Fragments 为 Android 中的每个选项卡单独返回堆栈

转载 作者:IT老高 更新时间:2023-10-28 12:56:05 26 4
gpt4 key购买 nike

我正在尝试在 Android 应用中实现导航标签。由于 TabActivity 和 ActivityGroup 已被弃用,我想改用 Fragments 来实现它。

我知道如何为每个选项卡设置一个 fragment ,然后在单击选项卡时切换 fragment 。但是如何才能为每个选项卡设置单独的后退堆栈呢?

例如,Fragment A 和 B 将位于 Tab 1 下,Fragment C 和 D 在 Tab 2 下。当应用程序启动时,会显示 Fragment A 并选择 Tab 1。然后, fragment A可能被 fragment B替换。选择选项卡2时应显示 fragment C。如果选项卡 1 被选中,那么应该再次显示 fragment B。此时应该可以使用返回按钮来显示 Fragment A。

此外,在设备旋转时保持每个选项卡的状态也很重要。

BR马丁

最佳答案

在使用此解决方案之前阅读此内容

哇,我仍然不敢相信这个答案是这个帖子中得票最多的答案。请不要盲目地遵循这个实现。我在 2012 年编写了这个解决方案(当时我只是 Android 的新手)。十年后,我可以看到这个解决方案存在一个可怕的问题。

我正在存储对 fragment 的硬引用以实现导航堆栈。这是一种糟糕的做法,会导致内存泄漏。让 FragmentManager 保存对 Fragment 的引用。如果需要,只需存储 fragment 标识符。

如果需要,我的答案可以与上述修改一起使用。但我不认为我们需要从头开始编写多堆栈导航实现。肯定有一个更好的现成解决方案。我现在对 Android 不太感兴趣,所以不能指点。

为了完整起见,我保留了原始答案。

原答案

这个问题我来得太晚了。但由于这个帖子对我很有帮助,我想我最好把我的两便士贴在这里。

我需要这样的屏幕流(一个简约的设计,每个标签有 2 个标签和 2 个 View ),

tabA
-> ScreenA1, ScreenA2
tabB
-> ScreenB1, ScreenB2

我过去有同样的要求,我使用 TabActivityGroup(当时也已弃用)和 Activity 来做到这一点。这次我想用 Fragments。

所以我就是这样做的。

1。创建一个基本的 Fragment 类

public class BaseFragment extends Fragment {
AppMainTabActivity mActivity;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivity = (AppMainTabActivity) this.getActivity();
}

public void onBackPressed(){
}

public void onActivityResult(int requestCode, int resultCode, Intent data){
}
}

您应用中的所有 fragment 都可以扩展此基类。如果你想使用像 ListFragment 这样的特殊 fragment ,你也应该为此创建一个基类。 onBackPressed()onActivityResult() 的用法,如果你完整阅读这篇文章,你就会很清楚了..

2. 创建一些 Tab 标识符,在项目中随处可见

public class AppConstants{
public static final String TAB_A = "tab_a_identifier";
public static final String TAB_B = "tab_b_identifier";

//Your other constants, if you have them..
}

这里没什么好解释的..

3。好的,主选项卡 Activity - 请查看代码中的注释..

public class AppMainFragmentActivity extends FragmentActivity{
/* Your Tab host */
private TabHost mTabHost;

/* A HashMap of stacks, where we use tab identifier as keys..*/
private HashMap<String, Stack<Fragment>> mStacks;

/*Save current tabs identifier in this..*/
private String mCurrentTab;

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

/*
* Navigation stacks for each tab gets created..
* tab identifier is used as key to get respective stack for each tab
*/
mStacks = new HashMap<String, Stack<Fragment>>();
mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

mTabHost = (TabHost)findViewById(android.R.id.tabhost);
mTabHost.setOnTabChangedListener(listener);
mTabHost.setup();

initializeTabs();
}


private View createTabView(final int id) {
View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
imageView.setImageDrawable(getResources().getDrawable(id));
return view;
}

public void initializeTabs(){
/* Setup your tab icons and content views.. Nothing special in this..*/
TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A);
mTabHost.setCurrentTab(-3);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_home_state_btn));
mTabHost.addTab(spec);


spec = mTabHost.newTabSpec(AppConstants.TAB_B);
spec.setContent(new TabHost.TabContentFactory() {
public View createTabContent(String tag) {
return findViewById(R.id.realtabcontent);
}
});
spec.setIndicator(createTabView(R.drawable.tab_status_state_btn));
mTabHost.addTab(spec);
}


/*Comes here when user switch tab, or we do programmatically*/
TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
public void onTabChanged(String tabId) {
/*Set current tab..*/
mCurrentTab = tabId;

if(mStacks.get(tabId).size() == 0){
/*
* First time this tab is selected. So add first fragment of that tab.
* Dont need animation, so that argument is false.
* We are adding a new fragment which is not present in stack. So add to stack is true.
*/
if(tabId.equals(AppConstants.TAB_A)){
pushFragments(tabId, new AppTabAFirstFragment(), false,true);
}else if(tabId.equals(AppConstants.TAB_B)){
pushFragments(tabId, new AppTabBFirstFragment(), false,true);
}
}else {
/*
* We are switching tabs, and target tab is already has atleast one fragment.
* No need of animation, no need of stack pushing. Just show the target fragment
*/
pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
}
}
};


/* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/
public void setCurrentTab(int val){
mTabHost.setCurrentTab(val);
}


/*
* To add fragment to a tab.
* tag -> Tab identifier
* fragment -> Fragment to show, in tab identified by tag
* shouldAnimate -> should animate transaction. false when we switch tabs, or adding first fragment to a tab
* true when when we are pushing more fragment into navigation stack.
* shouldAdd -> Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
* true in all other cases.
*/
public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
if(shouldAdd)
mStacks.get(tag).push(fragment);
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if(shouldAnimate)
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}


public void popFragments(){
/*
* Select the second last fragment in current tab's stack..
* which will be shown after the fragment transaction given below
*/
Fragment fragment = mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);

/*pop current fragment from stack.. */
mStacks.get(mCurrentTab).pop();

/* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.realtabcontent, fragment);
ft.commit();
}


@Override
public void onBackPressed() {
if(mStacks.get(mCurrentTab).size() == 1){
// We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
finish();
return;
}

/* Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action
* when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult()
* kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself.
*/
((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed();

/* Goto previous fragment in navigation stack of this tab */
popFragments();
}


/*
* Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
* in that fragment, and called it from the activity. But couldn't resist myself.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(mStacks.get(mCurrentTab).size() == 0){
return;
}

/*Now current fragment on screen gets onActivityResult callback..*/
mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
}
}

4. app_main_tab_fragment_layout.xml(如果有人感兴趣的话。)

<?xml version="1.0" encoding="utf-8"?>
<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

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

<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0"/>

<FrameLayout
android:id="@+android:id/realtabcontent"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"/>

<TabWidget
android:id="@android:id/tabs"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"/>

</LinearLayout>
</TabHost>

5。 AppTabAFirstFragment.java(Tab A 中的第一个 fragment ,所有选项卡都类似)

public class AppTabAFragment extends BaseFragment {
private Button mGotoButton;

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

mGoToButton = (Button) view.findViewById(R.id.goto_button);
mGoToButton.setOnClickListener(listener);

return view;
}

private OnClickListener listener = new View.OnClickListener(){
@Override
public void onClick(View v){
/* Go to next fragment in navigation stack*/
mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true);
}
}
}

这可能不是最完美和正确的方法。但在我的情况下它工作得很好。我也只有在纵向模式下才有这个要求。我从来不必在支持两种方向的项目中使用此代码。所以不能说我在那里面临什么样的挑战..

如果有人想要一个完整的项目,我已经将一个示例项目推送到 github .

关于android - 使用 Fragments 为 Android 中的每个选项卡单独返回堆栈,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6987334/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com