Настраиваемый атрибут представления и функции, недоступные из фрагмента (с использованием DataBinding)

Я пытаюсь здесь создать настраиваемое представление ConstraintLayout, обертывающее InputTextLayout, а также edittext вместе с textView. Однако функции настройки не работают при настройке во фрагменте (DataBinding). А также с edittext, я надеялся попробовать двустороннюю привязку для LiveData и Observer.

Попробуйте использовать Kotlin

Attrs.xml

<resources>
<declare-styleable name="ErrorCasesTextInputLayout">
    <attr name="isPass" format="boolean" />
    <attr name="errorCase" format="enum">
        <enum name="empty" value="0"/>
        <enum name="format" value="1"/>
        <enum name="identical" value="2"/>
    </attr>
    <attr name="text" format="string" value=""/>
    <attr name="hint" format="string" value=""/>
</declare-styleable>

Пользовательский макет просмотра

<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/custom_text_input_layout"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:layout_marginTop="20dp"
    android:layout_marginHorizontal="20dp"
    android:focusable="false"
    android:focusableInTouchMode="true"
    android:paddingBottom="2dp"
    android:background="@drawable/bg_edittext"
    app:hintEnabled="false"
    app:boxBackgroundMode="none"
    app:layout_constraintTop_toTopOf="parent">

    <com.google.android.material.textfield.TextInputEditText
        android:id="@+id/custom_edit_text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingStart="20dp"
        android:background="@null"
        android:imeOptions="actionGo"
        android:ellipsize="middle"
        android:singleLine="true"
        android:inputType="text"
        android:textSize="15sp">

    </com.google.android.material.textfield.TextInputEditText>

</com.google.android.material.textfield.TextInputLayout>

<TextView
    android:id="@+id/custom_error_message"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="This field is required"
    android:textSize="12sp"
    android:layout_marginBottom="5dp"
    android:textColor="@color/errorRed"
    android:visibility="gone"
    app:layout_constraintStart_toStartOf="@id/custom_text_input_layout"
    app:layout_constraintTop_toBottomOf="@id/custom_text_input_layout"/>

‹/Androidx.constraintlayout.widget.ConstraintLayout›

Пользовательский класс просмотра

class ErrorCasesTextInputLayout(context: Context, attrs: AttributeSet) :
ConstraintLayout(context, attrs) {

private var _errorCase: Int
private var _isPass: Boolean
private var _hint: String?
private var _text: String?



init {

    LayoutInflater.from(context)
        .inflate(R.layout.custom_error_case_text_input_layout, this, true)

    attrs.let {
        val attributes =
            context.obtainStyledAttributes(it, R.styleable.ErrorCasesTextInputLayout)

        attributes.apply {
            try {
                _isPass = this.getBoolean(R.styleable.ErrorCasesTextInputLayout_isPass, true)
                _errorCase = this.getInteger(R.styleable.ErrorCasesTextInputLayout_errorCase, 0)
                _hint = this.getString(R.styleable.ErrorCasesTextInputLayout_hint)
                _text = this.getString(R.styleable.ErrorCasesTextInputLayout_text)
                
                mSetErrorCase()
                mSetPass()
                mSetHint()

            } finally {
                recycle()
            }
        }

    }
}

fun setErrorCase(caseType: Int) {
    _isPass = false
    _errorCase = caseType
    invalidate()
    requestLayout()
}

private fun mSetHint() {
    val editText = findViewById<TextInputEditText>(R.id.custom_edit_text)

    if (_hint != null ) {
        editText.hint = _hint
    }

}

private fun mSetPass() {
    val layout = findViewById<View>(R.id.custom_text_input_layout)

    if (_isPass) {
        layout.setBackgroundResource(R.drawable.bg_edittext)
    } else {
        layout.setBackgroundResource(R.drawable.bg_edittext_error)
    }

}

private fun mSetErrorCase() {
    val errorText = findViewById<TextView>(R.id.custom_error_message)
    val layout = findViewById<View>(R.id.custom_text_input_layout)
    when (_errorCase) {
        0 -> {
            errorText.text = EdittextErrorCase.EMPTY.errorMessage
            errorText.visibility = View.VISIBLE
            layout.setBackgroundResource(R.drawable.bg_edittext_error)
        }
        1 -> {
            errorText.text = EdittextErrorCase.FORMAT.errorMessage
            errorText.visibility = View.VISIBLE
            layout.setBackgroundResource(R.drawable.bg_edittext_error)
        }
        2 -> {
            errorText.text = EdittextErrorCase.UNIDENTICAL.errorMessage
            errorText.visibility = View.VISIBLE
            layout.setBackgroundResource(R.drawable.bg_edittext_error)
        }
    }
}

fun setPass(pass: Boolean) {
    _isPass = pass
    invalidate()
    requestLayout()
}

fun setText(text: String) {
    _text = text
    invalidate()
    requestLayout()
}

fun setHint(hint: String) {
    _hint = hint
    invalidate()
    requestLayout()
}

fun getCurrentErrorCase(): Int {
    return _errorCase
}


@InverseBindingMethods(InverseBindingMethod(
    type = ErrorCasesTextInputLayout::class,
    attribute = "bind:text",
    event = "bind:textAttrChanged",
    method = "bind:getText")
)
class CustomEditTextBinder {
    companion object {
        @BindingAdapter("textAttrChanged")
        @JvmStatic
        fun setListener(view: ErrorCasesTextInputLayout, listener: InverseBindingListener) {

            val input: TextInputEditText = view.findViewById(R.id.custom_edit_text)
            input.addTextChangedListener(object : TextWatcher{

                override fun afterTextChanged(p0: Editable?) {
                    listener.onChange()
                }

                override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                }

                override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                }
            })
        }

        @BindingAdapter("text")
        @JvmStatic
        fun setTextValue(view: ErrorCasesTextInputLayout, value: String?) {
            if (value != view._text) view.setText(value.toString())
        }

        @InverseBindingAdapter(attribute = "text", event = "textAttrChanged")
        @JvmStatic
        fun getTextValue(view: ErrorCasesTextInputLayout): String? = view._text
    }

}

}

Рабочий фрагмент

class ChangeNumberFragment : Fragment() {

lateinit var binding: FragmentChangeNumberBinding
private val viewModel by viewModels<ChangeNumberViewModel> { getVmFactory() }

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    super.onCreate(savedInstanceState)
    binding = FragmentChangeNumberBinding.inflate(inflater, container, false)
    binding.viewModel = viewModel
    binding.lifecycleOwner = this

    binding.editTextNumber.setHint("Enter New Number")
    binding.editTextNumber.setPass(true)

    viewModel.newNumber.observe(viewLifecycleOwner, Observer {

        if (it.isNullOrEmpty()) {
            binding.editTextNumber.setErrorCase(1)
        } else {
            Logger.i(it)
            binding.editTextNumber.setPass(true)
        }
    })

    return binding.root
}

}

Двусторонняя привязка с liveData

app:text="@={viewModel.newNumber}"

person Willy Chuang    schedule 18.11.2020    source источник
comment
Первое, что мне приходит в голову, это то, что DataBinding раздувается после возврата View, поэтому при вызове binding.customView.setText () представление еще не создано. Но мне все еще не терпится узнать, как выполнить двустороннюю привязку и программно установить атрибуты.   -  person Willy Chuang    schedule 18.11.2020


Ответы (1)


После дня исследования и попытки ошибки мне удалось добиться успеха в двусторонней привязке.

То, что я не назначил представление editText методу getText, было очевидной ошибкой и с неправильной формой обработки null для setText.

class CustomEditTextBinder {

    companion object {
        @JvmStatic
        @BindingAdapter(value = ["textAttrChanged"])
        fun setListener(view: ErrorCasesTextInputLayout, listener: InverseBindingListener?) {
            if (listener != null) {
                view.custom_edit_text.addTextChangedListener(object : TextWatcher {
                    override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {

                    }

                    override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {

                    }

                    override fun afterTextChanged(editable: Editable) {
                        listener.onChange()
                    }
                })
            }
        }

        @JvmStatic
        @InverseBindingAdapter(attribute = "text")
        fun getText(view: ErrorCasesTextInputLayout): String {
            return view.custom_edit_text.text.toString()
        }

        @JvmStatic
        @BindingAdapter("text")
        fun setText(view: ErrorCasesTextInputLayout, text: String?) {
            text?.let {
                if (it != view.custom_edit_text.text.toString()) {
                    view.custom_edit_text.setText(it)
                }
            }
        }
    }

}

Надеюсь, это поможет тем, кто сталкивается с той же проблемой.

person Willy Chuang    schedule 19.11.2020