Úvod do knihovny Anko pro Android (3/4)

Ve třetím díle mini série o knihovně Anko budeme vylepšovat naši demo aplikaci, aby vypadala o něco lépe po stránce designu. Také se ale naučíme, jaké další extension funkce Anko nabízí, například k nastavení layoutu pro portrait/landscape mód.

V prvním článku jsme si řekli něco o vytvoření UI, které jsme definovali jako samostatné komponenty (AnkoComponent). Taky jsme si ukázali, jak je lze používat uvnitř aktivit. V předchozím článku jsme přidali do naší demo aplikace trochu té logiky. Nyní se vrátíme trochu zpátky a zaměříme se na vylepšení UI.

Přehled dílů série:

1. Vytvoření projektu v Android Studio 3 a první UI
2. Tlačítka, toasty, dialogy, intenty, uživatelská rozhraní a práce na pozadí (background threads)
3. Vylepšujeme naše UI
4. Fragmenty & RecyclerView

Jako první přidáme padding do root UI elementu (vertical layout) v SignInView komponentě:

 

SignInView.kt – bez paddingu

 

// SignInView.createView
verticalLayout {
    padding = dip(20)
    ...
}

 

SignInView.kt – přidaný padding

 

Výše uvedené DSL by v XML vypadalo následovně:

<LinearLayout
    android:padding="20dip"
    ...
 />

 

Všimněte si, že v SignInView komponentě nastavuje stejnou velikost textu oběma komponentám (username a password), konkrétně textSize = 24f.

 

val username = editText {
  ...
  textSize = 24f
}

val password = editText {
  ...
  textSize = 24f
}

 

Anko obsahuje další užitečnou extension funkci: aplikování parametrů do view rekurzivně, tedy applyRecursively{}. Můžeme tak nastavit textSize parametr rekurzivně všem UI komponentám, například typu EditText:

 

verticalLayout {
  padding = dip(20)
  ...
  val username = editText {
    id = R.id.usernameEditText
    hintResource = R.string.sign_in_username
  }.lparams(width = matchParent, height = wrapContent)

  val password = editText {
    id = R.id.passwordEditText
    hintResource = R.string.signIn_password
  }.lparams(width = matchParent, height = wrapContent)
   
  button {...}.lparams(width = matchParent, height = wrapContent)
}.applyRecursively { view ->
    when (view) {
        is EditText -> view.textSize = 24f
    }
}

 

V dalším kroku přidáme do SignInView komponentu pro skrolování a zarovnáme formulář na střed. Po dokončení těchto kroků bude UI vypadat následovně:

 

 

Nebudu popisovat každou změnu krok za krokem, ale rozebereme si ty nejdůležitější části. Zkopírujte si následující kód, použijte ho ve vaší demo aplikaci a vyzkoušejte aplikaci spustit.

 

ackage com.example.android.anko.sample.sign_in

import android.os.Build
import android.support.v4.content.ContextCompat
import android.view.Gravity
import android.view.ViewGroup
import android.widget.EditText
import android.widget.LinearLayout
import com.example.android.anko.sample.R
import org.jetbrains.anko.*

/**
 * @author vsouhrada
 * @since 0.1.0
 * @see[AnkoComponent]
 * @see[SignInActivity]
 */
class SingInView : AnkoComponent<SignInActivity> {

    private lateinit var ankoContext: AnkoContext<SignInActivity>

    override fun createView(ui: AnkoContext<SignInActivity>) = with(ui) {
        ankoContext = ui

        verticalLayout {
            this.gravity = Gravity.CENTER

            scrollView {

                verticalLayout {

                    verticalLayout {
                        id = R.id.formLogin
                        gravity = Gravity.CENTER
                        padding = dip(20)

                        lparams(width = dip(300), height = matchParent) {
                            this.gravity = Gravity.CENTER
                            // API >= 16
                            doFromSdk(version = Build.VERSION_CODES.JELLY_BEAN) {
                                background = ContextCompat.getDrawable(ctx, android.R.color.white)
                            }
                            clipToPadding = false
                            bottomMargin = dip(16)
                        }

                        val username = editText {
                            id = R.id.usernameEditText
                            hintResource = R.string.sign_in_username

                        }.lparams(width = matchParent, height = wrapContent)

                        val password = editText {
                            id = R.id.passwordEditText
                            hintResource = R.string.signIn_password

                        }.lparams(width = matchParent, height = wrapContent)

                        button {
                            id = R.id.signIn_button
                            textResource = R.string.signIn_button

                            onClick {
                                handleOnSignInButtonPressed(username = username.text.toString(), password = password.text.toString())
                            }

                        }.lparams(width = matchParent, height = wrapContent)

                    }.applyRecursively { view ->
                        when (view) {
                            is EditText -> view.textSize = 24f
                        }
                    }

                }.lparams(width = matchParent, height = matchParent)

            }.lparams(width = matchParent, height = wrapContent)

        }
    }

    private fun handleOnSignInButtonPressed(username: String, password: String) {
        with(ankoContext) {
            if (username.isBlank() or password.isBlank()) {
                alert(title = R.string.sigIn_alert_invalid_user_title,
                        message = R.string.sigIn_alert_invalid_user_message) {

                    positiveButton(R.string.dialog_button_close) {}
                }.show()
            } else {
                owner.authorizeUser(username, password)
            }
        }
    }

    fun showAccessDeniedAlertDialog() {
        with(ankoContext) {
            alert(title = R.string.sigIn_alert_access_denied_title,
                    message = R.string.sigIn_alert_access_denied_msg) {

                positiveButton(R.string.dialog_button_close) {}
            }.show()
        }
    }

}

 

Řádek 26: sem jsme přidali nový parametr gravity se zarovnáním na střed
Řádek 28: přidána scrollView komponenta s vnořenou verticalLayout obalující Sign in formulář
Řádek 32: zde se nově nachází naše původní verticalLayout, do které jsme přidali opět parametr gravity se zarovnáním na střed
Řádek 37: nastavení 300dp pro velikost formuláře

Poznámka: Asi vás zajímá, proč máme definici lparams na řádku 37 uvnitř verticalLayout komponenty a nemáme ji podle doporučení za ukončením layoutu. Důvodem je, že nastavení délky layout ve verzi 0.9 funguje pouze touto mnou uvedenou definicí. Navíc to bude fungovat pouze pokud přesuneme daný kód do funkce apply:

 

...
verticalLayout {
....
}.apply {
    lparams(width = dip(300), height = matchParent) {
        this.gravity = Gravity.CENTER
        // API >= 16
        doFromSdk(version = Build.VERSION_CODES.JELLY_BEAN) {
            background = ContextCompat.getDrawable(
                   ctx, android.R.color.white)
        }
        clipToPadding = false
        bottomMargin = dip(16)
    }
}

 

Vraťme se k původnímu kódu bez použití funkce apply. Na řádku 40 si můžete všimnout následujícího kódu:

 

// API >= 16
doFromSdk(version = Build.VERSION_CODES.JELLY_BEAN) {
    background = ContextCompat.getDrawable(
           ctx,android.R.color.white)
}

 

Použití View#setBackground vyžaduje API >= 16. Anko obsahuje několik funkcí pro tyto účely a pro nastavení při různém natočení mobilního zařízení (portrait a landscape):

 

// Code inside for this block code is from original Anko //Documentation
configuration(screenSize = ScreenSize.LARGE, orientation = Orientation.LANDSCAPE) {
    /* 
      This code will be only executed
      if the screen is large and its orientation is landscape
    */
}

 

V našem případě, kdy nerozlišujeme natočení, můžeme použít také následující zápis:

 

configuration(fromSdk = Build.VERSION_CODES.JELLY_BEAN) {
    background = ContextCompat.getDrawable(
           ctx, android.R.color.white)
}

 

Shrnutí 3. části

Tentokrát jsme se naučili, jak přidat další parametry do UI komponent (padding, gravity a další) a přidali jsme také scroll view komponentu. Taky jsme si vyzkoušeli, jak můžeme tyto parametry aplikovat rekurzivně (třeba textSize).

Na závěr jsme si ukázali, jak se v Anko knihovně dá zacházet s portrait/landscape módem a jak můžeme odlišit, pro jaké Android API se má daný kód použít.

V příštím článku se naučíme práci s fragmenty. To znamená, že si řekneme, co nám Anko poskytuje za vylepšení při psaní fragmentů.

Václav Souhrada
Kotlin & Android Developer at eMan, Czech Kotlin User Group Founder and Organizer

RSS