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

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

<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

Когда вызывается метод inflate(), ViewStub заменяется указанным макетом, и этот макет становится частью интерфейса пользователя.

FrameLayout

Легкий достаточно одного measure-прохода чтобы понять как отображать своих детей внутри себя.

LinearLayout

Легкий если не использовать веса, достаточного одного прохода для отображения детей. Если использовать веса нужно 2 прохода: узнать веса и расставить относительно друг друга.

ConstraintLayout

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

MotionLayout

Предоставляет возможность создания сложных анимаций и переходов между различными состояниями пользовательского интерфейса. Является частью ConstraintLayout. Позволяет синхронизировать анимации нескольких элементов.

<androidx.constraintlayout.motion.widget.MotionLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Элементы интерфейса -->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <View
            android:id="@+id/myView"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:background="@android:color/holo_blue_light"/>
            
    </androidx.constraintlayout.widget.ConstraintLayout>

    <!-- MotionScene для определения анимаций -->
    <androidx.constraintlayout.motion.widget.MotionScene
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

        <Transition
            app:constraintSetStart="@id/start"
            app:constraintSetEnd="@id/end"
            app:duration="1000">
            <OnSwipe
                app:dragDirection="dragUp"
                app:touchAnchorId="@id/myView"
                app:touchAnchorSide="top"
                app:touchRegionId="@id/myView"/>
        </Transition>

        <ConstraintSet android:id="@+id/start">
            <Constraint
                android:id="@id/myView"
                android:layout_width="100dp"
                android:layout_height="100dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"/>
        </ConstraintSet>

        <ConstraintSet android:id="@+id/end">
            <Constraint
                android:id="@id/myView"
                android:layout_width="100dp"
                android:layout_height="100dp"
                app:layout_constraintTop_toBottomOf="parent"
                app:layout_constraintLeft_toRightOf="parent"/>
        </ConstraintSet>

    </androidx.constraintlayout.motion.widget.MotionScene>
    
</androidx.constraintlayout.motion.widget.MotionLayout>

MotionScene

Определяет анимации и переходы, включая состояния и переходы между ними.

Transition

Описывает, как и когда происходит переход между двумя состояниями.

OnSwipe

Обработчик для начала перехода на основе жеста.

RelativeLayout

Нужно 2 прохода чтобы отобразить детей. Динозавр.

RemoteViews

View которая может отображаться в другом процессе. Поддерживает ограниченное количество виджетов. Используется для виджетов или уведомлений. Клики обрабатываются через PendingIntent.

TextView

PrecomputedText

Возможность выполнить все измерения текста в любом потоке. Добавлен в Android 9.

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

Spannable – это интерфейс, характеризующий текст, который имеет стилистическую разметку. Например с помощью Spannable можно создать текст, часть которого окрашена в иной цвет. Метод Spannable.setSpan() принимает произвольный объект типа Object, который используется для разметки. Этот метод не бросает исключений. Классы, которые реализуют интерфейс Spannable, должны игнорировать неподдерживаемые объекты разметки. Пример: ForegroundColorSpan для изменения цвета текста.

Compound Drawable

Это drawable который можно добавить в TextView. Эффективнее чем использовать LinearLayout.

RecyclerView

Улучшенная замена ListView. Bмеет более гибкий API, хороший дизайн, соответствует принципу единственной ответственности. Переиспользует viewholders при скролле. Разделяет хранение и отображение данных. Можно изменить UI в runtime с помощью LayoutManager. Логика анимаций вынесена в ItemAnimator.

Несколько recyclerview внутри scrollview отображают количество элементов максимум с высоту девайса

Заменить ScrollView на NestedScrollView.

DiffUtil

Для оптимизации: метод areContentTheSame проверяет только те поля объекта, которые видны на экране.

RecycledViewPool

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

AdapterDataObserver

В методе onItemRangeInserted отслеживать загрузку списка.

setHasFixedSize

