Android Kotlin Fundamentals 02.4: Основы привязки данных

Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .

Введение

В предыдущих практических занятиях этого курса вы использовали функцию findViewById() для получения ссылок на представления. Если в вашем приложении сложная иерархия представлений, findViewById() требует больших затрат и замедляет работу приложения, поскольку Android обходит иерархию представлений, начиная с корня, пока не найдёт нужное представление. К счастью, есть способ получше.

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

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

Что вам уже следует знать

Вам должно быть знакомо:

  • Что такое активность и как настроить активность с макетом в onCreate() .
  • Создание текстового представления и настройка текста, отображаемого в текстовом представлении.
  • Использование findViewById() для получения ссылки на представление.
  • Создание и редактирование базового XML-макета для представления.

Чему вы научитесь

  • Как использовать библиотеку привязки данных для устранения неэффективных вызовов findViewById() .
  • Как получить доступ к данным приложения напрямую из XML.

Что ты будешь делать?

  • Измените приложение так, чтобы оно использовало привязку данных вместо findViewById() и получало доступ к данным напрямую из XML-файлов макета.

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

Вот что делает приложение AboutMe:

  • Когда пользователь открывает приложение, оно отображает имя, поле для ввода псевдонима, кнопку « Готово» , изображение звездочки и прокручиваемый текст.
  • Пользователь может ввести псевдоним и нажать кнопку «Готово» . Редактируемое поле и кнопка заменяются текстовым полем, отображающим введённый псевдоним.


Вы можете использовать код, созданный вами в предыдущей лабораторной работе, или загрузить код AboutMeDataBinding-Starter с GitHub.

Код, который вы написали в предыдущих практических занятиях, использует функцию findViewById() для получения ссылок на представления.

Каждый раз, когда вы используете findViewById() для поиска представления после его создания или пересоздания, система Android просматривает иерархию представлений во время выполнения, чтобы найти его. Если в вашем приложении всего несколько представлений, это не проблема. Однако в рабочих приложениях макет может содержать десятки представлений, и даже при самом лучшем дизайне будут вложенные представления.

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

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

Привязка данных имеет следующие преимущества:

  • Код короче, его легче читать и поддерживать, чем код, использующий findByView() .
  • Данные и представления чётко разделены. Это преимущество привязки данных станет ещё более важным далее в этом курсе.
  • Система Android обходит иерархию представлений только один раз, чтобы получить каждое представление, и это происходит во время запуска приложения, а не во время выполнения, когда пользователь взаимодействует с приложением.
  • Вы получаете типобезопасность для доступа к представлениям. ( Типобезопасность означает, что компилятор проверяет типы во время компиляции и выдает ошибку, если вы пытаетесь присвоить переменной неправильный тип.)

В этой задаче вы настроите привязку данных и используете привязку данных для замены вызовов findViewById() вызовами объекта привязки.

Шаг 1: Включите привязку данных

Чтобы использовать привязку данных, необходимо включить её в файле Gradle, поскольку по умолчанию она отключена. Это связано с тем, что привязка данных увеличивает время компиляции и может повлиять на время запуска приложения.

  1. Если у вас нет приложения AboutMe из предыдущей лабораторной работы, скачайте код AboutMeDataBinding-Starter с GitHub и откройте его в Android Studio.
  2. Откройте файл build.gradle (Module: app) .
  3. Внутри раздела android , перед закрывающей фигурной скобкой, добавьте раздел dataBinding и установите для параметра enabled значение true .
dataBinding {
    enabled = true
}
  1. При появлении запроса синхронизируйте проект. Если запрос не появится, выберите «Файл» > «Синхронизировать проект с файлами Gradle» .
  2. Вы можете запустить приложение, но никаких изменений вы не увидите.

Шаг 2: Измените файл макета, чтобы его можно было использовать с привязкой данных.

Для работы с привязкой данных необходимо обернуть XML-макет тегом <layout> . Это позволит корневому классу перестать быть группой представлений, а стать макетом, содержащим группы представлений и представления. В этом случае объект привязки сможет получить информацию о макете и представлениях в нём.

  1. Откройте файл activity_main.xml .
  2. Перейдите на вкладку Текст .
  3. Добавьте <layout></layout> как самый внешний тег вокруг <LinearLayout> .
<layout>
   <LinearLayout ... >
   ...
   </LinearLayout>
</layout>
  1. Выберите Код > Переформатировать код , чтобы исправить отступ кода.

    Объявления пространства имен для макета должны находиться в самом внешнем теге.
  1. Вырежьте объявления пространств имён из тега <LinearLayout> и вставьте их в тег <layout> . Открывающий тег <layout> должен выглядеть так, как показано ниже, а тег <LinearLayout> должен содержать только свойства представления.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
  1. Создайте и запустите приложение, чтобы убедиться, что вы все сделали правильно.

Шаг 3: Создайте объект привязки в основном действии

Добавьте ссылку на объект привязки к основному действию, чтобы можно было использовать его для доступа к представлениям:

  1. Откройте файл MainActivity.kt .
  2. Перед onCreate() на верхнем уровне создайте переменную для объекта привязки. Эта переменная обычно называется binding .

    Тип binding , класс ActivityMainBinding , создаётся компилятором специально для этой основной активности. Название происходит от имени файла макета, то есть activity_main + Binding .
