Selbst gemachter "Zucker" für ein Android-Projekt oder "Wie man es nicht macht"

Dieser Artikel ist ein Satz kleiner Würfelzucker für ein Android-Projekt, zu dem ich zu gegebener Zeit gekommen bin und das sich als nützlich erwiesen hat. Einige Dinge sind möglicherweise keine idealen Lösungen, aber sie können für Sie genauso nützlich sein, wie sie mir einmal nützlich waren.

Anwendung und Toast


Das erste, was immer nützlich sein kann und manchmal zu jedem Zeitpunkt im Programm benötigt wird, ist ein Link zur Anwendung. Dies wird durch eine einfache Klasse gelöst, deren Link in AndroidManifest registriert ist.

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

Dank dessen haben Sie immer Zugriff auf den Kontext der gesamten Anwendung und können von überall aus Leitungen / Ressourcen abrufen. Und das ist zumindest für folgendes Zuckerkorn notwendig:

 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() } 

Eine Kleinigkeit, aber dank Kotlin und der Tatsache, dass wir Zugriff auf den Kontext haben, können Sie Toast jetzt von überall in der Anwendung kurz und präzise aufrufen. Damit die Methode überall verfügbar ist, kann sie in einer Datei markiert werden, ohne die Stammklasse anzugeben.

Wer ist auf dem Bildschirm?


Programme mit einer Aktivität werden immer beliebter. Für unsere Architektur wurde jedoch beschlossen, mehrere Aktivitäten zu verwenden. Zumindest um die Berechtigungslogik und den Hauptteil der Anwendung zu trennen. Im Laufe der Zeit musste verstanden werden, ob der Bildschirm sichtbar ist und um welchen Teil der Anwendung es sich handelt. In Zukunft war es auch erforderlich, Zeichenfolgen im Anwendungsgebietsschema abzurufen. Aber das Wichtigste zuerst. Damit sich unser APP-Wert nicht alleine langweilt, wird das Unternehmen ihn dazu bringen:

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

Und dann erstellen wir unsere Basisklasse, die von AppCompatActivity geerbt wurde:

 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 } } 

Ja, für neue Anleitungen können Sie Lifecycle with Activity an den erforderlichen Stellen abonnieren. Aber wir haben was wir haben.

Lokalisierte Zeichenfolgen


Es gibt Funktionen von zweifelhaftem Nutzen, aber TK ist TK. Dieser Funktionalität würde ich die Wahl der Sprache in der Anwendung anstelle der Systemsprache zuschreiben. Lange Zeit gibt es einen Code, mit dem Sie die Sprache programmgesteuert ersetzen können. Aber wir waren mit einem Fehler konfrontiert, der sich wahrscheinlich nur bei uns wiederholt. Der Kern des Fehlers besteht darin, dass, wenn Sie die Zeile durch den Anwendungskontext und nicht durch den Aktivitätskontext führen, die Zeile an die Systemgebietsschemata zurückgegeben wird. Und es ist nicht immer bequem, den Kontext einer Aktivität zu werfen. Die folgenden Methoden kamen zur Rettung:

 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) 

Und jetzt können wir von überall in der Anwendung die Zeichenfolge im richtigen Gebietsschema abrufen.

SharedPreferences


Wie bei allen Anwendungen müssen unsere einige Einstellungen in SharedPreferences speichern. Und um das Leben zu vereinfachen, wurde eine Klasse erfunden, die ein bisschen Logik in sich verbirgt. Zu Beginn wurde ein neuer Freund für die APP-Variable hinzugefügt:

 lateinit var settings: SharedPreferences 

Es wird beim Start der Anwendung initialisiert und steht uns immer zur Verfügung.

 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() {} } 

Natürlich müssen Sie für jeden Variablentyp eine solche Klasse generieren, aber Sie können Singleton durch alle erforderlichen Einstellungen ersetzen, zum Beispiel:

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

Und jetzt können Sie die Einstellungen immer als Feld bezeichnen, und das Laden / Speichern und die Kommunikation über den Listener werden ausgeblendet.

Orientierung und Tablette


Mussten Sie beide Orientierungen unterstützen? Und wie haben Sie festgestellt, in welcher Ausrichtung Sie sich jetzt befinden? Wir haben dafür eine bequeme Methode, die wiederum von überall aufgerufen werden kann und sich nicht um den Kontext kümmert:

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

