В этой статье пойдет речь о вынисении UI в отдельный блок, компоновкой стандартных элементов.
Я расскажу о проблемах с которыми вст��етился сам, для искушенных пользователей, прилагаю ссылку на более подробный ресурс.
Основной пример будет рассмотрен на простой задаче когда нам необходим Switch в котором будет и текст и описание.

Как создать что-то подобное и не нагружать верстку ?
По началу может показаться что это муторно, однако в больших проектах без этого не обойтись, иначе будет много вложенностей и код постепенно станет не читабельным.
первое что потребуется сделать это выделить один элемент и вынести верстку из файла xml.
Создаем файл CusomSwitch.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingStart="22dp" android:paddingEnd="22dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <com.example.customviewblock.CustomSwitch android:id="@+id/first" android:layout_width="match_parent" android:layout_height="wrap_content" android:checked="false" app:cs_subtitle="SubTitle" android:title="Title" /> <com.example.customviewblock.CustomSwitch android:id="@+id/second" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:checked="true" app:cs_subtitle="SubTitle" android:title="Title" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
Теперь создаем в папке с UI папку view (для хранения кодовой части ваших кастомных view). И создаем файл CustomSwitch.kt
class CustomSwitch ( context: Context, attributeSet: AttributeSet ) : LinearLayout(context, attributeSet) { private var binding: CustomSwitchBinding = CustomSwitchBinding.inflate(LayoutInflater.from(context), this, false) var addOnChangeListener: ((Boolean) -> Unit)? = null var isChecked: Boolean get() = binding.switchSC.isChecked set(value) { binding.switchSC.isChecked = value } init { addView(binding.root) val typedArray = context.theme.obtainStyledAttributes( attributeSet, R.styleable.CustomSwitch, 0, 0 ) binding.apply { val title = typedArray.getString(R.styleable.CustomSwitch_android_title) val subTitle = typedArray.getString(R.styleable.CustomSwitch_cs_subtitle) val isChecked = typedArray.getBoolean(R.styleable.CustomSwitch_android_checked, false) switchSC.isChecked = isChecked titleTV.text = title subTitleTV.text = subTitle switchSC.setOnCheckedChangeListener { _, _isChecked -> addOnChangeListener?.invoke(_isChecked) } } } fun setTitle(text: String){ binding.titleTV.text = text } fun setSubTitle(text: String) { binding.subTitleTV.text = text } }
Сейчас этот файл будет ругаться на то что он не может найти R.styleable.CustomSwitch.
В этом файле вы будете указывать какие атрибуты можно задать для вашего кастового view в файле xml. давайте создадим его.
идем в res далее в values и создаем файл attrs.
<declare-styleable name="CustomSwitch"> <attr name="cs_subtitle" format="string"/> <attr name="android:title"/> <attr name="android:checked" /> </declare-styleable>
в поле name - обязательно нужно указывать имя вашего класса в нашем случае это был CustomSwitch.kt - соответственно поле name будет CustomSwitch. если назвать его иначе то в файле xml мы не будем получать подсказок, какие поля еще имеются для заполнения.

весьма полезная функция чтобы ее потерять)
Также хочу заметить что в этом файле имеется два типа полей, те которые добавляются из анроида и те которые мы создаем лично.
поле checked имеется в платформе и соответственно может переиспользываться, также как и title. вы можете переиспользовать их сколь угодно раз.
subtitle так же имеется в платформе, однако для примера я не стал указывать что хочу переиспользовать его и не указал слово android:.
будте внимательны так как новое имя может использоваться только в одной кастовой вю по этому я добавил абривиатуру cs - которая обозначает что этот атрибут будет использоваться только с CustomSwitch.
в файле CustomSwitch - обязательно в блоке init() не забудьте указать addView(binding.root). что привяжет вашу верстку к файлу kt.
теперь все готово к спользыванию. далее с этой вю мы работаем как и с любым другим экраном, у нас имеется binding c помощью которого мы можем обращаться к вю и файл в котором мы можем размещать наш функционал.
В примере имеются несколько базовых функций для работы с этой вю:
setTitle, setSubTitle - для установки соответствующих полей, isChecked - для проверки/устаноки состояния и addOnChangeListener - для прослушивания нажатий.
вот как можно применить это в коде:
class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.run { first.isChecked = !first.isChecked // get or set checked first.addOnChangeListener = { boolean -> // boolean on change } first.setTitle("text") // set title first.setSubTitle("text2") // set subTitle } } }