private lateinit var binding: ActivityMainBinding
  1. Если Android Studio предложит импортировать ActivityMainBinding , нажмите Alt+Enter ( Option+Enter на Mac) ActivityMainBinding чтобы импортировать отсутствующий класс. (Дополнительные сочетания клавиш см. в разделе Сочетания клавиш ).

    Оператор import должен выглядеть примерно так, как показано ниже.
import com.example.android.aboutme.databinding.ActivityMainBinding

Затем вы заменяете текущую функцию setContentView() инструкцией, которая выполняет следующие действия:

  • Создает объект привязки.
  • Использует функцию setContentView() из класса DataBindingUtil для связывания макета activity_main с MainActivity . Эта функция setContentView() также отвечает за настройку привязки данных для представлений.
  1. В onCreate() замените вызов setContentView() следующей строкой кода.
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  1. Импорт DataBindingUtil .
import androidx.databinding.DataBindingUtil

Шаг 4: Используйте объект привязки для замены всех вызовов findViewById()

Теперь вы можете заменить все вызовы findViewById() ссылками на представления, находящиеся в объекте привязки. При создании объекта привязки компилятор генерирует имена представлений в объекте привязки на основе идентификаторов представлений в макете, преобразуя их в CamelCase. Например, done_button в объекте привязки становится doneButton , nickname_edit становится nicknameEdit , а nickname_text становится nicknameText .

  1. В onCreate() замените код, который использует findViewById() для поиска done_button , кодом, который ссылается на кнопку в объекте привязки.

    Замените этот код: findViewById<Button>(R.id. done_button )
    с: binding.doneButton

    Ваш готовый код для установки прослушивателя щелчков в onCreate() должен выглядеть следующим образом.
binding.doneButton.setOnClickListener {
   addNickname(it)
}
  1. Сделайте то же самое для всех вызовов findViewById() в функции addNickname() .
    Замените все вхождения findViewById< View >(R.id. id_view ) на binding. idView . Сделайте это следующим образом:
  • Удалите определения переменных editText и nicknameTextView вместе с вызовами findViewById() . Это приведёт к ошибкам.
  • Исправьте ошибки, получив представления nicknameText , nicknameEdit и doneButton из объекта binding вместо (удаленных) переменных.
  • Замените view.visibility на binding.doneButton.visibility . Использование binding.doneButton вместо переданного view делает код более последовательным.

    Результатом будет следующий код:
binding.nicknameText.text = binding.nicknameEdit.text
binding.nicknameEdit.visibility = View.GONE
binding.doneButton.visibility = View.GONE
binding.nicknameText.visibility = View.VISIBLE
  • Функциональность не изменилась. При желании вы можете удалить параметр view и обновить все случаи использования view , чтобы использовать binding.doneButton внутри этой функции.
  1. nicknameText требуется String , а nicknameEdit.textEditable . При использовании привязки данных необходимо явно преобразовать Editable в String .
binding.nicknameText.text = binding.nicknameEdit.text.toString()
  1. Вы можете удалить выделенные серым цветом импортные операции.
  2. Котлинизируйте функцию с помощью apply{} .
binding.apply {
   nicknameText.text = nicknameEdit.text.toString()
   nicknameEdit.visibility = View.GONE
   doneButton.visibility = View.GONE
   nicknameText.visibility = View.VISIBLE
}
  1. Создайте и запустите свое приложение... и оно должно выглядеть и работать точно так же, как и прежде.

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

В этом примере вместо указания имени и псевдонима с помощью строковых ресурсов вы создаёте класс данных для имени и псевдонима. Этот класс данных становится доступным для представления с помощью привязки данных.

Шаг 1: Создайте класс данных MyName

  1. В Android Studio в каталоге java откройте файл MyName.kt . Если у вас его нет, создайте новый файл Kotlin и назовите его MyName.kt .
  2. Определите класс данных для имени и псевдонима. Используйте пустые строки в качестве значений по умолчанию.
data class MyName(var name: String = "", var nickname: String = "")

Шаг 2: Добавьте данные в макет

В файле activity_main.xml имя в настоящее время задано в TextView из строкового ресурса. Необходимо заменить ссылку на имя ссылкой на данные в классе данных.

  1. Откройте activity_main.xml на вкладке Текст .
  2. В верхней части макета, между тегами <layout> и <LinearLayout> , вставьте тег <data></data> . Именно здесь вы свяжете представление с данными.
<data>
  
</data>

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

  1. Внутри тега <data> добавьте тег <variable> .
  2. Добавьте параметр name , чтобы присвоить переменной имя "myName" . Добавьте параметр type и задайте тип как полное имя класса данных MyName (имя пакета + имя переменной).
<variable
       name="myName"
       type="com.example.android.aboutme.MyName" />

Теперь вместо использования строкового ресурса для имени вы можете ссылаться на переменную myName .

  1. Замените android:text="@string/name" на код ниже.

