工具栏 - 仅使用一个活动从抽屉切换到返回按钮

42
我已经搜索了一段时间,想知道如何将抽屉打开/关闭图标(从汉堡包变成箭头)更改为简单的返回箭头。目前,我的应用程序只有一个Activity,它在多个片段之间切换。在某一点上,我想在一个主要片段(即,抽屉中的一个片段)和一个在先前片段下分层的片段(即,“添加新”片段)之间进行过渡。在这个新的片段中,我希望工具栏显示返回按钮而不是抽屉按钮。
我已经寻找并尝试了不同的解决方案很长时间。以下是最值得注意的:

目前,我正在考虑一种长而费力的方法来创建一个自定义图标,我需要隐藏和显示它(并且隐藏/显示原生抽屉图标)。不过,有没有更好的方法来在抽屉按钮和返回按钮之间进行切换?

作为一个旁边但相关的问题,我一直在查看Material Design文档,有几个示例在左上角有一个X。实现它与实现抽屉 vs 返回/向上按钮有多大的不同?

谢谢~

编辑:

我知道如何替换图标,但是如何获取点击事件呢?

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

我现在尝试过的:

  • 在必要时禁用DrawerToggle (即mDrawerToggle.setDrawerIndicatorEnabled(useDrawer);)
  • 在我的NavigationDrawerFragment、我的Activity以及我当前正在测试的DialogFragment中的onOptionsItemSelected中添加日志,如果item.getItemId() == android.R.id.home为true,则运行。但是,这些日志语句都没有触发。
为了更好地理解,我现在有一个全屏片段,在菜单中添加了一个“保存”按钮,并将抽屉图标更改为“X”。该片段可以获取保存菜单事件,但即使活动和抽屉也无法获取X被点击的事件。
编辑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);
}

导航抽屉片段

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();
            }
        });
}

1
使用ActionBarDrawerListener的v7支持库。如果你调用构造函数和实现方法,它将为您处理一切。确保您使用v7版本,而不是v4版本,因为它已被弃用且不起作用。我从数小时的挫败中知道了这一点。 - Dylan Vander Berg
6个回答

48

将此代码放入您的ActivityonCreate()中。 对我运行良好,即使使用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);
                        }
                    });
                }
            }
        });

2
这是我目前找到的最佳解决方案,你应该获得被接受的答案和至少最高价值。有了这个解决方案,我就不必担心协调箭头按钮和加载先前的片段。在我看来,这应该内置于工具栏中,如果有人想修改其行为,则覆盖一个方法即可。谢谢@matusalem - AndaluZ
是的,这个解决方案使用片段替换方法自动工作,而不需要调用任何方法。很高兴看到我能帮到你。欢迎使用。编码愉快 :) - matusalem
我不知道我做错了什么,但它对我没有起作用。 - kgandroid
如果(id==R.id.ranking_item) { fragment = new KPIDetailFragment(); } if (fragment != null) { FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.main_layout, fragment); fragmentTransaction.commit(); // 设置工具栏标题 getSupportActionBar().setTitle("KPI DETAIL"); } - kgandroid
它帮了我很多,不像其他许多提示那样。而且它非常容易和自然(toolbar.setNavigationOnClickListener())!! 非常感谢。 - iramm

28

它应该适用于最新的API 24

在你的活动(activity)的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);
        }
    }
});

3
你的 getToolbarNavigationClickListener() 激发了我使用 setToolbarNavigationClickListener() 并解决了我的问题!非常感谢!!! - Lancelot

12

也许这不是你想听到的,但从概念上来看,我会选择创建一个新活动而不是片段。

你的主要活动与抽屉紧密相关,因此在没有任何对抽屉的访问权限的情况下加载新片段对我来说毫无意义(但如果你认为有意义,请随时等待其他答案)。新活动将解决这两个问题,因为它没有抽屉,并且可以是主要活动的子级。

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

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

