Crear componente PassCodeDotsIndicator


Para la creación del componente que nos indica cuantas entradas llevamos y las que nos queda para completar el código pin, vaya lo de los circulitos, he extraido la parte del componente de amirarcane/lock-screen la he modificado como tambien transcrita en Kotlin

Crear clase del componente PassCodeDotsIndicator.kt

class PassCodeDotsIndicator @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    annotation class IndicatorType {
        @IntDef(FIXED, FILL, FILL_WITH_ANIMATION)
        @Retention(AnnotationRetention.SOURCE)
        annotation class override
        companion object {
            const val FIXED = 0
            const val FILL = 1
            const val FILL_WITH_ANIMATION = 2

        }
    }

    private val DEFAULT_PIN_LENGTH = 4
    private val DEFAULT_ANIMATION_DURATION = 350L

    private var mDotDiameter = 0
    private var mDotSpacing = 0

    @DrawableRes
    private var mFillDrawable = 0

    @DrawableRes
    private var mEmptyDrawable = 0

    private var mPinLength = 0

    @IndicatorType
    private var mIndicatorType = IndicatorType.FIXED

    private var mPreviousLength = 0

    init {

        val typedArray: TypedArray =
            context.obtainStyledAttributes(attrs, R.styleable.IndicatorLocks)

        try {
            mDotDiameter = typedArray.getDimension(
                R.styleable.IndicatorLocks_dotDiameter,
                getDimensionInPx(getContext(), R.dimen.dot_diameter)
            ).toInt()
            mDotSpacing = typedArray.getDimension(
                R.styleable.IndicatorLocks_dotSpacing,
                getDimensionInPx(getContext(), R.dimen.dot_spacing)
            ).toInt()
            mFillDrawable = typedArray.getResourceId(
                R.styleable.IndicatorLocks_dotFilledBackground,
                R.drawable.dot_filled
            )
            mEmptyDrawable = typedArray.getResourceId(
                R.styleable.IndicatorLocks_dotEmptyBackground,
                R.drawable.dot_empty
            )
            mPinLength = typedArray.getInt(R.styleable.IndicatorLocks_pinLength, DEFAULT_PIN_LENGTH)
            mIndicatorType = typedArray.getInt(
                R.styleable.IndicatorLocks_indicatorType,
                IndicatorType.FIXED
            )
        } finally {
            typedArray.recycle()
        }

        initView(context)

    }

    private fun initView(context: Context) {
        when (mIndicatorType) {
            IndicatorType.FIXED -> {
                for (i in 0 until mPinLength) {
                    val dot = View(context)
                    emptyDot(dot)
                    val params = LayoutParams(mDotDiameter, mDotDiameter)
                    params.setMargins(mDotSpacing, 0, mDotSpacing, 0)
                    dot.layoutParams = params
                    addView(dot)
                }
            }
            IndicatorType.FILL_WITH_ANIMATION -> {
                val layoutTransition = LayoutTransition()
                layoutTransition.setDuration(DEFAULT_ANIMATION_DURATION.toLong())
                layoutTransition.setStartDelay(LayoutTransition.APPEARING, 0)
                setLayoutTransition(layoutTransition)
            }
        }
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        // If the indicator type is not fixed
        if (mIndicatorType != IndicatorType.FIXED) {
            val params = this.layoutParams
            params.height = mDotDiameter
            requestLayout()
        }
    }

    fun updateDot(length: Int) {
        when (mIndicatorType) {
            IndicatorType.FIXED -> {
                if (length < 0 || length > childCount) return
                if (length > 0) {
                    if (length > mPreviousLength) {
                        val dot: View? = getChildAt(length - 1)
                        dot?.let { fillDot(it) }
                    } else {
                        val dot: View? = getChildAt(length)
                        dot?.let { emptyDot(it) }
                    }
                    mPreviousLength = length
                } else {
                    // When {@code mPinLength} is 0, we need to reset all the views back to empty
                    for (i in 0 until childCount) {
                        val v = getChildAt(i)
                        emptyDot(v)
                    }
                    mPreviousLength = 0
                }
            }
            IndicatorType.FILL,
            IndicatorType.FILL_WITH_ANIMATION -> {
                if (length > 0) {
                    if (length > mPreviousLength) {

                        val dot = View(context)
                        fillDot(dot)
                        val params = LayoutParams(mDotDiameter, mDotDiameter)
                        params.setMargins(mDotSpacing, 0, mDotSpacing, 0)
                        dot.layoutParams = params
                        addView(dot, length - 1)

                    } else {
                        removeViewAt(length)
                    }
                    mPreviousLength = length
                } else {
                    removeAllViews()
                    mPreviousLength = 0
                }
            }
        }

    }

    private fun emptyDot(dot: View) {
        dot.setBackgroundResource(mEmptyDrawable)
    }

    private fun fillDot(dot: View) {
        dot.setBackgroundResource(mFillDrawable)
    }

    fun getPinLength(): Int {
        return mPinLength
    }

    fun setPinLength(value: Int) {
        mPinLength = value
        removeAllViews()
        mPreviousLength = 0
        initView(context)
    }

    @IndicatorType
    fun getIndicatorType(): Int {
        return mIndicatorType
    }

    fun setIndicatorType(@IndicatorType type: Int) {
        mIndicatorType = type
        removeAllViews()
        initView(context)
    }

    private fun getDimensionInPx(context: Context, @DimenRes id: Int): Float {
        return context.resources.getDimension(id)
    }

}

Recursos graficos de los circulos, para el identificador circular vacio, drawable/dot_empty.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@android:color/transparent"/>
    <stroke
        android:width="@dimen/dot_border"
        android:color="?attr/colorControlNormal"/>
</shape>

Para el indicador ciruclar lleno drawable/dot_filled.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/colorAccent"/>
</shape>

Atributos propios res/values/attr.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="IndicatorLocks">
        <attr name="pinLength" format="integer" />

        <attr name="dotEmptyBackground" format="reference" />
        <attr name="dotFilledBackground" format="reference" />
        <attr name="dotDiameter" format="dimension" />
        <attr name="dotSpacing" format="dimension" />
        <attr name="indicatorType" format="enum">
            <enum name="FIXED" value="0" />
            <enum name="FILL" value="1" />
            <enum name="FILL_ANIMATION" value="2" />
        </attr>
    </declare-styleable>

</resources>

Usar el componente

Su uso es muy fácil, para, en la declaración del componente podemos especificar el estilo del identificador, que puede ser:

  • FIXED: Se mostrarán los circulos vacios, otorgando la visibilidad del tamaño del pin a entrar, si es de 4, se verá 4 circulos
  • FILL: Se mostrará solo un indentificador por cada entrada
  • FILL_ANIMATE: Al aparecer en pantalla lo hará con una transición, perfecto para la ventana de creación del código pin

Se puede especificar las preferencias directamente en la declaración del componente

app:indicatorType="FIXED"
app:pinLength="4"

O bien en Kotlin de forma dinámica

inidicatorDots.setPingLenght(4) //Para asignar el tamño del pincode y restablecer el componente
indicatorDots.updateDot(1) //Para marcar el primer circulo

Recursos

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: