工具栏 - 只有一个活动从抽屉切换到后退按钮

Posted

技术标签:

【中文标题】工具栏 - 只有一个活动从抽屉切换到后退按钮【英文标题】:Toolbar - Switching from drawer to back button with only one Activity 【发布时间】:2015-04-16 09:04:33 【问题描述】:

我一直在寻找有关如何在抽屉打开/关闭图标(从汉堡包到箭头)到简单的后退箭头之间进行更改的方法。目前我的应用程序只有一个 Activity 可以在多个片段之间切换。有一次,我想在一个主要片段(即抽屉中的一个片段)之间转换到分层位于前一个片段下的片段(即“添加新”片段)。在这个新片段中,我希望工具栏显示后退按钮而不是抽屉按钮。

很长一段时间以来,我一直在环顾四周并尝试不同的解决方案。以下是最值得注意的:

Change drawer icon back to back arrow - 我成功删除了抽屉图标,但在原地……什么都没有。没有向上插入符号,没有后退按钮,没有图标。我怀疑这是因为我的 Activity 没有父级,但除了便宜的解决方法(创建另一个 Activity 作为启动主 Activity 的父级)之外,我不知道该怎么做。 Switching between android Navigation Drawer image and Up caret when using fragments - 与上述类似,但有更多细节。最终,图标仍然没有变成后退按钮。 Android lollipop toolbar switch between open/close drawer and back button - 我觉得这很难理解,但最终抽屉图标可以被点击并且什么也不做(尽管我相信我知道如何让它充当后压)。但是,图标不会改变。

目前,我正在考虑一种漫长而艰巨的方法来创建我隐藏和显示的自定义图标(以及隐藏/显示本机抽屉图标)。但是,有没有更好的方法在抽屉按钮和后退按钮之间切换?

作为一个相关的问题,我一直在查看 Material Design 文档,其中一些示例的左上角有一个 X。实现与实现抽屉和后退/向上按钮有什么不同?

谢谢~

编辑:

我可以弄清楚如何替换图标,但如何获得点击事件?

到目前为止,这是我最好的线索:

Cannot catch toolbar home button click event

我现在尝试过的:

必要时禁用 DrawerToggle(即mDrawerToggle.setDrawerIndicatorEnabled(useDrawer);) 在我的 NavigationDrawerFragment、我的 Activity 以及我当前正在测试的 DialogFragment 中添加了 onOptionsItemSelected 日志,如果 item.getItemId() == android.R.id.home 为 true,则运行这些日志。这些日志语句都没有发生

为了更好的上下文,我现在有一个全屏片段,它在菜单中添加了一个“保存”按钮并将抽屉图标更改为“X”。 Fragment 可以获取保存菜单事件,但是点击 X 时连 Activity 和 Drawer 都不能获取。

编辑2:

根据要求,这里有一些代码。请注意,这一切都来自this Github repo,我正在积极研究(请注意,我在这里或那里有一些来自快速测试的无用功能)。

ActivityMain

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Add the toolbar
    mToolbar = (Toolbar) findViewById(R.id.toolbar);
    if (mToolbar != null) 
        setSupportActionBar(mToolbar);
    

    // Initialize the drawer
    mNavigationDrawerFragment = (NavigationDrawerFragment)
            getSupportFragmentManager().findFragmentById(R.id.navigation_drawer);

    // Set up the drawer
    mNavigationDrawerFragment.setUp(
            R.id.navigation_drawer,
            (DrawerLayout) findViewById(R.id.drawer_layout),
            mToolbar);

    // TODO: Check if this helps to catch the main toolbar button click
    getSupportActionBar().setDisplayShowHomeEnabled(true);

    // Get the titles for the Toolbar
    mTitles = getResources().getStringArray(R.array.drawer_items);

    mDrawerPosition = -1;
    if (savedInstanceState == null) 
        // If there was no saved position, then the default, starting position should be used
        forceChangeItemSelected(0);
    
    else 
        // Otherwise, get the saved position from the bundle
        int position = savedInstanceState.getInt(KEY_DRAWERPOS);
        mNavigationDrawerFragment.setSelectedItem(position);
        // Title needs to be re-set
        getSupportActionBar().setTitle(mTitles[position]);
    

    // If I include the below bit, then the DrawerToggle doesn't function
        // I don't know how to switch it back and forth
    mToolbar.setNavigationOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            Log.d(LOG_TAG, "Navigation was clicked");

        
    );


