Работа с AppBar

Верхняя панель приложения – это область в верхней части окна приложения, где показывается информация и действия, относящиеся к текущему экрану.

пример верхней панели приложения
Рисунок 1. Пример верхней панели приложения.

В зависимости от потребностей приложения панель приложения может быть реализована как ActionBar, принадлежащий родительскому объекту Activity, или как панель инструментов в макете фрагмента.

Если на всех экранах используется одна и та же панель приложения, которая всегда находится вверху и занимает всю ширину экрана, используйте панель действий, предоставляемую темой и размещенную в действии. Использование панелей приложений темы помогает поддерживать единообразный внешний вид и предоставляет место для размещения меню параметров и кнопки «Вверх».

Используйте панель инструментов, размещенную во фрагменте, если вы хотите лучше контролировать размер, размещение и анимацию панели приложения на нескольких экранах. Например, вам может понадобиться сворачиваемая панель приложения или панель, которая занимает только половину ширины экрана и выровнена по центру по вертикали.

В разных ситуациях требуются разные подходы, например для развертывания меню и реагирования на действия пользователя. Понимание различных подходов и использование лучшего из них для вашего приложения экономит ваше время и помогает обеспечить правильную работу вашего приложения.

В примерах, приведенных в этом разделе, используется ExampleFragment, содержащий редактируемый профиль. Фрагмент разворачивает следующее меню, определенное в 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, фрагменты могут взаимодействовать с ней, переопределяя методы фреймворка, которые вызываются при создании фрагмента.

Зарегистрироваться на мероприятие

Вы должны сообщить системе, что фрагмент панели приложения участвует в заполнении меню параметров. Для этого вызовите setHasOptionsMenu(true) в методе onCreate(Bundle) фрагмента, как показано в примере ниже.

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) сообщает системе, что ваш фрагмент хочет получать обратные вызовы, связанные с меню. Когда происходит событие, связанное с меню, например клик, метод обработки событий сначала вызывается для действия, а затем – для фрагмента.

Однако не полагайтесь на этот порядок в логике приложения. Если в одном действии размещено несколько фрагментов, каждый из них может предоставлять пункты меню. В этом случае порядок обратного вызова зависит от порядка добавления фрагментов.

Развернуть меню

Чтобы объединить меню с меню параметров панели приложения, переопределите onCreateOptionsMenu() во фрагменте. Этот метод получает текущее меню панели приложения и MenuInflater в качестве параметров. Используйте MenuInflater, чтобы создать экземпляр меню фрагмента, а затем объедините его с текущим меню, как показано в следующем примере:

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 показано обновленное меню.

Теперь меню параметров содержит фрагмент меню.
Рисунок 2. Теперь в меню параметров есть фрагмент меню.

Обработка событий клика

Каждое действие и фрагмент, участвующие в меню параметров, могут реагировать на касания. Метод onOptionsItemSelected() фрагмента получает выбранный пункт меню в качестве параметра и возвращает логическое значение, указывающее, было ли касание принято. Если действие или фрагмент возвращает true из onOptionsItemSelected(), другие фрагменты не получают обратный вызов.

В реализации onOptionsItemSelected() используйте оператор switch для itemId пункта меню. Если выбранный элемент принадлежит вам, обработайте касание соответствующим образом и верните 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);
    }
}

Если вам нужно обновить меню, например когда пользователь нажимает кнопку Изменить, чтобы изменить данные профиля, вызовите invalidateOptionsMenu() в основном действии, чтобы запросить вызов onCreateOptionsMenu(). После этого вы сможете внести изменения в onCreateOptionsMenu(). После этого система вызывает метод onPrepareOptionsMenu() и обновляет меню в соответствии с текущим состоянием фрагмента.

Kotlin

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

Java

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

Панель приложения, принадлежащая фрагменту

Если большинству экранов в вашем приложении не нужна панель приложения или если одному экрану нужна другая панель приложения, чем другим, вы можете добавить Toolbar в макет фрагмента. Хотя вы можете добавить Toolbar в любое место иерархии View фрагмента, обычно он находится в верхней части экрана. Чтобы использовать Toolbar во фрагменте, укажите идентификатор и получите ссылку на него во фрагменте, как и в случае с любым другим представлением. Вы также можете анимировать панель инструментов, используя поведение CoordinatorLayout.

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

При использовании панели приложения, принадлежащей фрагменту, Google рекомендует использовать API Toolbar напрямую. Не используйте setSupportActionBar() и API меню Fragment, которые подходят только для панелей приложений, принадлежащих действиям.

Разверните меню.

Вспомогательный метод inflateMenu(int) класса Toolbar принимает в качестве параметра идентификатор ресурса меню. Чтобы добавить ресурс меню 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) с новым идентификатором меню, как показано в следующем примере:

Kotlin

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

Java

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

        viewBinding.myToolbar.getMenu().clear()

    }

}

Обработка событий клика

Вы можете передать OnMenuItemClickListener непосредственно на панель инструментов с помощью метода setOnMenuItemClickListener(). Этот прослушиватель вызывается, когда пользователь выбирает пункт меню из командных кнопок, представленных в конце панели инструментов или связанного переполнения. Выбранный объект 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;
            }
        });
    }
}

Динамическое изменение меню

Если панель приложения принадлежит фрагменту, вы можете изменять 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.
            }
        });
    }
}