首页 > 解决方案 > 自定义视图具有隐藏实际美学的背景

问题描述

我正在开发一个应用程序屏幕,用户必须输入他们将通过 SMS 接收的 OTP 代码。我想要的外观如下(请注意,屏幕的其余部分看起来像预期的那样并且没有问题,所以我只会显示什么不起作用):

OTP输入设计

如您所见,此设计具有单独的圆角矩形,其中包含来自 OTP 代码的单个字符。对于我的问题,所有颜色和字体都不重要(因为它们可以在资源 xml 文件中进行修改。

然而,我当前的代码并没有像我预期的那样呈现出来,如下所示:

OTP 输入渲染

这是与视图关联的 kotlin 代码以及关联的attrs.xmldimens.xml文件:

OtpTextField.kt

package com.example.myapp.widget

import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.PaintDrawable
import android.text.InputFilter
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.widget.addTextChangedListener
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import com.example.myapp.Event
import com.example.myapp.util.extension.dismissKeyboard
import com.example.myapp.R
import com.example.myapp.databinding.ViewOtpEdittextBinding

class OtpTextField(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {

    private var binding: ViewOtpEdittextBinding =
        ViewOtpEdittextBinding.inflate(LayoutInflater.from(context), this)

    var length: Int = -1
        set(value) {
            if (field != value) {
                field = value
                applyLength()
            }
        }

    var otp: String
        get() = binding.otpInput.text?.toString().orEmpty()
        set(value) {
            binding.otpInput.setText(value)
        }

    var cellWidth: Int = -1
    var cellHeight: Int = -1
    var cellSpacing: Int = -1
    var cellCornerRadius: Int = -1
    var cellBackgroundColor: Int = -1

    private var otpDisplayTexts: List<TextView> = emptyList()

    private var _otpEntered = MediatorLiveData<Event<Unit>>()
    val otpEntered: LiveData<Event<Unit>> = _otpEntered

    init {
        applyUserAttributes(context, attrs)

        bindEditTextToDisplay()
    }

    private fun applyUserAttributes(context: Context, attrs: AttributeSet?) {
        val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.OtpTextField, 0, 0)
        length =
            typedArray.getInt(R.styleable.OtpTextField_otp_length, DEFAULT_OTP_LENGTH)
        cellWidth = typedArray.getDimension(
            R.styleable.OtpTextField_cell_width,
            resources.getDimension(R.dimen.otp_cell_width)
        ).toInt()
        cellHeight = typedArray.getDimension(
            R.styleable.OtpTextField_cell_height,
            resources.getDimension(R.dimen.otp_cell_height)
        ).toInt()
        cellSpacing = typedArray.getDimension(
            R.styleable.OtpTextField_cell_spacing,
            resources.getDimension(R.dimen.otp_cell_spacing)
        ).toInt()
        cellCornerRadius = typedArray.getDimension(
            R.styleable.OtpTextField_cell_corner_radius,
            resources.getDimension(R.dimen.otp_cell_corner_radius)
        ).toInt()
        val defaultBackgroundColor = resources.getColor(R.color.secondary, context.theme)
        cellBackgroundColor = typedArray.getColor(
            R.styleable.OtpTextField_cell_background_color,
            Color.argb(
                @Suppress("MagicNumber")
                26,
                Color.red(defaultBackgroundColor),
                Color.green(defaultBackgroundColor),
                Color.blue(defaultBackgroundColor)
            )
        )

        typedArray.recycle()
    }

    private fun applyLength() {
        binding.otpInput.filters = arrayOf(InputFilter.LengthFilter(length))

        val linearRoot = binding.otpDisplayedContentLinear

        linearRoot.removeAllViews()
        otpDisplayTexts = List(length) {
            applyCellAttributes(linearRoot.context)
                .also {
                    linearRoot.addView(it)
                }
        }
    }

    private fun applyCellAttributes(context: Context): TextView {
        return TextView(context).apply {
            layoutParams = LinearLayout.LayoutParams(cellWidth, cellHeight, 1.0f).apply {
                setMargins(cellSpacing / 2, 0, cellSpacing / 2, 0)
            }
            background = PaintDrawable(cellBackgroundColor).apply {
                setCornerRadius(cellCornerRadius.toFloat())
            }
            textSize = resources.getDimension(R.dimen.text_size_h5)
            setTypeface(typeface, Typeface.BOLD)
            setTextColor(resources.getColor(R.color.text_color_primary, context.theme))
            includeFontPadding = false
            gravity = Gravity.CENTER
        }
    }

    private fun bindEditTextToDisplay() {
        binding.otpInput.addTextChangedListener(afterTextChanged = { newText ->
            otpDisplayTexts.forEach { it.text = "" }
            newText?.toList()?.forEachIndexed { index, char ->
                otpDisplayTexts.getOrNull(index)?.text = "$char"
            }

            if (newText?.length == length) {
                binding.root.dismissKeyboard()
                _otpEntered.value = Event(Unit)
            }
        })
    }

    companion object {
        private const val DEFAULT_OTP_LENGTH = 6
    }
}

view_otp_edittext.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:parentTag="android.widget.FrameLayout">

    <!--
         This EditText is hidden from the user as the content is displayed somewhere else
         Only use it to manage keyboard management, focus etc ...
     -->
    <com.example.myapp.widget.NonSelectableEditText
        android:id="@+id/otp_input"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@null"
        android:textColor="@color/transparent" />

    <LinearLayout
        android:id="@+id/otp_displayed_content_linear"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

    </LinearLayout>
</merge>

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="OtpTextField">
        <attr name="otp_length" format="integer" />
        <attr name="cell_width" format="dimension" />
        <attr name="cell_height" format="dimension" />
        <attr name="cell_corner_radius" format="dimension" />
        <attr name="cell_spacing" format="dimension" />
        <attr name="cell_background_color" format="reference|color"/>
    </declare-styleable>
</resources>

尺寸.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- OTP Text Field default dimensions -->
    <dimen name="otp_cell_width">56dp</dimen>
    <dimen name="otp_cell_height">60dp</dimen>
    <dimen name="otp_cell_corner_radius">12dp</dimen>
    <dimen name="otp_cell_spacing">11dp</dimen>
</resources>

如您所见,该OtpTextField.kt文件具有我想要的外观的正确声明和定义,但白色背景以某种方式存在,我不确定为什么。任何帮助将不胜感激!

标签: androidxmlkotlinandroid-layoutandroid-custom-view

解决方案


推荐阅读