在使用片段时切换Android导航抽屉图像和向上插入符号

177

当使用导航抽屉时,Android开发人员建议在ActionBar中“只有那些在导航抽屉中表示的屏幕才实际拥有导航抽屉图像”,而“所有其他屏幕都有传统的向上符号”。

有关详细信息,请参见此处:http://youtu.be/F5COhlbpIbY

我正在使用一个活动来控制多个级别的片段,并且可以在所有级别上显示和使用导航抽屉图像。

创建较低级别片段时,我可以调用 ActionBarDrawerToggle setDrawerIndicatorEnabled(false)来隐藏导航抽屉图像并显示向上符号

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout, 
lowFrag, "lowerFrag").addToBackStack(null).commit();

我遇到的问题是,当我返回顶级片段时,“向上”字符仍然显示,而不是原始的导航抽屉图像。有没有建议如何在顶级片段上“刷新”操作栏以重新显示导航抽屉图像?


解决方案

汤姆的建议对我有用。以下是我的做法:

MainActivity

这个活动控制应用程序中的所有片段。

当准备新的片段来替换其他片段时,我会像这样设置 DrawerToggle setDrawerIndicatorEnabled(false)

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout,   
lowFrag).addToBackStack(null).commit();

接下来,我在 onBackPressed 的覆盖中,通过像这样将 DrawerToggle 设置为 setDrawerIndicatorEnabled(true) 来恢复上述操作:

@Override
public void onBackPressed() {
    super.onBackPressed();
    // turn on the Navigation Drawer image; 
    // this is called in the LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

在LowerLevelFragments中

我修改了片段中的onCreateonOptionsItemSelected,如下所示:

onCreate中添加了setHasOptionsMenu(true)以启用配置选项菜单。还设置setDisplayHomeAsUpEnabled(true)以在操作栏中启用<

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // needed to indicate that the fragment would 
    // like to add items to the Options Menu        
    setHasOptionsMenu(true);    
    // update the actionbar to show the up carat/affordance 
    getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

然后在onOptionsItemSelected中,每当按下<时,它都会调用活动中的onBackPressed()来在层次结构中向上移动一级并显示导航抽屉图像:

@Override
public boolean onOptionsItemSelected(MenuItem item) {   
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            getActivity().onBackPressed();
            return true;
        … 
    }

2
在你的onBackPressed()方法中,你可以使用getFragmentManager().getBackStackEntryCount()方法检查后退栈中有多少条目,并仅在结果为0时启用抽屉指示器。在这种情况下,每个LowerLevelFragments中都不需要启用homeAsUpIndicator。 - pvshnik
3
非常有用!您应该将帖子中的“解决方案”部分移动并将其作为实际的“答案”。这样,您会获得更多的点赞,并且它毕竟是一个答案。 - Oleksiy
为什么要在这里替换片段:.replace(R.id.frag_layout。如果这是一个更高层次的层次结构,我会期望您将其.add到后退栈中。 - JJD
5
兄弟,你怎样在Fragment中引用theDrawerToggle.setDrawerIndicatorEnabled(false);?我认为它是在MainActivity的类文件中声明的。我找不到引用它的方法。有什么提示吗? - Skynet
1
当使用工具栏时,我不得不切换显示选项,以便在此期间不使用主页作为向上选项。否则,在第二次进入相同的片段时,ToolbarWidgetWrapper(内部android.support.v7.internal.widget包中的)中的setDisplayOptions()方法将无法重新创建图标。将此留在这里,以便其他人遇到此问题时可以参考。 - Wolfram Rittmeyer
@Wolfram Rittmeyer,您能详细解释一下您是如何解决使用工具栏时遇到的问题的吗? - Sanif SS
12个回答

83

这很简单,就像1-2-3一样。

如果你想要实现:

1)抽屉指示器 - 当没有片段在后退栈中或抽屉被打开时

2)箭头 - 当一些片段在后退栈中时

private FragmentManager.OnBackStackChangedListener
        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        syncActionBarArrowState();
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    getSupportActionBar().setDisplayShowHomeEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    mDrawerToggle = new ActionBarDrawerToggle(
            this,             
            mDrawerLayout,  
            R.drawable.ic_navigation_drawer, 
            0, 
            0  
    ) {

        public void onDrawerClosed(View view) {
            syncActionBarArrowState();
        }

        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
    getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}

@Override
protected void onDestroy() {
    getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
    super.onDestroy();
}

