快播根据相关法律法规和政策-asf转换mp4
![compoundbutton](/uploads/image/0233.jpg)
2023年4月4日发(作者:手机绑定)
Databinding双向绑定详解
图⽚来⾃必应
Databinding是Google推出的⼀个⽀持View与ViewModel绑定的Library,可以说Databinding建⽴了⼀个UI与数据模型之间的桥
梁,即UI的变化可以通知到ViewModel,ViewModel的变化同样能够通知到UI从⽽使UI发⽣改变,⼤⼤减少了之前View与Model之间
的胶⽔代码,如findViewById,改变及获取TextView的内容还需要调⽤setText()、getText(),获取EditText编辑之后的内容需要调
⽤getText(),⽽有了Databinding的双向绑定,这些重复的⼯作都将被省去。下⾯我们就来看⼀下如何使⽤Databinding来双向绑定
⾸先我们先定⼀个ViewModel,将这个ViewModel的变量content与布局⽂件中的TextView绑定:
classMyViewModel:ViewModel(){
varcontent:ObservableFiled
}
复制代码
官⽅⽀持的双向绑定这⾥的官⽅⽀持指的是Databinding库中已经定义了⼀些View的双向绑定,我们如果要使⽤的话只需要将xml⽂件
中的"@{}"改成"@={}",如下代码
(test_):
<?xmlversion="1.0"encoding="utf-8"?>
xmlns:android="/apk/res/android">
name="model"
type="l"/>
android:layout_width="match_parent"
android:layout_height="match_parent"
>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{t}"
/>
复制代码
上述代码中注释部分就是将单向绑定改为双向绑定的代码
官⽅⽀持的双向绑定:
AbsListViewandroid:selectedItemPosition
CalendarViewandroid:date
CompoundButtonandroid:checked
DatePickerandroid:year,android:month,android:day
NumberPickerandroid:value
RadioGroupandroid:checkedButton
RatingBarandroid:rating
SeekBarandroid:progress
TabHostandroid:currentTab
TextViewandroid:text
TimePickerandroid:hour,android:minute
那么双向绑定是怎么实现的呢?⾸先来看⼀下Databinding的源码:
⾸先是我们在编译之后会⽣成⼏个相关⽂件如test_,test_,,BR⽂件等。我们主要
来看⼀下这个⽂件。这个⽂件的主要作⽤是声明xml内的View控件以及声明的ViewModel,如本例中声明了⼀个
TextView:
@NonNull
LayoutmboundView0;
@NonNull
ewmboundView1;
//variables
@Nullable
lmModel;
复制代码
上述代码中mboundView0为根布局、mboundView1为声明了双向绑定的TextView,mModel为与View绑定的ViewModel。(注意:并不
是所有的变量都会被声明,有三种情况:在xml布局中声明id的,将会被定义为静态变量,可以直接通过Databinding对象访问;引⽤了
model数据的;根布局)
接下来当我们定义了双向绑定的时候,会⽣成这样⼀段代码:
eBindingListenermboundView1androidTextAttrChanged=eBindingListener(){
@Override
publicvoidonChange(){
//()
//(()callbackArg_0)
//调⽤定义的TextViewBindingAdapter中的getTextString(TextViewview)⽅法得到mboundView1(即布局中定义的TextView)的值
callbackArg_0=tString(mboundView1);
//localizevariablesforthreadsafety
//model
lmodel=mModel;
//()
//当前View绑定的ViewModel中的content的值
modelContentGet=null;
//model!=null
booleanmodelJavaLangObjectNull=false;
//t
ableField<>modelContent=null;
//t!=null
booleanmodelContentJavaLangObjectNull=false;
modelJavaLangObjectNull=(model)!=(null);
if(modelJavaLangObjectNull){
modelContent=tent();
modelContentJavaLangObjectNull=(modelContent)!=(null);
if(modelContentJavaLangObjectNull){
//将View中的值取出并复制给相应的model中绑定的数据
((()(callbackArg_0)));
}
}
}
};
复制代码
通过上述代码可以看到实现逆向绑定的关键部分,可以看到调⽤InverseBindingListener接⼝的onChange()⽅法就可以将view的值传回
ViewModel。那么它在哪⾥被⽤到呢?看下⾯的代码:
@Override
protectedvoidexecuteBindings(){
...
if((dirtyFlags&0x4L)!=0){
tWatcher(...,mboundView1androidTextAttrChanged);
}
}
复制代码
可以看到在executeBindings这个⽅法中,将InverseBindingListener的值传给setTextWatcher,这个⽅法怎么来的后⾯会提到。
上⾯的代码中提到⼀个TextViewBindingAdapter,通过它的名字就可以看出是绑定View与ViewModel的适配器,那么来看⼀下
TextViewBindingAdapter做了什么(在包rs下):
@BindingAdapter("android:text")
publicstaticvoidsetText(TextViewview,CharSequencetext){
finalCharSequenceoldText=t();
//为防⽌双向绑定⽆限循环调⽤⽐较新旧内容是否相同
if(text==oldText||(text==null&&()==0)){
return;
}
if(textinstanceofSpanned){
if((oldText)){
return;//Nochangeinthespans,sodon'tsetanything.
}
}elseif(!haveContentsChanged(text,oldText)){
return;//Nocontentchanges,sodon'tsetanything.
}
t(text);
}
复制代码
上述代码相信⼤家并不陌⽣,当Databinding在给某⼀个控件的XXX属性赋值的时候,需要去找到相应的setXXX()⽅法,然后将model中
的值给这个View。⽽@BindingAdapter("xxx")正是将这个setXXX()⽅法与xxx属性关联起来,所以上⾯部分的代码作⽤总结起来就是做
了t(text)。(注:可以看到代码中有对新旧内容的⽐较,只有当内容不同的时候才会执⾏下⼀步,这是为了防⽌双向绑定循环
调⽤)
再来看下⼀个⽅法:
@InverseBindingAdapter(attribute="android:text",event="android:textAttrChanged")
publicstaticStringgetTextString(TextViewview){
t().toString();
}
复制代码
这个⽅法在中已经说明了,是双向绑定取值时调⽤的⽅法,@InverseBindingAdapter注解和@BindingAdapter
注解作⽤类似。
再来看下⼀个关键⽅法:
@BindingAdapter(value={"android:beforeTextChanged","android:onTextChanged",
"android:afterTextChanged","android:textAttrChanged"},requireAll=false)
publicstaticvoidsetTextWatcher(TextViewview,finalBeforeTextChangedbefore,
finalOnTextChangedon,finalAfterTextChangedafter,
finalInverseBindingListenertextAttrChanged){
finalTextWatchernewValue;
//listener为空则不作处理
if(before==null&&after==null&&on==null&&textAttrChanged==null){
newValue=null;
}else{
//创建⼀个TextWatcher监听text内容的变化
newValue=newTextWatcher(){
@Override
publicvoidbeforeTextChanged(CharSequences,intstart,intcount,intafter){
if(before!=null){
TextChanged(s,start,count,after);
}
}
@Override
publicvoidonTextChanged(CharSequences,intstart,intbefore,intcount){
if(on!=null){
Changed(s,start,before,count);
}
//如果设定了双向绑定,则InverseBindingListener不为空,可参见之前的对其的赋值。
if(textAttrChanged!=null){
//调⽤onChange()⽅法(实现在中),即将view的值传回给ViewModel
ge();
}
}
@Override
publicvoidafterTextChanged(Editables){
if(after!=null){
extChanged(s);
}
}
};
}
finalTextWatcheroldValue=istener(view,newValue,tcher);
if(oldValue!=null){
TextChangedListener(oldValue);
}
if(newValue!=null){
tChangedListener(newValue);
}
}
复制代码
从上⾯的代码我们可以看到@BindingAdapter绑定了⼏个属性,其中有⼀个叫做"android:textAttrChanged",那么这个属性就代表了当
TextView的text属性变化,xxxAttrChanged。可以想象得到那这个⽅法就是在android:text属性发⽣变化的时候会被调⽤。我们在这个⽅
法中设置了对android:text这个属性内容变化的监听(注意是内容变化,不是属性变化),以后当每次改内容变化的时候,就会调⽤
ge()这个⽅法将TextView的值传回给ViewModel。
上述的是通过官⽅⽀持的源码来看双向绑定,那么我们要⾃定义的View使⽤双向绑定应该怎么做呢?
⾃定义View双向绑定
通过对官⽅源码分析,我们发现要实现双向绑定关键是需要实现Adapter,实现setter,getter,以及Listene⽅法。下⾯就让我们来
实现⼀个⾃定义View的双向绑定吧。对MyModel稍作修改:
classMyModel:ViewModel(){
varcontent:ObservableField
funonChangeClick(view:View){
if(viewisTestEditView){
Log.v("---TAG---",())
ue("改变后")
Log.v("---TAG---","click:${()}")
}
}
}
复制代码
对test_修改(主要是使⽤⾃定义的View),加了⼀个点击事件,点击之后修改View的内容:
android:layout_width="match_parent"
android:layout_height="50dp"
app:content="@={t}"
android:onClick="@{(view)->geClick(view)}"
/>
复制代码
这⾥我们引⼊⼀个⾃定义View:这是⼀个类似表单的View,左边是个⼩标题,右边可以填⼀些信息等:
classTestEditView:LinearLayout{
privatelateinitvarcontentView:TextView
privatevaronContentedListener:OnContentChangedListener?=null
{
...
}
privatefuninitView(context:Context){
valview=e(context,_edit_layout,this)
contentView=ewById(_content)
}
funsetOnContentListener(
listener:OnContentChangedListener?){
entedListener=listener
}
publicfunsetValue(content:String){
if(y()||(!y()&&content==)){
return
}
=content
onContentedListener?.onContentChanged()
}
fungetContent():String{
ng()
}
//内容改变Listener
interfaceOnContentChangedListener{
funonContentChanged()
}
}
复制代码
省略了部分代码,主要就是定义了⼀个内容改变的Listener。下⾯是TestEditViewAdapter的代码:
//使⽤InverbaseBindingMethod注解,与使⽤@BindingAdapter效果⼀样其中,event、method可声明也可以不声明,不声明的话会默认设置xxxAttrChanged与ge
//@InverseBindingMethods(InverseBindingMethod(type=TestEditView::class,attribute="content",event="contentAttrChanged",method="getContent"))
objectTestEditViewAdapter{
@JvmStatic
@BindingAdapter("app:content")
funsetContent(view:TestEditView,content:String){
if(mpty()&&tent()==content){
return
}
ue(content)
}
@JvmStatic
@InverseBindingAdapter(attribute="app:content",event="contentAttrChanged")
fungetContent(view:TestEditView):String{
tent()
}
@JvmStatic
@BindingAdapter(value="app:contentAttrChanged",requireAll=false)
funsetContentAttrChangedListener(view:TestEditView,bindingListener:InverseBindingListener){
if(bindingListener==null){
ontentListener(null)
}else{
//如果设置了双向绑定则为其添加监听
ontentListener(object:OnContentChangedListener{
overridefunonContentChanged(){
ge()
}
})
}
}
}
复制代码
接下来我们只需要在Fragment中为content赋值,然后运⾏程序,点击TestEditView,就可以看到Log输出了content的初始值与改变后
的值(不贴图展⽰了)。
valbinding=e(inflater!!,container,false)
valmodel=MyModel()
("初始值")
=model
复制代码
总结以上就是Databinding双向绑定的使⽤⽅法,总的来说应该注意两点:
1、修改"@{}"为"@={}"
2、写setXXX(),xxxAttrChanged(),getXXX()⽅法。
更多推荐
compoundbutton
发布评论