使用片段实现ActionBar的返回导航

94

我有一个带有三个选项卡的操作栏/viewpager布局,分别为ABC。在选项卡C中添加了另一个碎片,称为碎片D


 DFragment f= new DFragment();
 ft.add(android.R.id.content, f, "");
 ft.remove(CFragment.this);
 ft.addToBackStack(null);
 ft.commit();

我在DFragment的onResume中修改actionbar以添加返回按钮:

ActionBar ab = getActivity().getActionBar();
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
ab.setDisplayHomeAsUpEnabled(true);
ab.setDisplayShowHomeEnabled(true);

现在在DFragment中,当我按下硬件(手机)返回按钮时,我会返回到原始的Tabbed(ABC)布局,并选择CFragment。如何使用动作栏向上按钮实现此功能?


可能是重复的问题:如何在Fragment中实现onBackPressed()? - Code-Apprentice
10个回答

189

实现OnBackStackChangedListener并将此代码添加到您的Fragment Activity中。

@Override
public void onCreate(Bundle savedInstanceState) {
    //Listen for changes in the back stack
    getSupportFragmentManager().addOnBackStackChangedListener(this);
    //Handle when activity is recreated like on orientation Change
    shouldDisplayHomeUp();
}

@Override
public void onBackStackChanged() {
    shouldDisplayHomeUp();
}

public void shouldDisplayHomeUp(){
   //Enable Up button only  if there are entries in the back stack
   boolean canGoBack = getSupportFragmentManager().getBackStackEntryCount()>0;
   getSupportActionBar().setDisplayHomeAsUpEnabled(canGoBack);
}

@Override
public boolean onSupportNavigateUp() {
    //This method is called when the up button is pressed. Just the pop back stack.
    getSupportFragmentManager().popBackStack();
    return true;
}

22
关于 onSupportNavigateUp(),"Method does not override method from its superclass"。 该方法没有覆盖其超类中的方法。 - Fred
10
如果您已经有一个 onOptionsItemSelected,也可以检查 itemId android.R.id.home 来代替添加 onSupportNavigateUp - domsom
1
如果API版本>=14,请使用onNavigateUp而不是onSupportNavigateUp。@Override public boolean onNavigateUp() { //当按下向上按钮时调用此方法。只需弹出回退堆栈。 getFragmentManager().popBackStack(); return true; } - Tejasvi Hegde
1
在“ActionBar”中,您的应用程序图标旁边是否应该显示一个向上的插入符号?当我实现这段代码时,我没有看到它。我只能点击图标,但它什么也不做。适用于Android 4.0+。 - Azurespot
3
如果没有重写onBackStackChanged()方法,请确保您的Activity实现了FragmentManager.OnBackStackChangedListener接口。 - CBA110
显示剩余5条评论

43

我明白了。只需在承载活动中覆盖onOptionsItemSelected并弹出后退栈,例如:


我明白了。只需要在承载活动中重写 onOptionsItemSelected 方法并弹出回退栈即可,例如:
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home: {
            FragmentManager fm = getSupportFragmentManager();
            if (fm.getBackStackEntryCount() > 0) {
                fm.popBackStack();
                return true;
            }
            break;
        }
    }
    return super.onOptionsItemSelected(item);
}

按照下面的答案,在onBackStackChanged()中调用getActionBar().setDisplayHomeAsUpEnabled(boolean);getActionBar().setHomeButtonEnabled(boolean);


3
一旦你弹出后退堆栈,还需要调用getActivity().getActionBar().setDisplayHomeAsUpEnabled(false)来移除返回按钮。 - JoP
这不是正确的做法。它会保持启用上按钮。 - Roger Garzon Nieto
1
你应该将那段代码放在switch语句中的case android.R.id.home里面。 - Fred

18
如果您有一个父活动,并希望此“向上”按钮作为返回按钮工作,则可以使用以下代码:
将以下代码添加到主活动类的onCreate中。
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            int stackHeight = getSupportFragmentManager().getBackStackEntryCount();
            if (stackHeight > 0) { // if we have something on the stack (doesn't include the current shown fragment)
                getSupportActionBar().setHomeButtonEnabled(true);
                getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            } else {
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                getSupportActionBar().setHomeButtonEnabled(false);
            }
        }

    });

然后添加onOptionsItemSelected,就像这样:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            getSupportFragmentManager().popBackStack();
            return true;
     ....
 }

我通常一直使用这个,看起来相当可靠。

1
我尝试在我的一个“Activity”中使用这段代码,希望它能返回到它开始的片段。当我进入我的“Activity”时,返回按钮出现在我的应用图标附近,但是当我点击图标时什么也没有发生(它应该返回到片段)。有什么想法吗?谢谢。 - Azurespot
1
@Daniel,你的代码很棒...它确实可以工作。你可能想要用try catch选项将其包围起来,以防万一...你知道,以防止任何意外异常和应用程序崩溃。 - Zuko
1
@NoniA。如果您在新活动中膨胀了1个片段,则此操作仅会返回到先前的片段(例如,片段B->片段A),而不会返回到先前的活动。 - Daniel Jonker

11

你可以使用向上按钮回到之前的页面,就像使用后退按钮一样;

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            super.onBackPressed();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

9

我知道这个问题很老了,但是可能还有其他人(比如我)也需要它。

如果你的Activity扩展了AppCompatActivity,你可以使用一个更简单的(两步)解决方案:

1 - 每当您添加一个非主页Fragment时,只需在提交片段事务后立即显示向上按钮。就像这样:

    // ... add a fragment
    // Commit the transaction
    transaction.commit();

    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

2 - 当“上”按钮被按下时,您需要将其隐藏。

@Override
public boolean onSupportNavigateUp() {
    getSupportActionBar().setDisplayHomeAsUpEnabled(false);        
    return true;
}

就这样。


8
我使用了Roger Garzon Nietosohailaziz的答案的组合。我的应用程序有一个单一的MainActivity和加载到其中的A、B、C三个片段。我的“主页”片段(A)实现了OnBackStackChangedListener,并检查backStack的大小;如果小于1,则隐藏UP按钮。片段B和C总是加载后退按钮(在我的设计中,B从A启动,C从B启动)。MainActivity本身只在UP按钮点击时弹出backstack,并具有显示/隐藏按钮的方法,这些方法由片段调用:

MainActivity:

public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        // Respond to the action bar's Up/Home button
        case android.R.id.home:
            getSupportFragmentManager().popBackStack();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

public void showUpButton() { getSupportActionBar().setDisplayHomeAsUpEnabled(true); }
public void hideUpButton() { getSupportActionBar().setDisplayHomeAsUpEnabled(false); }

fragmentA(实现FragmentManager.OnBackStackChangedListener):

public void onCreate(Bundle savedinstanceSate) {
    // listen to backstack changes
    getActivity().getSupportFragmentManager().addOnBackStackChangedListener(this);

    // other fragment init stuff
    ...
}

public void onBackStackChanged() {
    // enable Up button only  if there are entries on the backstack
    if(getActivity().getSupportFragmentManager().getBackStackEntryCount() < 1) {
        ((MainActivity)getActivity()).hideUpButton();
    }
}

fragmentB,fragmentC:

public void onCreate(Bundle savedinstanceSate) {
    // show the UP button
    ((MainActivity)getActivity()).showUpButton();

    // other fragment init stuff
    ...
}

5
Kotlin:
class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        supportFragmentManager.addOnBackStackChangedListener { setupHomeAsUp() }
        setupHomeAsUp()
    }

    private fun setupHomeAsUp() {
        val shouldShow = 0 < supportFragmentManager.backStackEntryCount
        supportActionBar?.setDisplayHomeAsUpEnabled(shouldShow)
    }

    override fun onSupportNavigateUp(): Boolean = 
        supportFragmentManager.popBackStack().run { true }

    ...
}

5
这对我很有帮助。例如,重写 onSupportNavigateUp 和 onBackPressed 方法(Kotlin 代码示例):
override fun onBackPressed() {
    val count = supportFragmentManager.backStackEntryCount
    if (count == 0) {
        super.onBackPressed()
    } else {
        supportFragmentManager.popBackStack()
    }
}

override fun onSupportNavigateUp(): Boolean {
    super.onSupportNavigateUp()
    onBackPressed()
    return true
}

现在在片段中,如果你显示上箭头

activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)

点击它将带您返回上一个活动。

2
这是一个非常好的可靠解决方案:http://vinsol.com/blog/2014/10/01/handling-back-button-press-inside-fragments/。该作者制作了一个抽象片段,处理了 backPress 行为,并使用策略模式在活动片段之间进行切换。对于一些人来说,抽象类可能有一些缺点...简单地说,链接中的解决方案如下:
// Abstract Fragment handling the back presses

public abstract class BackHandledFragment extends Fragment {
    protected BackHandlerInterface backHandlerInterface;
    public abstract String getTagText();
    public abstract boolean onBackPressed();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(!(getActivity()  instanceof BackHandlerInterface)) {
            throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
        } else {
            backHandlerInterface = (BackHandlerInterface) getActivity();
        }
    }

    @Override
    public void onStart() {
        super.onStart();

        // Mark this fragment as the selected Fragment.
        backHandlerInterface.setSelectedFragment(this);
    }

    public interface BackHandlerInterface {
        public void setSelectedFragment(BackHandledFragment backHandledFragment);
    }
}   

在活动中的使用:

// BASIC ACTIVITY CODE THAT LETS ITS FRAGMENT UTILIZE onBackPress EVENTS 
// IN AN ADAPTIVE AND ORGANIZED PATTERN USING BackHandledFragment

public class TheActivity extends FragmentActivity implements BackHandlerInterface {
    private BackHandledFragment selectedFragment;

    @Override
    public void onBackPressed() {
        if(selectedFragment == null || !selectedFragment.onBackPressed()) {
            // Selected fragment did not consume the back press event.
            super.onBackPressed();
        }
    }

    @Override
    public void setSelectedFragment(BackHandledFragment selectedFragment) {
        this.selectedFragment = selectedFragment;
    }
}

虽然这个链接可能回答了问题,但最好包括答案的必要部分在此处,并提供参考链接。仅有链接的答案如果链接页面发生更改,可能会变得无效。 - bummi
顺便说一句:如果您发现重复内容,请标记它们。谢谢。 - bummi
在 onStart 中设置 setSelectedFragment 很重要吗? - VLeonovs

0
如果您想返回到先前的活动,而此活动具有空片段堆栈:
这可能很有用,如果您有一个MainActivity,并且您正在导航到具有嵌套prefernceScreens的SettingsActivity。 NavigateUp将弹出片段,直到您完成SettingsActivity以返回到parentActivity / root。
/**
 * On actionbar up-button popping fragments from stack until it is empty.
 * @return true if fragment popped or returned to parent activity successfully.
 */
@Override
public boolean onSupportNavigateUp() {
    //Pop back stack if the up button is pressed.
    boolean canGoBack = getSupportFragmentManager().getBackStackEntryCount()>0;
    if (canGoBack) {
        getSupportFragmentManager().popBackStack();
    } else {
        finish();
        return super.onSupportNavigateUp();
    }
    return true;
}

注意:在Fragment活动的onCreate()方法中,调用setDisplayHomeAsUpEnabled(true);


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