Пару дней назад Google выпустил Android Studio 3.6 Canary 11, главным нововведением в которой стал View Binding, о котором было рассказано еще в мае на Google I/O 2019.
View Binding — это инструмент, который позволяет проще писать код для взаимодейтсвия с view. При включении View Binding в определенном модуле он генерирует binding классы для каждого файла разметки (layout) в модуле. Объект сгенерированного binding класса содержит ссылки на все view из файла разметки, для которых указан android:id
.
Как включить
Чтобы включить View Binding в модуле надо добавить элемент в файл build.gradle
:
android {
...
viewBinding {
enabled = true
}
}
Также можно указать, что для определенного файла разметки генерировать binding класс не надо. Для этого надо указать аттрибут tools:viewBindingIgnore="true"
в корневой view в нужном файле разметки.
Как использовать
Каждый сгенерированный binding класс содержит ссылку на корневой view разметки (root
) и ссылки на все view, которые имеют id. Имя генерируемого класса формируется как "название файла разметки", переведенное в camel case + "Binding".
Например, для файла разметки result_profile.xml
:
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>
Будет сгенерирован класс ResultProfileBinding
, содержащий 2 поля: TextView name
и Button button
. Для ImageView
ничего сгенерировано не будет, как как оно не имеет id
. Также в классе ResultProfileBinding
будет метод getRoot()
, возвращающий корневой LinearLayout
.
Чтобы создать объект класса ResultProfileBinding
, надо вызвать статический метод inflate()
. После этого можно использовать корневой view как content view
в Activity
:
private lateinit var binding: ResultProfileBinding
@Override
fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
}
Позже binding
можно использовать для получения view:
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
Отличия от других подходов
Главные преимущества View Binding — это Null safety и Type safety.
При этом, если какая-то view имеется в одной конфигурации разметки, но отсутствует в другой (layout-land
, например), то для нее в binding классе будет сгенерировано @Nullable
поле.
Также, если в разных конфигурациях разметки имеются view с одинаковыми id, но разными типами, то для них будет сгенерировано поле с типом android.view.View
.
(По крайней мере, в версии 3.6 Canary 11)
А вообще, было бы удобно, если бы сгенерированное поле имело наиболее возможный специфичный тип. Например, чтобы для Button
в одной конфигурации и TextView
в другой генерировалось поле с типом TextView
(public class Button extends TextView
).
При использовании View Binding все несоответствия между разметкой и кодом будут выявляться на этапе компиляции, что позволит избежать ненужных ошибок во время работы приложения.
Использование в RecyclerView.ViewHolder
Ничего не мешает использовать View Binding при создании view
для RecyclerView.ViewHolder
:
class PersonViewHolder(private val itemPersonBinding: ItemPersonBinding) :
RecyclerView.ViewHolder(itemPersonBinding.root) {
fun bind(person: Person) {
itemPersonBinding.name.text = person.name
}
}
Однако, для создания такого ViewHolder
придется написать немного бойлерплейта:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemPersonBinding = ItemPersonBinding.inflate(layoutInflater, parent, false)
return PersonViewHolder(itemPersonBinding)
}
Было бы удобнее, если при работе с RecyclerView.ViewHolder
метод inflate(...)
не будет иметь параметр layoutInflater
, а будет сам получать его из передаваемого parent
.
Тут нужно ещё упомянуть, что при использовании View Binding поиск view
через findViewById()
производится только один раз при вызове метода inflate()
. Это дает преимущество над kotlin-android-extensions
, в котором кеширование view
по умолчанию работало только в Activity
и Fragment
, а для RecyclerView.ViewHolder
требовалась дополнительная настройка.
В целом, View Binding это очень удобная вещь, которую легко начать использовать в существующих проектах. Создатель Butter Knife уже рекомендует переключаться на View Binding.
Немного жаль, что такой инструмент не появился несколько лет назад.
Комментарии (20)
agent10
13.09.2019 12:48Погодите, а чем собственно отличается от текущего механизма DataBinding?
Ок, Build speed impact… так а в чём отличие кодогенерации для DataBinding и ViewBinding?LootKeeper
13.09.2019 16:07В официальной доке в самом конце указаны отличия от DataBinding. Вкратце:
1) DataBinding работает только с вью которые строятся через layout тег.
2) ViewBinding не поддерживает биндинг переменных и выражения в разметке.
abbath0767
13.09.2019 14:32Использование в RecyclerView.ViewHolder
— это решение отсебятины вы ведь сами написали, в отличии от всего того что выше прямиком пришедшее из документации?
UPD: добавьте тег перевод, пожалуйста
qwert2603 Автор
13.09.2019 15:45Да, про использование в RecyclerView.ViewHolder написал я, так как это распространенный случай получения view в коде.
agent10
13.09.2019 20:53Всё что описано больше именно технические моменты. Но, мне кажется, всё немного глубже, видимо Гугл понял, что не очень хорошо добавлять логику внутрь xml.
А ViewBindings выглядит как некий промежуточный вариант между текущим DataBinding и вероятным будущим Jetpack Compose
DanManDKo
14.09.2019 12:30Зачем нужен велосипед? Есть же databindig, отличная вещь
qwert2603 Автор
14.09.2019 12:32Не во всех проектах используется databinding, а View Binding решает очень распространенную задачу, и начать его применять достаточно легко.
evseev
16.09.2019 10:45А в чем проблема начать использовать DataBinding? Включается он так-же. Добавить тег layout вроде-бы не сильно сложно. Не нравятся лямбды в xml или привязка данных и методов из ViewModel? Так в чем сложность? Их можно не использовать. Хотя, честно говоря, я не совсем понимаю, кто в здравом уме откажется от этого. Когда нужно в старом проекте что-то посмотреть или исправить, то без того-же DataBinding-а чувствуешь себя так, как будто тебе обрезали крылья и дали костыли.
DanManDKo
16.09.2019 11:23DataBinding влючается в два клика буквально. Необязательно сразу же использовать его фичи вроде переменных. И он ни с чем не конфликтует.
ChPr
14.09.2019 13:26ResultProfileBinding.inflate(layoutInflater)
А там только 1 параметр передать можно? Без родительского контейнера как в обычном
inflate(id, viewGroup, attach)
? Таким образом не потеряются ли LayoutParameters, которые я описал в xml? Например в том же примере с RecyclerView, если я задамitem_person.xml
высоту в 50dp, то разве она не будет заменена на WRAP_CONTENT, поскольку он является значением по-умолчанию для LinearLayoutManager? Или если этот layout – кусок ConstraintLayout'a, который я добавляю в рантайме?qwert2603 Автор
14.09.2019 13:28Сгенерированный метод
ResultProfileBinding.inflate
имеет перегрузку с параметрами (inflater, viewGroup, attach)ChPr
14.09.2019 14:23Тогда используйте эту перегрузку в примере с RecycleView. Иначе складывается впечатление, что такой важной функциональности нет, и заданные в xml LayoutParameters безнадежно теряются.
Firsto
16.09.2019 09:22Джва года ждал этого, но с DataBinding перелезать уже не хочется, так как привык в разметке задавать переменные и утилитарные классы непосредственно оттуда вызывать.
kdk96
16.09.2019 11:25Проект не компилируется, если добавить один layout в другой через include и добавить id
FirsofMaxim
17.09.2019 11:39Спасибо, не забываем что viewBinding доступен с версии Android Studio 3.6 (пока в canary state).
SannX
Переизобрели ButterKnife?
qwert2603 Автор
View Binding отличается от Butter Knife типобезопасностью и отсутствием бойлерплейта.