
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
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
Observe que não ui, mas ui.owner é passado para a função with
como um objeto.
Assim, temos o seguinte algoritmo:
- A instância do componente é criada.
- O método createView cria a visualização a ser adicionada.
- 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")
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)
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) {
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 .