Creación de una aplicación de Android con diseños de Anko y corrinas de Anko

imagen


Hace aproximadamente un año, comencé a usar Kotlin en mis proyectos de Android. Quería probar algo nuevo que sería interesante aprender. Entonces me encontré con Anko . En ese momento, escribir la interfaz de usuario en xml era bastante aburrido. Siempre me gustó escribir la interfaz con mis manos, sin recurrir a WYSIWYG y al marcado xml utilizado en Android Studio. Lo único negativo es que tendrá que reiniciar la aplicación para verificar cualquier cambio. Puede usar un complemento que muestra cómo se verá ui sin iniciar la aplicación, pero me pareció bastante extraño. También tiene la genial habilidad de convertir xml a Anko Layouts DSL.


El mayor inconveniente de la biblioteca es la falta casi completa de documentación. Para descubrir cómo usarlo correctamente, a menudo tenía que buscar en la fuente. En este artículo, se discutirá en detalle el desarrollo de aplicaciones usando Anko Layouts y Anko Coroutines.


La propia biblioteca de Anko se divide en 4 partes independientes:


  • Anko Layouts: construcción de una interfaz de usuario.
  • Anko Commons - Herramientas y características útiles.
  • Anko SQLite: trabaje con la base de datos SQLite.
  • Anko Coroutines son herramientas útiles para trabajar con corutinas.

Para agregar una biblioteca al proyecto, solo agregue una línea dependiendo del proyecto:


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


donde anko_version es la versión actual de la biblioteca, registrada en el archivo build.gradle en el nivel del proyecto:


ext.anko_version='0.10.8'


Diseños de Anko


Anko Layouts le permite desarrollar aplicaciones de UI para Android de manera más eficiente de lo que estaba usando Java.


El jugador principal en el campo es la AnkoComponent<T> con un único método createView que acepta AnkoContext<T> y devuelve una Vista. Es en este método que se crea toda la IU. La AnkoContext<T> es un contenedor sobre el ViewManager . Más sobre esto será más tarde.


Al comprender un poco cómo funciona AnkoComponent<T> , intentemos crear una interfaz de usuario simple en la clase de nuestra Actividad. Vale la pena aclarar que dicha ortografía "directa" de la interfaz de usuario solo es posible en Actividad, ya que se escribe una función de extensión separada ankoView para ella, en la que se llama al método addView , y AnkoContextImpl<T> se crea en el propio método con el parámetro 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 } } } 

Obviamente, para algo más de un TextView, el método onCreate se convertirá rápidamente en un volcado. Intentemos separar la clase Actividad de la IU. Para hacer esto, cree otra clase en la que se describirá.


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

Ahora, para pasar nuestra IU a nuestra Actividad, puede usar


 AppActivityUi().setContentView(this) 

Ok, pero ¿qué pasa si queremos crear una interfaz de usuario para el fragmento? Para hacer esto, puede usar el método createView directamente llamándolo desde el método de fragmento onCreateView. Se ve así:


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

Como ya se mencionó, AnkoContext<T> es un contenedor sobre el ViewManager. Su objeto complementario tiene tres métodos principales que devuelven AnkoContext<T> . Los analizaremos con más detalle.


crear


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

y su hermano gemelo


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

devuelve AnkoContextImpl<T> .


Los métodos se utilizan en todos los casos estándar, como, por ejemplo, en los ejemplos anteriores con Actividad y Fragmento. Los más interesantes aquí son las opciones propietario y setContentView. El primero le permite pasar un Fragmento, Actividad u otra cosa específica al método de instancia 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 //     } } 

El segundo parámetro, setContentView, intentará agregar automáticamente la vista resultante si Context es una instancia de una Activity o ContextWrapper . Si no tiene éxito, lanzará una IllegalStateException.


createDelegate


Este método puede ser muy útil, pero la documentación oficial sobre el gihab no dice nada al respecto. Lo encontré en la fuente cuando resolví el problema de hinchar las clases de IU:


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

Le permite agregar el resultado del componente createView al propietario.


Considere su uso como un ejemplo. Supongamos que tenemos una clase grande que describe una de las pantallas de aplicación: AppFragmentUi.


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

Lógicamente, se puede dividir en dos partes: la barra de herramientas y el contenido, AppFragmentUiToolbar y AppFragmentUiContent, respectivamente. Entonces nuestra clase principal AppFragmentUi será mucho más 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 } } } 

Tenga en cuenta que no ui, pero ui.owner se pasa a la función with como un objeto.
Por lo tanto, tenemos el siguiente algoritmo:


  1. Se crea la instancia del componente.
  2. El método createView crea la Vista que se agregará.
  3. La vista resultante se agrega al propietario.

Una aproximación más cercana: this.addView(AppFragmentUiToolbar().createView(...))
Como puede ver, la opción con createDelegate es más legible.


createReusable


Parece el AnkoContext.create estándar, pero con una pequeña adición: la última vista se considera la vista raíz:


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

En la implementación estándar, si se establece la vista raíz, un intento de establecer la segunda vista en paralelo arrojará una excepción .


El método createReusable devuelve la clase ReusableAnkoContext , que hereda de AnkoContextImpl y anula el método alreadyHasView() .


Customview


Afortunadamente, Anko Layouts no se limita a esta funcionalidad. Si necesitamos mostrar nuestro propio CustomView, no tenemos que escribir


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

Para hacer esto, puede agregar su propio contenedor, que hará lo mismo.


Los componentes principales aquí son el método de extensión <T: View>ankoView(factory: (Context) -> T, theme: Int, init: T.() -> Unit) desde ViewManager, Context o Activity.


  • factory - una función, a la entrada de la cual se pasa el Contexto y se devuelve la Vista. De hecho, es la fábrica donde tiene lugar la creación de la Vista.
  • El tema es un recurso de estilo que se aplicará a la vista actual.
  • init es una función en la que se establecerán los parámetros necesarios para la Vista creada.

Agregue su implementación para nuestro CustomView


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

Ahora nuestro CustomView se crea de manera muy simple:


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

Puede usar lparams para aplicar LayoutParams a la Vista.


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

Vale la pena señalar que esto no es aplicable a todas las vistas: todos los métodos lparams generalmente se declaran en contenedores. Por ejemplo, _RelativeLayout es un contenedor sobre RelativeLayout . Y así para todos.


Afortunadamente, se han escrito varios contenedores para la Biblioteca de soporte de Android, por lo que solo puede incluir las dependencias en el archivo 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 otras cosas, la biblioteca permite una implementación más conveniente de varios oyentes. Un pequeño ejemplo del repositorio:


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

y ahora usando Anko


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

Además, algunos oyentes admiten corutinas:


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

Anko coroutines


Para transferir de forma segura objetos sensibles a fugas de memoria, se asReference método asReference . Se basa en WeakReference y devuelve un objeto Ref.

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

Suponga que desea agregar soporte para corutina en ViewPager.OnPageChangeListener estándar. Hagámoslo tan genial como el ejemplo de la barra de búsqueda.
Primero, cree una clase separada y herede de ViewPager.OnPageChangeListener .


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

Almacenaremos lambdas en variables que serán llamadas por 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 

Implementamos la inicialización de una de estas variables (el resto se realiza de la misma manera)


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

Y al final implementamos una función con el mismo nombre.


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

Queda por agregar una función de extensión para que funcione


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

Y pegue todo esto en ViewPager


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

Código completo aquí
 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) } 

También en la biblioteca Anko Layouts hay muchos métodos útiles, como las traducciones a varias métricas .

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


All Articles