Некоторые фишки в Android разработке

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Сегодня первый большой снегопад в моем городе и у меня появилось хорошее настроение наскрябать небольшую статейку с некоторыми фишками из Android разработки.

Надеюсь, что моя статейка окажется полезной и ее не закидают плохими словами злые программисты.

Да именно злые, зло плодится, а вы не знали? Шучу конечно :)

Ну поехали :)

Получение цвета темы

Недавно я писал свою кастомную вьюху и мне нужно было установить цвет по умолчанию primaryColor и поэтому я создал небольшую Kotlin функцию, которая получает его из темы приложения:

// Kotlin расширение для получения цвета
fun Context.themeColor(@AttrRes attrRes: Int): Int {
	val typedValue = TypedValue()
  theme.resolveAttribute (attrRes, typedValue, true)
	return typedValue.data
}

// получаем primaryColor из темы прилы
context.themeColor(android.R.attr.colorPrimary)

Перевести dp в пиксели

Иногда нам нужно прописать оступ в коде и чтобы мы могли использовать независимые от плотности пиксели я довольно часто пишу следующее Kotlin расширения для своих кастомных вьюшках:

// Kotlin расширение, определенное внутри кастомной вьюшки
private fun Int.dp() = (context.resources.displayMetrics.density * this)
  .toInt()
                        
// устанавливаем padding в 10 dp
setPadding(10.dp(), 10.dp(), 10.dp(), 10.dp())                        

Создание круглого изображения

С этой проблемой я сталкивался ни один раз и поэтому решений довольно много, но я решил показать одно из самых неочевидных по моему мнению, создание кастомной вьюшки:

// отображает круглое изображение
class RoundedImageView @JvmOverloads constructor(
    ctx: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatImageView(ctx, attrs, defStyleAttr) {

  	// обратите внимание, здесь мы не приводим к Int, поэтому возвращается Float
    private fun Int.dp() = context.resources.displayMetrics.density * this

    override fun draw(canvas: Canvas) {

      	// создаем Path с полностью закругленным прямоугольником
        val path = Path().apply {
            val rectF = RectF(0f, 0f, width.toFloat(), height.toFloat())
            val radius = 100.dp()
            addRoundRect(rectF, radius, radius, Path.Direction.CW)
        }

        // юзаем clipPath для закругления
        canvas.clipPath(path)

        super.draw(canvas)
    }

}

Использование:

  <ru.freeit.personalapp.RoundedImageView
        android:id="@+id/avatar_img"
        android:layout_width="150dp"
        android:layout_height="150dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:src="@drawable/pony"
        android:scaleType="centerCrop" />

Анимация в кастомных вьюшках

В основном, я использую ValueAnimator:

 override fun onTouchEvent(event: MotionEvent): Boolean {
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
              	// запускаем анимацию, когда произошло прикосновение к экрану
                val animator = ValueAnimator.ofFloat(0f, width.toFloat())
                animator.addUpdateListener {
                  	// обновляем ширину и перерисовываем View
                    animWidth = it.animatedValue as Float
                    invalidate()
                }
                // задержка анимации
                animator.duration = 400L
                animator.doOnEnd {
                  	// возвращает к исходному состоянию
                    animWidth = 0f
                    invalidate()
                }
                animator.start()
                true
            }
            MotionEvent.ACTION_UP -> {
                listener.firstOrNull()?.invoke()
                true
            }
            else -> super.onTouchEvent(event)
        }
    }

    override fun dispatchDraw(canvas: Canvas) {
        canvas.drawRect(8f, 8f, width - 8f, height - 8f, borderPaint)
        // значение animWidth меняется с каждым кадром анимации
        canvas.drawRect(0f, 0f, animWidth, height.toFloat(), bgPaint)
        super.dispatchDraw(canvas)
    }

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

Небольшая оберточка для SharedPreferences

В одном из приложений для простоты использования я реализовал простенькую обертку вокруг SharedPreferences и до сих пор ее юзаю:

// сохранение и чтение Int значений
interface IntPrefs {
    fun saveInt(ket: String, value: Int)
    fun int(key: String, default: Int) : Int
}

// сохранение и чтение String значений
interface StringPrefs {
    fun saveStr(key: String, value: String)
    fun str(key: String, default: String) : String
}

// обертка вокруг SharedPreferences
class LocalPrefsDataSource(ctx: Context) : IntPrefs, StringPrefs {

    private val prefsName = "app_prefs"
    private val sharedPrefs = ctx.getSharedPreferences(prefsName, Context.MODE_PRIVATE)

    override fun saveInt(key: String, value: Int) {
        sharedPrefs.edit().putInt(key, value).apply()
    }

