Как стать автором
Обновить

Custom View — разбиваем функционал

Время на прочтение4 мин
Количество просмотров4.7K

В этой статье пойдет речь о вынисении 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
        }
    }
}

Теги:
Хабы:
Всего голосов 4: ↑4 и ↓0+4
Комментарии2

Публикации

Истории

Работа

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн