Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
Помогли мы вам |
3. Проверь код навигации приложения. При использовании паттерна «Одна активность», когда в приложении есть всего одна активность (Activity), а все экраны представляют собой фрагменты (Fragment), ты можешь заметить, что при перемещении на новый фрагмент старый фрагмент не уничтожается. Это стандартное поведение фрагментов, поэтому заботиться об освобождении памяти должен ты сам. Для этого достаточно самостоятельно уничтожать все View в методе onDestroyView(
.
Другие советы:
notifyItemChanged()
вместо notifyDataSetChanged()
;Mastering API Visibility in Kotlin — статья о том, как сделать интерфейсы библиотек как можно более закрытыми, сохранив гибкость, возможности тестирования и возможность взаимодействовать с кодом на Java.
Internal — твой друг. Этот модификатор видимости чем-то похож на package private в Java, но покрывает не пакет, а целый модуль. Все классы, поля и методы, помеченные этим ключевым словом, будут видны только внутри текущего модуля.
Модификатор internal
можно использовать совместно с аннотацией @VisibleForTesting
, чтобы тесты могли достучаться до нужных методов и полей:
@VisibleForTesting(otherwise = PRIVATE)
internal var state: State
В Java нет модификатора internal
, поэтому в байт-коде все, что помечено этим ключевым словом, станет public
, но с одним важным отличием: к его имени прибавится название модуля. Например, метод createEntity
со стороны Java будет выглядеть как createEntity$имяМодуля
. Этого можно избежать с помощью аннотации @JvmName
, позволяющей указать другое имя для использования из Java:
class Repository {
@JvmName("pleaseDoNotCallThisMethod")
internal fun createEntity() { ... }
}
Если же метод не должен быть виден вообще, можно использовать аннотацию @JvmSynthetic
:
class Repository {
@JvmSynthetic
internal fun createEntity() { ... }
}
Explicit API mode — твой второй друг. В Kotlin все объявления по умолчанию получают модификатор public. А это значит, что шанс забыть сделать метод internal
или private
высок. Специально для борьбы с этой проблемой в Kotlin 1.4 появился Explicit API mode, который заставляет добавлять модификатор видимости к любым объявлениям. Чтобы его включить, достаточно добавить три строки в конфиг Gradle:
kotlin {
explicitApi()
}
Одно из неожиданных следствий использования internal
— инлайновые функции не смогут использовать методы, помеченные этим ключевым словом. Так происходит потому, что код инлайновой функции полностью встраивается в вызывающий код, а он не имеет доступа к методам, помеченным как internal
. Решить эту проблему можно с помощью аннотации @PublishedApi
. Она сделает метод доступным для инлайновых функций, но оставит закрытым для всех остальных:
@PublishedApi
internal fun secretFunction() {
println("through the mountains")
}
public inline fun song() {
secretFunction()
}
fun clientCode() {
song() // ok
secretFunction() // Нет доступа
}
All you need to know about ArrayMap & SparseArray — статья об ArrayMap и SparseArray, двух фирменных, но не так хорошо известных коллекциях Android. Обе коллекции по сути аналоги HashMap из Java с тем исключением, что они созданы специально, чтобы минимизировать потребление оперативной памяти.
В отличие от HashMap, который для хранения каждого объекта создает новый объект и сохраняет его в массиве, ArrayMap не создает дополнительный объект, но использует два массива: mHashes для последовательного хранения хешей ключей и mArray для хранения ключей и их значений (друг за другом). Начальный размер первого — четыре, второго — восемь.
При добавлении элемента ArrayMap сначала добавляет его хеш в первый массив, а затем ключ и значение во второй массив, где индекс ключа высчитывается как индекс хеша в массиве mHashes, умноженный на два, а индекс значения как индекс ключа плюс один. В случае коллизии (когда два разных ключа имеют одинаковый хеш) ArrayMap производит линейный поиск ключа в mArray и, если он не найден, добавляет новый хеш в mHashes и новые ключ:значение в mArray. При достижении предельного размера массивов ArrayMap копирует их в новый массив, размер которого высчитывается так: oldSize+(
(4 -> 8 -> 12 -> 18 -> 27 -> ...).
SparseArray представляет собой тот же ArrayMap, но предназначенный для работы с типами данных, где ключ — это int, а значение может быть либо объектом, либо простым типом данных: int, long, boolean (SparseIntArray, SparseLongArray, SparseBooleanArray). В итоге SparseArray нет необходимости хранить обертки над простыми типами данных.
Благодаря избавлению от необходимости хранить дополнительный объект для каждого элемента, ArrayMap оказывается примерно на 25% экономнее HashMap, а SparseArray почти в два раза экономнее.
В то же время ArrayMap и SparseArray в целом в два раза медленнее HashMap.
Выводы:
How to make a RecyclerView in Jetpack Compose — краткая заметка о том, как создать собственный RecyclerView, используя библиотеку Jetpack Compose.
RecyclerView — известный и очень популярный элемент интерфейса Android, позволяющий создать динамически формируемый (бесконечный) список элементов с ленивой загрузкой и переиспользуемыми элементами UI. Говоря простыми словами: RecyclerView — это быстрый список из произвольного количества элементов, который будет расходовать память только на те элементы, которые в данный момент находятся на экране.
RecyclerView — очень мощный и сложный инструмент. Чтобы создать список с его помощью, необходимо создать сам RecyclerView, подключить к нему адаптер, который будет наполнять его элементами, подключить менеджер лейаутов и создать один или несколько viewHolder’ов, которые будут хранить графическое представление элементов списка.
А теперь посмотрим, как создать аналог RecyclerView с использованием фреймворка Jetpack Compose:
data class ItemViewState(
val text: String
)
@Composable
fun MyComposeList(
modifier: Modifier = Modifier,
itemViewStates: List<ItemViewState>
) {
LazyColumnFor(modifier = modifier, items = itemViewStates) { viewState ->
MyListItem(itemViewState = viewState)
}
}
@Composable
fun MyListItem(itemViewState: ItemViewState) {
Text(text = itemViewState.text)
}
Это действительно все.
Using Flows for Form Validation in Android — короткая заметка о том, как реализовать валидацию форм с помощью Kotlin Flow. Интересна в первую очередь в качестве простой и наглядной демонстрации работы недавно появившегося StateFlow.
Допустим, у нас есть форма с тремя полями: First Name, Password и User Id. Наша задача — сделать так, чтобы кнопка Submit активировалась лишь в том случае, если поле First Name содержит только символы латинского алфавита, поле Password содержит как минимум восемь символов, а поле User Id содержит хотя бы один символ подчеркивания.
Для хранения текущего значения поля будем использовать StateFlow:
private val _firstName = MutableStateFlow("")
private val _password = MutableStateFlow("")
private val _userID = MutableStateFlow("")
Дополнительно создадим три метода, чтобы записывать значения в эти StateFlow:
fun setFirstName(name: String) {
_firstName.value = name
}
fun setPassword(password: String) {
_password.value = password
}
fun setUserId(id: String) {
_userID.value = id
}
Теперь объединим все три StateFlow в один Flow, который будет отдавать только значения true или false:
val isSubmitEnabled: Flow<Boolean> = combine(_firstName, _password, _userID) { firstName, password, userId ->
val regexString = "[a-zA-Z]+"
val isNameCorrect = firstName.matches(regexString.toRegex())
val isPasswordCorrect = password.length > 8
val isUserIdCorrect = userId.contains("_")
return@combine isNameCorrect and isPasswordCorrect and isUserIdCorrect
}
Этот код будет запускаться каждый раз, когда состояние любого из трех StateFlow изменится.
Теперь осталось только привязать три первых StateFlow к полям ввода:
private fun initListeners() {
editText_name.addTextChangedListener {
viewModel.setFirstName(it.toString())
}
editText_password.addTextChangedListener {
viewModel.setPassword(it.toString())
}
editText_user.addTextChangedListener {
viewModel.setUserId(it.toString())
}
}
А состояние кнопки Submit привязать к полученному в результате преобразования Flow:
private fun collectFlow() {
lifecycleScope.launch {
viewModel.isSubmitEnabled.collect { value ->
submit_button.isEnabled = value
}
}
}
Что делает весь этот код? При изменении любого из полей ввода будет автоматически изменено значение одного из трех StateFlow. Это, в свою очередь, повлечет за собой запуск функции combine
, которая в итоге выпустит новое значение в поток isSubmitEnabled
. На это действие среагирует код внутри функции collectFlow(
. В итоге он изменит состояние кнопки.
|
|