使用应用栏

顶部应用栏在应用窗口顶部提供了一个一致的位置,用于显示当前屏幕的信息和操作。

an example top app bar
图 1. 顶部应用栏示例。

应用栏的所有权因应用的需求而异。使用 fragment 时,应用栏可以实现为由宿主 activity 拥有的 ActionBar,或 fragment 布局中的工具栏。

如果您的所有屏幕都使用始终位于顶部且横跨屏幕宽度的相同应用栏,请使用 Activity 托管的主题提供的操作栏。使用主题应用栏有助于保持一致的外观,并提供了托管选项菜单和“向上”按钮的位置。

如果您想更精细地控制应用栏在多个屏幕上的大小、位置和动画,请使用 Fragment 托管的工具栏。例如,您可能需要可折叠的应用栏,或者只占用屏幕宽度一半并垂直居中的应用栏。

不同的情况需要不同的方法,例如填充菜单和响应用户交互。了解不同的方法并为您的应用采用最佳方法,可以节省您的时间并帮助确保您的应用正常运行。

本主题中的示例引用了一个包含可编辑个人资料的 ExampleFragment。该 fragment 在其应用栏中填充以下 XML 定义的菜单

<!-- sample_menu.xml -->
<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/action_settings"
        android:icon="@drawable/ic_settings"
        android:title="@string/settings"
        app:showAsAction="ifRoom"/>
    <item
        android:id="@+id/action_done"
        android:icon="@drawable/ic_done"
        android:title="@string/done"
        app:showAsAction="ifRoom|withText"/>

</menu>

该菜单包含两个选项:一个用于导航到个人资料屏幕,另一个用于保存所做的任何个人资料更改。

Activity 拥有的应用栏

应用栏最常见的是由宿主 Activity 拥有。当应用栏由 Activity 拥有时,Fragment 可以通过重写在 Fragment 创建期间调用的框架方法来与应用栏交互。

向 Activity 注册

您必须通知系统,您的应用栏 Fragment 正在参与选项菜单的填充。为此,请在您的 Fragment 的 onCreate(Bundle) 方法中调用 setHasOptionsMenu(true),如以下示例所示

Kotlin

class ExampleFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setHasOptionsMenu(true)
    }
}

Java

public class ExampleFragment extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }
}

setHasOptionsMenu(true) 告诉系统,您的 fragment 希望接收与菜单相关的回调。当发生与菜单相关的事件(例如点击)时,事件处理方法会先在 activity 上调用,然后再在 fragment 上调用。

但是,在您的应用逻辑中不要依赖此顺序。如果同一 Activity 托管多个 Fragment,每个 Fragment 都可以提供菜单选项,在这种情况下,回调顺序取决于 Fragment 添加的顺序。

填充菜单

要将您的菜单合并到应用栏的选项菜单中,请在您的 Fragment 中重写 onCreateOptionsMenu()。此方法接收当前应用栏菜单和 MenuInflater 作为参数。使用菜单填充器创建您的 Fragment 菜单的实例,然后将其合并到当前菜单中,如以下示例所示

Kotlin

class ExampleFragment : Fragment() {
    ...
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.sample_menu, menu)
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
       inflater.inflate(R.menu.sample_menu, menu);
    }
}

图 2 显示了更新后的菜单。

the options menu now contains your menu fragment
图 2. 选项菜单现在包含您的菜单 fragment。

处理点击事件

参与选项菜单的每个 Activity 和 Fragment 都可以响应触摸。Fragment 的 onOptionsItemSelected() 接收所选菜单项作为参数,并返回布尔值以指示是否已消耗触摸。一旦 Activity 或 Fragment 从 onOptionsItemSelected() 返回 true,其他参与的 Fragment 将不再接收回调。

在您的 onOptionsItemSelected() 实现中,对菜单项的 itemId 使用 switch 语句。如果所选项目是您的,请适当地处理触摸并返回 true 以指示已处理点击事件。如果所选项目不是您的,请调用 super 实现。默认情况下,super 实现返回 false 以允许菜单处理继续。

Kotlin

class ExampleFragment : Fragment() {
    ...
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.action_settings -> {
                // Navigate to settings screen.
                true
            }
            R.id.action_done -> {
                // Save profile changes.
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_settings:  {
                // Navigate to settings screen.
                return true;
            }
            case R.id.action_done: {
                // Save profile changes.
                return true;
            }
            default:
                return super.onOptionsItemSelected(item);
        }

    }

}

动态修改菜单

将隐藏或显示按钮或更改图标的逻辑放在 onPrepareOptionsMenu() 中。此方法在菜单显示之前调用。

继续前面的示例,保存按钮在用户开始编辑之前应不可见,并在用户保存后消失。将此逻辑添加到 onPrepareOptionsMenu() 可使菜单正确显示。

Kotlin