@Override
public boolean onOptionsItemSelected(MenuItem item) 
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    Log.d(LOG_TAG, "Activity responding to menu click...");
    if(item.getItemId() == android.R.id.home) Log.d(LOG_TAG, "Activity got it....");

    // If the fragment is supposed to handle things, then let it
    if(mIsFragmentHandlingMenus) return false;

    int id = item.getItemId();
    if(id == R.id.save) 
        // This isn't implemented! If chosen, then there's a bug!
        Log.e(LOG_TAG, "onOptionsItemSelected: Save was selected!");
    

    return super.onOptionsItemSelected(item);


@Override
public void fragmentHandlingMenus(boolean isFragmentHandlingMenus) 
    // Simply store the setting
    mIsFragmentHandlingMenus = isFragmentHandlingMenus;

    // Toggle the drawer as necessary
    mNavigationDrawerFragment.toggleDrawerUse(!isFragmentHandlingMenus);

NavigationDrawerFragment:

public void toggleDrawerUse(boolean useDrawer) 
    // Enable/Disable the icon being used by the drawer
    mDrawerToggle.setDrawerIndicatorEnabled(useDrawer);

    // TODO: Enable/Disable the drawer even being able to open/close


@Override
public boolean onOptionsItemSelected(MenuItem item) 
    Log.d(LOGTAG, "Drawer responding to menu click...");
    if(item.getItemId() == android.R.id.home) Log.d(LOGTAG, "Drawer got it....");
    if (mDrawerToggle.onOptionsItemSelected(item)) 
        return true;
    

    return super.onOptionsItemSelected(item);

GoalAdderFragment:

@Override
public void onActivityCreated(Bundle savedInstanceState) 
    super.onActivityCreated(savedInstanceState);
    // Allow this fragment to handle toolbar menu items
    setHasOptionsMenu(true);

    // Set up the toolbar
    ((ActionBarActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    ((ActionBarActivity) getActivity()).getSupportActionBar().setHomeAsUpIndicator(android.R.drawable.ic_menu_close_clear_cancel);
    ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(getResources().getString(R.string.title_addgoal));


@Override
public void onAttach(Activity activity) 
    super.onAttach(activity);

    // Cache the Activity as the frag handler if necessary
    if(mFragHandler == null)
        mFragHandler = (TransactionHandler.FragmentTransactionHandler) getActivity();
    // Tell the Activity to let fragments handle the menu events
    mFragHandler.fragmentHandlingMenus(true);


@Override
public void onDetach() 
    super.onDetach();

    // Tell the Activity that it can now handle menu events once again
    mFragHandler.fragmentHandlingMenus(false);


@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) 
    inflater.inflate(R.menu.save_menu, menu);


@Override
public boolean onOptionsItemSelected(MenuItem item) 
    Log.d(LOGTAG, "Item id: " + item.getItemId() + " | Save id: " + R.id.save);
    Toast.makeText(getActivity(), "Fragment activated!", Toast.LENGTH_SHORT).show();

    switch (item.getItemId()) 
        case R.id.save:
            return true;
        case android.R.id.home:
            return true;
        default:
            break;
    

    return false;

解决方案:

在下面 natario 的回答的帮助下,这是我最终得到的最终解决方案:

NavigationDrawerFragment

private View.OnClickListener mOriginalListener;

public void setUp(int fragmentId, DrawerLayout drawerLayout, Toolbar toolbar) 
     /* Rest of setting up code */

     // Save the default listener after setting everything else up
     mOriginalListener = mDrawerToggle.getToolbarNavigationClickListener();


// Tells the toolbar+drawer to switch to the up button or switch back to the normal drawer
public void toggleDrawerUse(boolean useDrawer) 
    // Enable/Disable the icon being used by the drawer
    mDrawerToggle.setDrawerIndicatorEnabled(useDrawer);

    // Switch between the listeners as necessary
    if(useDrawer)
        mDrawerToggle.setToolbarNavigationClickListener(mOriginalListener);
    else
        mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                Toast.makeText(getActivity(), "Custom listener", Toast.LENGTH_SHORT).show();
            
        );