Если true - никакой контент или дочерние view внутри RecyclerView не влияют на его высоту или ширину. Владея этой информацией, RecyclerView не выполнит лишние измерения.

setHasStableIds

Если true - сообщает RecyclerView, что нужно ориентироваться, помимо позиций, ещё и на stableId элементов. Рекомендуется использовать stableId для каждого ViewHolder — это помогает RecyclerView переиспользовать view, если меняется список.

setNestedScrollingEnabled

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

Лагает прокрутка. Что делать?

- не выполнять тяжелые операции в onBindViewHolder.
- использовать
DiffUtil вместо notifyDataSetChanged обновлять одну строку вместо отрисовки всего списка.
- использовать
RecycledViewPool для вложенных списков.
- использовать
ViewStub для ленивого создания view в случае если нужно отобразить ошибку вместо хранения в памяти целого view.
- в XML использовать merge чтобы не добавлять лищние view в иерархию.
- использовать
VectorDrawable вместо тяжелых PNG и JPG, использовать nine-patch для background.
- явно указывать размер view если он фиксирован чтобы не тратить ресурсы на вычисление.
- использовать
setHasFixedSize setHasStableIds setItemViewCacheSize.
- обновлять индикаторы через
findViewHolderForAdapterPosition а не через DiffUtil.

AdapterDelegates

Favor composition over inheritance for RecyclerView Adapters.

FastAdapter

The bullet proof fast and easy to use adapter library which minimizes developing time to a fraction.

ScrollView

fillViewport

Определяет должно ли ScrollView растягивать свое содержимое для заполнения области просмотра.

Constraintlayout

ViewGroup которая позволяет гибко размещать и изменять размеры виджетов.

Guideline

Вспомогательный класс работающий внутри Constraintlayout. Виджеты могут использовать Guideline для позиционирования.

Flow

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

chain

Группа виджетов связанных друг с другом двунаправленными ограничениями.

barrier

Ограничить view набором других view.

baseline

Выровнять view по базовой линии текста.

guideline

Добавить горизонтальную или вертикальную направляющую по которой можно выровнять другие view.

CoordinatorLayout

Сверхмощный FrameLayout. Предназначен для взаимодействия дочерних виджетов между собой например: DrawerLayout FloatingActionButton.

ViewPager2

Передать 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.

setDomStorageEnabled

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

setSupportZoom

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

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
    }
}
Android UI Components. Вопросы на собесе
  1. Для чего нужен CoordinatorLayout?

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

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

    FrameLayout LinearLayout ConstraintLayout RelativeLayout

  1. С помощью какого компонента RecyclerView можно добавить разделители?

    ItemDecoration

  1. Лагает RecyclerView в чем может быть проблема?

    Тяжелые операции в методе onBindViewHolder

    Неэффективная работа с изображениями

    Частый вызов notifyDataSetChanged

    Неправильная настройка DiffUtil

  1. Как работает DiffUtil в RecyclerView?

    DiffUtil оптимизирует обновление списка, сравнивая старый и новый наборы данных. Вычисляет разницу между ними и сообщает RecyclerView, какие элементы нужно обновить, вставить или удалить. Вызывается метод calculateDiff, который анализирует изменения. Это повышает производительность и предотвращает мерцание при обновлениях. Под капотом следующие алгоритмы: алгоритм поиска наименьших различий и метод наибольшей общей подпоследовательности.

  1. Какие методы есть у DiffUtil?

    areItemsTheSame - проверяет, идентичны ли два элемента (по позиции) в старом и новом списках.

    areContentsTheSame - проверяет, равны ли содержимое двух элементов (по позиции) в старом и новом списках.

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

    LazyView

    AsnycLayoutinflater

    ViewStub

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

    2

    1

    3

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

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

    notifyDataSetChanged

    updateAdapter

    refreshAdapter

    invalidateAdapter

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

    setOnTouchListener

    setOnClickListener

    setClickListener

    setOnClick

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

    textStyle

    textAppearance

    textFont

    textSize

  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