private void syncActionBarArrowState() {
    int backStackEntryCount = 
        getSupportFragmentManager().getBackStackEntryCount();
    mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

3) 两个指标应根据它们的形状采取行动

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (mDrawerToggle.isDrawerIndicatorEnabled() && 
        mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    } else if (item.getItemId() == android.R.id.home && 
               getSupportFragmentManager().popBackStackImmediate()) {
        return true;
    } else {
        return super.onOptionsItemSelected(item);
    }
}

附注:有关在Android开发者网站上创建导航抽屉的更多技巧,请参见Creating a Navigation Drawer on Android Developers.


3
我最终采用了类似于你的解决方案。我认为这种方法(使用 BackStackChangedListener 来切换所显示的内容)是最优雅的。然而,我做了两个修改:1)我不在 onDrawerClosed/onDrawerOpened 函数中调用/更改抽屉指示器 - 这是不必要的;2)我让 Fragment 自己处理向上导航,通过创建一个所有 Fragment 都继承的 AbstractFragment,并实现 onOptionsItemSelected(..) 函数,该函数总是调用 setHasOptionsMenu(true)。 - Espen Riskedal
2
你还应该在箭头模式下锁定导航抽屉的滑动手势:mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);并在抽屉指示器模式下重新启用它。 - artkoenig
5
顺便说一下,我最终是在我的NavigationDrawer片段中完成了所有这些工作。顺利运行,谢谢。 - frostymarvelous
2
当我从片段A转到B并将A添加到backstack时,它没有为我显示向上按钮。 :( - aandis
1
@zack,对我而言,上述方法并不够用。为了使按钮显示出来,我采用了一个奇怪但是行得通的解决方案:https://dev59.com/HF4c5IYBdhLWcg3wl7Xm#29594947 。之后,向上箭头无法点击,因此我按照这个解决方案:https://dev59.com/mJHea4cB1Zd3GeqPlCF7#33872425 最终解决了问题。 - Dale Cooper
显示剩余11条评论

30
你写道,为了实现较低级别的片段,你替换了现有的片段,而不是在新活动中实现较低级别的片段。
我认为,这样你就必须手动实现后退功能:当用户按下后退键时,你需要编写代码来弹出栈(例如,在`Activity::onBackPressed`中重写)。因此,无论你何时这样做,都可以将`setDrawerIndicatorEnabled`反转。

谢谢Tom,那个方法很有效!我已经更新了原帖并附上了我使用的解决方案。 - EvilAsh
6
如何在较低级别的fragment中引用theDrawerToggle?它已经在主Activity中被定义,我无法理解这个问题! - Skynet
1
你可以从fragment中获取activity。因此,只需在主activity中添加一个getter方法来访问抽屉开关即可。 - Raphael Royer-Rivard

14

我使用了下一个东西:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0){
                    mDrawerToggle.setDrawerIndicatorEnabled(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                }
                else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    mDrawerToggle.setDrawerIndicatorEnabled(true);
                }
            }
        });

1
非常感谢,这是唯一一个真正起作用的。一旦我添加了这个,drawerToggle.setToolbarNavigationClickListener(,当点击箭头时就会调用此侦听器。 - EpicPandaForce
谢谢@Yuriy,这帮助我解决了我的问题。 - Muhammad Shoaib Murtaza

12

如果您的导航栏上的“返回”按钮无法正常工作,请不要忘记添加监听器:

// Navigation back icon listener
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
});

我在实现一个带有首页按钮的抽屉式导航时遇到了一些问题,除了操作按钮之外,一切都正常。


10

尝试在MainActivity中根据DrawerToggle的状态处理Home项目选择。这样,您就不必将相同的代码添加到每个片段中。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            onBackPressed();
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

+1个简洁的解决方案。为了使您的答案完全可用,您应该在后退堆栈上添加一个检查。当它为空时,自动将抽屉指示器设置为true。 - HpTerm
@HpTerm 我在 onBackPressed() 中处理后退栈,因为我想要两者的行为相同。 - dzeikei

6

跟进

@dzeikei提供的解决方案很巧妙,但在使用片段时,可以对其进行扩展,以自动处理当返回堆栈为空时设置抽屉指示器的情况。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            // Use getSupportFragmentManager() to support older devices
            FragmentManager fragmentManager = getFragmentManager();
            fragmentManager.popBackStack();
            // Make sure transactions are finished before reading backstack count
            fragmentManager.executePendingTransactions();
            if (fragmentManager.getBackStackEntryCount() < 1){
                mDrawerToggle.setDrawerIndicatorEnabled(true);  
            }
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

编辑

针对@JJD的问题。

