UI Components

https://developer.android.com/develop/ui/views/layout/declaring-layout
https://developer.android.com/develop/ui/views/layout/responsive-adaptive-design-with-views
https://developer.android.com/develop/ui/views/layout/window-size-classes
https://developer.android.com/develop/ui/views/layout/support-multi-window-mode
https://developer.android.com/develop/ui/views/layout/constraint-layout
https://developer.android.com/develop/ui/views/layout/recyclerview
https://developer.android.com/develop/ui/views/layout/recyclerview-custom
https://developer.android.com/develop/ui/views/layout/cardview
https://developer.android.com/develop/ui/views/layout/twopane
https://developer.android.com/develop/ui/views/layout/linear
https://developer.android.com/develop/ui/views/layout/binding
https://developer.android.com/develop/ui/views/layout/relative
https://developer.android.com/reference/kotlin/android/widget/RemoteViews
https://developer.android.com/reference/kotlin/android/widget/FrameLayout
ViewStub

Загружает и отображает представление только при необходимости. Используется для оптимизации производительности, чтобы избежать ненужной загрузки ресурсов до того, как они понадобятся.

<LinearLayout 
		xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/your_layout_to_inflate" />
        
</LinearLayout>
class MainActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Получаем ViewStub
        val viewStub: ViewStub = findViewById(R.id.view_stub)

        // Загружаем макет
        viewStub.inflate()
    }
}
inflate

Загружает макет из ресурса и возвращает его как иерархию View, которую можно добавить в контейнер.

FrameLayout

Контейнер, который размещает элементы один на другом. Используется для простого наложения или отображения одного элемента в конкретном месте.

LinearLayout

Контейнер для расположения элементов в одном ряду или колонке. Все элементы выстраиваются последовательно, либо по горизонтали, либо по вертикали.

ConstraintLayout

Контейнер для гибкого расположения элементов интерфейса с помощью ограничений. Упрощает создание сложных макетов без вложенных контейнеров и улучшает производительность.

MotionLayout

Позволяет создавать сложные анимации и переходы между различными состояниями UI. Он является расширением ConstraintLayout и упрощает работу с анимациями, особенно для интерфейсов с динамическими переходами.

<MotionLayout 
		xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/motion_scene">

    <ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Click me"
            android:layout_centerInParent="true" />
            
    </ConstraintLayout>

</MotionLayout>
MotionScene

Описывает анимации и переходы между различными состояниями интерфейса.

<MotionScene 
		xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Transition
        app:constraintSetStart="@id/startState"
        app:constraintSetEnd="@id/endState"
        app:motionDuration="1000">
        
        <OnSwipe
            app:touchAnchorId="@id/button"
            app:dragDirection="dragHorizontal"
            app:touchAnchorSide="center" />
            
    </Transition>

    <ConstraintSet 
		    android:id="@+id/startState">
		    
        <Constraint
            android:id="@id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true" />
            
    </ConstraintSet>

    <ConstraintSet android:id="@+id/endState">
    
        <Constraint
            android:id="@id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true" />
            
    </ConstraintSet>
    
</MotionScene>
Transition

Описание анимации, которая определяет, как элементы будут изменяться между различными состояниями. Transition связывает два состояния (start и end) и управляет анимацией, которая происходит при переходе между ними.

<MotionLayout 
		xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/motion_scene">

    <ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Click me"
            android:layout_centerInParent="true" />
            
    </ConstraintLayout>

    <Transition
        app:motionDuration="500"
        app:motionStagger="0.1"
        app:motionInterpolator="easeInOut"
        app:constraintSetStart="@id/startState"
        app:constraintSetEnd="@id/endState" />
        
</MotionLayout>
OnSwipe

Cобытие, которое отслеживает свайпы пользователя (горизонтальные или вертикальные движения) и на основе этого изменяет состояние анимации или переходов в MotionLayout.

<MotionLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/motion_scene">

    <ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Swipe Me"
            android:layout_centerInParent="true" />
            
    </ConstraintLayout>

    <OnSwipe
        app:touchAnchorId="@id/button"
        app:touchAnchorSide="center"
        app:dragDirection="dragHorizontal"
        app:motionProgress="0.5" />

</MotionLayout>
RemoteViews

Класс для обновления UI в компонентах, таких как виджеты и уведомления.

val remoteViews = RemoteViews(packageName, R.layout.widget_layout)
remoteViews.setTextViewText(R.id.widget_text_view, "Hello, RemoteViews!")

val appWidgetManager = AppWidgetManager.getInstance(context)
appWidgetManager.updateAppWidget(ComponentName(context, MyWidgetProvider::class.java), remoteViews)
TextView

Отображает текст.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Пример текста"
    android:textSize="18sp"
    android:textColor="#000000"
    android:textStyle="bold" />
