Pull to refresh

Долгожданный View Binding в Android

Reading time3 min
Views58K

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


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

Tags:
Hubs:
Total votes 11: ↑11 and ↓0+11
Comments24

Articles