Пару дней назад 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)


  1. SannX
    13.09.2019 11:51

    Переизобрели ButterKnife?


    1. qwert2603 Автор
      13.09.2019 15:43

      View Binding отличается от Butter Knife типобезопасностью и отсутствием бойлерплейта.


  1. agent10
    13.09.2019 12:48

    Погодите, а чем собственно отличается от текущего механизма DataBinding?
    Ок, Build speed impact… так а в чём отличие кодогенерации для DataBinding и ViewBinding?


    1. LootKeeper
      13.09.2019 16:07

      В официальной доке в самом конце указаны отличия от DataBinding. Вкратце:
      1) DataBinding работает только с вью которые строятся через layout тег.
      2) ViewBinding не поддерживает биндинг переменных и выражения в разметке.


  1. kemsky
    13.09.2019 14:15

    Лучше поздно, чем никогда.


  1. abbath0767
    13.09.2019 14:32

    Использование в RecyclerView.ViewHolder — это решение отсебятины вы ведь сами написали, в отличии от всего того что выше прямиком пришедшее из документации?


    UPD: добавьте тег перевод, пожалуйста


    1. qwert2603 Автор
      13.09.2019 15:45

      Да, про использование в RecyclerView.ViewHolder написал я, так как это распространенный случай получения view в коде.


  1. md_45
    13.09.2019 15:46

    чем это лучше чем kotlin synthetic?


    1. qwert2603 Автор
      13.09.2019 15:49

      тем, что View Binding обеспечивает Null Safety, например, в ситуации, когда view есть в одной конфигурации, но нет в другой.


  1. agent10
    13.09.2019 20:53

    Всё что описано больше именно технические моменты. Но, мне кажется, всё немного глубже, видимо Гугл понял, что не очень хорошо добавлять логику внутрь xml.
    А ViewBindings выглядит как некий промежуточный вариант между текущим DataBinding и вероятным будущим Jetpack Compose


  1. DanManDKo
    14.09.2019 12:30

    Зачем нужен велосипед? Есть же databindig, отличная вещь


    1. qwert2603 Автор
      14.09.2019 12:32

      Не во всех проектах используется databinding, а View Binding решает очень распространенную задачу, и начать его применять достаточно легко.


      1. evseev
        16.09.2019 10:45

        А в чем проблема начать использовать DataBinding? Включается он так-же. Добавить тег layout вроде-бы не сильно сложно. Не нравятся лямбды в xml или привязка данных и методов из ViewModel? Так в чем сложность? Их можно не использовать. Хотя, честно говоря, я не совсем понимаю, кто в здравом уме откажется от этого. Когда нужно в старом проекте что-то посмотреть или исправить, то без того-же DataBinding-а чувствуешь себя так, как будто тебе обрезали крылья и дали костыли.


      1. DanManDKo
        16.09.2019 11:23

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


  1. ChPr
    14.09.2019 13:26

    ResultProfileBinding.inflate(layoutInflater)

    А там только 1 параметр передать можно? Без родительского контейнера как в обычном inflate(id, viewGroup, attach)? Таким образом не потеряются ли LayoutParameters, которые я описал в xml? Например в том же примере с RecyclerView, если я задам item_person.xml высоту в 50dp, то разве она не будет заменена на WRAP_CONTENT, поскольку он является значением по-умолчанию для LinearLayoutManager? Или если этот layout – кусок ConstraintLayout'a, который я добавляю в рантайме?


    1. qwert2603 Автор
      14.09.2019 13:28

      Сгенерированный метод ResultProfileBinding.inflate имеет перегрузку с параметрами (inflater, viewGroup, attach)


      1. ChPr
        14.09.2019 14:23

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


  1. Firsto
    16.09.2019 09:22

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


  1. kdk96
    16.09.2019 11:25

    Проект не компилируется, если добавить один layout в другой через include и добавить id


  1. FirsofMaxim
    17.09.2019 11:39

    Спасибо, не забываем что viewBinding доступен с версии Android Studio 3.6 (пока в canary state).