如何将Android View的margin属性绑定到ViewModel的String类型LiveData上?
问题原因
layout_marginTop 这类布局边距属性在DataBinding中默认只接收dimension类型值,你传入带dp后缀的字符串无法被系统自动解析,导致数据绑定类 FragmentBindingImpl 生成失败,才会抛出找不到符号的报错。你之前给TextView的text属性赋值正常,是因为text本身就支持字符串类型的入参。
另外你当前代码还存在一个逻辑错误:getLocationOnScreen 返回的是屏幕像素值,你直接给像素值拼接dp后缀会导致最终显示的位置完全不符合预期,在不同密度的设备上偏差会非常大。
解决方案
方案1:自定义BindingAdapter(改动最小,适配现有写法)
自定义数据绑定适配器处理字符串类型的margin值,在项目任意kt文件中添加以下代码即可:
import android.util.TypedValue import android.view.View import android.view.ViewGroup import androidx.databinding.BindingAdapter @BindingAdapter("android:layout_marginTop") fun setMarginTop(view: View, marginStr: String?) { marginStr ?: return if (marginStr.endsWith("dp")) { val dpValue = marginStr.removeSuffix("dp").toFloatOrNull() ?: return val marginPx = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, dpValue, view.resources.displayMetrics ).toInt() val layoutParams = view.layoutParams as ViewGroup.MarginLayoutParams layoutParams.topMargin = marginPx view.layoutParams = layoutParams } }
添加后无需修改你现有的XML、ViewModel代码,直接编译即可正常运行。
方案2:直接使用像素值绑定(推荐,逻辑更合理)
因为你获取的位置本身就是像素值,完全没必要转成带dp的字符串,直接修改代码用Int类型的LiveData即可:
- 修正ViewModel代码:
// 直接存像素值,无需转字符串 private var _location1 = MutableLiveData<Int>() val location1: LiveData<Int> = _location1 val setLocation1: (Int) -> Unit = { value -> _location1.value = value }
- 直接在XML中绑定,无需做任何转换,DataBinding默认支持Int类型的像素值赋值给margin属性:
<ImageView android:id="@+id/pointer1a" android:layout_width="42dp" android:layout_height="26dp" android:layout_marginTop="@{viewModel.location1}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
方案3:代码动态设置margin(兼容性最好)
如果不想用DataBinding处理属性,直接在Fragment中观察LiveData变化后手动设置margin即可:
- XML中删除
android:layout_marginTop="@{viewModel.location1}"的绑定逻辑 - 在Fragment的
onViewCreated中添加观察逻辑:
viewModel.location1.observe(viewLifecycleOwner) { marginPx -> val lp = binding.pointer1a.layoutParams as ViewGroup.MarginLayoutParams lp.topMargin = marginPx binding.pointer1a.layoutParams = lp }
额外优化建议
- 你当前在
onBindViewHolder中直接调用getLocationOnScreen时机不对,此时View还未完成布局,大概率会拿到0值,建议用post等待布局完成后再获取位置:
holder.textView1?.post { it.getLocationOnScreen(location1) liveDataSetters[0](location1[1]) }
- 不要直接把ViewModel的setter传入Adapter,容易造成内存泄漏,建议给Adapter定义回调接口,由Fragment实现回调后再调用ViewModel的方法。
内容的提问来源于stack exchange,提问作者LAO
相关产品推荐
相关产品推荐