Wenn Sie values ​​/ dimen.xml eingeben:

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

Und in values-large / dimen.xml:

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

Dann können Sie auch eine Methode erstellen:

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

Datumsformat


Multithreading. Manchmal ist es ein gruseliges Wort. Einmal haben wir einen sehr seltsamen Fehler entdeckt, als wir im Hintergrund Datumszeilen gebildet haben. Es stellte sich heraus, dass SimpleDateFormat nicht threadsicher ist. Daher wurde folgendes geboren:

 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 } } 

Und ein Anwendungsbeispiel (ja, dies wird wieder im Singleton verwendet):

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

Für die gesamte Anwendung hat sich nichts geändert, und das Problem mit den Threads wurde behoben.

Textwatcher


Und Sie haben sich nie darum gekümmert, dass Sie TextWatcher verwenden und 3 (!) Methoden implementieren müssen, wenn Sie den in EditText eingegebenen Text erfassen möchten. Nicht kritisch, aber nicht bequem. Und alles wird von der Klasse entschieden:

 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) {} } 

Tastatur


Was immer gebraucht wird. Sie müssen die Tastatur sofort anzeigen und dann irgendwann ausblenden. Und dann werden die folgenden zwei Methoden benötigt. Im zweiten Fall müssen Sie die Stammansicht übergeben.

 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) } 

Und vielleicht ist jemand nützlich, eine Funktion zum Kopieren einer beliebigen Zeile in eine Zwischenablage mit Toast:

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

RecyclerView und TableLayout


Und am Ende dieses kleinen Artikels möchte ich mitteilen, was ich kürzlich lösen musste. Vielleicht wird jemand nützlich sein.

Die Anfangsdaten sind wie folgt:

  1. 1k + anzuzeigende Daten.
  2. Jede "Zelle" besteht aus ungefähr 10 Feldern.
  3. Es ist notwendig, Swipe, Click, DoubleClick, LongClick zu fangen.
  4. und ... Die Daten werden alle 300 bis 500 Millisekunden aktualisiert.

Wenn Sie den ersten Punkt vergessen. Dann ist TableLayout die am besten funktionierende Lösung. Warum nicht RecyclerView? Und wegen 3 und 4 Punkten. Das Blatt enthält Optimierungen, die die Ansicht wiederverwenden, jedoch nicht immer. Zum Zeitpunkt der Erstellung einer neuen Ansicht gibt es keine Touch-Handler. Und okay, wenn es nur den Wisch betrifft, aber von Zeit zu Zeit wird das Problem mit dem üblichen Tippen reproduziert. Selbst das Aktualisieren der Daten direkt in der Ansicht hilft nicht und nicht durch Benachrichtigen. Daher wurde beschlossen, TableLayout zu verwenden. Und alles war in Ordnung, bis die Daten nicht mehr als 100 waren. Und dann - willkommen in der Welt der Einfrierungen.

Ich habe zwei Möglichkeiten gesehen, um zu lösen - oder TableLayout beizubringen, Zellen wiederzuverwenden und beim Scrollen zu zaubern. Oder versuchen Sie, Freunde RecyclerView und häufige Updates zu finden. Und ich ging den zweiten Weg. Da Berührungen und Wischvorgänge (hauptsächlich aufgrund von Wischbewegungen) von einer selbstgeschriebenen Klasse basierend auf View.OnTouchListener verarbeitet wurden, erwies es sich als effektive Lösung, die Berührungsverarbeitung durch Überschreiben der dispatchTouchEvent-Methode auf die RecyclerView-Ebene zu verschieben.

Der Algorithmus ist einfach:

  • Berührung
  • Bestimmen Sie mit findChildViewUnder (x, y), welches Kind die Berührung fliegt.
  • Rufen Sie die Position des Elements im LayoutManager ab
  • Wenn es sich um MotionEvent.ACTION_MOVE handelt, überprüfen wir mit derselben Position, an der wir arbeiten wie zuvor oder nicht
  • Führen Sie die eingebettete Logik zum Berühren aus

Vielleicht wird es in Zukunft noch Probleme mit dieser Methode geben, aber im Moment funktioniert alles und das ist gut so.

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


All Articles