这些碎片由一个活动进行管理。上面的代码只写了一次,但仅处理onOptionsItemSelected中的向上箭头。

在我的某个应用程序中,我还需要处理当按下返回按钮时向上箭头的行为。这可以通过覆盖onBackPressed来处理。

@Override
public void onBackPressed() {
    // Use getSupportFragmentManager() to support older devices
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.executePendingTransactions();
    if (fragmentManager.getBackStackEntryCount() < 1){
        super.onBackPressed();
    } else {
        fragmentManager.executePendingTransactions();
        fragmentManager.popBackStack();
        fragmentManager.executePendingTransactions();
        if (fragmentManager.getBackStackEntryCount() < 1){
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    }
};

请注意onOptionsItemSelectedonBackPressed之间的代码重复,可以通过创建一个方法并在两个地方调用该方法来避免重复。
另外,请注意我添加了两次executePendingTransactions,在我的情况下是必需的,否则我会遇到奇怪的返回箭头行为。

这是我需要添加以实现“向上”符号行为的完整代码,还是您指的是其他帖子之一?请澄清一下。 - JJD
感谢您的编辑。实际上,我在 NavigationDrawerFragment 类中维护了 mDrawerToggle。为了让它正常工作,我还需要切换主页按钮/指示器的状态 - 请参见:NavigationDrawerFragment#toggleDrawerIndicator。此外,我不确定您是否需要 onOptionsItemSelected 中的初始检查:我已将其取消注释。- [简化示例](http://pastebin.com/7Ughhn5D) - JJD
修订后的实现:你在 onOptionsItemSelected 中的初始检查是正确的。这确保了导航抽屉仍然在顶级层次结构中打开。但是,我将代码移动到了 NavigationDrawerFragment#onOptionsItemSelected 中。这有助于我不将 mDrawerToggle 暴露给 MainActivity - JJD
@JJD 我记得我曾经为每个层次结构的向上符号(up caret)让一切都正常工作而苦苦挣扎。只要它对你也有效,那就没问题了。当然,正如你所说,你可以将代码移动到其他地方以避免暴露它。 - HpTerm

2

这个答案本来可以工作,但是存在一个小问题。 getSupportActionBar().setDisplayHomeAsUpEnabled(false)并没有被显式调用,这导致即使在后退栈中没有项目时,抽屉图标也会被隐藏,所以改变了setActionBarArrowDependingOnFragmentsBackStack()方法对我有用。

private void setActionBarArrowDependingOnFragmentsBackStack() {
        int backStackEntryCount = getSupportFragmentManager()
                .getBackStackEntryCount();
        // If there are no items in the back stack
        if (backStackEntryCount == 0) {
            // Please make sure that UP CARAT is Hidden otherwise Drawer icon
            // wont display
            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            // Display the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        } else {
            // Show the Up carat
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            // Hide the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(false);
        }

    }

在我的情况下,正确的解决方案是仅使用actionBarDrawerToggle.setDrawerIndicatorEnabled(getSupportFragmentManager().getBackStackEntryCount() < 0); - Gorets

2

我为托管活动创建了一个接口,用于更新汉堡菜单的视图状态。对于顶层片段,我将切换设置为true,对于需要显示向上箭头的片段,我将切换设置为false

public class SomeFragment extends Fragment {

    public interface OnFragmentInteractionListener {
        public void showDrawerToggle(boolean showDrawerToggle);
    }

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            this.mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mListener.showDrawerToggle(false);
    }
}

然后在我的Activity中...
public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

    public void showDrawerToggle(boolean showDrawerIndicator) {
        mDrawerToggle.setDrawerIndicatorEnabled(showDrawerIndicator);
    }

}

1
如果您查看GMAIL应用程序并在此搜索插入符号/可负担性图标,则我要求您这样做,上述答案都不清楚。我能够修改接受的答案。
  • NavigationDrawer--> ListView包含子片段。


  • 子片段将列出如下

  • firstFragment ==位置0 --->这将有子片段-->片段

  • secondFragment
  • thirdFragment等等....

在firstFragment中,您有其他片段。

在DrawerActivity上调用此函数

getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getFragmentManager().getBackStackEntryCount() > 0) {
                mDrawerToggle.setDrawerIndicatorEnabled(false);
            } else {
                mDrawerToggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

和片段相关

    setHasOptionsMenu(true);    

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            activity.onBackPressed();
            return true;
    }
    return false;
}

在OnBackPressed抽屉活动方法中,将抽屉切换设置为true,以重新启用导航列表图标。
谢谢, Pusp

1

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