Criando um aplicativo Android usando o Anko Layouts e o Anko Coroutines

imagem


Há cerca de um ano, comecei a usar o Kotlin em meus projetos Android. Eu queria experimentar algo novo que seria interessante de aprender. Então me deparei com Anko . Naquele momento, escrever a interface do usuário em xml era bastante monótono. Eu sempre gostei de escrever a interface com as mãos, sem recorrer ao WYSIWYG e à marcação xml usada no Android Studio. O único aspecto negativo é que você precisará reiniciar o aplicativo para verificar se há alterações. Você pode usar um plug - in que mostra como será a interface do usuário sem iniciar o aplicativo, mas me pareceu um pouco estranho. Ele também tem a capacidade legal de converter xml para o Anko Layouts DSL.


A maior desvantagem da biblioteca é a quase completa falta de documentação. Para descobrir como usá-lo corretamente, muitas vezes tive que procurar na fonte. Neste artigo, o desenvolvimento de aplicativos usando o Anko Layouts e o Anko Coroutines será discutido em detalhes.


A própria biblioteca Anko é dividida em 4 partes independentes:


  • Anko Layouts - criando uma interface do usuário.
  • Anko Commons - Ferramentas e recursos úteis.
  • Anko SQLite - trabalhe com o banco de dados SQLite.
  • Anko Coroutines são ferramentas úteis para trabalhar com corotinas.

Para adicionar uma biblioteca ao projeto, basta adicionar uma linha, dependendo do projeto:


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


em que anko_version é a versão atual da biblioteca, registrada no arquivo build.gradle no nível do projeto:


ext.anko_version='0.10.8'


Layouts de Anko


O Anko Layouts permite que você desenvolva aplicativos Android de interface do usuário com mais eficiência do que estava usando Java.


O principal player em campo é a interface AnkoComponent<T> com um único método createView que aceita AnkoContext<T> e retorna uma View. É nesse método que toda a interface do usuário é criada. A interface AnkoContext<T> é um invólucro sobre o ViewManager . Mais sobre isso será mais tarde.


Compreendendo um pouco como o AnkoComponent<T> , vamos tentar criar uma interface simples na classe da nossa Atividade. Vale esclarecer que tal ortografia “direta” da interface do usuário é possível apenas em Activity, uma vez que uma função de extensão separada ankoView é gravada para ela, na qual o método addView é chamado , e AnkoContextImpl<T> é criado no próprio método com o 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 mais que um TextView, o método onCreate se transformará rapidamente em um despejo. Vamos tentar separar a classe Activity da interface do usuário. Para fazer isso, crie outra classe na qual será descrita.


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

Agora, para passar nossa interface do usuário para a nossa atividade, você pode usar


 AppActivityUi().setContentView(this) 

Ok, mas e se quisermos criar uma interface do usuário para o fragmento? Para fazer isso, você pode usar o método createView diretamente, chamando-o do método de fragmento onCreateView. É assim:


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

Como já mencionado - o AnkoContext<T> é um invólucro sobre o ViewManager. Seu objeto complementar possui três métodos principais que retornam AnkoContext<T> . Vamos analisá-los com mais detalhes.


criar


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

e seu irmão gêmeo


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

retornar AnkoContextImpl<T> .


Os métodos são usados ​​em todos os casos padrão, como, por exemplo, nos exemplos anteriores com Activity e Fragment. As mais interessantes aqui são as opções owner e setContentView. A primeira permite passar um fragmento, atividade ou outra coisa específica para o método de instância 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 //     } } 

O segundo parâmetro - setContentView - tentará adicionar automaticamente a exibição resultante se Context for uma instância de uma Activity ou ContextWrapper . Se ele não conseguir, ele lançará uma IllegalStateException.


createDelegate


Esse método pode ser muito útil, mas a documentação oficial no gihab não diz nada sobre ele. Me deparei com isso na fonte quando resolvi o problema de inchar as classes da interface do usuário:


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

Permite adicionar o resultado do componente createView ao proprietário.


Considere seu uso como um exemplo. Suponha que tenhamos uma classe grande que descreva uma das telas do aplicativo - AppFragmentUi.


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

Logicamente, ele pode ser dividido em duas partes - a barra de ferramentas e o conteúdo, AppFragmentUiToolbar e AppFragmentUiContent, respectivamente. Então nossa classe principal AppFragmentUi se tornará muito mais simples:


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

Observe que não ui, mas ui.owner é passado para a função with como um objeto.
Assim, temos o seguinte algoritmo:


  1. A instância do componente é criada.
  2. O método createView cria a visualização a ser adicionada.
  3. A vista resultante é adicionada ao proprietário.

Uma aproximação mais próxima: this.addView(AppFragmentUiToolbar().createView(...))
Como você pode ver, a opção com createDelegate é mais legível.


createReusable


Parece o AnkoContext.create padrão, mas com um pequeno acréscimo - a visualização mais recente é considerada a visualização raiz:


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

Na implementação padrão, se a visualização raiz estiver configurada, uma tentativa de configurar a segunda visualização em paralelo gerará uma exceção .


O método createReusable retorna a classe ReusableAnkoContext , que herda de AnkoContextImpl e substitui o método alreadyHasView() .


Customview


Felizmente, o Anko Layouts não se limita a essa funcionalidade. Se precisarmos mostrar nosso próprio CustomView, não precisamos escrever


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

Para fazer isso, você pode adicionar seu próprio wrapper, que fará o mesmo.


Os principais componentes aqui são o método de extensão <T: View>ankoView(factory: (Context) -> T, theme: Int, init: T.() -> Unit) do ViewManager, Contexto ou Atividade.


  • factory - uma função, para a entrada da qual o Contexto é passado e o View é retornado. De fato, é a fábrica na qual a criação da View ocorre.
  • O tema é um recurso de estilo que será aplicado à exibição atual.
  • init é uma função na qual os parâmetros necessários serão definidos para a Visualização criada.

Adicione sua implementação ao nosso CustomView


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

Agora nosso CustomView é criado de maneira muito simples:


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

Você pode usar lparams para aplicar LayoutParams à View.


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

Vale ressaltar que isso não é aplicável a todos os métodos View - todos os lparams geralmente são declarados em wrappers. Por exemplo, _RelativeLayout é um wrapper sobre RelativeLayout . E assim para todos.


Felizmente, vários wrappers foram escritos para a Biblioteca de suporte do Android, portanto, você pode incluir apenas as dependências no arquivo 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 outras coisas, a biblioteca permite uma implementação mais conveniente de vários ouvintes. Um pequeno exemplo do repositório:


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

e agora usando Anko


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

Além disso, alguns ouvintes suportam corotinas:


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

Anko coroutines


Para transferir com segurança objetos sensíveis à vazamento de memória, o método asReference é asReference . É baseado em WeakReference e retorna um objeto Ref.

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

Suponha que você queira adicionar suporte para corutin no ViewPager.OnPageChangeListener padrão. Vamos torná-lo tão legal quanto o exemplo da barra de busca.
Primeiro, crie uma classe separada e herda de ViewPager.OnPageChangeListener .


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

Armazenaremos lambdas em variáveis ​​que serão chamadas pelo 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 a inicialização para uma dessas variáveis ​​(o restante é feito da mesma maneira)


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

E no final, implementamos uma função com o mesmo nome.


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

Resta adicionar uma função de extensão para fazê-la funcionar


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

E cole tudo isso no ViewPager


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

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

Também na biblioteca do Anko Layouts, existem muitos métodos úteis, como traduções para várias métricas .

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


All Articles