Кулинарная книга на большом экране.

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

Звездные рейтинги

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

Пятизвездочный рейтинг Соответствует критериям уровня 1, дифференцированный большой экран
Четырехзвездочный рейтинг Соответствует критериям уровня 2, оптимизирован для большого экрана
Трехзвездочный рейтинг Соответствует критериям уровня 3, готовность к большому экрану
Двухзвездочный рейтинг Предоставляет некоторые возможности большого экрана, но не соответствует рекомендациям по качеству приложений для больших экранов.
Одна звезда Соответствует потребностям конкретного варианта использования, но не поддерживает должным образом большие экраны.

Поддержка камеры Chromebook

Трехзвездочный рейтинг

Будьте замечены в Google Play пользователями Chromebook.

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

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

Лучшие практики

Универсальные приложения для камеры поддерживают все устройства независимо от конфигурации камеры — устройства с фронтальными камерами, задними камерами, внешними камерами, подключенными через USB.

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

Ингредиенты

  • Разрешение CAMERA : предоставляет вашему приложению доступ к камерам устройства.
  • Элемент манифеста <uses-feature> : информирует магазины приложений о функциях, используемых вашим приложением.
  • required атрибут: указывает магазинам приложений, может ли ваше приложение работать без указанной функции.

Шаги

Краткое содержание

Объявите разрешение CAMERA . Объявите функции камеры, обеспечивающие базовую поддержку камеры. Укажите, требуется ли каждая функция.

1. Объявите разрешение CAMERA .

Добавьте следующее разрешение в манифест приложения:

<uses-permission android:name="android.permission.CAMERA" />
2. Объявите основные функции камеры.

Добавьте в манифест приложения следующие функции:

<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
3. Укажите, требуется ли каждая функция

Установите android:required="false" для функции android.hardware.camera.any , чтобы разрешить доступ к вашему приложению устройствам, имеющим любую встроенную или внешнюю камеру или вообще не имеющим камеры.

Для других функций установите android:required="false" чтобы такие устройства, как Chromebook, у которых нет задней камеры, автофокусировки или вспышки, могли получить доступ к вашему приложению в магазинах приложений.

Полученные результаты

Пользователи Chromebook могут загрузить и установить ваше приложение из Google Play и других магазинов приложений. А устройства с полнофункциональной поддержкой камеры, такие как телефоны, не будут ограничены в функциях камеры.

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

Дополнительные ресурсы

Дополнительные сведения см. в разделе Аппаратные функции камеры в документации <uses-feature> .

Ориентация приложения ограничена на телефонах, но не на устройствах с большим экраном.

Двухзвездочный рейтинг

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

Как можно сделать и то, и другое — ограничить приложение портретной ориентацией на маленьких экранах, но включить альбомную ориентацию на больших?

Лучшие практики

Лучшие приложения учитывают предпочтения пользователя, такие как ориентация устройства.

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

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

Ингредиенты

  • screenOrientation : параметр манифеста приложения, который позволяет указать, как ваше приложение реагирует на изменения ориентации устройства.
  • Jetpack WindowManager : набор библиотек, позволяющих определять размер и соотношение сторон окна приложения; обратная совместимость с уровнем API 14
  • Activity#setRequestedOrientation() : метод, с помощью которого вы можете изменить ориентацию приложения во время выполнения.

Шаги

Краткое содержание

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

1. Укажите настройку ориентации в манифесте приложения.

Вы можете либо не объявлять элемент screenOrientation манифеста приложения (в этом случае ориентация по умолчанию имеет значение unspecified ), либо установить ориентацию экрана на fullUser . Если пользователь не заблокировал вращение с помощью датчика, ваше приложение будет поддерживать все ориентации устройства.

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

Разница между использованием unspecified и fullUser незначительна, но важна. Если вы не объявляете значение screenOrientation , система выбирает ориентацию, и политика, которую система использует для определения ориентации, может отличаться от устройства к устройству. С другой стороны, указание fullUser более точно соответствует поведению, которое пользователь определил для устройства: если пользователь заблокировал вращение на основе датчика, приложение следует предпочтениям пользователя; в противном случае система допускает любую из четырех возможных ориентаций экрана (портретная, альбомная, перевернутая книжная или перевернутая альбомная). См. android:screenOrientation .

2. Определите размер экрана

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

Добавьте библиотеки Jetpack WindowManager в файл build.gradle или build.gradle.kts модуля:

Котлин

implementation("androidx.window:window:version")
implementation("androidx.window:window-core:version")

классный

implementation 'androidx.window:window:version'
implementation 'androidx.window:window-core:version'

Используйте метод Jetpack WindowManager WindowMetricsCalculator#computeMaximumWindowMetrics() чтобы получить размер экрана устройства в виде объекта WindowMetrics . Метрики окна можно сравнить с классами размеров окон, чтобы решить, когда ограничить ориентацию.

Классы размеров Windows обеспечивают точки останова между маленькими и большими экранами.

Используйте точки останова WindowWidthSizeClass#COMPACT и WindowHeightSizeClass#COMPACT чтобы определить размер экрана:

Котлин

/** Determines whether the device has a compact screen. **/
fun compactScreen() : Boolean {
    val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
    val width = metrics.bounds.width()
    val height = metrics.bounds.height()
    val density = resources.displayMetrics.density
    val windowSizeClass = WindowSizeClass.compute(width/density, height/density)

    return windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT ||
        windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT
}

Джава

/** Determines whether the device has a compact screen. **/
private boolean compactScreen() {
    WindowMetrics metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this);
    int width = metrics.getBounds().width();
    int height = metrics.getBounds().height();
    float density = getResources().getDisplayMetrics().density;
    WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density);
    return windowSizeClass.getWindowWidthSizeClass() == WindowWidthSizeClass.COMPACT ||
                windowSizeClass.getWindowHeightSizeClass() == WindowHeightSizeClass.COMPACT;
}
    Примечание:
  • Приведенные выше примеры реализованы как методы деятельности; и поэтому действие разыменовывается как this в аргументе computeMaximumWindowMetrics() .
  • Метод computeMaximumWindowMetrics() используется вместо метода computeCurrentWindowMetrics() поскольку приложение можно запустить в многооконном режиме, который игнорирует настройку ориентации экрана. Нет смысла определять размер окна приложения и переопределять настройку ориентации, если окно приложения не занимает весь экран устройства.

Инструкции по объявлению зависимостей, чтобы сделать метод computeMaximumWindowMetrics() доступным в вашем приложении, см. в WindowManager.

3. Переопределить настройку манифеста приложения.

Когда вы определили, что устройство имеет компактный размер экрана, вы можете вызвать Activity#setRequestedOrientation() чтобы переопределить настройку screenOrientation манифеста:

Котлин

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    val container: ViewGroup = binding.container

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(object : View(this) {
        override fun onConfigurationChanged(newConfig: Configuration?) {
            super.onConfigurationChanged(newConfig)
            requestedOrientation = if (compactScreen())
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
                ActivityInfo.SCREEN_ORIENTATION_FULL_USER
        }
    })
}

Джава

@Override
protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstanceState);
    if (compactScreen()) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    } else {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
    }
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    ViewGroup container = binding.container;

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(new View(this) {
        @Override
        protected void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            if (compactScreen()) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
            }
        }
    });
}

Добавив логику в методы onCreate() и View.onConfigurationChanged() , вы сможете получить максимальные метрики окна и переопределить настройку ориентации всякий раз, когда действие изменяется или перемещается между дисплеями, например, после поворота устройства или когда складное устройство сложено или разложено. Дополнительные сведения о том, когда происходят изменения конфигурации и когда они вызывают воссоздание активности, см. в разделе Обработка изменений конфигурации.

Полученные результаты

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

Дополнительные ресурсы

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

Приостановка и возобновление воспроизведения мультимедиа с помощью клавиши пробела на внешней клавиатуре

Четырехзвездочный рейтинг

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

Когда мультимедиа является единственным элементом окна (например, полноэкранное воспроизведение видео), реагируйте на события нажатия клавиш на уровне активности или, в Jetpack Compose, на уровне экрана.

Лучшие практики

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

