"Gula" buatan rumah untuk proyek Android atau "Bagaimana tidak melakukannya"

Artikel ini adalah satu set gula batu kecil untuk proyek android, yang saya datangi pada waktunya dan itu berguna. Beberapa hal mungkin bukan solusi yang ideal, tetapi mereka mungkin berguna bagi Anda sama seperti mereka berguna pada satu waktu.

Aplikasi dan Roti Bakar


Hal pertama yang selalu berguna dan kadang-kadang dibutuhkan di setiap titik dalam program adalah tautan ke Aplikasi. Ini dipecahkan oleh kelas sederhana, tautan yang terdaftar di AndroidManifest.

class App : Application() { init { APP = this } companion object { lateinit var APP: App } } 

Berkat ini, selalu ada akses ke konteks seluruh aplikasi dan Anda bisa mendapatkan jalur / sumber daya dari mana saja. Dan setidaknya ini diperlukan untuk butiran gula berikut:

 fun Toast(messageId: Int) { Toast.makeText(App.APP, messageId, Toast.LENGTH_LONG).show() } fun Toast(message: String) { Toast.makeText(App.APP, message, Toast.LENGTH_LONG).show() } 

Sedikit, tetapi berkat Kotlin dan fakta bahwa kami memiliki akses ke konteksnya, sekarang Anda dapat menghubungi Toast dengan cepat dan singkat dari mana saja dalam aplikasi. Agar metode ini dapat diakses di mana saja, itu dapat ditandai dalam file tanpa menentukan kelas root.

Siapa yang ada di layar?


Program dengan satu Kegiatan semakin populer. Tetapi untuk arsitektur kami, diputuskan untuk menggunakan beberapa Activity. Setidaknya untuk memisahkan logika otorisasi dan bagian utama aplikasi. Seiring waktu, ada kebutuhan untuk memahami apakah layar terlihat dan bagian mana dari aplikasi itu. Dan di masa depan, itu juga diperlukan untuk mendapatkan string di aplikasi lokal. Tetapi hal pertama yang pertama. Agar nilai APP kami tidak bosan sendiri, perusahaan akan membuatnya:

 var screenActivity: AppCompatActivity? = null var onScreen: Boolean = false 

Dan kemudian, kami membuat kelas dasar kami, yang diwarisi dari AppCompatActivity:

 open class BaseActivity : AppCompatActivity() { override fun onStart() { super.onStart() App.onScreen = true } override fun onStop() { super.onStop() if (App.screenActivity == this) { App.onScreen = false if (isFinishing()) App.screenActivity = null } } override fun onDestroy() { super.onDestroy() if (App.screenActivity == this) App.screenActivity = null } override fun onCreate(savedInstanceState: Bundle?) { App.screenActivity = this } override fun onRestart() { super.onRestart() App.screenActivity = this } } 

Ya, untuk panduan baru, Anda dapat berlangganan Siklus Hidup dengan Aktivitas di tempat yang diperlukan. Tetapi kita memiliki apa yang kita miliki.

String terlokalisasi


Ada fungsional utilitas yang meragukan, tetapi TK adalah TK. Untuk fungsi seperti itu, saya akan mengaitkan pilihan bahasa dalam aplikasi dan bukan dengan sistem. Untuk waktu yang lama ada kode yang memungkinkan Anda mengganti bahasa pemrograman. Tapi kami dihadapkan dengan satu bug, yang mungkin hanya diulang dengan kami. Inti dari bug adalah bahwa jika Anda mengambil garis melalui konteks aplikasi, dan bukan melalui konteks Aktivitas, maka garis tersebut dikembalikan ke sistem lokal. Dan tidak selalu nyaman untuk membuang konteks suatu Kegiatan. Metode berikut sampai pada penyelamatan:

 fun getRes(): Resources = screenActivity?.resources ?: APP.resources fun getLocalizedString(stringId: Int): String = getRes().getString(stringId) fun getLocalizedString(stringId: Int, vararg formatArgs: Any?): String = getRes().getString(stringId, *formatArgs) 

Dan sekarang dari mana saja di aplikasi kita bisa mendapatkan string di lokasi yang benar.

SharedPreferences


Seperti halnya semua aplikasi, aplikasi kita harus menyimpan beberapa pengaturan dalam SharedPreferences. Dan untuk menyederhanakan hidup, sebuah kelas diciptakan yang menyembunyikan sedikit logika dalam dirinya sendiri. Untuk memulai, teman baru telah ditambahkan untuk variabel APP:

 lateinit var settings: SharedPreferences 

Ini diinisialisasi ketika aplikasi dimulai dan selalu tersedia untuk kita.

 class PreferenceString(val key: String, val def: String = "", val store: SharedPreferences = App.settings, val listener: ModifyListener? = null) { var value: String get() { listener?.customGet() return store.getString(key, def) ?: def } set(value) { store.edit().putString(key, value).apply() listener?.customSet() } } interface ModifyListener { fun customGet() {} fun customSet() {} } 

Tentu saja, Anda harus membuat kelas seperti itu untuk setiap jenis variabel, tetapi Anda dapat mengganti singleton dengan semua pengaturan yang diperlukan, misalnya:

 val PREF_LANGUAGE = PreferenceString("pref_language", "ru") 

Dan sekarang Anda selalu dapat merujuk ke pengaturan sebagai bidang, dan memuat / menyimpan dan komunikasi melalui pendengar akan disembunyikan.

Orientasi dan Tablet


Sudahkah Anda mendukung kedua orientasi? Dan bagaimana Anda menentukan orientasi Anda sekarang? Kami memiliki metode yang mudah untuk ini, yang sekali lagi dapat dipanggil dari mana saja dan tidak peduli dengan konteksnya:

 fun isLandscape(): Boolean { return getRes().configuration.orientation == Configuration.ORIENTATION_LANDSCAPE } 