这个模式有一个“保存”正按钮在右上方,和一个X。概念上,X按钮是用来取消/中止一个进程,而不是导航到某个返回堆栈。这意味着您正在关闭某些东西,而没有任何操作发生。这非常适合您想要做的事情。
从设计的角度来看,它可以通过一个新的Activity很容易地实现,并且可以保持在其他活动的顶部。此外,如果片段的重点基本上是能够在平板电脑和更大的屏幕上同时表示两个或多个 - 再次 - 我不会对我的左侧有一个旧片段和右侧有一个“添加新”片段感到满意。
相反,在平板电脑上,我会选择一个浮动对话框,如指南所建议的那样。

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

对于手机,全屏活动应该有一个X按钮,对于平板电脑,应该有一个浮动对话框(底部带有按钮)。在我的看法中,这是最符合指南的方法。


我建议阅读整个链接。关于 <- 与 X 的区别:
X 与向上箭头不同,向上箭头在视图状态经常保存或应用程序具有草稿或自动保存功能时使用。例如,在设置中使用向上箭头,因为所有更改都会立即提交。
此外,
触摸 此设置示例 中的 X 将放弃所有更改。仅在触摸保存时才保存更改。

谢谢你的回答!对话框模式(特别是在较小的设备上使用全屏对话框)似乎是“添加新”功能的好主意。至于使用多个活动...那是我的最初意图。然而,我最近一直在关注过渡效果,尝试为Lollipop之前的设备创建Activity过渡效果似乎太过繁琐,而且收益微薄(因为它根本没有得到本地支持)。我能想到的最好方法是使用片段来实现过渡动画。 - DragonJawad
这是一个很好的答案,但我很可能需要尽快切换工具栏按钮,所以我仍然会保持问题开放。再次感谢~ - DragonJawad
不客气。至于活动转换,您可以使用android.view.Animation框架以及overridePendingTransitions()。虽然不像片段转换那么流畅,但效果还是相当不错的。 - natario
实际上,我选择了全屏对话框(片段)路线,但不知怎么就遇到了类似的障碍:我无法获取抽屉/主页按钮的点击事件。我在我的问题中添加了更详细的描述。你能再次帮我一下吗?谢谢! - DragonJawad
我无法向您链接的答案中添加任何内容。如果您发布一些代码,我们可以看一下。这取决于您何时以及如何将抽屉图标替换为X。 - natario
显示剩余4条评论

4
@matusalem的答案非常好。 我只有一个要补充的地方——要小心,因为抽屉也可以通过从屏幕左侧划入来打开。 对于某些人来说,这可能是需要的,但对于我来说,我禁用了抽屉,因为在除主要片段之外的任何片段中都没有意义。 禁用滑动很容易,在此处操作 - 导航抽屉-禁用滑动
这可能应该作为评论回复答案,但我没有足够的声望。 不好意思。

你说得没错,但只是在你的特殊情况下。我认为抽屉随时都可以被滑动,并没有任何理由不能这样做。抽屉就是为此而发明的。请看谷歌应用程序设计-谷歌商店,Gmail等。 - matusalem

0
//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)
                        }
                    })
                }
            }
        })

    }

如果您在Android Studio中使用自动生成的导航布局,则需要注释掉“toggle = ActionBarDrawerToggle(this,drawer_layout,toolbar,R.string.navigation_drawer_open,R.string.navigation_drawer_close)toggle.syncState() drawer_layout.setDrawerListener(toggle)”(4-7行),否则返回菜单按钮的行为将不稳定。这就是我所做的,对我来说完美地解决了问题。希望这能帮助到某些人。

0

我曾经遇到过同一个活动中在不同片段之间切换汉堡菜单和返回箭头的问题。这是我的解决方案,希望能对某些人有所帮助。

您活动中的监听器:

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);
...
...

你感兴趣的部分(返回的类是我的某个类,可以设置为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;
    }

注意:调用方法的顺序很重要!如果可以像这样写成2行最好,但不可行(至少对我来说):

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

如果你对于为什么方法调用的顺序会搞砸事情感兴趣,可以看一下这些方法的实现。


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