Du «sucre» fait maison pour un projet Android ou «Comment ne pas le faire»

Cet article est un ensemble de petits morceaux de sucre pour un projet Android, auquel je suis arrivé en temps voulu et qui m'a été utile. Certaines choses peuvent ne pas être des solutions idéales, mais elles peuvent vous être utiles de la même manière qu'elles m'ont été utiles à un moment donné.

Application et toast


La première chose qui peut toujours être utile et qui est parfois nécessaire à tout moment du programme est un lien vers Application. Ceci est résolu par une classe simple, dont le lien est enregistré dans AndroidManifest.

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

Grâce à cela, il y a toujours accès au contexte de l'application entière et vous pouvez obtenir des lignes / ressources de n'importe où. Et au moins cela est nécessaire pour le grain de sucre suivant:

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

Une bagatelle, mais grâce à Kotlin et au fait que nous avons accès au contexte, vous pouvez maintenant appeler Toast rapidement et de manière concise depuis n'importe où dans l'application. Pour que la méthode soit accessible partout, elle peut être marquée dans un fichier sans spécifier la classe racine.

Qui est à l'écran?


Les programmes avec une seule activité gagnent en popularité. Mais pour notre architecture, il a été décidé d'utiliser plusieurs Activity. Au moins pour séparer la logique d'autorisation et la partie principale de l'application. Au fil du temps, il a fallu comprendre si l'écran était visible et quelle partie de l'application il s'agissait. Et à l'avenir, il était également nécessaire d'obtenir des chaînes dans les paramètres régionaux de l'application. Mais tout d'abord. Pour que notre valeur APP ne s'ennuie pas seule, l'entreprise lui fera:

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

Et puis, nous créons notre classe de base, héritée d'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 } } 

Oui, pour les nouveaux guides, vous pouvez vous abonner à Lifecycle with Activity aux endroits nécessaires. Mais nous avons ce que nous avons.

Chaînes localisées


Il existe des fonctions fonctionnelles douteuses, mais TK est TK. À une telle fonctionnalité, j'attribuerais le choix de la langue dans l'application au lieu de celle du système. Pendant longtemps, il existe un code qui vous permet de remplacer par programme la langue. Mais nous avons été confrontés à un bug, qui ne se répète probablement qu'avec nous. L'essence du bogue est que si vous prenez la ligne dans le contexte de l'application et non dans le contexte de l'activité, la ligne est renvoyée aux paramètres régionaux du système. Et il n'est pas toujours pratique de jeter le contexte d'une activité. Les méthodes suivantes sont venues à la rescousse:

 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) 

Et maintenant, de n'importe où dans l'application, nous pouvons obtenir la chaîne dans les paramètres régionaux corrects.

SharedPreferences


Comme pour toutes les applications, la nôtre doit stocker certains paramètres dans SharedPreferences. Et pour simplifier la vie, une classe a été inventée qui cache un peu de logique en soi. Pour commencer, un nouvel ami a été ajouté pour la variable APP:

 lateinit var settings: SharedPreferences 

Il est initialisé au démarrage de l'application et est toujours à notre disposition.

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

Bien sûr, vous devrez générer une telle classe pour chaque type de variable, mais vous pouvez remplacer singleton par tous les paramètres nécessaires, par exemple:

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

Et maintenant, vous pouvez toujours vous référer aux paramètres en tant que champ, et le chargement / enregistrement et la communication via l'auditeur seront masqués.

Orientation et tablette


Avez-vous dû soutenir les deux orientations? Et comment avez-vous déterminé dans quelle orientation vous vous trouvez maintenant? Nous avons une méthode pratique pour cela, qui peut encore être appelée de n'importe où et sans se soucier du contexte:

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

Si vous mettez des valeurs / dimen.xml:

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

Et dans values-large / dimen.xml:

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

Ensuite, vous pouvez également créer une méthode:

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

Format de date


Multithreading. Parfois, c'est un mot effrayant. Une fois, nous avons attrapé un bug très étrange lorsque nous avons formé des lignes de dates en arrière-plan. Il s'est avéré que SimpleDateFormat n'est pas sûr pour les threads. Par conséquent, est né:

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

Et un exemple d'utilisation (oui, c'est encore utilisé à l'intérieur du singleton):

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

Pour toute l'application, rien n'a changé et le problème avec les threads a été résolu.

Textwatcher


Et vous n'avez jamais dérangé que si vous avez besoin d'attraper le texte entré dans EditText, vous devez utiliser TextWatcher et implémenter 3 (!) Méthodes. Pas critique, mais pas pratique. Et tout est décidé par la classe:

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

Clavier


Ce qui est toujours nécessaire. Vous devez immédiatement montrer le clavier, puis à un moment donné, vous devez le cacher. Et puis les deux méthodes suivantes sont nécessaires. Dans le second cas, vous devez passer la vue racine.

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

Et, peut-être que quelqu'un viendra à portée de main, une fonction pour copier n'importe quelle ligne dans un presse-papiers avec un toast montrant:

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

RecyclerView et TableLayout


Et à la fin de ce petit article, je veux partager ce que j'ai récemment dû résoudre. Peut-être que quelqu'un vous sera utile.

Les données initiales sont les suivantes:

  1. 1k + données à afficher.
  2. Chaque «cellule» comprend environ 10 champs.
  3. il est nécessaire d'attraper glisser, cliquer, doubleClick, longClick.
  4. et ... les données sont mises à jour toutes les 300 à 500 millisecondes.

Si vous oubliez le premier point. alors la solution la plus efficace est TableLayout. Pourquoi pas RecyclerView? Et à cause de 3 et 4 points. Il y a des optimisations à l'intérieur de la feuille et elle réutilise la vue, mais pas toujours. Et au moment de créer une nouvelle vue, les gestionnaires tactiles n'existent pas. Et bien, si cela n'affecte que le balayage, mais de temps en temps le problème se reproduit avec le robinet habituel. Même la mise à jour des données directement dans la vue n'aide pas, et non par notification. Par conséquent, il a été décidé d'utiliser TableLayout. Et tout allait bien, jusqu'à ce que les données ne dépassent pas 100. Et puis - bienvenue dans le monde des gels.

J'ai vu 2 façons de résoudre - ou d'apprendre à TableLayout à réutiliser les cellules et à faire de la magie lors du défilement. Ou essayez de vous faire des amis RecyclerView et des mises à jour fréquentes. Et je suis allé dans la deuxième voie. Étant donné que les touches et les balayages (principalement dus aux balayages) ont été traités par une classe auto-écrite basée sur View.OnTouchListener, il s'est avéré être une solution efficace pour déplacer le traitement tactile au niveau RecyclerView en remplaçant la méthode dispatchTouchEvent.

L'algorithme est simple:

  • attraper le toucher
  • déterminer quel enfant le toucher vole avec findChildViewUnder (x, y)
  • obtenir la position de l'élément à partir du LayoutManager
  • s'il s'agit de MotionEvent.ACTION_MOVE, alors nous vérifions avec la même position que nous travaillons comme avant ou non
  • effectuer la logique intégrée au toucher

Peut-être qu'à l'avenir il y aura toujours des problèmes avec cette méthode, mais pour le moment tout fonctionne et c'est bien.

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


All Articles