【问题讨论】:

使用ActionBarDrawerListener的v7支持库。如果您调用构造函数并实现方法,它将为您处理所有事情。确保您使用的是 v7 版本,而不是 v4 版本,因为它已被弃用且无法使用。我从几个小时的沮丧中知道这一点。 【参考方案1】:

将此代码放入您的Activity 中的onCreate()。对我来说效果很好。即使使用compileSdk 23 及更高版本。

    drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    if(toolbar != null) 
        toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        toggle.syncState();
        drawer.setDrawerListener(toggle);
        getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() 
            @Override
            public void onBackStackChanged() 
                if (getSupportFragmentManager().getBackStackEntryCount() > 0) 
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true); // show back button
                    toolbar.setNavigationOnClickListener(new View.OnClickListener() 
                        @Override
                        public void onClick(View v) 
                            onBackPressed();
                        
                    );
                 else 
                    //show hamburger
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    toggle.syncState();
                    toolbar.setNavigationOnClickListener(new View.OnClickListener() 
                        @Override
                        public void onClick(View v) 
                            drawer.openDrawer(GravityCompat.START);
                        
                    );
                
            
        );

【讨论】:

这是迄今为止我找到的最好的解决方案,你应该得到公认的答案,至少是最高值。有了这个解决方案,我不必担心协调箭头按钮和加载前一个片段。在我看来,这应该建立在工具栏中,如果有人想修改它的行为,而不是覆盖一个方法。谢谢@matusalem 是的,此解决方案自动使用片段替换方法,无需调用任何方法。很高兴看到我提供了帮助。欢迎你。快乐编码:) 不知道我做错了什么。但这对我不起作用: if(id==R.id.ranking_item) fragment = new KPIDetailFragment(); if (fragment != null) FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.main_layout, 片段); fragmentTransaction.commit(); // 设置工具栏标题 getSupportActionBar().setTitle("KPI DETAIL"); 与许多其他技巧不同,它帮助了我。它是如此简单和自然(toolbar.setNavigationOnClickListener())!非常感谢。【参考方案2】:

它应该适用于最新的 API 24

在您的活动onCreate() 中执行以下操作:

final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
final DrawerLayout drawer = (DrawerLayout) view.findViewById(R.id.drawer_layout);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);

final ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, 
    R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();

final View.OnClickListener originalToolbarListener = toggle.getToolbarNavigationClickListener();

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() 
    @Override
    public void onBackStackChanged() 
        if (getSupportFragmentManager().getBackStackEntryCount() > 0) 
            toggle.setDrawerIndicatorEnabled(false);
            toggle.setToolbarNavigationClickListener(new View.OnClickListener() 
                @Override
                public void onClick(View v) 
                    getSupportFragmentManager().popBackStack();
                
            );
         else 
            toggle.setDrawerIndicatorEnabled(true);
            toggle.setToolbarNavigationClickListener(originalToolbarListener);
        
    
);

【讨论】:

您的getToolbarNavigationClickListener() 启发了我使用setToolbarNavigationClickListener() 并解决了我的问题!!非常感谢!!!【参考方案3】:

这可能不是您想听到的,但即使从概念的角度来看,我也会选择新的活动而不是片段。

