Передача фото в редактор зависит от типа редактора:
1. Внешний редактор (например, Google Photos, Snapseed).
2. Встроенный редактор внутри приложения.
Если редактор — другое приложение, используем
Intent.ACTION_EDIT. Как передать фото в редактор?
fun openPhotoEditor(context: Context, uri: Uri) {
val intent = Intent(Intent.ACTION_EDIT).apply {
setDataAndType(uri, "image/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // Разрешение на чтение
}
context.startActivity(Intent.createChooser(intent, "Выберите редактор"))
}Если редактор — внутри приложения, можно передавать фото через
Intent с Uri. Отправка фото в редактор
fun openEditor(context: Context, photoFile: File) {
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", photoFile)
val intent = Intent(context, PhotoEditorActivity::class.java).apply {
putExtra("PHOTO_URI", uri.toString())
}
context.startActivity(intent)
}Получение фото в редакторе (
PhotoEditorActivity)class PhotoEditorActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uriString = intent.getStringExtra("PHOTO_URI")
val uri = uriString?.let { Uri.parse(it) }
uri?.let {
imageView.setImageURI(it) // Показываем фото
}
}
}Если фото уже в памяти (
byte[]), можно передать его как Parcelable. Отправка
val bitmap = ... // Получили Bitmap
val byteArray = ByteArrayOutputStream().apply {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, this)
}.toByteArray()
val intent = Intent(context, PhotoEditorActivity::class.java).apply {
putExtra("PHOTO_BYTES", byteArray)
}
context.startActivity(intent)
Получение
val byteArray = intent.getByteArrayExtra("PHOTO_BYTES")
byteArray?.let {
val bitmap = BitmapFactory.decodeByteArray(it, 0, it.size)
imageView.setImageBitmap(bitmap)
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
В data class можно использовать только свойства, объявленные в первичном конструкторе (val или var). Эти свойства автоматически участвуют в:
- equals, hashCode;
- toString;
- copy и componentN.
Остальные свойства не считаются частью "данных" класса.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Чтобы начать рисовать пользовательский интерфейс (UI) на экране в Android-проекте, необходимо выполнить несколько шагов, которые включают настройку проекта, создание макета и взаимодействие с основными компонентами Android.
Первый шаг — создание Android-проекта в Android Studio. Это можно сделать, выбрав шаблон «Empty Activity», который предоставляет минимальный набор для разработки приложения.
Укажите имя проекта.
Выберите язык программирования (Java или Kotlin).
Убедитесь, что минимальная версия SDK подходит для вашей целевой аудитории.
Android UI в основном создаётся с использованием XML-файлов, которые определяют структуру интерфейса.
По умолчанию файл макета находится в каталоге:
res/layout/activity_main.xml
#### Пример простого макета:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Привет, мир!"
android:textSize="24sp"
android:padding="16dp"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Нажми меня"/>
</LinearLayout>
Макет нужно связать с логикой приложения в Java или Kotlin. Это делается с помощью метода
setContentView() в Activity.class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Указываем, какой XML-файл использовать для интерфейса
setContentView(R.layout.activity_main)
// Найдём элементы интерфейса и добавим логику
val textView = findViewById<TextView>(R.id.textView)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
textView.text = "Кнопка нажата!"
}
}
}Если нужно нарисовать что-то вручную (например, графику, линии, или кастомные фигуры), можно создать свой собственный класс, унаследованный от
View, и переопределить метод onDraw().class CustomView(context: Context) : View(context) {
private val paint = Paint().apply {
color = Color.RED
strokeWidth = 10f
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// Рисуем линию
canvas?.drawLine(100f, 100f, 400f, 400f, paint)
}
}Чтобы использовать этот класс, можно добавить его в макет или программно
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Устанавливаем кастомный View вместо макета
setContentView(CustomView(this))
}
}Теперь можно запустить приложение на эмуляторе или реальном устройстве:
Нажмите Run в Android Studio.
Убедитесь, что устройство подключено или выбран эмулятор.
После запуска вы увидите созданный интерфейс.
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
– src/ — исходный код,
– res/ — ресурсы (строки, изображения, макеты),
– manifest — описание компонентов, разрешений, запуска,
– build.gradle — настройки сборки, зависимости,
– libs/ — внешние библиотеки,
– assets/ — файлы, доступные в рантайме.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4
Data Class и Sealed Class решают разные задачи и обеспечивают улучшения в организации кода, управлении состоянием и безопасности типов. Они вносят значительные упрощения и повышают читаемость кода в Kotlin-проектах.
Data Class предназначены для хранения данных и автоматически предоставляют ряд полезных методов, что упрощает разработку и уменьшает объем шаблонного кода. Основные причины использования Data Class:
Автоматически генерирует методы
equals(), hashCode(), и toString(), а также copy() и компонентные функции для объектов данных. Это избавляет от необходимости ручной реализации этих методов, что уменьшает количество кода и возможность ошибок.Идеально подходят для передачи данных между различными частями приложения, например, между слоями в архитектуре MVVM или при передаче данных между активностями и фрагментами.
С помощью них легко создавать неизменяемые объекты, что способствует безопасной работе с данными, особенно в многопоточной среде.
Sealed Class используются для определения закрытых иерархий классов, где все потомки известны и ограничены. Они полезны по следующим причинам:
Гарантируют, что все возможные подтипы обработаны в выражениях
when, что предотвращает ошибки во время выполнения из-за пропущенных случаев. Это упрощает управление состояниями и делает код более безопасным и предсказуемым.Ограничивают возможность создания подклассов за пределами файла, в котором они объявлены. Это предотвращает неожиданное наследование и сохраняет иерархию классов контролируемой и понятной.
Поскольку все расширения таких классов должны быть объявлены в том же файле, это способствует лучшей инкапсуляции и организации кода.
data class User(val name: String, val age: Int)
Пример Sealed Class
sealed class Result {
data class Success(val data: String) : Result()
data class Failure(val error: Throwable) : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("Success with data: ${result.data}")
is Result.Failure -> println("Failure with error: ${result.error.message}")
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
В RxJava управление потоками осуществляется через subscribeOn и observeOn. Первый определяет поток, в котором будет происходить генерация данных (источник), второй — поток, в котором обрабатываются все дальнейшие операторы и подписка.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2🔥1
В Android при нажатии пользователя на экран вызывается событие ACTION_DOWN.
Android использует систему обработки касаний через MotionEvent. Когда пользователь касается экрана, система генерирует разные типы событий:
ACTION_DOWN – вызывается в момент первого касания.
ACTION_MOVE – вызывается, когда пользователь двигает палец по экрану.
ACTION_UP – вызывается, когда пользователь убирает палец с экрана.
ACTION_CANCEL – вызывается, если система прерывает касание (например, из-за входящего звонка).
Для обработки событий касания нужно переопределить метод
onTouchEvent() в View или использовать setOnTouchListener(). override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d("TouchEvent", "Палец коснулся экрана")
return true
}
MotionEvent.ACTION_UP -> {
Log.d("TouchEvent", "Палец отпущен")
}
}
return super.onTouchEvent(event)
}Можно также назначить слушатель
view.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
Log.d("TouchEvent", "Нажатие зафиксировано")
true
} else {
false
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥4👍1
Чтобы обрабатывать жесты в Android, используйте класс
GestureDetector. Он помогает отслеживать стандартные жесты: одиночные нажатия, свайпы, долгие нажатия, двойные касания и т.д.Создайте экземпляр
GestureDetector, передав Context и слушателя (GestureDetector.OnGestureListener).Передавайте события касания в
gestureDetector.onTouchEvent(event) из метода onTouchEvent().class GestureActivity : AppCompatActivity(), GestureDetector.OnGestureListener {
private lateinit var gestureDetector: GestureDetector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gestureDetector = GestureDetector(this, this)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
}
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
val deltaX = e2!!.x - e1!!.x
if (deltaX > 0) Log.d("Gesture", "Swipe Right") else Log.d("Gesture", "Swipe Left")
return true
}
override fun onDown(e: MotionEvent?) = true
override fun onShowPress(e: MotionEvent?) {}
override fun onSingleTapUp(e: MotionEvent?) = false
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float) = false
override fun onLongPress(e: MotionEvent?) {}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
1. Используйте метод startActivityForResult:
- Передайте Intent, а затем обработайте результат в методе onActivityResult.
2. В современных API используйте ActivityResultLauncher для управления результатами.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2
RxJava может вызывать утечки памяти, если подписки (
Disposable) не очищаются правильно. Утечки памяти происходят, когда RxJava-события продолжают выполняться после уничтожения
Activity/Fragment, удерживая ссылки на View или Context. CompositeDisposable собирает все подписки и очищает их при onDestroy(). class MainActivity : AppCompatActivity() {
private val disposables = CompositeDisposable() // Собираем подписки
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val disposable = Observable.interval(1, TimeUnit.SECONDS)
.subscribe { println("Tick: $it") }
disposables.add(disposable) // Добавляем подписку
}
override fun onDestroy() {
super.onDestroy()
disposables.clear() // ✅ Очищаем подписки, предотвращая утечку памяти
}
}Если используем Jetpack ViewModel или
LifecycleOwner, можно автоматически отвязывать подписки. class MyFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Observable.interval(1, TimeUnit.SECONDS)
.autoDispose(viewLifecycleOwner) // ✅ Автоочистка при `onDestroyView()`
.subscribe { println("Tick: $it") }
}
}Если подписка не в
CompositeDisposable, её можно очистить вручную. private var disposable: Disposable? = null
fun startObserving() {
disposable = Observable.interval(1, TimeUnit.SECONDS)
.subscribe { println("Tick: $it") }
}
fun stopObserving() {
disposable?.dispose() // ✅ Очищаем подписку
}
RxJava может удерживать
Activity/Fragment, вызывая утечки памяти. Лучше использовать
ViewModel + LiveData! class MyViewModel : ViewModel() {
private val _timer = MutableLiveData<Long>()
val timer: LiveData<Long> = _timer
init {
Observable.interval(1, TimeUnit.SECONDS)
.subscribe { _timer.postValue(it) }
}
}Подписка в
Activity (без утечек!) viewModel.timer.observe(this) { println("Tick: $it") }Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Да. Можно использовать:
- sendBroadcast(Intent) — для обычной широковещательной рассылки.
- sendOrderedBroadcast() — для последовательной доставки.
- LocalBroadcastManager (устарел, но можно использовать аналоги).
Это позволяет передавать события между компонентами приложения или уведомлять другие приложения (если экспортировано).
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
Чтобы
Retrofit мог возвращать Observable, Single, Maybe или Flowable из RxJava, нужно добавить RxJava Adapter. В
build.gradle.kts (Kotlin DSL)dependencies {
implementation("com.squareup.retrofit2:adapter-rxjava3:2.9.0") // Адаптер для RxJava 3
implementation("io.reactivex.rxjava3:rxjava:3.1.8") // RxJava 3
}Добавляем адаптер в
Retrofit.Builderval retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create()) // Преобразование JSON
.addCallAdapterFactory(RxJava3CallAdapterFactory.create()) // Поддержка RxJava
.build()
Теперь можно возвращать RxJava-объекты вместо
Call<>. interface ApiService {
@GET("users/{id}")
fun getUser(@Path("id") userId: Int): Single<User>
}Пример с
Observable<> (несколько данных или обновления) interface ApiService {
@GET("users")
fun getUsers(): Observable<List<User>>
}Пример с
Flowable<> (если нужен Backpressure) interface ApiService {
@GET("posts")
fun getPosts(): Flowable<List<Post>>
}Пример подписки в
ViewModel (RxJava 3 + LiveData) class UserViewModel(private val apiService: ApiService) : ViewModel() {
private val _userLiveData = MutableLiveData<User>()
val userLiveData: LiveData<User> = _userLiveData
fun fetchUser(userId: Int) {
apiService.getUser(userId)
.subscribeOn(Schedulers.io()) // Запрос в фоновом потоке
.observeOn(AndroidSchedulers.mainThread()) // Обновление UI в главном потоке
.subscribe({ user ->
_userLiveData.value = user
}, { error ->
Log.e("UserViewModel", "Ошибка загрузки", error)
})
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3💊2
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Чтобы добавить кастомные атрибуты в
Custom View, нужно: Создать
attrs.xml и описать атрибуты. Добавить их в
styleable и получить в Custom View. Использовать атрибуты в
XML или Kotlin. Создаём файл
res/values/attrs.xml, если его нет: <resources>
<declare-styleable name="CustomButton">
<attr name="customText" format="string"/>
<attr name="customTextSize" format="dimension"/>
<attr name="customTextColor" format="color"/>
</declare-styleable>
</resources>
Теперь создаём
CustomButton.ktclass CustomButton @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatButton(context, attrs, defStyleAttr) {
init {
context.theme.obtainStyledAttributes(attrs, R.styleable.CustomButton, 0, 0).apply {
try {
val text = getString(R.styleable.CustomButton_customText) ?: "Default"
val textSize = getDimension(R.styleable.CustomButton_customTextSize, 16f)
val textColor = getColor(R.styleable.CustomButton_customTextColor, Color.BLACK)
setText(text)
setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
setTextColor(textColor)
} finally {
recycle() // Освобождаем ресурсы
}
}
}
}
Теперь можно использовать
CustomButton в activity_main.xml <com.example.customviews.CustomButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:customText="Нажми меня"
app:customTextSize="20sp"
app:customTextColor="@android:color/holo_red_dark"/>
Можно обновлять свойства прямо в Kotlin
val button = findViewById<CustomButton>(R.id.customButton)
button.text = "Новый текст"
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24f)
button.setTextColor(Color.BLUE)
Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Привет, ребят, мне нужно несколько человек, которые помогут разметить тегами вопросы с собеседований.
Работа срочная, есть дедлайн.
Заплачу примерно 3000-5000 руб.
Работа на 1-2 дня.
Если интересно напишите мне @kivaiko сообщение:
Работа срочная, есть дедлайн.
Заплачу примерно 3000-5000 руб.
Работа на 1-2 дня.
Если интересно напишите мне @kivaiko сообщение:
Привет, я по поводу разметки тегами вопросов для собеседований Android разработчика
Факап — это ситуация, когда задача провалилась из-за недосмотра, неправильных ожиданий или отсутствия проверки. Часто бывает при ошибочной миграции, неправильной работе с API или при недокументированном поведении Android.
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊11🔥2🤔2
Если нужно собирать данные из нескольких RSS-источников, можно реализовать это так:
Загрузить данные из нескольких RSS-лент (через
Retrofit, Jsoup, XmlPullParser). Парсить XML и извлекать статьи.
Объединять данные в общий список (
List<Article>). Сортировать по дате.
Сохранять в БД (Room) для офлайн-доступа.
Показывать в
RecyclerView или LazyColumn (Compose). В
build.gradle.ktsdependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0") // HTTP-клиент
implementation("com.squareup.retrofit2:converter-simplexml:2.9.0") // XML-парсер
implementation("org.jsoup:jsoup:1.16.1") // Альтернативный XML-парсер
implementation("androidx.room:room-runtime:2.6.1") // Room для кеша
kapt("androidx.room:room-compiler:2.6.1")
}RSS-лента содержит заголовок, ссылку, дату и описание
@Root(name = "item", strict = false)
data class RssItem(
@field:Element(name = "title") var title: String = "",
@field:Element(name = "link") var link: String = "",
@field:Element(name = "pubDate", required = false) var pubDate: String = "",
@field:Element(name = "description", required = false) var description: String = ""
)
Создаём API для загрузки RSS (Retrofit + XML)
interface RssService {
@GET("rss.xml")
fun getFeed(): Call<RssFeed>
}
@Root(name = "rss", strict = false)
data class RssFeed(
@field:Element(name = "channel") var channel: RssChannel = RssChannel()
)
@Root(name = "channel", strict = false)
data class RssChannel(
@field:ElementList(entry = "item", inline = true) var items: List<RssItem> = listOf()
)Создадим
Repository, который загружает и объединяет данные: class RssRepository(private val services: List<RssService>) {
fun fetchAllFeeds(): List<RssItem> {
val allItems = mutableListOf<RssItem>()
services.forEach { service ->
val response = service.getFeed().execute()
if (response.isSuccessful) {
response.body()?.channel?.items?.let { allItems.addAll(it) }
}
}
return allItems.sortedByDescending { parseDate(it.pubDate) }
}
private fun parseDate(dateString: String): Long {
return SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH)
.parse(dateString)?.time ?: 0
}
}Создаём Entity для БД
@Entity
data class ArticleEntity(
@PrimaryKey val link: String,
val title: String,
val pubDate: Long,
val description: String
)
Создаём DAO
@Dao
interface RssDao {
@Query("SELECT * FROM ArticleEntity ORDER BY pubDate DESC")
fun getArticles(): LiveData<List<ArticleEntity>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertArticles(articles: List<ArticleEntity>)
}
Сохраняем в БД после загрузки
class RssRepository(private val dao: RssDao, private val services: List<RssService>) {
fun fetchAndCacheFeeds() {
val articles = fetchAllFeeds().map {
ArticleEntity(it.link, it.title, parseDate(it.pubDate), it.description)
}
dao.insertArticles(articles)
}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊2👍1
Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
Методы
get и replace в Kotlin относятся к работе с коллекциями, карты (Map) или к строкам. В зависимости от контекста, они вызываются через разные классы. Давайте разберем каждый случай отдельно.В контексте работы с картами (
Map), методы get и replace относятся к получению значений по ключу и замене существующих значений.Метод
getМетод
get используется для извлечения значения из карты по указанному ключу.val map = mapOf("key1" to "value1", "key2" to "value2")
println(map.get("key1")) // value1
println(map["key2"]) // value2 (альтернатива `get`)Метод
replace используется для обновления значения, связанного с определённым ключом, если он существует. Этот метод доступен только для изменяемых карт (MutableMap).val mutableMap = mutableMapOf("key1" to "value1", "key2" to "value2")
mutableMap.replace("key1", "newValue1")
println(mutableMap) // {key1=newValue1, key2=value2}В контексте строк, методы
get и replace работают с символами и подстроками.Метод
get используется для доступа к символу строки по индексу. Это альтернатива квадратным скобкам.val text = "Kotlin"
println(text.get(0)) // K
println(text[1]) // o (альтернатива `get`)
Метод
replace заменяет символы или подстроки в строке на заданные.val text = "Kotlin is awesome"
val newText = text.replace("awesome", "powerful")
println(newText) // Kotlin is powerful
Если вы пишете свои классы, вы можете переопределить оператор
get и метод replace, чтобы использовать их для своих нужд.class CustomList<T>(private val items: List<T>) {
operator fun get(index: Int): T {
return items[index]
}
}
fun main() {
val customList = CustomList(listOf(1, 2, 3))
println(customList[0]) // 1
}Пример с
replaceclass CustomMap<K, V>(private val map: MutableMap<K, V>) {
fun replace(key: K, value: V) {
if (map.containsKey(key)) {
map[key] = value
}
}
}
fun main() {
val customMap = CustomMap(mutableMapOf("key1" to "value1"))
customMap.replace("key1", "newValue1")
println(customMap) // {key1=newValue1}
}Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1