Crear pantalla de acceso con CodePass en Android Kotlin


Buenas,
Tenia en mente des de hace tiempo de querer programar una pantalla de petición de código PIN (PassCode), similar a la pantalla de desbloqueo de muchos smarphones Android, para así añadir una capa de privacidad en apps que lo requieren.

Dividire los apuntes en 3 partes:

1 Diseño de la pantalla de petición del código PIN

Diseño del layout de la pantalla de petición de código pin, con ConstraintLayout ha sido más fácil de lo que me imaginaba

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LockScreenActivity">


    <ImageView
        android:id="@+id/iconStateLock"
        android:layout_width="32dp"
        android:layout_height="32dp"
        android:layout_marginTop="24dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_lock_outline_24dp" />

    <TextView
        android:id="@+id/tvTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="@string/auth_screen_enter_passcode"
        android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/iconStateLock" />


    <com.webserveis.testlockscreen.IndicatorKeyDots
        android:id="@+id/indicatorDots"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        app:indicatorType="FIXED"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle"
        app:pinLength="4" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/keyPad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:background="?attr/colorSurface"
        android:padding="24dp"
        android:theme="@style/KeyPad"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/indicatorDots">

        <LinearLayout
            android:id="@+id/btnDigit1"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintEnd_toStartOf="@+id/btnDigit2"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="1"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=" "
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit2"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginStart="24dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintEnd_toStartOf="@+id/btnDigit3"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/btnDigit1"
            app:layout_constraintTop_toTopOf="@+id/btnDigit1">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="2"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="abc"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit3"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginStart="24dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/btnDigit2"
            app:layout_constraintTop_toTopOf="@+id/btnDigit2">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="3"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="def"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit4"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginTop="24dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintStart_toStartOf="@+id/btnDigit1"
            app:layout_constraintTop_toBottomOf="@+id/btnDigit1">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="4"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="ghi"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit5"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintStart_toStartOf="@+id/btnDigit2"
            app:layout_constraintTop_toTopOf="@+id/btnDigit4">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="5"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="jkl"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit6"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintStart_toStartOf="@+id/btnDigit3"
            app:layout_constraintTop_toTopOf="@+id/btnDigit5">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="6"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="mno"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit7"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginTop="24dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintStart_toStartOf="@+id/btnDigit4"
            app:layout_constraintTop_toBottomOf="@+id/btnDigit4">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="7"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="pqrs"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit8"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintStart_toStartOf="@+id/btnDigit5"
            app:layout_constraintTop_toTopOf="@+id/btnDigit7">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="8"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="tuv"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit9"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintStart_toStartOf="@+id/btnDigit6"
            app:layout_constraintTop_toTopOf="@+id/btnDigit8">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="9"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="wxyz"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <LinearLayout
            android:id="@+id/btnDigit0"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:layout_marginTop="16dp"
            android:gravity="center"
            android:orientation="vertical"
            app:layout_constraintStart_toStartOf="@+id/btnDigit8"
            app:layout_constraintTop_toBottomOf="@+id/btnDigit8">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="0"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="+"
                android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
                android:textColor="?android:textColorSecondary" />

        </LinearLayout>

        <ImageView
            android:id="@+id/btnDigitDel"
            style="@style/KeyPad.DigitKey"
            android:layout_width="56dp"
            android:layout_height="56dp"
            android:scaleType="center"
            app:layout_constraintStart_toStartOf="@+id/btnDigit9"
            app:layout_constraintTop_toTopOf="@+id/btnDigit0"
            app:srcCompat="@drawable/ic_backspace_24dp" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Estilo propio para personalización res/values/styles.xml

    <style name="KeyPad" parent="AppTheme" />
    <style name="KeyPad.Light" parent="Theme.MaterialComponents.Light" />
    <style name="KeyPad.Dark" parent="Theme.MaterialComponents" />

    <style name="KeyPad.DigitKey">
        <item name="android:background">?attr/selectableItemBackgroundBorderless</item>
        <item name="android:clickable">true</item>
        <item name="android:focusable">true</item>
    </style>

2 Crear componente PassCodeIndicator

Ver el apunte Crear componente PassCodeDotsIndicator

3 Parte lógica

Teniendo la parte visual realizada, ahora falta darle la funcionalidad, detectar las pulsaciones de los botones, ir llenando el passCode y su validación

Para hacerlo más fácil crear un Manager PinLockManager.kt

class PinLokManager {