Ингредиенты

  • KEYCODE_SPACE : константа кода клавиши для клавиши пробела.

Сочинить

  • onPreviewKeyEvent : Modifier , который позволяет компоненту перехватывать события аппаратных клавиш, когда он (или один из его дочерних элементов) находится в фокусе.
  • onKeyEvent : Подобно onPreviewKeyEvent , этот Modifier позволяет компоненту перехватывать события аппаратных клавиш, когда он (или один из его дочерних элементов) находится в фокусе.

Взгляды

  • onKeyUp() : вызывается, когда клавиша отпускается и не обрабатывается представлением внутри действия.

Шаги

Краткое содержание

Приложения на основе представления и приложения на основе Jetpack Compose реагируют на нажатия клавиш клавиатуры аналогичным образом: приложение должно прослушивать события нажатия клавиш, фильтровать события и реагировать на выбранные нажатия клавиш, например нажатие клавиши пробела.

1. Слушайте события клавиатуры

Взгляды

В действии вашего приложения переопределите метод onKeyUp() :

Котлин

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
    ...
}

Джава

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    ...
}

Метод вызывается каждый раз при отпускании нажатой клавиши, поэтому он срабатывает ровно один раз для каждого нажатия клавиши.

Сочинить

С помощью Jetpack Compose вы можете использовать модификатор onPreviewKeyEvent или onKeyEvent на экране, который управляет нажатием клавиши:

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

или

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

2. Фильтрация нажатия клавиши пробела

Внутри метода onKeyUp() или методов-модификаторов Compose onPreviewKeyEvent и onKeyEvent отфильтруйте KeyEvent.KEYCODE_SPACE , чтобы отправить правильное событие в ваш медиа-компонент:

Взгляды

Котлин

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback()
    return true
}
return false

Джава

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback();
    return true;
}
return false;

Сочинить

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

или

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

Полученные результаты

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

Дополнительные ресурсы

Дополнительные сведения о событиях клавиатуры и способах управления ими см. в разделе Обработка ввода с клавиатуры .

Отказ от стилуса

Пятизвездочный рейтинг

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

Лучшие практики

Ваше приложение должно распознавать посторонние события касания и игнорировать их. Android отменяет прикосновение ладони, отправляя объект MotionEvent . Проверьте объект на наличие ACTION_CANCEL или ACTION_POINTER_UP и FLAG_CANCELED , чтобы определить, следует ли отклонять жест, вызванный прикосновением ладони.

Ингредиенты

  • MotionEvent : представляет события касания и движения. Содержит информацию, необходимую для определения того, следует ли игнорировать событие.
  • OnTouchListener#onTouch() : получает объекты MotionEvent .
  • MotionEvent#getActionMasked() : возвращает действие, связанное с событием движения.
  • ACTION_CANCEL : константа MotionEvent , указывающая, что жест следует отменить.
  • ACTION_POINTER_UP : константа MotionEvent , указывающая, что указатель, отличный от первого, поднялся вверх (то есть прекратил контакт с экраном устройства).
  • FLAG_CANCELED : константа MotionEvent , указывающая, что подъем указателя вверх вызвал непреднамеренное событие касания. Добавлено в события ACTION_POINTER_UP и ACTION_CANCEL на Android 13 (уровень API 33) и более поздних версиях.

Шаги

Краткое содержание

Изучите объекты MotionEvent , отправленные в ваше приложение. Используйте API MotionEvent для определения характеристик событий:

  • События с одним указателем — проверьте ACTION_CANCEL . В Android 13 и более поздних версиях также проверьте наличие FLAG_CANCELED .
  • События с несколькими указателями . В Android 13 и более поздних версиях проверьте ACTION_POINTER_UP и FLAG_CANCELED .

Реагируйте на события ACTION_CANCEL и ACTION_POINTER_UP / FLAG_CANCELED .

1. Получите объекты событий движения.

Добавьте OnTouchListener в свое приложение:

Котлин

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        // Process motion event.
    }
}

Джава

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    // Process motion event.
});
2. Определите действие и флаги события.

Проверьте ACTION_CANCEL , который указывает на событие с одним указателем на всех уровнях API. В Android 13 и более поздних версиях проверьте ACTION_POINTER_UP на FLAG_CANCELED.

Котлин

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        when (event.actionMasked) {
            MotionEvent.ACTION_CANCEL -> {
                //Process canceled single-pointer motion event for all SDK versions.
            }
            MotionEvent.ACTION_POINTER_UP -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                   (event.flags and MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                    //Process canceled multi-pointer motion event for Android 13 and higher.
                }
            }
        }
        true
    }
}

Джава

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_CANCEL:
            // Process canceled single-pointer motion event for all SDK versions.
        case MotionEvent.ACTION_UP:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
               (event.getFlags() & MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                //Process canceled multi-pointer motion event for Android 13 and higher.
            }
    }
    return true;
});
3. Отменить жест

После того как вы определили прикосновение ладони, вы можете отменить экранные эффекты этого жеста.

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

Полученные результаты

Теперь ваше приложение может идентифицировать и отклонять прикосновения ладони для событий с несколькими указателями на уровнях API Android 13 и более поздних версий, а также для событий с одним указателем на всех уровнях API.

Дополнительные ресурсы

Для получения дополнительной информации см. следующее:

Управление состоянием WebView

Трехзвездочный рейтинг

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

Лучшие практики

Минимизируйте количество воссозданий WebView . WebView хорошо управляет своим состоянием, и вы можете использовать это качество, управляя как можно большим количеством изменений конфигурации. Ваше приложение должно обрабатывать изменения конфигурации, поскольку воссоздание Activity (способ обработки изменений конфигурации системой) также воссоздает WebView , что приводит к потере WebView своего состояния.

Ингредиенты

  • android:configChanges : Атрибут элемента манифеста <activity> . Перечисляет изменения конфигурации, обрабатываемые действием.
  • View#invalidate() : метод, вызывающий перерисовку представления. Унаследовано WebView .

Шаги

Краткое содержание

Чтобы сохранить состояние WebView , по возможности избегайте воссоздания Activity , а затем дайте WebView сделать недействительным, чтобы он мог изменить размер, сохраняя свое состояние.

1. Добавьте изменения конфигурации в файл AndroidManifest.xml вашего приложения.

Избегайте повторения активности, указав изменения конфигурации, обрабатываемые вашим приложением (а не системой):

<activity
  android:name=".MyActivity"
  android:configChanges="screenLayout|orientation|screenSize
      |keyboard|keyboardHidden|smallestScreenSize" />

2. Делайте WebView недействительным всякий раз, когда ваше приложение получает изменение конфигурации.

Котлин

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    webView.invalidate()
}

Джава

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    webview.invalidate();
}

Этот шаг применим только к системе представлений, поскольку Jetpack Compose не нужно ничего делать недействительным для правильного изменения размера Composable элементов. Однако Compose часто воссоздает WebView , если им неправильно управлять. Используйте оболочку Accompanist WebView для сохранения и восстановления состояния WebView в ваших приложениях Compose.

Полученные результаты

Компоненты WebView вашего приложения теперь сохраняют свое состояние и положение прокрутки при многочисленных изменениях конфигурации: от изменения размера до изменения ориентации, а также сворачивания и развертывания.

Дополнительные ресурсы

Дополнительные сведения об изменениях конфигурации и способах управления ими см. в разделе Обработка изменений конфигурации .

Управление состоянием RecyclerView

Трехзвездочный рейтинг

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

Лучшие практики

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

Ингредиенты

  • RecyclerView.Adapter#setStateRestorationPolicy() : определяет, как RecyclerView.Adapter восстанавливает свое состояние после изменения конфигурации.
  • ViewModel : сохраняет состояние действия или фрагмента.

Шаги

Краткое содержание

Установите политику восстановления состояния RecyclerView.Adapter , чтобы сохранить положение прокрутки RecyclerView . Сохраните состояние элементов списка RecyclerView . Добавьте состояние элементов списка в адаптер RecyclerView и восстановите состояние элементов списка, когда они привязаны к ViewHolder .

1. Включите политику восстановления состояния Adapter