PrecomputedText

Используется для предварительного вычисления текста.

fun TextView.setTextAsync(longString: String) {
    val params = this.textMetricsParams
    val ref = WeakReference(this)

    CoroutineScope.launch(Dispatchers.Default) {
        val precomputedText = PrecomputedText.create(longString, params)
        withContext(Dispatchers.Main) {
            ref.get()?.let { textView ->
                textView.text = precomputedText
            }
        }
    }
}
Spannable

Позволяет изменять и стилизовать текст на уровне символов или подстрок в тексте.

val spannable = SpannableString("Текст с разными стилями")
spannable.setSpan(StyleSpan(Typeface.BOLD), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(ForegroundColorSpan(Color.RED), 5, 7, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannable
Compound Drawable

Способ добавления изображений (drawable) внутри TextView.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Пример текста"
    android:drawableLeft="@drawable/ic_example"
    android:drawablePadding="8dp" />
ScrollView

Позволяет отображать контент, выходящий за пределы экрана, с возможностью прокрутки.

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView android:text="Элемент 1" />
        <TextView android:text="Элемент 2" />
    </LinearLayout>
    
</ScrollView>
fillViewport

Атрибут, который позволяет растянуть его дочерний элемент до полного размера экрана, если контента недостаточно для прокрутки.

android:fillViewport="true"
Constraintlayout

Гибкий и мощный контейнер для размещения UI-элементов в Android. Позволяет создавать сложные интерфейсы с минимальным количеством вложенных элементов, упрощая управление расположением.

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Flow

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

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.Flow
        android:id="@+id/flow"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:flow_wrapMode="chain"
        app:flow_horizontalAlign="start"
        app:flow_verticalAlign="top">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 1" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 2" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 3" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 4" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button 5" />

    </androidx.constraintlayout.widget.Flow>

</androidx.constraintlayout.widget.ConstraintLayout>
Guideline

Вспомогательная линия в ConstraintLayout, которая помогает выравнивать элементы по заданным процентам или фиксированным позициям относительно родительского контейнера.

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        app:orientation="vertical"
        app:layout_constraintGuide_percent="0.5" /> <!-- 50% ширины экрана -->

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        app:layout_constraintStart_toStartOf="@id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
chain

Группа элементов, связанных между собой, которые могут быть выровнены по одной оси (горизонтальной или вертикальной) с возможностью динамического изменения расстояний между ними.

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 2"
        app:layout_constraintStart_toEndOf="@id/textView1"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 3"
        app:layout_constraintStart_toEndOf="@id/textView2"
        app:layout_constraintTop_toTopOf="parent" />

    <!-- Chain definition -->
    <androidx.constraintlayout.widget.ConstraintLayout
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/textView3"
        app:layout_constraintHorizontal_chainStyle="spread" />

</androidx.constraintlayout.widget.ConstraintLayout>
barrier

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

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item 2"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@id/textView1" />

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        app:constraint_referenced_ids="textView1,textView2"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintTop_toBottomOf="@id/barrier"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
baseline

Используется для выравнивания элементов по базовой линии текста.

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="World"
        app:layout_constraintBaseline_toBaselineOf="@id/textView1"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
CoordinatorLayout

Контейнер для сложных интерфейсов с прокруткой, который координирует поведение дочерних элементов, таких как DrawerLayout или FloatingActionButton.

<androidx.coordinatorlayout.widget.CoordinatorLayout>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        app:layout_anchor="@id/textView"
        app:layout_anchorGravity="bottom|end" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
ViewPager2

Компонент для прокручиваемых страниц в Android. Он улучшает оригинальный ViewPager, поддерживая работу с фрагментами, вертикальную прокрутку и лучшее использование RecyclerView для производительности.

Передать currentItem как аргумент фрагменту
class PagerViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle
): ViewModel() {
    val tabLiveData: LiveData<Int> = savedStateHandle.getLiveData<Int>("tab")
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.tabLiveData.observe(viewLifecycleOwner) { tab ->
        view.post { binding.viewPager.setCurrentItem(tab, false) }
    }
}

companion object {
    fun create(tab: Int) = PagerFragment().apply {
        arguments = bundleOf("tab" to tab)
    }
}
WebView

Позволяет отображать веб-страницы внутри вашего приложения.

• Использование WebView может представлять потенциальные угрозы безопасности, так как оно позволяет выполнять JavaScript и обрабатывать веб-контент.

• Важно правильно управлять жизненным циклом WebView, например, освобождать ресурсы и сохранять состояние, если это необходимо.

<WebView
    android:id="@+id/my_webview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
class MainActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       
        val webView: WebView = findViewById(R.id.my_webview)
        webView.settings.javaScriptEnabled = true  // Включение JavaScript
        webView.webViewClient = WebViewClient()    // Обработка ссылок внутри WebView
        webView.loadUrl("https://www.example.com") // Загрузка веб-страницы
    }
    
    override fun onPause() {
		    super.onPause()
		    webView.onPause()
		}

		override fun onResume() {
		    super.onResume()
		    webView.onResume()
		}

		override fun onDestroy() {
		    super.onDestroy()
		    webView.destroy()
		}
}

