安卓系列之Jetpack架构(AAC):DataBinding基础篇

480 阅读3分钟

dataBinding,数据绑定。

优点

  1. 自动检测null值,避免空指针异常
  2. 防止内存泄漏
  3. 可双向绑定数据

引用

在app的build.gradle文件的Android{}里面添加如下代码:

dataBinding {
    enabled = true
}

相关语法

layout

xml文件最外层的标签。

data

xml文件数据位置的标签。

import

xml文件中引用文件的标签。内有alias(别名)。

variable

xml文件中的变量。内有name(命名)和type(类型)。

@{}

在大括号内写变量,对象变量或者表达式。

@={}

双向数据绑定。

/*变量*/
android:text="@{String.valueOf(position)}"
/*对象变量*/
android:text="@{user.name}"
/*表达式*/
android:text="@{user.name !=null ? user.name : "用户"}"

Null合并运算符??

左边内容不为空,则使用左边内容,否则使用右边内容。

android:text="@{user.displayName ?? user.lastName}"
/*等价于*/
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

引用视图

官方例子

<EditText
        android:id="@+id/example_text"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"/>
<TextView
        android:id="@+id/example_output"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{exampleText.text}"/>
  

二级页面使用对象

如果二级页面的variable的name是secondaryPageBean,那么一级页面include的布局可以直接引用传递对象,使用app开头或者bind开头,代码如下:
二级页面

<?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">

    <data>

        <variable
            name="secondaryPageBean"
            type="com.elaine.testdatabinding.secondarypage.SecondaryPageBean" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{secondaryPageBean.name}"
            android:textSize="24sp"
            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>

一级页面

<?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">

    <data>

        <variable
            name="bean"
            type="com.elaine.testdatabinding.secondarypage.SecondaryPageBean" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".secondarypage.SecondaryPageActivity">

        <include
            layout="@layout/layout_secondary_page"
            app:secondaryPageBean="@{bean}" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

自定义属性BindingAdapter

使用注解@BindingAdapter来定义方法,即可在xml文件中使用。
注意:在要引入自定义属性的时候,需要先把xml文件的最外层改为layout。MyBindingAdapter.java

package com.elaine.testdatabinding.testbindingadapter;

import android.text.TextUtils;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.databinding.BindingAdapter;

/**
 * bindingAdapter 文件
 * author: elaine
 * date: 2021/5/13
 */
public class MyBindingAdapter {

    /**
     * 单个参数
     *
     * @param imageView 图片控件
     * @param res       资源
     */
    @BindingAdapter("myImageRes")
    public static void setMyImageViewRes(ImageView imageView, int res) {
        imageView.setImageResource(res);
    }

    /**
     * 多个参数
     * requireAll = false 表示可以传一个参数
     *
     * @param textView       文字控件
     * @param content        内容
     * @param defaultContent 默认内容
     */
    @BindingAdapter(value = {"content""defaultContent"}, requireAll = false)
    public static void setMyTextViewContent(TextView textView, String content, String defaultContent) {
        if (!TextUtils.isEmpty(content)) {
            textView.setText(content);
        } else {
            textView.setText(defaultContent);
        }
    }
}

TestBindingAdapterActivity.java

package com.elaine.testdatabinding.testbindingadapter;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import com.elaine.testdatabinding.R;
import com.elaine.testdatabinding.databinding.ActivityTestBindingAdapterBinding;

public class TestBindingAdapterActivity extends AppCompatActivity {

    private ActivityTestBindingAdapterBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_test_binding_adapter);
        mBinding.setMyImageRes(R.mipmap.ic_launcher_round);
        mBinding.setContent("正式数据");
        mBinding.setDefaultContent("默认数据");
    }
}

activity_test_binding_adapter.xml

<?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">

    <data>

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

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

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

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".testbindingadapter.TestBindingAdapterActivity">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginTop="64dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.498"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:myImageRes="@{myImageRes}" />

        <TextView
            android:id="@+id/tv_content"
            content="@{content}"
            defaultContent="@{defaultContent}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/imageView" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

结合liveData,viewModel使用

TestWithLiveDataActivity.java

package com.elaine.testdatabinding.testwithlivedata;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;

import com.elaine.testdatabinding.R;
import com.elaine.testdatabinding.databinding.ActivityTestWithLiveDataBinding;

public class TestWithLiveDataActivity extends AppCompatActivity {

    private ActivityTestWithLiveDataBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_test_with_live_data);
        TestWithLiveDataViewModel viewModel = new ViewModelProvider(thisnew ViewModelProvider.AndroidViewModelFactory(getApplication())).get(TestWithLiveDataViewModel.class);
        mBinding.setViewModel(viewModel);
        mBinding.setLifecycleOwner(this);
    }
}

TestWithLiveDataViewModel.java

package com.elaine.testdatabinding.testwithlivedata;

import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

/**
 * author: elaine
 * date: 2021/5/13
 */
public class TestWithLiveDataViewModel extends ViewModel {
    private MutableLiveData<String> content;

    public MutableLiveData<StringgetContent() {
        if (content == null) {
            content = new MutableLiveData<>();
            content.setValue("默认数据");
        }
        return content;
    }

    public void setContent(String con) {
        content.setValue(con);
    }
}

activity_test_with_live_data.xml

<?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">

    <data>

        <variable
            name="viewModel"
            type="com.elaine.testdatabinding.testwithlivedata.TestWithLiveDataViewModel" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".testwithlivedata.TestWithLiveDataActivity">

        <TextView
            android:id="@+id/tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="120dp"
            android:text="@{viewModel.content}"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.524"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="200dp"
            android:onClick="@{() -> viewModel.setContent(`现在是正式数据`)}"
            android:text="更改数据"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.537"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_content" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

小tips

  1. 设计阶段可以用tools属性显示默认值
  2. xml文件中转义字符<用&lt表示

顺带提一下ViewBinding

viewBinding,视图绑定,用来替代findViewById。

引用

在app的build.gradle文件的Android{}里面添加如下代码:

viewBinding {
    enabled = true
}

对比findViewById

优点
  1. null安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。
  2. 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。
  3. 方便使用:不需要特地写控件变量和findViewById()方法。

对比ViewBinding

优点
  1. 编译更快:不需要处理注释。
  2. 易于使用。
缺点
  1. 不支持双向数据绑定。
  2. 不支持布局变量或布局表达式。

视图绑定方法

MainActicity.java

package com.elaine.testdatabinding;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import com.elaine.testdatabinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(activityMainBinding.getRoot());
    }
}

MainFragment.java

package com.elaine.testdatabinding;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.fragment.app.Fragment;

import com.elaine.testdatabinding.databinding.FragmentMainBinding;

public class MainFragment extends Fragment {
    private FragmentMainBinding fragmentMainBinding;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        fragmentMainBinding = FragmentMainBinding.inflate(inflater, container, false);
        return fragmentMainBinding.getRoot();
    }
}

上面的开关随下面的开关变换,同理下面的开关。

项目github地址

github.com/ElaineTaylo…

更多详情参考官方文档

developer.android.google.cn/topic/libra…

若帅哥美女对该系列文章感兴趣,可微信搜索公众号(木子闲集)关注更多更新文章哦,谢谢~