您的主要活动与抽屉严格相关,因此在不访问抽屉的情况下加载新片段对我来说毫无意义(但如果您这么认为,请随时等待其他答案)。一个新的活动将解决这两个问题,因为它没有抽屉,并且可能是主要活动的孩子。

你的附带问题看起来也很到位。 “添加新”活动可以很好地适应指南中的“全屏对话框”视觉模式。见:

http://www.google.com/design/spec/components/dialogs.html#dialogs-full-screen-dialogs

此模式有一个“保存”、右上角的正按钮和一个 X。从概念上讲,X 按钮是取消/中止一个进程,而不是向上导航一些 backstack。这意味着您在不让任何行动发生的情况下解雇某些事情。这非常适合您想做的事情。

从设计的角度来看,它很容易由新的Activity 制作,可以保持领先地位。此外,如果片段的点基本上能够在平板电脑和更大的屏幕上同时表示两个或多个 - 再次 - 我不会对左侧的旧片段和右侧的“添加新”片段感到满意.

相反,在平板电脑上,我会按照指南的建议使用浮动对话框。

http://www.google.com/design/spec/components/dialogs.html#dialogs-confirmation-dialogs

因此,全屏活动带有用于手机的 X 按钮和用于平板电脑的浮动对话框(底部带有按钮)。对我来说,这是最符合准则的方法。


我建议阅读整个链接。关于

X 与向上箭头不同,向上箭头用于不断保存视图状态或应用具有草稿或自动保存功能时。例如,设置中使用向上箭头,因为所有更改都会立即提交。

还有

触摸this Settings example 中的 X 将放弃所有更改。只有在触摸“保存”时才会保存更改。

【讨论】:

感谢您的回答!对话框模式(特别是在较小的设备上使用全屏对话框)似乎是“添加新”功能的好主意。至于使用多个Activity……那是我的初衷。但是,我最近一直专注于转换,尝试为棒棒糖之前的设备创建 Activity 转换似乎太费力了(因为它根本不受本机支持)。我能想到的使用过渡动画的最佳方法是使用片段。 这是一个很好的答案,但是我很可能需要尽快切换工具栏按钮,所以我会保持问题的开放性。再次感谢~ 不客气。至于活动转换,您可以使用android.view.Animation 框架和overridePendingTransitions()。不像片段过渡那样平滑,但可以相当不错。 实际上,我沿着全屏对话框(片段)路线走下去,不知何故遇到了类似的障碍:我无法获得抽屉/主页按钮单击事件。我为我的问题添加了更深入的描述。你介意再帮我一次吗?谢谢! 我无法向answer you linked 添加任何内容。如果您发布一些代码,我们可以看看。这取决于您如何/何时用 X 替换抽屉图标。【参考方案4】:

@matusalem 的回答效果很好。我只需要添加一点——要小心,因为抽屉也可以通过从屏幕左侧滑入来打开。对于某些人来说,这可能是需要的,但对我来说,我禁用了抽屉,因为除了我的主要片段之外,它在任何片段中都没有意义。滑动在这里很容易被禁用 - Navigation drawer - disable swipe

这可能属于对答案的评论,但我没有足够的声誉。我很抱歉。

【讨论】:

你是对的,但仅限于你的特殊情况。我认为没有理由不能随时刷入抽屉。为此目的发明了抽屉。请参阅 Google 应用设计 - Google Play、Gmail 等。【参考方案5】:

在更改片段时,我在同一活动内的汉堡菜单和后退箭头之间切换时遇到了同样的问题。这是我的工作解决方案,希望对某人有所帮助。

Activity 中的监听器:

private View.OnClickListener toolbarMenuListener = new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            //will be called only if toggle.setDrawerIndicatorEnabled(false); !
            Log.v(tag,"toggle onClick:"+v.getId()+" android.R.id.home:"+android.R.id.home);
            onBackPressed();
        
    ;

代码 onCreate() 类似:

