Création d'une application Android à l'aide des mises en page Anko et des coroutines Anko

image


Il y a environ un an, j'ai commencé à utiliser Kotlin dans mes projets Android. Je voulais essayer quelque chose de nouveau qui serait intéressant à apprendre. Puis je suis tombé sur Anko . À ce moment-là, l'écriture de l'interface utilisateur en xml était assez ennuyeuse. J'ai toujours aimé écrire l'interface avec mes mains, sans recourir à WYSIWYG et au balisage xml utilisé dans Android Studio. Le seul point négatif est que vous devrez redémarrer l'application pour vérifier les modifications. Vous pouvez utiliser un plugin qui montre à quoi ressemblera l'interface utilisateur sans lancer l'application, mais cela m'a semblé plutôt étrange. Il a également la capacité intéressante de convertir xml en Anko Layouts DSL.


Le plus gros inconvénient de la bibliothèque est le manque presque complet de documentation. Pour savoir comment l'utiliser correctement, je devais souvent chercher la source. Dans cet article, le développement d'applications utilisant Anko Layouts et Anko Coroutines sera discuté en détail.


La bibliothèque Anko elle-même est divisée en 4 parties indépendantes:


  • Anko Layouts - crĂ©ation d'une interface utilisateur.
  • Anko Commons - Outils et fonctionnalitĂ©s utiles.
  • Anko SQLite - fonctionne avec la base de donnĂ©es SQLite.
  • Anko Coroutines sont des outils utiles pour travailler avec des coroutines.

Pour ajouter une bibliothèque au projet, ajoutez simplement une ligne en fonction du projet:


implementation "org.jetbrains.anko:anko:$anko_version"


où anko_version est la version actuelle de la bibliothèque, enregistrée dans le fichier build.gradle au niveau du projet:


ext.anko_version='0.10.8'


Agencements Anko


Anko Layouts vous permet de développer des applications Android UI plus efficacement qu'il n'utilisait Java.


Le principal acteur sur le terrain est l' AnkoComponent<T> avec une seule méthode createView qui accepte AnkoContext<T> et renvoie une vue. C'est dans cette méthode que l'interface utilisateur entière est créée. L' AnkoContext<T> est un wrapper sur le ViewManager . Plus d'informations à ce sujet plus tard.


Ayant un peu de compréhension du fonctionnement d' AnkoComponent<T> , essayons de créer une interface utilisateur simple dans la classe de notre activité. Il convient de préciser qu'une telle orthographe "directe" de l'interface utilisateur n'est possible que dans Activity, car une fonction d'extension distincte ankoView est écrite pour elle, dans laquelle la méthode addView est appelée , et AnkoContextImpl<T> est créé dans la méthode elle-même avec le paramètre setContentView = true .


 class AppActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) verticalLayout { lparams(matchParent, matchParent) gravity = Gravity.CENTER textView("Cool Anko TextView") { gravity = Gravity.CENTER } } } 

Évidemment, pour quelque chose de plus d'un TextView, la méthode onCreate se transformera rapidement en un vidage. Essayons de séparer la classe Activity de l'interface utilisateur. Pour ce faire, créez une autre classe dans laquelle elle sera décrite.


 class AppActivityUi: AnkoComponent<AppActivity> { override fun createView(ui: AnkoContext<AppActivity>): View = with(ui) { verticalLayout { lparams(matchParent, matchParent) gravity = Gravity.CENTER textView("Cool Anko TextView") { gravity = Gravity.CENTER } } } } 

Maintenant, afin de transmettre notre interface utilisateur à notre activité, vous pouvez utiliser


 AppActivityUi().setContentView(this) 

D'accord, mais que faire si nous voulons créer une interface utilisateur pour le fragment? Pour ce faire, vous pouvez utiliser directement la méthode createView en l'appelant à partir de la méthode du fragment onCreateView. Cela ressemble à ceci:


 class AppFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return AppFragmentUi().createView(AnkoContext.create(requireContext(), this)) } } 

Comme déjà mentionné - AnkoContext<T> est un wrapper sur le ViewManager. Son objet compagnon a trois méthodes principales qui renvoient AnkoContext<T> . Nous les analyserons plus en détail.


créer


  fun <T> create(ctx: Context, owner: T, setContentView: Boolean = false): AnkoContext<T> 

et son frère jumeau


 fun create(ctx: Context, setContentView: Boolean = false): AnkoContext<Context> 

retourne AnkoContextImpl<T> .