@={} — это директива для получения данных, на которые есть ссылка внутри фигурных скобок.

myName ссылается на переменную myName , которую вы ранее определили, которая указывает на класс данных myName и извлекает свойство name из класса.

android:text="@={myName.name}"

Шаг 3: Создание данных

Теперь у вас есть ссылка на данные в файле макета. Далее вы создаёте сами данные.

  1. Откройте файл MainActivity.kt .
  2. Над onCreate() создайте приватную переменную, также называемую myName . Присвойте ей экземпляр класса данных MyName , передав ей имя.
private val myName: MyName = MyName("Aleks Haecky")
  1. В onCreate() установите значение переменной myName в файле макета равным значению только что объявленной вами переменной myName . Вы не можете получить к ней прямой доступ в XML. Доступ к ней осуществляется через объект привязки.
binding.myName = myName
  1. Это может привести к ошибке, поскольку после внесения изменений необходимо обновить объект привязки. Соберите приложение, и ошибка должна исчезнуть.

Шаг 4: Используйте класс данных для псевдонима в TextView.

Последний шаг — также использовать класс данных для псевдонима в TextView .

  1. Откройте activity_main.xml .
  2. В текстовом представлении nickname_text добавьте text свойство. Добавьте ссылку на nickname в классе данных, как показано ниже.
android:text="@={myName.nickname}"
  1. В ActivityMain замените
    nicknameText.text = nicknameEdit.text.toString()
    с кодом для установки псевдонима в переменной myName .
myName?.nickname = nicknameEdit.text.toString()

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

  1. Добавьте invalidateAll() после установки псевдонима, чтобы пользовательский интерфейс обновлялся с использованием значения в обновленном объекте привязки.
binding.apply {
   myName?.nickname = nicknameEdit.text.toString()
   invalidateAll()
   ...
}
  1. Создайте и запустите свое приложение — оно должно работать точно так же, как и прежде.

Проект Android Studio: AboutMeDataBinding

Шаги по использованию привязки данных для замены вызовов findViewById() :

  1. Включите привязку данных в разделе android файла build.gradle :
    dataBinding { enabled = true }
  2. Используйте <layout> в качестве корневого представления в вашем XML-макете.
  3. Определите переменную привязки:
    private lateinit var binding: ActivityMainBinding
  4. Создайте объект привязки в MainActivity , заменив setContentView :
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
  5. Замените вызовы findViewById() ссылками на представление в объекте привязки. Например:
    findViewById<Button>(R.id.done_button) ⇒ binding.doneBu
    (В этом примере имя представления генерируется в стиле CamelCase из id представления в XML.)

Шаги по привязке представлений к данным:

  1. Создайте класс данных для ваших данных.
  2. Добавьте блок <data> внутри тега <layout> .
  3. Определите <variable> переменную> с именем и типом, который является классом данных.
<data>
   <variable
       name="myName"
       type="com.example.android.aboutme.MyName" />
</data>
  1. В MainActivity создайте переменную с экземпляром класса данных. Например:
    private val myName: MyName = MyName("Aleks Haecky")
  1. В объекте привязки задайте переменную, которую вы только что создали:
    binding.myName = myName
  1. В XML-файле укажите содержимое представления в переменной, определённой в блоке <data> . Для доступа к данным внутри класса данных используйте точечную нотацию.
    android:text="@={myName.name}"

Курс Udacity:

Документация для разработчиков Android:

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

  • При необходимости задавайте домашнее задание.
  • Объясните учащимся, как следует сдавать домашние задания.
  • Оцените домашние задания.

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

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

Ответьте на эти вопросы

Вопрос 1

Почему вы хотите минимизировать явные и неявные вызовы findViewById() ?

  • Каждый раз при вызове findViewById() выполняется обход иерархии представлений.
  • findViewById() работает в основном или пользовательском потоке.
  • Эти вызовы могут замедлить работу пользовательского интерфейса.
  • Вероятность сбоя вашего приложения снижается.

Вопрос 2

Как бы вы описали привязку данных?

Например, вот что можно сказать о привязке данных:

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

Вопрос 3

Что из перечисленного НЕ является преимуществом привязки данных?

  • Код становится короче, его легче читать и поддерживать.
  • Данные и представления четко разделены.
  • Система Android обходит иерархию представлений только один раз, чтобы получить каждое представление.
  • Вызов findViewById() приводит к ошибке компилятора.
  • Безопасность типов для доступа к представлениям.

Вопрос 4

Какова функция тега <layout> ?

  • Вы оборачиваете его вокруг корневого представления в макете.
  • Привязки создаются для всех представлений в макете.
  • Он обозначает представление верхнего уровня в XML-макете, использующее привязку данных.
  • Вы можете использовать тег <data> внутри <layout> , чтобы привязать переменную к классу данных.

Вопрос 5

Какой правильный способ ссылки на связанные данные в XML-макете?

  • android:text="@={myDataClass.property}"
  • android:text="@={myDataClass}"
  • android:text="@={myDataClass.property.toString()}"
  • android:text="@={myDataClass.bound_data.property}"

Начните следующий урок: 3.1: Создание фрагмента

Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .