使用自定义视图的Android数据绑定

86

Android数据绑定指南讨论了在活动或片段中绑定值的方法,但是否有一种方法可以使用自定义视图执行数据绑定?

我想做类似于以下的事情:

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.mypath.MyCustomView
        android:id="@+id/my_view"
        android:layout_width="match_parent"
        android:layout_height="40dp"/>

</LinearLayout>

使用my_custom_view.xml自定义视图:

<layout>

<data>
    <variable
        name="myViewModel"
        type="com.mypath.MyViewModelObject" />
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{myViewModel.myText}" />

</LinearLayout>

</layout>

虽然似乎可以通过在自定义视图上设置自定义属性来实现这一点,但如果有很多值需要绑定,这将很快变得繁琐。

有没有好的方法来实现我想做的事情?


你尝试过在自定义视图(MyCustomView)中填充视图后绑定数据吗? - Meiyappan Kannappa
我尝试了几种方法来完成这个任务,但绑定函数似乎需要在活动或片段中包含的信息。他们的示例没有提供如何在自定义视图中完成此操作的示例。非常感谢能提供一个简短的代码片段来完成此操作。 - Dave
FYI:按照惯例,XML布局文件的命名采用下划线表示法。 - IgorGanapolsky
是的,为了更清晰,我已经更正了文件名。 - Dave
本文介绍了使用自定义视图和自定义属性进行双向数据绑定的过程:https://medium.com/@douglas.iacovelli/custom-two-way-databinding-made-easy-f8b17a4507d2 - Ricardo
8个回答

110
在您的自定义视图中,按照通常的方式填充布局并为您要设置的属性提供一个setter方法:
private MyCustomViewBinding mBinding;
public MyCustomView(...) {
    ...
    LayoutInflater inflater = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mBinding = MyCustomViewBinding.inflate(inflater);
}

public void setMyViewModel(MyViewModelObject obj) {
    mBinding.setMyViewModel(obj);
}

然后在你使用它的布局中:

<layout xmlns...>
    <data>
        <variable
            name="myViewModel"
            type="com.mypath.MyViewModelObject" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.mypath.MyCustomView
            android:id="@+id/my_view"
            app:myViewModel="@{myViewModel}"
            android:layout_width="match_parent"
            android:layout_height="40dp"/>

    </LinearLayout>
</layout>
在上述情况中,由于存在名为setMyViewModel的setter方法,因此将为app:myViewModel创建自动绑定属性。

3
MyCustomViewBinding是数据绑定框架的一部分,通过注释处理器生成。XML文件(Dave使用的是MyCustomView.xml,但他真正想要的是my_custom_view.xml,因为资源文件应该全部小写)会导致默认的Binding类名称为MyCustomViewBinding(驼峰命名法,加上Binding后缀)。你也可以自定义Binding类名称:http://developer.android.com/tools/data-binding/guide.html#custom_binding_class_names - George Mount
2
在 https://dev59.com/tVoV5IYBdhLWcg3wmPvp#36068337 中描述的解决方案(也由George Mount发布)是对我有效的。 - Bitcoin Cash - ADA enthusiast
1
我们在构造函数中错误地留下了 inflate(getContext(), R.layout.my_custom_view, this);,导致了奇怪的问题(最终我们膨胀了两个视图而不是一个)。 - Muxa
12
使用LayoutInflater.from(context),它更简洁,不需要类型转换。 - Jan Kalfus
16
请记得定义并附加到根视图:inflate(inflater, this, true)。在我的情况下,我需要它来使自定义视图可见。 - bearlysophisticated
显示剩余9条评论

16

即使只有合并的父级被设置为"this"并且附加到父级为true,数据绑定也可以工作。

binding = DataBindingUtil.inflate(inflater, R.layout.view_toolbar, this, true)

14

首先,如果此自定义视图已经被包括在其他布局中(例如活动等),请勿执行此操作。否则,您将收到有关标记的异常不正确的价值的异常。数据绑定已经对其运行了绑定,所以您可以设置。

您尝试使用onFinishInflate来运行绑定了吗?(Kotlin示例)

override fun onFinishInflate() {
    super.onFinishInflate()
    this.dataBinding = MyCustomBinding.bind(this)
}

请记住,如果您在视图中使用绑定,它将无法以编程方式创建,即使您可以支持两者,也会非常复杂。


上帝保佑你,伙计:)) - Hossein

11

按照george提出的解决方案,Android Studio中的图形编辑器无法再渲染自定义视图。原因是以下代码中实际上没有填充任何视图:

public MyCustomView(...) {
    ...
    LayoutInflater inflater = (LayoutInflater)
        context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mBinding = MyCustomViewBinding.inflate(inflater);
}

我想绑定可以处理膨胀,但是图形编辑器不喜欢它。

在我的具体用例中,我想要绑定单个字段而不是整个视图模型。我想到了(kotlin即将到来):

class LikeButton @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

    val layout: ConstraintLayout = LayoutInflater.from(context).inflate(R.layout.like_button, this, true) as ConstraintLayout

    var numberOfLikes: Int = 0
      set(value) {
          field = value
          layout.number_of_likes_tv.text = numberOfLikes.toString()
      }
}

赞按钮由图像和文本视图组成。文本视图保存点赞数,我希望通过数据绑定设置它。

通过在以下xml中将numberOfLikes的setter作为属性使用,数据绑定会自动建立关联:

<views.LikeButton
  android:id="@+id/like_btn"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:numberOfLikes="@{story.numberOfLikes}" />

更多阅读:https://medium.com/google-developers/android-data-binding-custom-setters-55a25a7aea47


我找到了关于它的报告问题:https://issuetracker.google.com/issues/150887082 - Arcao

6
今天,我想在我的自定义视图类上使用数据绑定。但我不知道如何将数据绑定到我的类上。所以我在StackOverflow上搜索答案。 首先,我尝试了以下方法:
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
BottomBarItemCustomViewBinding binding = BottomBarItemCustomViewBinding.inflate(inflater);

但是,我发现这对我的代码不起作用。

所以我换了另一种方法:

LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
BottomBarItemCustomViewBinding binding = DataBindingUtil.inflate(inflater, R.layout.bottom_bar_item_custom_view, this, true);

对我来说它正在运行。

完整的代码如下:

bottom_bar_item_custom_view.xml

<data>

    <variable
        name="contentText"
        type="String" />

    <variable
        name="iconResource"
        type="int" />

</data>

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

    <ImageView
        android:id="@+id/bottomBarItemIconIv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginTop="2dp"
        android:src="@{iconResource}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/bottomBarItemContentTv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text="@{contentText}"
        android:textColor="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/bottomBarItemIconIv" />


</androidx.constraintlayout.widget.ConstraintLayout>

BottomBarItemCustomView.java

public class BottomBarItemCustomView extends ConstraintLayout {

public BottomBarItemCustomView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context, attrs);
}

private void init(Context context, AttributeSet attrs) {
    //use dataBinding on custom view.
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    BottomBarItemCustomViewBinding binding = DataBindingUtil.inflate(inflater, R.layout.bottom_bar_item_custom_view, this, true);

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BottomBarItemCustomView);
    int iconResourceId = typedArray.getResourceId(R.styleable.BottomBarItemCustomView_bottomBarIconResource, R.drawable.my_account_icon);
    binding.setIconResource(iconResourceId);

    String contentString = typedArray.getString(R.styleable.BottomBarItemCustomView_bottomBarContentText);
    if (contentString != null) {
        binding.setContentText(contentString);
    }

    typedArray.recycle();
}

希望对您有用!


2
在 Kotlin 中,我们可以直接使用 ViewBinding:
class BenefitView(context: Context, attrs: AttributeSet) : ConstraintLayout(context, attrs) {

    init {
        val binding = BenefitViewBinding.inflate(LayoutInflater.from(context), this, true)
        val attributes = context.obtainStyledAttributes(attrs, R.styleable.BenefitView)
        binding.image.setImageDrawable(attributes.getDrawable(R.styleable.BenefitView_image))
        binding.caption.text = attributes.getString(R.styleable.BenefitView_text)
        attributes.recycle()

    }
}

0

这里已经有一些不错的答案了,但我想提供我认为最简单的方法。

使用布局标签创建自定义控件,就像创建其他任何布局一样。例如,可以参考下面的工具栏。在每个活动类中都可以使用它。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto">

<data>
    <variable name="YACustomPrefs" type="com.appstudio35.yourappstudio.models.YACustomPreference" />
</data>

<android.support.design.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/YATheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            app:popupTheme="@style/YATheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

</android.support.design.widget.CoordinatorLayout>

现在,这个自定义布局就是每个Activity的孩子。您只需在onCreate绑定设置中将其视为此类即可。
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    binding.yaCustomPrefs = YACustomPreference.getInstance(this)
    binding.toolbarMain?.yaCustomPrefs = YACustomPreference.getInstance(this)
    binding.navHeader?.yaCustomPrefs = YACustomPreference.getInstance(this)

    binding.activity = this
    binding.iBindingRecyclerView = this
    binding.navHeader?.activity = this

    //local pointer for notify txt badge
    txtNotificationCountBadge = txtNotificationCount

    //setup notify if returned from background so we can refresh the drawer items
    AppLifeCycleTracker.getInstance().addAppToForegroundListener(this)

    setupFilterableCategories()
    setupNavigationDrawer()
}

注意,我在设置父级内容的同时也设置了子级内容,并且通过点符号访问进行了所有操作。只要文件被包围在布局标签中并且已命名,就很容易完成。

现在,如果自定义类具有其自己的相关代码膨胀,则可以轻松地在其onCreate或构造函数中执行自己的绑定,但您已经明白了。如果您有自己的类,请将以下内容放入构造函数中以匹配其命名绑定类。它遵循布局文件帕斯卡大小写的命名约定,因此很容易找到和自动填充。

    LayoutInflater inflater = (LayoutInflater)
    context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mBinding = NameOfCustomControlBinding.inflate(inflater);

希望能有所帮助。


0

当我尝试在我的宿主片段(嵌套片段UI/UX)中向一个LinearLayout添加子视图时,我遇到了同样的问题。

这是我的解决方案:

var binding: LayoutAddGatewayBinding? = null
binding = DataBindingUtil.inflate(layoutInflater, R.layout.layout_add_gateway,
            mBinding?.root as ViewGroup?, false)
binding?.lifecycleOwner=this
val nameLiveData = MutableLiveData<String>()
nameLiveData.value="INTIAL VALUE"
binding?.text=nameLiveData

这里 mBinding 是子碎片的 ViewDataBinding 对象,我已经使用了 nameLiveData 进行双向数据绑定。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接