Fragment中使用DataBinding的自定义ConstraintLayout控件不显示如何解决
问题原因
你当前的写法存在几个核心问题,直接导致控件无法正常加载显示:
- 自定义控件
MyEditText本身继承自ConstraintLayout,但加载内部布局时inflate出的视图对象没有挂载到当前控件的视图树中:inflate方法第三个参数attachToRoot传了false,仅创建了布局实例却没有添加到MyEditText自身,自然不会被渲染。 - 初始化逻辑设计错误:把内部布局加载逻辑放到了单独的
setCustomView方法中,需要外部Fragment手动调用,既破坏了自定义控件的封装性,调用时传入的container还是Fragment的外层容器,不是MyEditText自身,就算执行addView也会把内部布局加到错误的父节点上。 - 命名不匹配:代码里inflate用的布局资源是
R.layout.my_edit_text,但绑定类是EditTextMyBinding,DataBinding生成的绑定类名和布局文件名一一对应,命名不匹配会直接导致绑定失效。
正确实现步骤
- 第一步:重构MyEditText的初始化逻辑,把布局加载、属性初始化的逻辑全部移到构造函数的init块中,不需要对外暴露初始化方法,保证控件在被inflate的时候自动完成自身初始化。
正确的Kotlin代码如下:
class MyEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { private lateinit var binding: EditTextMyBinding private lateinit var hint: String private lateinit var defaultError: String init { // 加载内部布局时直接把当前MyEditText作为父容器,attachToRoot传true自动完成挂载 binding = DataBindingUtil.inflate( LayoutInflater.from(context), R.layout.edit_text_my, // 注意这里布局名要和绑定类对应,edit_text_my.xml对应EditTextMyBinding this, true ) // 读取自定义属性、初始化交互逻辑 initCustomAttrs(attrs) setupView() } private fun initCustomAttrs(attrs: AttributeSet?) { attrs ?: return val typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyEditText) hint = typedArray.getString(R.styleable.MyEditText_hint) ?: "" defaultError = typedArray.getString(R.styleable.MyEditText_defaultError) ?: "" typedArray.recycle() // 把属性值设置给内部控件 binding.hintText.text = hint } private fun setupView() { // 你之前实现的炫酷动画、输入监听、焦点变化逻辑都放这里,直接通过binding引用内部控件即可 } // 对外暴露业务方法,比如获取输入内容、触发错误提示等 fun getInputContent(): String = binding.inputEdit.text?.toString()?.trim() ?: "" fun showErrorTip(errorMsg: String = defaultError) { binding.errorText.text = errorMsg binding.errorText.visibility = VISIBLE // 错误提示动画逻辑 } }
- 第二步:优化内部布局
edit_text_my.xml的根节点,用merge标签减少冗余布局层级,注意DataBinding布局需要外层包裹<layout>标签:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <merge> <!-- 你自定义控件里的输入框、提示文字、错误提示等控件直接放在merge下 --> <EditText android:id="@+id/inputEdit" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@null" android:paddingTop="24dp" app:layout_constraintTop_toTopOf="parent"/> <TextView android:id="@+id/hintText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/gray_666" app:layout_constraintStart_toStartOf="@id/inputEdit" app:layout_constraintTop_toTopOf="@id/inputEdit"/> <TextView android:id="@+id/errorText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/red" android:visibility="gone" app:layout_constraintTop_toBottomOf="@id/inputEdit"/> </merge> </layout>
这里用merge标签的原因是MyEditText本身已经是ConstraintLayout,内部布局不需要再套一层ConstraintLayout,merge标签下的控件会直接挂载到MyEditText根节点上,减少一层无用布局嵌套,提升渲染性能。
- 第三步:删除Fragment中多余的
setCustomView调用,自定义控件完成自包含封装后,外部使用不需要做任何额外初始化,直接通过Fragment的DataBinding实例调用控件即可:
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = FragmentLoginBinding.inflate(layoutInflater) // 直接使用控件即可,不需要额外调用初始化方法 binding.loginBtn.setOnClickListener { val email = binding.userEmailEt.getInputContent() if (email.isEmpty()) { binding.userEmailEt.showErrorTip("请输入邮箱地址") } } return binding.root }
内容的提问来源于stack exchange,提问作者Seb
相关产品推荐
相关产品推荐