    override fun saveStr(key: String, value: String) {
        sharedPrefs.edit().putString(key, value).apply()
    }

    override fun str(key: String, default: String) : String {
        return sharedPrefs.getString(key, default) ?: default
    }

    override fun int(key: String, default: Int) : Int {
        return sharedPrefs.getInt(key, default)
    }

}

Добавление навигации по кнопки назад в WebView

Если вы юзали WebView то вы знаете, что по умолчанию по нажатию на кнопку назад мы просто выйдем из приложения (если конечно backstack состоит только из одной активити или одного фрагмента).

Для организации навигации в WebView есть простое решение:

class MainActivity : AppCompatActivity() {

    private lateinit var webView: WebView

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

        webView = findViewById(R.id.ali_express_web_view)
        val progressPageLoading = findViewById<CircularProgressIndicator>(R.id.progress_page_loading)
        webView.webViewClient = object: WebViewClient() {
            override fun onPageFinished(view: WebView?, url: String?) {
                super.onPageFinished(view, url)
                progressPageLoading.visibility = View.GONE
            }
        }
        webView.settings.javaScriptEnabled = true
      	// грузим наш любимый AliExpress :)
        webView.loadUrl("https://best.aliexpress.ru/")
    }

    // переопределяем onKeyDown для корректной навигации по сайтам в WebView
    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (event?.action == KeyEvent.ACTION_DOWN) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                if (webView.canGoBack()) {
                    webView.goBack()
                } else {
                    finish()
                }
                return true
            }
        }
        return super.onKeyDown(keyCode, event)
    }
}

Создание кастомного фрагмента для Google Maps

В документации по Google Maps рекомендуется использовать предопределенный фрагмент SupportMapFragment.

Но бывают ситуации, когда нам нужно кастомизировать View фрагмента, добавить поверх карты какие-либо элементы и поэтому нужно реализовать свой GoogleMapFragment:

class GoogleMapFragment : Fragment() {

  	private var mapView : MapView? = null
    private var googleMap: GoogleMap? = null

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val binding = GoogleMapFragmentBinding.inflate(inflater, container, false)
        this.mapView = binding.mapView

      	// MapView имеет методы жизненного цикла которые нужно вызывать
        binding.mapView.onCreate(savedInstanceState)

        binding.mapView.getMapAsync { googleMap ->
          	// можем юзать Google Maps API
            this.googleMap = googleMap
        }

        return binding.root
    }
    
    override fun onStart() {
        super.onStart()
        mapView?.onStart()
    }

    override fun onResume() {
        super.onResume()
        mapView?.onResume()
    }

    override fun onPause() {
        super.onPause()
        mapView?.onPause()
    }

    override fun onStop() {
        super.onStop()
        mapView?.onStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        mapView?.onDestroy()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        mapView?.onSaveInstanceState(outState)
    }

    override fun onLowMemory() {
        super.onLowMemory()
        mapView?.onLowMemory()
    }

}

Разметка нашего фрагмента (google_map_fragment.layout):

<FrameLayout
    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" >

    <com.google.android.gms.maps.MapView
        android:id="@+id/map_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <!-- мы можем добавить что-нибудь поверх карты  -->

</FrameLayout>

Заключение

Ну и напоследок я хотел бы поделиться некоторыми моими репозиторчиками:

  • Kotlin-Algorithms-and-Design-Patterns  - я почти каждый день добавляю новые алгоритмы, структуры данных и паттерны проектирования.

  • LearningApps - репозиторий с мини приложениями, в каждом из которых проработана одна или несколько тем по Android разработке.

Как сказал, один из индийских разработчиков, изучайте и делитесь знаниями!!!

Источник: https://habr.com/ru/post/586888/


Интересные статьи

Интересные статьи

Бывают ситуации, когда у заказчиков есть свои экосистемы и несколько приложений, и они решают упаковать их в SDK и встроить в другие, расширив таким образом функционал. SDK — не единичный Fragment/Act...
1) Устройство: Mедицинский ems Читать далее
Перевод статьи подготовлен в преддверии старта курса «Java QA Engineer». Разработка программного обеспечения — процесс сложный. Еще на заре разработки программного обеспечени...
У некоторых бизнес-тренеров в области е-коммерса и консультантов по увеличению интернет-продаж на многие вопросы часто можно слышать универсальную отмазку — «надо тестировать» или другую (чтобы не...
Некоторое время назад на Хабре вышла статья замечательной девушки fur_habr о проблемах безопасности, приватности и конфиденциальности мобильных коммуникаций и о путях решения этих проблем на ...