    var pinCode: String = ""
    var passCode: String = "1234"
    var listener: PinLockListener? = null

    fun appendDigit(c: Char) {
        if (pinCode.isEmpty()) listener?.onPinLockStarted()
        if (pinCode.length in 0..passCode.length) {
            pinCode += c
            listener?.onPinLockProgress(pinCode.length)
        }
        if (pinCode.length == passCode.length)
            listener?.onPinLockValidated(pinCode == passCode)

    }

    fun removeLastDigit() {
        pinCode = pinCode.dropLast(1)
        listener?.onPinLockProgress(pinCode.length)
        if (pinCode.isEmpty()) {
            listener?.onPinLockCleared()
        }
    }

    fun clearPinLock() {
        pinCode = ""
        listener?.onPinLockCleared()
    }

    fun passCodeSize(): Int {
        return passCode.length
    }

    interface PinLockListener {
        fun onPinLockStarted()
        fun onPinLockProgress(value: Int)
        fun onPinLockValidated(isValid: Boolean)
        fun onPinLockCleared()
    }

}

Crear el LockScreenActivity.kt

class LockScreenActivity : AppCompatActivity()  {

    private val uiScope = CoroutineScope(Dispatchers.Main + Job())
    private val mPinLockManager: PinLokManager = PinLokManager()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_lock_screen)

        mPinLockManager.passCode = "1234"
        mPinLockManager.listener = pinLockListener()

        indicatorDots.setPinLength(mPinLockManager.passCodeSize())
        btnDigit0.setOnClickListener {
            mPinLockManager.appendDigit('0')
        }

        btnDigit1.setOnClickListener {
            mPinLockManager.appendDigit('1')
        }
        btnDigit2.setOnClickListener {
            mPinLockManager.appendDigit('2')
        }
        btnDigit3.setOnClickListener {
            mPinLockManager.appendDigit('3')
        }
        btnDigit4.setOnClickListener {
            mPinLockManager.appendDigit('4')
        }
        btnDigit5.setOnClickListener {
            mPinLockManager.appendDigit('5')
        }
        btnDigit6.setOnClickListener {
            mPinLockManager.appendDigit('6')
        }
        btnDigit7.setOnClickListener {
            mPinLockManager.appendDigit('7')
        }
        btnDigit8.setOnClickListener {
            mPinLockManager.appendDigit('8')
        }
        btnDigit9.setOnClickListener {
            mPinLockManager.appendDigit('9')
        }

        btnDigitDel.setOnClickListener {
            mPinLockManager.removeLastDigit()
        }

    }

    private fun pinLockListener(): PinLokManager.PinLockListener? = object : PinLokManager.PinLockListener {
        override fun onPinLockStarted() {}

        override fun onPinLockProgress(value: Int) {
            indicatorDots.updateDot(value)
        }

        override fun onPinLockValidated(isValid: Boolean) {
            uiScope.launch {
                if (isValid) {
                    iconStateLock.setImageResource(R.drawable.ic_lock_open_24dp)
                    delay(500L)
                    setResult(Activity.RESULT_OK)
                    finish()
                } else {
                    shake()
                    delay(500L)
                    mPinLockManager.clearPinLock()
                }
            }
        }

        override fun onPinLockCleared() {
            indicatorDots.setPinLength(mPinLockManager.passCodeSize())
        }

    }


    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        Log.d("TAG", "onKeyDown() called with: keyCode = [$keyCode], event = [$event]")
        if (event != null && keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_DOWN) {
            if (mPinLockManager.pinCode.isNotEmpty()) {
                btnDigitDel.performClick()
            } else {
                setResult(Activity.RESULT_CANCELED)
                finish()
            }
            return true
        } else {

            when (keyCode) {
                in 7..16 -> {
                    mPinLockManager.appendDigit((keyCode - 7).toChar())
                }
                KeyEvent.KEYCODE_DEL -> mPinLockManager.removeLastDigit()
            }

            return super.onKeyDown(keyCode, event)
        }

    }

    private fun shake() {
        val objectAnimator = ObjectAnimator.ofFloat(
            keyPad,
            View.TRANSLATION_X,
            0F, 15F, -15F, 15F, -15F, 6F, -6F, 3F, -3F, 0F
        ).setDuration(750)
        objectAnimator.start()
    }

}

Anuncio publicitario

Publicado por Codelaby

Mobile DevDesigner

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

A %d blogueros les gusta esto: