Я пытаюсь здесь создать настраиваемое представление 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}"