Включите политику восстановления состояния адаптера RecyclerView , чтобы положение прокрутки RecyclerView сохранялось при изменении конфигурации. Добавьте спецификацию политики в конструктор адаптера:

Котлин

class MyAdapter() : RecyclerView.Adapter() {
    init {
        stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
    }
    ...
}

Джава

class MyAdapter extends RecyclerView.Adapter {

    public Adapter() {
        setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY);
    }
    ...
}

2. Сохраните состояние элементов списка с сохранением состояния.

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

Котлин

input.addTextChangedListener(
    afterTextChanged = { text ->
        text?.let {
            // Save state here.
        }
    }
)

Джава

input.addTextChangedListener(new TextWatcher() {

    ...

    @Override
    public void afterTextChanged(Editable s) {
        // Save state here.
    }
});

Объявите обратный вызов в своем Activity или Fragment . Используйте ViewModel для хранения состояния.

3. Добавьте состояние элемента списка в Adapter

Добавьте состояние элементов списка в свой RecyclerView.Adapter . Передайте состояние элемента конструктору адаптера при создании Activity или Fragment хоста:

Котлин

val adapter = MyAdapter(items, viewModel.retrieveState())

Джава

MyAdapter adapter = new MyAdapter(items, viewModel.retrieveState());

4. Восстановить состояние элемента списка в ViewHolder адаптера.

В RecyclerView.Adapter при привязке ViewHolder к элементу восстановите состояние элемента:

Котлин

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    ...
    val item = items[position]
    val state = states.firstOrNull { it.item == item }

    if (state != null) {
        holder.restore(state)
    }
}

Джава

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    ...
    Item item = items[position];
    Arrays.stream(states).filter(state -> state.item == item)
        .findFirst()
        .ifPresent(state -> holder.restore(state));
}

Полученные результаты

Теперь ваш RecyclerView может восстановить положение прокрутки и состояние каждого элемента в списке RecyclerView .

Дополнительные ресурсы

Съемное управление с клавиатуры

Трехзвездочный рейтинг

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

Лучшие практики

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

Поддержка внешних клавиатур предполагает изменение конфигурации, управлять которым можно двумя способами:

  1. Позвольте системе воссоздать текущую активность, а вы сами позаботитесь об управлении состоянием вашего приложения.
  2. Управляйте изменением конфигурации самостоятельно (действие не будет воссоздано):
    • Объявите все значения конфигурации, связанные с клавиатурой.
    • Создайте обработчик изменения конфигурации

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

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

Поскольку единственный надежный способ прослушивания изменений конфигурации — это переопределить метод onConfigurationChanged() представления, вы можете добавить новый экземпляр View в действие вашего приложения и реагировать в обработчике onConfigurationChanged() представления на изменения конфигурации, вызванные переключением клавиатуры. прикрепленный или отсоединенный.

Ингредиенты

  • android:configChanges : Атрибут элемента <activity> манифеста приложения. Информирует систему об изменениях конфигурации, которыми управляет приложение.
  • View#onConfigurationChanged() : метод, реагирующий на распространение новой конфигурации приложения.

Шаги

Краткое содержание

Объявите атрибут configChanges и добавьте значения, связанные с клавиатурой. Добавьте View в иерархию представлений действия и прослушивайте изменения конфигурации.

1. Объявите атрибут configChanges .

Обновите элемент <activity> в манифесте приложения, добавив значения keyboard|keyboardHidden в список уже управляемых изменений конфигурации:

<activity
      …
      android:configChanges="...|keyboard|keyboardHidden">

2. Добавьте пустое представление в иерархию представлений.

Объявите новое представление и добавьте код обработчика в метод onConfigurationChanged() представления:

Котлин

val v = object : View(this) {
  override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    // Handler code here.
  }
}

Джава

View v = new View(this) {
    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Handler code here.
    }
};

Полученные результаты

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

Дополнительные ресурсы

Чтобы узнать, как сохранить состояние пользовательского интерфейса вашего приложения во время изменений конфигурации, например присоединения или отсоединения клавиатуры, см. раздел Сохранение состояний пользовательского интерфейса .

{% дословно %} {% дословно %} {% дословно %} {% дословно %}