class ExampleFragment : Fragment() {
    ...
    override fun onPrepareOptionsMenu(menu: Menu){
        super.onPrepareOptionsMenu(menu)
        val item = menu.findItem(R.id.action_done)
        item.isVisible = isEditing
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    @Override
    public void onPrepareOptionsMenu(@NonNull Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem item = menu.findItem(R.id.action_done);
        item.setVisible(isEditing);
    }
}

当您需要更新菜单时(例如,当用户按下编辑按钮编辑个人资料信息时),请在宿主 Activity 上调用 invalidateOptionsMenu() 以请求系统调用 onCreateOptionsMenu()。无效后,您可以在 onCreateOptionsMenu() 中进行更新。一旦菜单填充,系统就会调用 onPrepareOptionsMenu() 并更新菜单以反映 Fragment 的当前状态。

Kotlin

class ExampleFragment : Fragment() {
    ...
    fun updateOptionsMenu() {
        isEditing = !isEditing
        requireActivity().invalidateOptionsMenu()
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    public void updateOptionsMenu() {
        isEditing = !isEditing;
        requireActivity().invalidateOptionsMenu();
    }
}

Fragment 拥有的应用栏

如果您的应用中的大多数屏幕不需要应用栏,或者某个屏幕需要与众不同的应用栏,您可以向您的 fragment 布局添加 Toolbar。尽管您可以将 Toolbar 添加到 Fragment 视图层次结构中的任何位置,但通常将其保留在屏幕顶部。要在您的 Fragment 中使用 Toolbar,请提供一个 ID 并在您的 Fragment 中获取对其的引用,就像任何其他视图一样。您还可以考虑使用 CoordinatorLayout 行为来为工具栏设置动画。

<androidx.appcompat.widget.Toolbar
    android:id="@+id/myToolbar"
    ... />

当使用 Fragment 拥有的应用栏时,Google 建议直接使用 Toolbar API。不要使用 setSupportActionBar()Fragment 菜单 API,它们仅适用于 Activity 拥有的应用栏。

填充菜单

Toolbar 便利方法 inflateMenu(int) 接受菜单资源的 ID 作为参数。要将 XML 菜单资源填充到您的工具栏中,请将 resId 传递给此方法,如以下示例所示

Kotlin

class ExampleFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        viewBinding.myToolbar.inflateMenu(R.menu.sample_menu)
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        ...
        viewBinding.myToolbar.inflateMenu(R.menu.sample_menu);
    }

}

要填充另一个 XML 菜单资源,请再次调用该方法并传入新菜单的 resId。新的菜单项会添加到菜单中,而现有菜单项不会被修改或移除。

如果您想替换现有菜单集,请在调用 inflateMenu(int) 并传入新菜单 ID 之前清除菜单,如以下示例所示

Kotlin

class ExampleFragment : Fragment() {
    ...
    fun clearToolbarMenu() {
        viewBinding.myToolbar.menu.clear()
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    public void clearToolbarMenu() {

        viewBinding.myToolbar.getMenu().clear()

    }

}

处理点击事件

您可以使用 setOnMenuItemClickListener() 方法直接将 OnMenuItemClickListener 传递给工具栏。当用户从工具栏末尾显示的操作按钮或相关溢出菜单中选择菜单项时,会调用此监听器。所选的 MenuItem 将传递给监听器的 onMenuItemClick() 方法,并可用于处理此操作,如以下示例所示

Kotlin

class ExampleFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        viewBinding.myToolbar.setOnMenuItemClickListener {
            when (it.itemId) {
                R.id.action_settings -> {
                    // Navigate to settings screen.
                    true
                }
                R.id.action_done -> {
                    // Save profile changes.
                    true
                }
                else -> false
            }
        }
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        ...
        viewBinding.myToolbar.setOnMenuItemClickListener(item -> {
            switch (item.getItemId()) {
                case R.id.action_settings:
                    // Navigate to settings screen.
                    return true;
                case R.id.action_done:
                    // Save profile changes.
                    return true;
                default:
                    return false;
            }
        });
    }
}

动态修改菜单

当您的 fragment 拥有应用栏时,您可以像修改任何其他视图一样在运行时修改 Toolbar

继续前面的示例,保存菜单选项在用户开始编辑之前应不可见,并在点击后再次消失

Kotlin

class ExampleFragment : Fragment() {
    ...
    fun updateToolbar() {
        isEditing = !isEditing

        val saveItem = viewBinding.myToolbar.menu.findItem(R.id.action_done)
        saveItem.isVisible = isEditing

    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    public void updateToolbar() {
        isEditing = !isEditing;

        MenuItem saveItem = viewBinding.myToolbar.getMenu().findItem(R.id.action_done);
        saveItem.setVisible(isEditing);
    }

}

如果存在,导航按钮会显示在工具栏的开头。在工具栏上设置导航图标会使其可见。您还可以设置一个导航专用的 onClickListener(),每当用户点击导航按钮时就会调用该监听器,如以下示例所示

Kotlin

class ExampleFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        myToolbar.setNavigationIcon(R.drawable.ic_back)

        myToolbar.setNavigationOnClickListener { view ->
            // Navigate somewhere.
        }
    }
}

Java

public class ExampleFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        ...
        viewBinding.myToolbar.setNavigationIcon(R.drawable.ic_back);
        viewBinding.myToolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Navigate somewhere.
            }
        });
    }
}