Jika Anda memasukkan nilai / dimen.xml:

 <bool name="isTablet">false</bool> 

Dan dalam nilai-besar / dimen.xml:

 <bool name="isTablet">true</bool> 

Kemudian Anda juga dapat membuat metode:

 fun isTablet(): Boolean { return getRes().getBoolean(R.bool.isTablet) } 

Format Tanggal


Multithreading. Terkadang kata itu menakutkan. Suatu kali kami menangkap bug yang sangat aneh ketika kami membentuk garis tanggal di latar belakang. Ternyata SimpleDateFormat bukan utas yang aman. Maka lahirlah yang berikut ini:

 class ThreadSafeDateFormat(var pattern: String, val isUTC: Boolean = false, val locale: Locale = DEFAULT_LOCALE){ val dateFormatThreadLocal = object : ThreadLocal<SimpleDateFormat>(){ override fun initialValue(): SimpleDateFormat? { return SimpleDateFormat(pattern, locale) } } val formatter: SimpleDateFormat get() { val dateFormat = dateFormatThreadLocal.get() ?: SimpleDateFormat(pattern, locale) dateFormat.timeZone = if (isUTC) TimeZone.getTimeZone("UTC") else timeZone return dateFormat } } 

Dan contoh penggunaan (ya, ini lagi digunakan di dalam singleton):

 private val utcDateSendSafeFormat = ThreadSafeDateFormat("yyyy-MM-dd", true) val utcDateSendFormat: SimpleDateFormat get() = utcDateSendSafeFormat.formatter 

Untuk keseluruhan aplikasi, tidak ada yang berubah, dan masalah dengan utas telah diselesaikan.

Pengamat teks


Dan Anda tidak pernah terganggu bahwa jika Anda perlu menangkap teks apa yang dimasukkan dalam EditText, maka Anda perlu menggunakan TextWatcher dan menerapkan 3 Metode (!). Tidak kritis, tetapi tidak nyaman. Dan semuanya diputuskan oleh kelas:

 open class TextWatcherObject : TextWatcher{ override fun afterTextChanged(p0: Editable?) {} override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} } 

Keyboard


Apa yang selalu dibutuhkan. Anda perlu segera menampilkan keyboard, lalu di beberapa titik Anda perlu menyembunyikannya. Dan kemudian diperlukan dua metode berikut. Dalam kasus kedua, Anda harus melewati tampilan root.

 fun showKeyboard(view: EditText){ view.requestFocus(); (App.APP.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager?) ?.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) } fun hideKeyboardFrom(view: View) { (App.APP.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?) ?.hideSoftInputFromWindow(view.windowToken, 0) } 

Dan, mungkin seseorang akan berguna, fungsi untuk menyalin baris apa saja ke papan klip dengan roti panggang yang menunjukkan:

 fun String.toClipboard(toast: Int) { val clip = ClipData.newPlainText(this, this) (App.APP.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?)?.setPrimaryClip(clip) Toast(toast) } 

RecyclerView dan TableLayout


Dan di akhir artikel kecil ini, saya ingin membagikan apa yang baru-baru ini harus saya pecahkan. Mungkin seseorang akan berguna.

Data awal adalah sebagai berikut:

  1. 1k + data untuk ditampilkan.
  2. Setiap "sel" terdiri dari sekitar 10 bidang.
  3. perlu untuk menangkap usap, klik, klik dua kali, klik panjang.
  4. dan ... data diperbarui setiap 300 hingga 500 milidetik.

Jika Anda lupa tentang poin pertama. maka solusi yang paling berhasil adalah TableLayout. Mengapa tidak RecyclerView? Dan karena 3 dan 4 poin. Ada optimisasi di dalam lembar dan menggunakan kembali tampilan, tetapi tidak selalu. Dan pada saat membuat tampilan baru, penangan sentuh tidak ada. Dan oke, jika itu hanya mempengaruhi gesekan, tetapi dari waktu ke waktu masalah direproduksi dengan ketukan biasa. Bahkan memperbarui data secara langsung di Tampilan tidak membantu, dan tidak melalui pemberitahuan. Oleh karena itu, diputuskan untuk menggunakan TableLayout. Dan semuanya baik-baik saja, sampai data tidak lebih dari 100. Dan kemudian - selamat datang di dunia membeku.

Saya melihat 2 cara untuk memecahkan - atau mengajarkan TableLayout untuk menggunakan kembali sel dan melakukan sihir saat menggulir. Atau coba jadikan teman RecyclerView dan sering perbarui. Dan saya pergi ke jalan kedua. Karena sentuhan dan gesekan (terutama karena gesekan) diproses oleh kelas yang ditulis sendiri berdasarkan pada View.OnTouchListener, itu ternyata menjadi solusi yang efektif untuk memindahkan pemrosesan sentuhan ke tingkat RecyclerView dengan mengganti metode dispatchTouchEvent.

Algoritma ini sederhana:

  • menangkap sentuhan
  • tentukan anak mana yang disentuh terbang dengan findChildViewUnder (x, y)
  • dapatkan posisi elemen dari LayoutManager
  • apakah itu MotionEvent.ACTION_MOVE, maka kami memeriksa dengan posisi yang sama kami bekerja seperti sebelumnya atau tidak
  • melakukan logika tertanam untuk disentuh

Mungkin di masa depan masih akan ada masalah dari metode ini, tetapi saat ini semuanya berfungsi dan ini bagus.

Source: https://habr.com/ru/post/id472196/


All Articles