Android JetPack组件(六)DataBinding

Android JetPack组件(六)DataBinding,第1张

概述AndroidJetpack组件系列文章:AndroidJetpack组件(一)LifeCycleAndroidJetpack组件(二)NavigationAndroidJetpack组件(三)ViewModelAndroidJetpack组件(四)LiveDataAndroidJetpack组件(五)RoomAndroidJetPack组件(六)DataBindingAndroidJetpack组件(七)PagingAndroidJetpac

AndroID Jetpack组件系列文章:
Android Jetpack组件(一)LifeCycle
Android Jetpack组件(二)Navigation
Android Jetpack组件(三)ViewModel
Android Jetpack组件(四)LiveData
Android Jetpack组件(五)Room
Android JetPack组件(六)DataBinding
Android Jetpack组件(七)Paging
Android Jetpack组件(八)WorkManager

首语

AndroID 布局文件通常只负责UI的布局工作,页面通过setContentVIEw()关联布局文件,再通过UI控件的ID找到控件,接着在页面中通过代码对控件进行 *** 作,因此,页面承担了很大的工作量,为了减轻页面的工作量,Google推出了DataBinding。使得页面和布局之间的耦合度降低。

优势项目更加简介,代码可读性更高。不再需要findVIEwByID()。布局文件可以包含简单的业务逻辑。

DataBinding是我第一个使用的Jetpack的组件,用起来是真的舒服。之前为了繁杂的findVIEwByID(),一直使用ButterKnife(参考之前文章)来代替这些工作。现在官方已经不推荐使用它了,且停止维护。因此,使用DataBinding来代替它。

简单配置

要想使用DataBinding,首先需要在app.gradle中启用它。

androID {    .....    dataBinding{        enabled=true    }}

接着修改布局文件,需要在布局外层添加<layout></layout>标签,将鼠标移动至布局文件根目录的位置,使用快捷键(alt+enter),选择“Convert to data binding layout”选项,就会@R_301_6796@DataBinding布局文件。

<?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>    </data>    <androIDx.constraintlayout.Widget.ConstraintLayout        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent">        <TextVIEw            androID:ID="@+ID/text_home"            androID:layout_wIDth="match_parent"            androID:layout_height="wrap_content"            androID:layout_marginStart="8dp"            androID:layout_margintop="8dp"            androID:layout_marginEnd="8dp"            androID:textAlignment="center"            androID:textSize="20sp"            androID:text="0"            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>
使用

经过简单配置以后,就可以实例化布局文件了,我们删除掉传统的setContentVIEw(),通过 DataBindingUtil.setContentVIEw()来实例化文件。实例化返回的布局文件对象,名字和布局文字名字一致,遵循大驼峰命名规则,后面加上Binding。然后通过binding对象得到控件,控件命名遵循小驼峰规则。

ActivityMainBinding binding=DataBindingUtil.setContentVIEw(this,R.layout.activity_main);binding.textHome.setText("hello databinding!");
数据绑定

如何将数据传递到布局文件中呢?首先,在布局文件中定义布局变量<variable/>,指定对象的名字和类型,当然数据的 *** 作在<data></data>标签里。data标签里用于放在布局文件中各个UI控件所需要的数据,这些数据类型可以是自定义类型,也可以是基本类型。

<data>        <variable            name="book"            type="com.yhj.jetpackstudy.Book" />        <variable            name="number"            type="Integer" /></data>
public class Book {    private int ID;    private String Title;    private String author;}

有时我们需要在布局文件中引入一些Java工具类或静态类,处理一些简单的逻辑在布局中,我们可以使用<import />标签导入。使用alias,当类名有冲突时,其中一个类可使用别名重命名。默认导入java.lang.*

<data>          <import type="com.yhj.jetpackstudy.ui.home.Constants"alias="rename"/></data>

布局中的数据绑定使用“@{}”语法写入属性中,通过布局表达式的形式设置TextVIEw的text。

<TextVIEw androID:layout_wIDth="wrap_content"          androID:layout_height="wrap_content"          androID:text="@{book.Title}" />          <TextVIEw androID:layout_wIDth="wrap_content"          androID:layout_height="wrap_content"          androID:text="@{Constants.APP_ID}" />          

DataBinding为了方便使用,对布局变量提供了Setter类,因此,在Activity中,通过setBook(),将Book对象传递给布局变量。

Book book = new Book(0, "androID", "yhj");//BR类似于AndroID中的R类,由DataBinding@R_301_6796@,用于存放所有布局变量的ID。//DataBinding为了方便使用提供了Setter类,直接使用setXxx()//binding.setvariable(BR.book,book);binding.setBook(book);

绑定后,就不需要再Activity中设置内容了,实现了布局与页面的解耦。
DataBinding具有Null校验,如果绑定值为null,则分配默认值null,如果类型为int,默认值为0。

表达式语言

在布局中可以包含简单的数据逻辑,可以使用以下运算符和关键字。

算术运算符 + - / * %字符串连接运算符 +逻辑运算符 && ||二元运算符 & | ^一元运算符 + - ! ~移位运算符 >> >>> <<比较运算符 == > < >= <=(请注意,< 需要转义为 <)instanceof分组运算符 ()字面量运算符 - 字符、字符串、数字、null类型转换方法调用字段访问数组访问 []三元运算符 ?:Null 合并运算符视图引用
	androID:text="@{String.valueOf(index + 1)}"    androID:visibility="@{age > 13 ? VIEw.GONE : VIEw.VISIBLE}"    androID:Transitionname='@{"image_" + ID}'	    <!--Null 合并运算符-->    androID:text="@{user.displayname ?? user.lastname}"	    <!--集合--> 	androID:text="@{List[index]}" 	<!--字符串字面量,两种均可--> 	androID:text="@{map[`firstname`]}" 	androID:text='@{map["firstname"]}' 	<!--资源--> 	androID:padding="@{large? @dimen/largepadding : @dimen/smallpadding}"    <!--TextVIEw视图引用同一布局中的EditText视图-->    <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}"/>
事件响应

DataBinding在布局文件中除了绑定数据外,还能够响应用户事件。
首先创建一个事件类,用于接收和响应onClick()事件。

public class HandleListener {    private Context context;    public HandleListener(Context context) {        this.context = context;    }    public voID onClicked(VIEw vIEw) {        Toast.makeText(context, "the button was clicked!", Toast.LENGTH_SHORT).show();    }}

<data>标签中定义布局变量。通过布局表达式,调用onClicked().

 <variable            name="handler"            type="com.yhj.jetpackstudy.ui.home.HandleListener" />             androID:onClick="@{handler::onClicked}"

最后在Activity中将Activity与布局绑定,实例化HandleListener类,传入布局文件。

 binding.setHandler(new HandleListener(this));
二级页面的绑定

对于布局层次结构复杂的页面,我们会将部分布局独立成一个单独的布局文件,通过<include />标签去引用单独的布局文件,也被称为二级页面。
我们在一级页面中绑定数据后,如何将数据传递到二级页面呢?

 <!--自定义的命名空间--> <!--xmlns:bind="http://schemas.androID.com/apk/res-auto"--> <!--bind:book="@{book}"--> <data>        <variable            name="book"            type="com.yhj.jetpackstudy.Book" />    </data>    <androIDx.constraintlayout.Widget.ConstraintLayout        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        tools:context=".ui.home.HomeFragment">        <include            app:book="@{book}"            androID:ID="@+ID/include"            layout="@layout/fragment_dashboard"/>    </androIDx.constraintlayout.Widget.ConstraintLayout>

命名空间app名字可以自定义,布局变量book也是命名空间xmlns:app的一个属性。一级页面正是通过命名空间xmlns:app引用布局变量book,将数据传递给二级页面的。
需要注意的是,数据绑定不支持include作为merge元素的直接子布局。merge是用来帮助在视图树中减少重复布局的。
在二级页面中,我们需要定义一个和一级页面相同的布局变量,用于接收传递过来的数据。然后就可以使用book进行数据绑定了。

 <data>        <variable            name="book"            type="com.yhj.jetpackstudy.Book" /> </data>  <TextVIEw            androID:ID="@+ID/text_home"            androID:layout_wIDth="match_parent"            androID:layout_height="wrap_content"            androID:text="@{book.Title}"            app:layout_constraintStart_toStartOf="parent"            app:layout_constrainttop_totopOf="parent" />
BindingAdapter的原理

DataBinding为我们生成数据绑定需要的各种类,其中包含了大量的静态方法,这些静态方法都有@BindingAdapter注解,在注解中的别名对应UI控件在布局文件中的属性。

public class VIEwBindingAdapter {   ....    @BindingAdapter({"androID:padding"})    public static voID setpadding(VIEw vIEw, float paddingfloat) {        final int padding = pixelsToDimensionPixelSize(paddingfloat);        vIEw.setpadding(padding, padding, padding, padding);    }}
@RestrictTo(RestrictTo.Scope.liBRARY)@BindingMethods({        @BindingMethod(type = androID.Widget.ImageVIEw.class, attribute = "androID:tint", method = "setimageTintList"),        @BindingMethod(type = androID.Widget.ImageVIEw.class, attribute = "androID:tintMode", method = "setimageTintMode"),})public class ImageVIEwBindingAdapter {    @BindingAdapter("androID:src")    public static voID setimageUri(ImageVIEw vIEw, String imageUri) {        if (imageUri == null) {            vIEw.setimageURI(null);        } else {            vIEw.setimageURI(Uri.parse(imageUri));        }    }    @BindingAdapter("androID:src")    public static voID setimageUri(ImageVIEw vIEw, Uri imageUri) {        vIEw.setimageURI(imageUri);    }    @BindingAdapter("androID:src")    public static voID setimageDrawable(ImageVIEw vIEw, Drawable drawable) {        vIEw.setimageDrawable(drawable);    }}

DataBinding以静态方法的形式为UI控件各个属性绑定了相应的代码逻辑,如果在UI控件中的属性使用了布局表达式,那么当布局文件渲染时,绑定它的静态方法自动被调用。

自定义BindingAdapter

在项目开发中,经常使用ImageVIEw来加载网络图片,但是在布局文件中不能设置图片url,我们可以使用BindingAdapter来解决这个问题。

public class CustomImageVIEw extends AppCompatimageVIEw {    public CustomImageVIEw(Context context) {        super(context);    }    public CustomImageVIEw(Context context, AttributeSet attrs) {        super(context, attrs);    }    public CustomImageVIEw(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    /**     * @param vIEw 本身     * @param imageUrl 图片地址     */    @BindingAdapter(value = {"image_url"})    public static voID setimageUrl(CustomImageVIEw vIEw, String imageUrl) {	    //网络图片加载框架选择GlIDe        GlIDe.with(vIEw).load(imageUrl).into(vIEw);    }}

通过自定义ImageVIEw的方式添加静态方法,并给静态方法添加@BindingAdapter的注解,设置别名为image_url,布局文件通过别名来调用该方法。

<?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="imageUrl"            type="String" />    </data>    <androIDx.constraintlayout.Widget.ConstraintLayout        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent">        <com.yhj.jetpackstudy.ui.home.CustomImageVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            app:image_url="@{imageUrl}"            app:layout_constraintStart_toStartOf="parent"            app:layout_constrainttop_totopOf="parent" />    </androIDx.constraintlayout.Widget.ConstraintLayout></layout>

最后给布局变量赋值。

//Fragment中通过 inflater.inflate,相应的DataBinding通过DataBindingUtil.inflate//VIEw root = inflater.inflate(R.layout.fragment_notifications, container, false);FragmentNotificationsBinding binding= DataBindingUtil.inflate(inflater,R.layout.fragment_notifications,null,false);binding.setimageUrl("https://www.yanghujun.top/web_image/0d9e080e3da409db7d0e0bae3e88bc88.jpg");
多参数重载

在项目开发中除了设置网络图片外,还有如设置图片圆角,圆形图片等需求,通过BindingAdapter都可以实现。

	/**     * @param vIEw     本身     * @param imageUrl 图片地址     */    @BindingAdapter(value = {"image_url"})    public static voID setimageUrl(CustomImageVIEw vIEw, String imageUrl) {        setimageUrl(vIEw, imageUrl, false);    }    /**     * @param isCircle 是否圆形图片     */    @BindingAdapter(value = {"image_url", "isCircle"})    public static voID setimageUrl(CustomImageVIEw vIEw, String imageUrl, boolean isCircle) {        setimageUrl(vIEw, imageUrl, isCircle, 0);    }    /**     * @param radius 设置图片圆角     */    @BindingAdapter(value = {"image_url", "isCircle", "radius"}, requireAll = false)    public static voID setimageUrl(CustomImageVIEw vIEw, String imageUrl, boolean isCircle, int radius) {        SecureRandom secureRandom = new SecureRandom();        int i = secureRandom.nextInt(16);        Requestoptions mRequestoptions = null;        RequestBuilder<Drawable> builder = GlIDe.with(vIEw).load(imageUrl).placeholder(VERTICAL_IMAGES_BG[i]);        if (isCircle) {            mRequestoptions = Requestoptions.circleCroptransform();        } else if (radius > 0) {            //设置图片圆角角度            builder.transform(new RoundedCorners(PixUtils.dp2px(radius)));        }        if (mRequestoptions != null) {            builder.apply(mRequestoptions);        }        VIEwGroup.LayoutParams layoutParams = vIEw.getLayoutParams();        if (layoutParams != null && layoutParams.wIDth > 0 && layoutParams.height > 0) {            builder.overrIDe(layoutParams.wIDth, layoutParams.height);        }        builder.into(vIEw);    }

@BindingAdapter注解中,参数以value={"",""}的形式存在,变量requireAll设置参数是否必须赋值,默认为true,同时配合GlIDe设置图片的圆角、展位图和尺寸等。
最后在布局文件中根据需求来调用静态方法。

   <com.yhj.jetpackstudy.ui.home.CustomImageVIEw            androID:layout_wIDth="wrap_content"            androID:layout_height="wrap_content"            app:image_url="@{imageUrl}"            app:isCircle="@{true}"            app:radius="@{10}"/>
双向绑定

之前都是使用单向绑定来传递数据,对于一些与用户产生交互的控件,随着字段的变化能更新控件的内容,用户交互时也可以自动得到更新。这就是双向绑定。

使用

项目开发中登录页面必不可少,我们希望用户名字段内容变化时,EditText自动更新,当用户修改EditText的内容时,用户名字段同步得到更改。
首先创建一个LoginModel类,让LoginModel类的用户名字段和EditText双向绑定。

public class LoginModel extends BaSEObservable {    public String username;    public LoginModel() {        this.username = "yhj";    }    @Bindable    public String getUsername() {        return username;    }    public voID setUsername(String username) {	   //判断解决循环调用的问题        if (username != null && !username.equals(this.username)) {            this.username = username;            //通知观察者,数据已经更新            notifyPropertyChanged(BR.username);        }    }}

在构造器中设置字段初始值,并写了getter()setter(),在getter()设置@Bindable注解,告诉编译器,对这个字段进行绑定,setter()在用户编辑EditText内容时自动调用。需要进行手动更新。
完成双向绑定只需要将布局表达式中的@{}变为@={}即可。username字段会随着EditText内容的变化而变化。

 <EditText            androID:layout_wIDth="match_parent"            androID:layout_height="wrap_content"            androID:text="@={viewmodel.username}" />
优化

上面的做法有一些弊端,我们的类必须继承BaSEObservablegetter()添加@Bindable注解,setter()还需要手动刷新。
DataBinding提供了ObservableFIEld<T>,它能将普通对象包装成一个可观察对象。 ObservableFIEld可以包装各种基本类型、集合数组类型及自定义类型数据。将代码修改可以实现同样的效果。

public class LoginModel  {    public final ObservableFIEld<String> username=new ObservableFIEld<>();    public LoginModel() {        this.username.set("yhj");    }}

需要注意的是,此类的字段应声明为final,因为绑定仅检测字段值的变化,而不检测字段本身的变化。此类是可拆分和可序列化的,但是在对对象进行拆分/序列化时,将忽略回调,具体说明可参考源码。
其实,DatBinding将基本类型、集合数组、自定义类型进行了封装,提供了诸如ObservableIntObservableDoubleObservableArrayListObservableParcelable等特定的可观察类。
@H_568_419@

 public final ObservableInt age = new ObservableInt(); public ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); public ObservableArrayList<Object> List = new ObservableArrayList<>();
与liveData和viewmodel

ObservableFIEld和liveData作用相似,两者可替换使用,区别在于liveData与生命周期相关,通常在viewmodel中使用,需要通过observe()对变化监听。
和viewmodel使用时,可以把对控件的赋值、状态等在布局中进行处理,耦合度更低。

notificationsviewmodel = new viewmodelProvIDer(this).get(Notificationsviewmodel.class);notificationsviewmodel.getText().observe(getVIEwlifecycleOwner(), new Observer<String>() {         @OverrIDe         public voID onChanged(@Nullable String s) {            //binding.textvIEw.setText("yanghujun");            binding.setModel(notificationsviewmodel);         }});
public class Notificationsviewmodel extends viewmodel {    public mutablelivedata<String> mText = new mutablelivedata<>();        public Notificationsviewmodel() {        mText.setValue("yhj");    }    public liveData<String> getText() {        return mText;    }}
总结

以上是内存溢出为你收集整理的Android JetPack组件(六)DataBinding全部内容,希望文章能够帮你解决Android JetPack组件(六)DataBinding所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1027055.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-23
下一篇 2022-05-23

发表评论

登录后才能评论

评论列表(0条)

保存