Les méthodes sont utilisées dans tous les cas standard, comme, par exemple, dans les exemples précédents avec Activity et Fragment. Les plus intéressants ici sont le propriétaire et les options setContentView. Le premier vous permet de transmettre un fragment, une activité ou autre chose spécifique à la méthode d'instance createView.


 MyComponent().createView(AnkoContext.create(context, myVew)) class MyComponent: AnkoComponent<View> { override fun createView(ui: AnkoContext<View>): View = with(ui) { val myView: View= ui.owner //     } } 

Le deuxième paramètre - setContentView - tentera automatiquement d'ajouter la vue résultante si Context est une instance d'une Activity ou ContextWrapper . S'il ne réussit pas, il lèvera une exception IllegalStateException.


createDelegate


Cette méthode peut être très utile, mais la documentation officielle sur le gihab n'en dit rien . Je l'ai rencontré dans la source lorsque j'ai résolu le problème des ballonnements des classes d'interface utilisateur:


 fun <T: ViewGroup> createDelegate(owner: T): AnkoContext<T> = DelegatingAnkoContext(owner) 

Il vous permet d'ajouter le résultat d'un composant createView au propriétaire.


Considérez son utilisation comme exemple. Supposons que nous ayons une grande classe qui décrit l'un des écrans d'application - AppFragmentUi.


 verticalLayout { relativeLayout { id = R.id.toolbar //    view } relativeLayout{ id = R.id.content //     view } } 

Logiquement, il peut être divisé en deux parties - la barre d'outils et le contenu, AppFragmentUiToolbar et AppFragmentUiContent, respectivement. Ensuite, notre classe principale AppFragmentUi deviendra beaucoup plus simple:


 class AppFragmentUi: AnkoComponent<AppFragment> { override fun createView(ui: AnkoContext<AppFragment>) = with(ui) { verticalLayout { AppFragmentUiToolbar().createView(AnkoContext.createDelegate(this)) AppFragmentUiContent().createView(AnkoContext.createDelegate(this)) } } } class AppFragmentUiToolbar : AnkoComponent<_LinearLayout> { override fun createView(ui: AnkoContext<_LinearLayout>): View = with(ui.owner) { relativeLayout { id = R.id.toolbar //   view } } } 

Notez que non ui, mais ui.owner est passé à la fonction with en tant qu'objet.
Ainsi, nous avons l'algorithme suivant:


  1. L'instance de composant est créée.
  2. La méthode createView crée la vue à ajouter.
  3. La vue résultante est ajoutée au propriétaire.

Une approximation plus proche: this.addView(AppFragmentUiToolbar().createView(...))
Comme vous pouvez le voir, l'option avec createDelegate est plus lisible.


createReusable


Il ressemble au AnkoContext.create standard, mais avec un petit ajout - la dernière vue est considérée comme la vue racine:


 class MyComponent: AnkoComponent<MyObject> { override fun createView(ui: AnkoContext<MyObject>): View = with(ui) { textView("Some text") //       IllegalStateException: View is already set //     "Another text" textView("Another text") } } 

Dans l'implémentation standard, si la vue racine est définie, une tentative de définir la deuxième vue en parallèle lèvera une exception .


La méthode createReusable renvoie la classe ReusableAnkoContext , qui hérite d' AnkoContextImpl et remplace la méthode alreadyHasView() .


Vue personnalisée


Heureusement, Anko Layouts ne se limite pas à cette fonctionnalité. Si nous devons montrer notre propre CustomView, nous n'avons pas à écrire


 verticalLayout { val view = CustomView(context) //.... addView(view) //  addView(view.apply { ... }) } 

Pour ce faire, vous pouvez ajouter votre propre wrapper, qui fera de mĂŞme.


Les principaux composants ici sont la méthode d'extension <T: View>ankoView(factory: (Context) -> T, theme: Int, init: T.() -> Unit) partir de ViewManager, Context ou Activity.


  • factory - une fonction Ă  l'entrĂ©e de laquelle le contexte est transmis et la vue est renvoyĂ©e. En fait, c'est l'usine oĂą se dĂ©roule la crĂ©ation de la Vue.
  • Le thème est une ressource de style qui sera appliquĂ©e Ă  la vue actuelle.
  • init est une fonction dans laquelle les paramètres nĂ©cessaires seront dĂ©finis pour la vue créée.

Ajoutez votre implémentation pour notre CustomView


 inline fun ViewManager.customView(theme: Int = 0, init: (CustomView).() -> Unit): CustomView { return ankoView({ CustomView(it) }, theme, init) } 

Maintenant, notre CustomView est créé très simplement:


 customView { id = R.id.customview //   } 

Vous pouvez utiliser lparams pour appliquer LayoutParams Ă  la vue.


 textView("text") { textSize = 12f }.lparams(width = matchParent, height = wrapContent) { centerInParent() } 

Il convient de noter que cela ne s'applique pas à toutes les vues - toutes les méthodes lparams sont généralement déclarées dans des wrappers. Par exemple, _RelativeLayout est un wrapper sur RelativeLayout . Et donc pour tout le monde.


Heureusement, plusieurs wrappers ont été écrits pour la bibliothèque de support Android, vous ne pouvez donc inclure que les dépendances dans le fichier gradle.


  // Appcompat-v7 (Anko Layouts) implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version" implementation "org.jetbrains.anko:anko-coroutines:$anko_version" // CardView-v7 implementation "org.jetbrains.anko:anko-cardview-v7:$anko_version" // Design implementation "org.jetbrains.anko:anko-design:$anko_version" implementation "org.jetbrains.anko:anko-design-coroutines:$anko_version" // GridLayout-v7 implementation "org.jetbrains.anko:anko-gridlayout-v7:$anko_version" // Percent implementation "org.jetbrains.anko:anko-percent:$anko_version" // RecyclerView-v7 implementation "org.jetbrains.anko:anko-recyclerview-v7:$anko_version" implementation "org.jetbrains.anko:anko-recyclerview-v7-coroutines:$anko_version" // Support-v4 (Anko Layouts) implementation "org.jetbrains.anko:anko-support-v4:$anko_version" // ConstraintLayout implementation "org.jetbrains.anko:anko-constraint-layout:$anko_version" 

Entre autres choses, la bibliothèque permet une implémentation plus pratique de divers écouteurs. Un petit exemple du référentiel:


 seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) = Unit override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar) = Unit }) 

et maintenant en utilisant Anko


 seekBar { onSeekBarChangeListener { onProgressChanged { seekBar, progress, fromUser -> // do something } } } 

De plus, certains écouteurs prennent en charge les coroutines:


  verticalLayout{ val anyCoroutineContext = GlobalScope.coroutineContext onClick(anyCoroutineContext) { //this: CoroutineScope } } 

Anko coroutines


Pour transférer en toute sécurité des objets sensibles aux fuites de mémoire, la méthode asReference est asReference . Il est basé sur WeakReference et renvoie un objet Ref.

 verticalLayout{ val activity = ui.owner val activityReference: Ref<AppActivity> = activity.asReference() onClick(anyCoroutineContext) { ref().doSomething() } } 

Supposons que vous souhaitiez ajouter la prise en charge de la corutine dans le ViewPager.OnPageChangeListener standard. Rendons-le aussi cool que l'exemple de la barre de recherche.
Commencez par créer une classe distincte et héritez de ViewPager.OnPageChangeListener .


 class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { } 

Nous stockons les lambdas dans des variables qui seront appelées par ViewPager.OnPageChangeListener .


  private var onPageScrollStateChanged: ((Int, CoroutineContext) -> Unit)? = null private var onPageScrolled: ((Int, Float, Int, CoroutineContext) -> Unit)? = null private var onPageSelected: ((Int, CoroutineContext) -> Unit)? = null 

Nous implémentons l'initialisation pour l'une de ces variables (le reste se fait de la même manière)


  fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action } 

Et à la fin, nous implémentons une fonction du même nom.


  override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } } 

Il reste Ă  ajouter une fonction d'extension pour le faire fonctionner


 fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) } 

Et collez le tout sous ViewPager


 viewPager { onPageChangeListenerCoroutines { onPageScrolled { position, offset, pixels, coroutineContext -> //  -   . } } } 

Code complet ici
 class CoroutineOnPageChangeListener( private val coroutineContext: CoroutineContext = Dispatchers.Main ) : ViewPager.OnPageChangeListener { private var onPageScrollStateChanged: ((Int, CoroutineContext) -> Unit)? = null private var onPageScrolled: ((Int, Float, Int, CoroutineContext) -> Unit)? = null private var onPageSelected: ((Int, CoroutineContext) -> Unit)? = null fun onPageScrollStateChanged(action: ((Int, CoroutineContext) -> Unit)?) { onPageScrollStateChanged = action } fun onPageScrolled(action: ((Int, Float, Int, CoroutineContext) -> Unit)?) { onPageScrolled = action } fun onPageSelected(action: ((Int, CoroutineContext) -> Unit)?) { onPageSelected = action } override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { GlobalScope.launch(coroutineContext) { onPageScrolled?.invoke(position, positionOffset, positionOffsetPixels, coroutineContext) } } override fun onPageSelected(position: Int) { GlobalScope.launch(coroutineContext) { onPageSelected?.invoke(position, coroutineContext) } } override fun onPageScrollStateChanged(state: Int) { GlobalScope.launch(coroutineContext) { onPageScrollStateChanged?.invoke(state, coroutineContext) } } } fun ViewPager.onPageChangeListenerCoroutines(init: CoroutineOnPageChangerListener.() -> Unit) { val listener = CoroutineOnPageChangerListener() listener.init() addOnPageChangeListener(listener) } 

De plus, dans la bibliothèque Anko Layouts, il existe de nombreuses méthodes utiles, telles que des traductions en diverses mesures .

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


All Articles