Android进阶六:Databinding的双向绑定
在Android Studio 2.1 Preview 3之后,官方开始支持双向绑定了。
什么是双向绑定呢?
下面是Data Binding的基本使用:
<layout ...>
<data>
<variable type="com.example.myapp.User" name="user"/>
</data>
<RelativeLayout ...>
<EditText android:text="@{user.name}" .../>
</RelativeLayout>
</layout>
上面的EditText的text属性和对象user的name属性进行了绑定,但是代码如果只像上面这样这样的话,那EditView的text只会在第一次设置binding.setUser(user)的时候被赋予user.name值,之后如果user.name值变化的时候,EditView的text值不会变化。
我们理想的情况是user.name值变化的时候,EditText的text值也同时自动变化,同样,EditText的text值变化的时候,user.name值也同时自动变化。
那所谓双向绑定,包括正向绑定和反向绑定,
正向绑定:当user的name值变化的时候,EidtView的text也同时变化;
反向绑定:当EidtView的text变化的时候,user对象的name值也同时变化。
怎么实现正向绑定呢?
正向绑定
方法一:继承Observable的方式
具体如下:
1.我们的实体类(User类)继承BaseObservale类
2.Getter上使用注解@Bindable
3.在Setter里调用方法notifyPropertyChanged
让User类继承BaseObservable:
public class User extends BaseObservable
{
private String name;
@Bindable
public String getName() {
return username;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged( BR.name);
}
}
注意:当Getter方法getName()上加@Bindable以后,会自动在com.android.databinding.library.baseAdapters包的BR类中加了name属性
原理:当代码中设置user的name属性的时候user.setName(name),调用notifyPropertyChanged(BR.name),这个方法在BaseObservable类中定义,它会根据BR.name这个ID去通知相应的UI进行更新。以上就在user的name属性变化以后,EditView的text会自动的发送变化,就实现了正向绑定。
方法二:定义ObservableField方式
如果所有要绑定的都需要创建Observable类,那也太麻烦了。所以Data Binding还提供了一系列Observable,包括 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和ObservableParcelable。我们还能通过ObservableField泛型来申明其他类型,如:
private static class User {
public final ObservableField<String> firstName =new ObservableField<>();
public final ObservableField<String> lastName =new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
而在xml中,使用方法和普通的String,int一样,只是会自动刷新,但在java中访问则会相对麻烦:
user.firstName.set("Google");
int age = user.age.get();
那怎么实现反向绑定呢?
对于一部分属性,在XML中的引用中把“@{}”改成“@={}”,比如:
<layout ...>
<data>
<variable type="com.example.myapp.User" name="user"/>
</data>
<RelativeLayout ...>
<EditText android:text="@={user.name}" .../>
</RelativeLayout>
</layout>
这样当EditText的text发生改变的时候user.name属性也会同步变化,只有少部分控件的属性都支持“@={}”,如下:
AbsListView android:selectedItemPosition
CalendarView android:date
CompoundButton android:checked
DatePicker android:year, android:month, android:day
NumberPicker android:value
RadioGroup android:checkedButton
RatingBar android:rating
SeekBar android:progress
TabHost android:currentTab (估计没人用)
TextView android:text
TimePicker android:hour, android:minute
反向绑定
现在在来看下自定义属性如何实现反向绑定,
自定义一个SeedBar控件,可以调整最大最小值,调整HMIN,MHAX更新RangeBean的min和max属性:
Data Bean类如下:
public class RangeBean
{
int max;
int min;
public int getMax()
{
return max;
}
public void setMax(int max)
{
this.max = max;
}
public int getMin()
{
return min;
}
public void setMin(int min)
{
this.min = min;
}
}
自定义控件RangeSeekbar如下:
public class RangeSeekbar extends View {
……
private int max,min;
public void setMax(int max)
{
this.max = max;
invalidate();
}
public void setMin(int min)
{
this.min = min;
invalidate();
}
public int getMax()
{
return this.max;
}
public int getMin()
{
return this.min;
}
……
}
布局中添加RangeSeekbar:
<com.biyou.view.RangeSeekbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/id_rangeseekbar"
app:min = "@{rangeBean.min}"
app:max = "@{rangeBean.max}"
app:thumbdrawable = "@drawable/seekbar_thumb"
app:backgrounddrawable="@drawable/seekbar_bg_normal"
app:foregrounddrawable="@drawable/seekbar_fg_normal"/>
添加反向绑定:
1.添加@InverseBindingMethods
接下来我们要修改RangeSeekbar类,在类的顶部添加:
@InverseBindingMethods({
@InverseBindingMethod(
type = com.biyou.RangeSeekbar.class,
attribute = "min",
event = "minAttrChanged",
method = "getMin"),
@InverseBindingMethod(
type = com.biyou.RangeSeekbar.class,
attribute = "max",
event = "maxAttrChanged",
method = "getMax")})
public class RangeSeekbar extends View {
}
其中event和method不是必须的,因为系统会自动根据attribute值生成,
type 是需要反向绑定的类,一般是View或者ViewGroup,
attribute 是需要反向绑定的属性,本例是min和max,
event和method 的结构分别是:属性+AttrChanged,get+属性,一般不需要设置,系统自动生成。
method的定义还可以直接在方法上,比如min属性对应的getter:
@InverseBindingAdapter(attribute = "min", event = "minAttrChanged")
public int getMin()
{
return this.min;
}
2.添加@BindingAdapter,设置监听
private static InverseBindingListener minInverseBindingListener;
private static InverseBindingListener maxInverseBindingListener;
@BindingAdapter("minAttrChanged")
public static void setMinChangedListener(RangeSeekbar rangeSeekbar,final InverseBindingListener bindingListener) {
minInverseBindingListener = bindingListener;
}
@BindingAdapter("maxAttrChanged")
public static void setMaxChangedListener(RangeSeekbar rangeSeekbar,final InverseBindingListener bindingListener) {
maxInverseBindingListener = bindingListener;
}
注意方法名为:set+属性+ChangedListener,这个方法会在生成的databinding类中被调用
3.触发监听
当HMIN和HMAX进度条被拖动时,调用minInverseBindingListener和maxInverseBindingListener的onChange()方法,
onChange()方法会去改变rangeBean对象的min和max属性值:
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
switch (action)
{
case MotionEvent.ACTION_DOWN:
LogManager.LogEWithTag("GOODLUCK","ACTION_DOWN");
mStartX = (int) event.getX();
mStartY = (int) event.getY();
if(mLeftCursorRect.contains(mStartX,mStartY))
{
if(mLeftHited)
break;
mLeftHited = true;
}
else if(mRightCursorRect.contains(mStartX,mStartY))
{
if(mRightHited)
break;
mRightHited = true;
}
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getX();
int moveY = (int) event.getY();
if(mLeftHited)
{
if(mLeftCursorRect.left + moveX - mStartX < 0 ||
mLeftCursorRect.left + moveX - mStartX > (mSeekbarRect.right - mSeekbarRect.left))
return true;
mLeftCursorRect.left = mLeftCursorRect.left + moveX - mStartX;
mLeftCursorRect.right = mLeftCursorRect.left + mLeftCursor.getIntrinsicWidth();
this.min = (int) ((((float)mLeftCursorRect.left)/(mSeekbarRect.right - mSeekbarRect.left)) *255);
this.max = (int) ((((float)mRightCursorRect.left)/(mSeekbarRect.right - mSeekbarRect.left)) *255);
if(this.onSeekBarChangeListener != null) {
this.onSeekBarChangeListener.OnSeekBarChange(min,max);
}
else
{
minInverseBindingListener.onChange();
}
}
else if(mRightHited)
{
if(mRightCursorRect.left + moveX - mStartX < 0 ||
mRightCursorRect.left + moveX - mStartX > (mSeekbarRect.right - mSeekbarRect.left))
return true;
mRightCursorRect.left = mRightCursorRect.left + moveX - mStartX;
mRightCursorRect.right = mRightCursorRect.left + mRightCursor.getIntrinsicWidth();
this.min = (int) (((float)mLeftCursorRect.left)/(mSeekbarRect.right - mSeekbarRect.left) *255);
this.max = (int) (((float)mRightCursorRect.left)/(mSeekbarRect.right - mSeekbarRect.left) *255);
if(this.onSeekBarChangeListener != null) {
this.onSeekBarChangeListener.OnSeekBarChange(min,max);
}
else
{
maxInverseBindingListener.onChange();
}
}
if(mLeftCursorRect.left < mRightCursorRect.left) {
mSeekbarFgRect.left = mLeftCursorRect.left + mLeftCursor.getIntrinsicWidth() / 2;
mSeekbarFgRect.right = mRightCursorRect.left + mRightCursor.getIntrinsicWidth() / 2;
}
else
{
mSeekbarFgRect.left = mRightCursorRect.left + mLeftCursor.getIntrinsicWidth() / 2;
mSeekbarFgRect.right = mLeftCursorRect.left + mRightCursor.getIntrinsicWidth() / 2;
}
mStartX = moveX;
mStartY = moveY;
invalidate();
break;
case MotionEvent.ACTION_UP:
mLeftHited = false;
mRightHited = false;
break;
}
return true;
}
别忘了布局中把min和max的属性值,加上”@={}“:
<com.tomra.trsort.ui.view.RangeSeekbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/id_rangeseekbar"
app:min = "@={classBean.hMin}"
app:max = "@={classBean.hMax}"
app:thumbdrawable = "@drawable/seekbar_thumb"
app:backgrounddrawable="@drawable/seekbar_bg_normal"
app:foregrounddrawable="@drawable/seekbar_fg_normal"/>
避免死循环
双向绑定有可能会出现死循环,因为当你通过Listener反向设置数据时,数据也会再次发送事件给View。所以我们需要在设置一下避免死循环:
public void setMax(int max)
{
if(this.max == max)
return;
this.max = max;
invalidate();
}
public void setMin(int min)
{
if(this.min == min)
return;
this.min = min;
invalidate();
}
当值相等的时候返回,不更新UI。
上一篇: android软键盘弹出隐藏的监听
下一篇: 百度地图后台导航推送功能怎么开启?
推荐阅读
-
Android DataBinding的官方双向绑定示例
-
【Vue高级】MVVM实现原理(六)—— 双向数据绑定的实现
-
Android开发之关于MVVM架构中视图数据绑定框架dataBinding的基本用法
-
Android官方DataBinding(十二):双向绑定之反向绑定的InverseBindingMethods改造和实现
-
Android DataBinding 双向数据绑定、事件绑定、使用类方法
-
Android使用DataBinding实现双向绑定(一)
-
Android DataBinding (五) 自定义 View 的双向绑定
-
Android进阶六:Databinding的双向绑定
-
Android,DataBinding的官方双向绑定
-
Android DataBinding双向绑定相关注解