Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
В Android Studio 3.6 появилась возможность отказаться от вызовов методов findViewById() при помощи новой технологии ViewBinding. Это позволяет упростить код и избежать появления различных багов.
До появления ViewBinding другие разработчики пытались реализовать подобное, чтобы уменьшить количество одинакового кода. Большой популярностью пользовалась библиотека ButterKnife. В Kotlin позже появилась похожая возможность Kotlin synthetics. По уверениям разработчиков из Google ViewBinding является самым продвинутым вариантом.
Включается в build.gradle.
android {
buildFeatures {
viewBinding = true
}
}
Теперь студия будет автоматически генерировать binding-класс. Имя генерируемого класса формируется как "название файла разметки", переведённое в "camelCase" + "Binding" (activity_main.xml → ActivityMainBinding.java). Созданный класс содержит ссылку на корневой элемент разметки (root) и ссылки на все компоненты, которые имеют идентификатор. Связывание происходит с соблюдением типов и безопасен на null. Например, если какой-то компонент уже имеется в одной конфигурации разметки, но отсутствует в другой (например, layout-land), то для него в binding-классе будет сгенерировано @Nullable-поле. Также в классе будет метод getRoot(), возвращающий корневой элемент.
Увидеть созданный класс можно, переключившись в студии в режим Project вместо обычного Android. Путь будет приблизительно таким: ...\app\build\generated\data_binding_base_class_source_out\debug\out\your_package_path\databinding\ActivityMainBinding.java
Новый инструмент доступен на Java и Kotlin.
Связывание разметки с bindig-классом работает с активностями, фрагментами и даже с разметкой отдельных элементов для RecyclerView.
<TextView
...
tools:viewBindingIgnore="true" />
Либо укажите данный атрибут у корневого элемента, чтобы игнорировать всю разметку
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
Создадим проект с текстовой меткой и ImageButton. Я использовал старый проект Hello Kitty, который мы создавали в первом уроке при знакомстве с Android. Вот так будет выглядеть теперь код для активности.
package ru.alexanderklimov.hellokitty
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import ru.alexanderklimov.hellokitty.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//setContentView(R.layout.activity_main)
binding.textView.text = "Hello Kitty"
binding.imageButton.setOnClickListener {
binding.textView.text = "Привет, котик!"
}
}
}
На что обратить внимание? Класс активности импортирует автоматически создаваемый новый класс ru.alexanderklimov.hellokitty.databinding.ActivityMainBinding. Теперь не нужно вызывать findViewById() для каждого объекта. Достаточно создать переменную binding и получить доступ ко всем компонентам через вызов метода inflate().
Важный момент - связывание происходит в setContentView(), не забывайте удалять старый вариант с параметром R.layout.activity_main. Теперь нужно использовать binding.root.
Ещё один важный момент - разметка не должна использовать тег merge, при его наличии код будет другим, но в большинстве случаев тег не используется.
С фрагментами работает схожим образом. Также появится новый класс, основанный на имени класса фрагмента. Только не надо забывать про onDestroyView(), в котором нужно присвоить значение null типу fragmentBlankBinding.
class FirstFragment : Fragment(R.layout.first_fragment) {
private var fragmentFirstBinding: FragmentFirstBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentBlankBinding.bind(view)
fragmentFirstBinding = binding
binding.button.onClick {
showToast("Hello binding!")
}
}
override fun onDestroyView() {
// Consider not storing the binding instance in a field
// if not needed.
fragmentBlankBinding = null
super.onDestroyView()
}
}
Применимо и к RecyclerView.ViewHolder.
class CatViewHolder(private val itemCatBinding: ItemCatBinding) :
RecyclerView.ViewHolder(itemCatBinding.root) {
fun bind(cat: Cat) {
itemCatBinding.name.text = cat.name
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CatViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val itemCatBinding = ItemCatBinding.inflate(layoutInflater, parent, false)
return CatViewHolder(itemCatBinding)
}
ViewBinding может работать в связке с DataBinding.
Несмотря на всю привлекательность нового подхода, я по-прежнему предпочитаю использовать классический подход с findViewById(). Но вы можете перейти на современные рельсы программирования.