Android DataBinding& MVVM - 使用相同视图模型为不同条件使用相同的布局文件

时间:2016-09-07 08:25:37

标签: android android-layout android-studio android-databinding

我一直在开发一款带有数据绑定功能的应用程序。 MVVM。

我正在尝试在横向模式下为我的应用使用替代布局。我有:

layout/fragment_content.xml
layout-land/fragment_content.xml

两种布局都具有不同外观的相同视图,并从相同的视图模型获取提要,如下所示:

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

<data class="MyBinding">

    <variable
        name="viewModel"
        type="com.myapp.package.viewModel.VMFirst"/>

    <variable
        name="controlModel"
        type="com.myapp.package.viewModel.VMSecond"/>
</data>

<DIFFERENT CONTENT HERE>

两种布局都存在所有视图和ID。

嗯,问题是,它没有编译,错误只是"cannot find symbol method getViewModel"而另一个变量是getter。

到目前为止我尝试了什么:

  1. 使用布局和布局 - 土地文件夹(失败,错误在上面解释)

  2. 使用我在此处Use Layout Aliases找到的布局别名Issue 199344: Data binding does not work with layout aliases。尝试这种方法时,我没有更改xml文件中的任何内容。这也失败了,错误是Could not write to com.myapp.package.databinding.MyBinding

  3. 是否无法在多个布局文件中使用数据绑定data标记?在使用数据绑定时,我应该使用什么来为不同的状态使用不同的布局?谢谢!

    修改:删除class="MyBinding"并未更改错误。

3 个答案:

答案 0 :(得分:1)

我在我的应用程序中大量使用MVVM,并且还围绕它构建了一个库。

我遵循惯例,即每个XML中都有一个ViewModel。此外,viewmodel变量的名称在所有XML中都是相同的。

因此,在您的情况下,您可以创建另一个包含VMFirstVMSecond的ViewModel类。

public class ParentVM {
   VMFirst first;
   VMSecond second;
}

XML(纵向和横向)都具有相同的名称,例如activity_main.xml

<layout>
    <data>
      <variable 
          type="ParentViewModel"
          name="vm"/>
    </data>

然后在MainActivity代码中不需要检查。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ViewDataBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    binding.setVariable(BR.vm, new ParentViewModel());
}

这很有效。

单个ViewModel

的优点

事实上,因为我在所有xmls中都遵循相同的变量名,所以我能够将绑定逻辑包含在基类MvvmActivity本身中。所以,我的所有活动都是:

public class MainActivity extends MvvmActivity {

    @NonNull
    @Override
    protected ViewModel createViewModel() {
        return new MainViewModel();
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }
}

MvvmActivity实施:MvvmActivity.java

保持常量数据绑定变量的另一个好处是,您可以使用XML本身设置RecyclerView或ViewPager适配器。有关详细信息,请参阅Setup RecyclerView from XML

答案 1 :(得分:1)

如果有人搜索此问题,两年后我尝试做同样的事情,发现现在一切正常。

我在activity_mainlayout下创建了一个布局文件layout_sw600dp。以下是layout资源下的布局:

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

    <variable
        name="small_variable"
        type="Integer"/>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/myRoot"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <View
            android:id="@+id/small_square"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:background="@android:color/holo_blue_bright"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

这是layout_sw600dp文件夹下的布局:

<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <variable
        name="big_variable"
        type="Long"/>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/myRoot"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <View
            android:id="@+id/big_square"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:background="@android:color/holo_blue_bright"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

两者都有一个视图,但每个视图都有不同的ID:small_squarebig_square

我在手机和平​​板电脑上运行该项目。这是我的发现:

  • DataBinding创建一个实现,该实现在不同布局文件夹中相同名称的所有布局文件下包含 ALL 视图和变量。
  • 所有布局中存在的视图均不可为空,其他所有视图均可为空。在上述XML中,使用来自Kotlin的绑定时,myRoot不能为空的视图,而big_squaresmall_square 是可以为空的视图。变量是否可以为空,无论它们是否存在于所有布局中(这是预期行为)。
  • 您不能在每个文件中命名不同的绑定类。它必须相同(在以上示例中为MainBinding,或者如果您未默认定义为LayoutResourceName + Binding)。
  • 关于绑定实现的视图和变量的名称为驼峰式。因此,我的small_variablesmall_square在代码方面是binding.smallVariablebinding.smallSquare
  • 使用Kotlin,您可以只使用binding.bigSquare?.operation之类的视图,这很好,您无需事先检查它是平板电脑还是手机,或者视图是否为空。
  • 只是一个提示,即使不使用其所在的布局,您也可以分配binding字段。您仍然可以对代码说binding.smallVariable = 3,它将完成分配并保存值。我认为要小心一点。

答案 2 :(得分:0)

  

默认情况下,将根据布局文件的名称生成Binding类,将其转换为Pascal大小写和后缀&#34; Binding&#34;它。上面的布局文件是main_activity.xml,因此generate类是MainActivityBinding。 - Binding Data

并在编译时生成。

所以,用java代码选择不同的布局。

layout/
R.layout.activity_main
R.layout.activity_main_tablet

values/
    <bool name="is_mobile">true</bool>
    <bool name="is_tablet">false</bool>
values-w820dp/
    <bool name="is_mobile">false</bool>
    <bool name="is_tablet">true</bool>



@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if(getResources().getBoolean(R.bool.is_mobile)) {
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);            
    } else {
        ActivityMainTabletBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main_tablet);   
    }

}