...
...
setSupportActionBar(toolbar);
toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();

//set listener so you know when back on arrow is pressed
toggle.setToolbarNavigationClickListener(toolbarMenuListener);
...
...

您对 cme​​ts 感兴趣的部分(返回的类是我的一些类,可以设置为 void):

/**
     * Method to set up action bar drawer.
     * @param enableBackDrawerIcon set true if want to show drawer back arrow,
     *                             false to show hamburger menu.
     * @param title shown next to drawer icon
     */
    public BaseMenusActivity drawerSetupToggle(boolean enableBackDrawerIcon, String title) 
        //NOTE: order of methods call is important!
        // If you change order order of setDrawerIndicatorEnabled and setDisplayHomeAsUpEnabled
        // method calls it won't work, weird bugs will happen (like no icon at all)
        if(enableBackDrawerIcon)
            Log.v(tag,"show drawer back icon");
            //hides hamburger menu and enables View.OnClickListener to be called
            toggle.setDrawerIndicatorEnabled(false);
            //show back arrow
            if(getSupportActionBar()!=null)
                getSupportActionBar().setDisplayHomeAsUpEnabled(true);

         else 
            Log.v(tag,"show hamburger menu");
            //hide back arrow
            if(getSupportActionBar()!=null)
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            //shows hamburger menu and prevents View.OnClickListener to be called
            toggle.setDrawerIndicatorEnabled(true);
        

        setTitle(title);
        return this;
    

注意:调用方法的顺序很重要!如果能像这样写成两行就更好了,但是 WON'T WORK(至少对我来说):

toggle.setDrawerIndicatorEnabled(!enableBackDrawerIcon);
     getSupportActionBar().setDisplayHomeAsUpEnabled(enableBackDrawerIcon);

如果您对为什么方法调用的顺序会搞砸感兴趣,请查看这些方法的实现。

【讨论】:

【参考方案6】:
//This if block makes the menu back button to respond to clicks
    //The onOptionsItemSelected fun for whatever reason was not capturing back menu clicks
    if (toolbar != null) 
       /* toggle = ActionBarDrawerToggle(
                this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
        toggle.syncState()
        drawer_layout.setDrawerListener(toggle)*/
        supportFragmentManager.addOnBackStackChangedListener(object : FragmentManager.OnBackStackChangedListener 
            override fun onBackStackChanged() 
                if (supportFragmentManager.backStackEntryCount > 0) 
                    supportActionBar?.setDisplayHomeAsUpEnabled(true) // show back button
                    toolbar.setNavigationOnClickListener(object : View.OnClickListener 
                        override fun onClick(v: View) 
                            onBackPressed()
                        
                    )
                 else 
                    //show hamburger
                    supportActionBar?.setDisplayHomeAsUpEnabled(false)
                    toggle.syncState()
                    toolbar.setNavigationOnClickListener(object : View.OnClickListener 
                        override fun onClick(v: View) 
                            drawer_layout.openDrawer(GravityCompat.START)
                        
                    )
                
            
        )

    

你需要注释掉“toggle = ActionBarDrawerToggle( 这个,drawer_layout,工具栏,R.string.navigation_drawer_open,R.string.navigation_drawer_close) 切换.syncState() 如果您在 Android Studio 中使用自动生成的导航布局,drawer_layout.setDrawerListener(toggle)"(4-7 行),否则后退菜单按钮的行为将不稳定。这就是我所做的,它对我来说非常有效。希望这可以帮助某人

【讨论】:

以上是关于工具栏 - 只有一个活动从抽屉切换到后退按钮的主要内容,如果未能解决你的问题,请参考以下文章

像导航抽屉一样切换片段(带后退箭头)

后退按钮 - Android工作室

API等级19的抽屉活动

应用ActionBar中的返回按钮

Android - 将 ActionBar 后退按钮切换为导航按钮

导航抽屉活动:在按钮单击时从片段移动到片段