31 октября 2013 года Google представили новую версию ОС Android 4.4 KitKat, в которой появилось очень много интересных функций для разработчиков. Одна из этих функций — Immersive Mode (режим погружения). Режим погружения — это режим, в котором ваша программа показывается пользователю на весь экран, при этом не видны любые системные панели, в том числе и панель навигации (та, что с кнопкой Back). До Android 4.4 была также возможность скрывать системные панели (панель навигации и панель статуса). Но в существующем варианте был один недостаток — пользователь не мог полностью погрузится в контент, так как любое нажатие на контент снова вызывало отображение системных панелей. Новый режим погружения добавляет еще один способ взаимодействия с отображением системных панелей. В этом режиме, чтобы отобразить системные панели пользователю достаточно сделать свайп от верхнего или нижнего края экрана по направлению к центру экрана, при этом отображение панелей по нажатию на контент делать необязательно, тем самым можно создавать приложения и игры, в которых пользователь может полноценно взаимодействовать с приложением или игрой любыми жестами. Чтобы пользователь понял, как вызвать системные панели из полноэкранного режима, при первом запуске приложения ему автоматически покажется системное сообщение о том, как снова отобразить эти панели на экране.

image image

Также вы могли подумать, что есть другая проблема использования этого режима. Например, пользователь запустил программу для рисования и хочет нарисовать линию от края экрана и как же так, неужели ему в этом случае отобразится системная панель. Да, но при этом приложение также будет получать сообщения от нажатий на экран и будет рисовать линию. Кстати, теперь можно просто сделать системные панели полупрозрачными с помощью двух новых тем — Theme.Holo.NoActionBar.TranslucentDecor и Theme.Holo.Light.NoActionBar.TranslucentDecor. Но стоит помнить, что в этом случае ваш контент будет занимать всю область на экране и будет видим даже за полупрозрачными системными панелями. Если вам необходимо, чтобы какая-та часть интерфейса не заезжала за пределы системных панелей (например, какие-то дополнительные панели), вам необходимо указать для родительского layout этого контента атрибут fitsSystemWindows равным true. Не очень приятная вещь с этими темами в том, что тут используется неполноценная полупрозрачность для панелей, а градиентная (от черного цвета к полупрозрачному), что не всегда выглядит красиво. Если вы создаете свою кастомную тему, вы можете отнаследоваться от этих новых тем, либо добавить вручную два новых свойства windowTranslucentNavigation (прозрачна ли системная панель навигации) и windowTranslucentStatus (прозрачна ли системная панель статуса с часиками).

А как же снова попасть в полноэкранный режим, когда панели уже отображаются? У пользователя есть несколько вариантов — нажать на любой контент вне системных панелей, подождать некоторое время, чтобы панели скрылись автоматически, или сделать свайп от центра экрана к краю. Все эти варианты программист может предусмотреть в своем приложении. Для этого команда Android сделала так, чтобы эти способы взаимодействия с Immersive Mode можно было реализовать очень просто. Есть два типа режима погружения — обычный и sticky («липкий»). В обычном режиме программисту надо самому решать, когда скрывать панели, а в sticky-режиме системные панели будут скрываться автоматически. Для установки обоих типов режима погружения необходимо воспользоваться методом setSystemUiVisibility(), только передать в параметры разные флаги. Вызывать этот метод необходимо у так называемого Decor View, который представляет собой внешний вид окна Activity со всем его оформлением и содержимым. Ссылку на этот View получить очень просто.

mDecorView = getWindow().getDecorView();

Давайте рассмотрим оба типа режима погружения поподробнее.

Обычный Immersive Mode


Для установки обычного режима используется флаг SYSTEM_UI_FLAG_IMMERSIVE в качестве параметра метода setSystemUiVisibility().

Лучше всего для работы с обычным immersive-режимом сделать два простых метода, которые будут показывать и скрывать системные панели соответственно.

    private void hideSystemUI() {
        mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LOW_PROFILE
                | View.SYSTEM_UI_FLAG_IMMERSIVE);
    }

    private void showSystemUI() {
        mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    }

Для тех случаев, когда показывать и скрывать системные панели вам нужно по нажатию на контент (contentView), необходимо использовать следующий код. Это может пригодится, например, при просмотре видео, как в YouTube, где вам не нужно взаимодействовать с контентом во время его просмотра.

        contentView.setClickable(true);
        final GestureDetector clickDetector = new GestureDetector(this,
                new GestureDetector.SimpleOnGestureListener() {
                    @Override
                    public boolean onSingleTapUp(MotionEvent e) {
                        boolean visible = (mDecorView.getSystemUiVisibility()
                                & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
                        if (visible) {
                            hideSystemUI();
                        } else {
                            showSystemUI();
                        }
                        return true;
                    }
                });
        contentView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                return clickDetector.onTouchEvent(motionEvent);
            }
        });

Если при этом у вас в приложении есть какие-то свои панели (controlsView), которые тоже надо скрыть вместе с системными, вы это также можете просто сделать, написав следующий код. Тут скрытие и появление происходит с использованием анимации альфа-канала и позиции по Y вашей панели.

        mDecorView.setOnSystemUiVisibilityChangeListener(
                new View.OnSystemUiVisibilityChangeListener() {
                    @Override
                    public void onSystemUiVisibilityChange(int flags) {
                        boolean visible = (flags & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
                        controlsView.animate()
                                .alpha(visible ? 1 : 0)
                                .translationY(visible ? 0 : controlsView.getHeight());
                    }
                });

Если при запуске приложения вы хотите показать пользователю, что у приложения есть панели, а потом их скрыть через некоторое время, это тоже довольно просто делается.

    private static final int INITIAL_HIDE_DELAY = 300;
    
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        delayedHide(INITIAL_HIDE_DELAY);
    }

    private final Handler mHideHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            hideSystemUI();
        }
    };

    private void delayedHide(int delayMillis) {
        mHideHandler.removeMessages(0);
        mHideHandler.sendEmptyMessageDelayed(0, delayMillis);
    }

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

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if (hasFocus) {
            delayedHide(INITIAL_HIDE_DELAY);
        } else {
            mHideHandler.removeMessages(0);
        }
    }

В этом случае панель покажется и потом исчезнет через заданное вами время.

Immersive Mode Sticky


Для установки sticky-режима используется флаг SYSTEM_UI_FLAG_IMMERSIVE_STICKY в качестве параметра метода setSystemUiVisibility().

Этот режим чрезвычайно прост. Если вам необходима ситуация, при которой системные панели буд��т скрываться, когда окно получает фокус или проходит некоторое время после появлении панелей, то этот режим для вас. Для включения этого режима вам необходимо установить для окна, когда оно находится в фокусе, флаг SYSTEM_UI_FLAG_IMMERSIVE_STICKY. В этом режиме, если окно теряет фокус, оно автоматически сбрасывается до состояния, в котором окно было до установки флага SYSTEM_UI_FLAG_IMMERSIVE_STICKY, то есть со всеми панелями. Как только окно получает фокус, панели опять исчезают через некоторое время и вам нет нужды программировать эту ситуацию вручную. Чтобы показать системные панели надо также, как и в обычном режиме, сделать свайп от краев экрана. Включение stick-режима делается простым куском кода.

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
        }
    }


P.S. В статьи использовались материалы видео от Roman Nurik. Код полных примеров от Романа по использованию этого режима можно скачать тут.

P.P.S. 20 ноября появилась новая статья в Training Kit по этому режиму. Постараюсь дополнить статью в ближайшее время с учетом и ее.