javaScriptEnabled

Включает или отключает поддержку JavaScript.

webView.settings.javaScriptEnabled = true

setDomStorageEnabled

Включает или отключает поддержку DOM-хранилища.

webView.settings.domStorageEnabled = true

setSupportZoom

Включает или отключает поддержку масштабирования.

webView.settings.setSupportZoom(true)

goBack

Управлять возвратом на предыдущие страницы в WebView.

if (webView.canGoBack()) {
    webView.goBack()
} else {
    super.onBackPressed()
}

WebViewClient

По умолчанию, ссылки, которые пользователь нажимает в WebView, открываются в браузере. Чтобы ссылки открывались внутри WebView, нужно установить WebViewClient.

webView.webViewClient = object: WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
        view.loadUrl(url)
        return true
    }
}
RelativeLayout
RelativeLayout устарел. Мигрируй на ConstraintLayout.

Позволяет располагать дочерние элементы относительно друг друга или родительского контейнера.

<RelativeLayout 
		xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        android:layout_centerInParent="true" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click me"
        android:layout_below="@id/textView"
        android:layout_centerHorizontal="true" />
        
</RelativeLayout>
Вопросы на собесе (13)
  1. Для чего нужен CoordinatorLayout?

    Предназначен для управления поведением дочерних View, таких как FloatingActionButton и AppBarLayout. Он позволяет координировать взаимодействие между этими View, например, автоматическое скрытие или изменение их размера при прокрутке списка.

  1. Перечисли наследников ViewGroup?

    LinearLayout RelativeLayout FrameLayout ConstraintLayout TableLayout GridLayout CoordinatorLayout DrawerLayout ViewPager RecyclerView

  1. Ранжируй Layouts по производительности?

    FrameLayout LinearLayout ConstraintLayout RelativeLayout

  1. Как FrameLayout располагает дочерние элементы?

    FrameLayout располагает дочерние элементы в левом верхнем углу по умолчанию, накладывая их друг на друга. Каждый новый элемент размещается поверх предыдущих, если не заданы другие параметры размещения.

  1. Для чего нужен MotionLayout?

    Для создания сложных анимаций и переходов между состояниями интерфейса, объединяя возможности ConstraintLayout и анимации.

  1. Что такое WebView?

    Компонент, позволяющий приложениям отображать веб-контент внутри UI. Предоставляет возможность загружать и отображать веб-страницы, выполнять JavaScript и обрабатывать навигацию. WebView полезен для интеграции веб-контента в приложения, например, для отображения инструкций или материалов, не выходя из приложения.

  1. Что будет быстрее: 2 LinearLayout вложенные один в другой или ConstraintLayout?

    ConstraintLayout будет быстрее, чем два вложенных LinearLayout, так как он оптимизирует размещение элементов и снижает количество проходов по дереву представлений. ConstraintLayout лучше масштабируется для сложных интерфейсов, поскольку позволяет избежать глубокой вложенности и перегрузки измерений.

  1. С помощью какого компонента лучше выполнять отложенный (по запросу) inflate дочерней View?

    LazyView

    AsnycLayoutinflater

    ViewStub

  1. Сколько дочерних элементов может содержать ScrollView?

    2

    1

    3

    Сколько угодно

  1. Какой метод используется для настройки слушателя нажатий в View?

    setOnTouchListener

    setOnClickListener

    setClickListener

    setOnClick

  1. Какой из следующих атрибутов XML используется для установки стиля текста в TextView?

    textStyle

    textAppearance

    textFont

    textSize

  1. Сколько проходов по детям с вызовом метода onMeasure() выполняется в LinearLayout (без использования и дополнительных вызовов метода рефреша жизненного цикла View)

    1

    1 или 2

    2 или 3

    До 5 раз

  1. Что отрисуется на экране при inflate этого XML?
    <LinearLayout
    		xmlns:android="http://schemas.android.com/apk/res/android"
    		android:layout_width="match_parent"
    		android:layout_height="match_parent"
    		android:gravity="center"
    		android:orientation="horizontal">
    		
    		<Button
    				android:layout_width="wrap_content"
    				android:layout_height="wrap_content"
    				android:text="Button" />
    				
    		<Button
    				android:layout_width="wrap_content"
    				android:layout_height="wrap_content"
    				android:text="Button" />
    
    </LinearLayout>

    left-top

    right-top

    left-bottom

    right-bottom