databing 最佳实践

2020.0921更,使用最新版databinding
Databing 最佳实践
在使用Databing的项目中,存在三种情况。

  1. 双向绑定
  2. 单项绑定
  3. 使用Databinding,但不在此页面绑定

我们常规开发中遇到的页面,可以分为一下几大类:

  1. 需要展示信息
    毫不犹豫,单项绑定。
    能从Obj中获取信息的,直接在xml中使用:例如@{obj.name}
    如果Obj中获取的信息需要加工的,Viewmodel 中创建对应字段,例如:
  public ObservableField<String> deposit=new ObservableField<>();

在Obj赋值的地方,对deposit字段进行赋值。
总之,只有展示页面的,非常灵活,可以采用喜欢的形式。

并且同时具备点击按钮修改页面信息,以及提交。那么考虑使用双向绑定。

  1. 只是提交信息:
    ViewModel中创建,
  public ObservableField<String> deposit=new ObservableField<>();

Activity中,从binding中拿控件的值,赋值给 viewModel中的字段(deposit)。

那么对于String类型的,可以定义与之对应的ObserverField,使用双向绑定;对于Double long int date类型的,有相关复杂操作的,考虑双向绑定中的@InverseMethod() 。 如果没有复杂操作的,可考虑不绑定,在提交按钮的事件里,用binding获取组件值,回传到ViewModel里即可(用作提交用)。

一.单项绑定中,优先考虑如下前两种方式。

1. viewModel 类中创建新的Observers字段。布局中用viewmodel引用此字段。(目前很推荐)

1>viewModel中定义

    public ObservableInt configSize=new ObservableInt();

2>拿到数据的地方,为configSize赋值。

3>布局中使用。

  app:secondText="@{@string/room_condition_str(viewModel.configSize)}"

这种适合
添加带展示,比如一个页面,即实现添加商品,又实现展示商品。可以用这种。另外这种的转换都在ViewModel类里面,相对也好操作些。双向绑定的适合,阻力也小。

优点:

  1. activity层的代码少,几乎不需要特别关心。无论是那种页面类型。
  2. 字段分开,方便控制,并且不用担心空类型的小错误。
  3. 当需求变动,xml 组件的内容需要进行运算的时候,只需要添加相关代码即可。不需要改动原来的。符合开闭原则

缺点:
viewModel中,页面复杂时,定义的字段多
牵涉到展示的时候,需要单独为ObserverField赋值。

另外建议:
android:text="@{@string/percentage(daySummary.checkInRate)}"

最佳实践:用方法来做,在viewmodel中定义方法
public String getCheckRateStr(int rate){
//todo: 添加转换的方法
return str
}
旧的不要做,用这种方法。


图片

但是这种形式的,分开也很好,用两个view做。一个请输入的Edittext,一个元的Textview.

2. viewModel 类中创建一个Obj。布局中使用此bean实体值。(比较推荐)

其实这种方法也是推荐的,但是,当Obj中的是非String类型,double等,这时候,就需要在布局中转换。较为复杂的转换也不太容易实现。比如1-7转星期。
另外:很多时候,如果数值不同,可能字符串发生种种变化。比如20% 显示电量低,30% 显示电量中,90% 显示电量高。这种需要用方法来转换。
优点:

  1. activity层的代码少,几乎不需要特别关心。无论是那种页面类型。
  2. 字段在是一个实体bean中,完整性强
  3. viewModel中不需要定义大量的Observeable 字段
  4. 原来的get set 方法都可以用,字段也是很容易获取。

缺点:

  1. bean中通常含有大量非String类型字段,需要在xml中调用方法处理。xml嵌入方法多
  2. 不适合添加型的页面。
  3. 如果xml需要多个bean的数据的时候,xml显得有些凌乱。
  4. 你把一些逻辑写到了java中,一些写到了xml中,阅读代码的人必须要能理解这些才能真正的了解你的意图,此外layout文件是具备复用能力的,一旦你要复用layout,那么这些xml中的逻辑便成了其无法复用的根源,因此我强烈禁止在xml中写java逻辑。

这种适合:

  1. 结合@inverseMethod 注解一起使用。
  2. 只是用来展示的页面。比如XX详情页。

3. 直接将字段创建到data标签内,布局中直接使用。(不是很推荐)

除了一些用来显示,用来点击事件,viewmodel外,不建议将roomNumber这样的展示字段,放到data里,不建议这种方式。

<data>

    <variable
        name="onClickListener"
        type="android.view.View.OnClickListener" />

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

    <variable
        name="roomDetail"
        type="com. RoomDetailObj" />

    <variable
        name="viewModel"
        type="com. AddRoomResourceViewModel" />
</data>

缺陷:控制只能通过Activitiy,也就是view层(binding),违背了设计原则。灵活性也降低。
viewmodel本身就是用来封装数组层的,这样写,数据和布局的耦合太多。不合适。

4. 布局中引用对应的ViewModel的方法。(不太建议)
  public ArrayList<ConfigurationObj> getChamberConfiguration() {
        return mRoomDetailEvent.getValue().getChamberConfiguration()
    }

    public int  getConfigNum(){
        return mRoomDetailEvent.getValue().getChamberConfigurationNum();
    }

布局中:

        app:secondText="@{@string/room_condition_str(viewModel.getConfigNum)}"

不太建议这种方式,比如,布局中需要一个Arraylist的size,此时很容易出现空的情况。程序报错。

二. 尽可能少的variable和import

也许你是刚接触DataBinding,按照官网的Guide,很可能会定义一些非必需的variable或者import 一些自定义的静态类来进行字符串处理、View显示隐藏等等的操作。但是这不是一个好习惯,过多的variable除了会让你多做几次无谓的绑定外,数据也将变得难以管理。所以尽可能少的variable和import是一种较好的实践。

https://www.jianshu.com/p/015ad08c2c75

image.png

如果原来写了好多,那么移出的原则:

  1. 单层调用,单变量的,不犹豫,移除:
    比如:如图的isAdmin, 使用的时候viewModle.isAdamin就可以了,很方便。
  2. 多层的,且调用地方多。
    比如如图的daySummary,使用的时候,是获取她身上的值,如果也放到viewmodel中,调用的时候,就会使viewModel.daySummary.getName(),相对麻烦,修改的地方也多,如果都定义一个方法,那么xml中遇到daySumnary成员的地方,都需要包装方法。

三. 不是所有的时候都需要双向绑定,或者单项绑定。

例子:我又一个页面用来添加房间信息:


image.png

最佳实践:

  1. viewModel创建:
public ObservableField<String> area=new ObservableField<>();
  1. EditText,要求输入Double long
  2. activtivity中,确认添加的事件触发的时候,为viewModel中的Area赋值。
  3. 添加post请求的参数里面把area.get() 传入进去。

使用双向绑定,可以节省3步骤。但是容易造成请输入面积哪里有一个0.0的显示。
如果想去掉,会用到@inverseMethod方法。代码又麻烦了。因其本身,就是一个田间也面,不牵涉到展示数据。所以推荐上述最佳实践。

实际会出现一个未输入,未添加的时候,请输入面积显示为0.0的情况。这不是我们想要的。
所以,还是采用原本的类型。

二. 常用单项绑定总结:

1. java类里面定义一些方法,然后布局文件中引入工具类,的形式:

android:text="@{DateUtils.getMonthDay(rentalItem.latestNotifyDate)}"

2. 根据数据,选择String.xml中的不同的字符串
android:text="@{rentalItem.latestNotifyDate > 0 ? @string/remind_again : @string/remind_again}"
//添加带数值的字符串。
  android:text="@{@string/positive_amount(item.amount)}"
android:text="@{meterBase.electricityReadNumber==0? @string/default_str:String.valueOf(electricityReadNumber)}"
3. 设置默认值:
android:text="@{user.firstName, default=my_default}"
4. 非字符串类型,往字符串类型转换

android:text="@{String.valueOf(index + 1)}"
android:transitionName='@{"image_" + id}'

5. view可见性
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
6. 备用值

如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null,则选择右边运算数。

android:text="@{user.displayName ?? user.lastName}"    

相当于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

7. 集合快速映射:
<data>
       <import type="android.util.SparseArray"/>
       <import type="java.util.Map"/>
       <import type="java.util.List"/>
       <variable name="list" type="List&lt;String>"/>
       <variable name="sparse" type="SparseArray&lt;String>"/>
       <variable name="map" type="Map&lt;String, String>"/>
       <variable name="index" type="int"/>
       <variable name="key" type="String"/>
   </data>
   …
   android:text="@{list[index]}"
   …
   android:text="@{sparse[index]}"
   …
   android:text="@{map[key]}"

@{map[key]} 可替换为 @{map.key}

7. 三元运算符支持:

三元运算嵌套三元运算,为同一个组件设置多种背景。示例如下:

       android:background="@{isOpenForBusiness==2? @drawable/round_click_layout_gold_bg: (isCheckedIn == 1 ? @drawable/round_click_layout_gold_bg : @drawable/round_clickable_bg )}"
      
8. 自由拼接字符串
android:text="@{String.valueOf(roomInfo.rent)+'/'+StringUtil.getRentCycleStrByCode(roomInfo.rentCycle)}"

8. switch组件,双向绑定。

如下是错误的:

        <Switch
            android:id="@+id/switch_all_day_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:checked="@={mViewModel.isAllDay?true:false}"
            android:text=""
            android:visibility="@{mViewModel.alwaysMode ? View.VISIBLE : View.GONE}"
            app:layout_constraintBottom_toBottomOf="@+id/all_day_bg"
            app:layout_constraintEnd_toEndOf="@+id/all_day_bg"
            app:layout_constraintTop_toTopOf="@+id/all_day_bg" />

如下是正确的:

        <Switch
            android:id="@+id/switch_all_day_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:checked="@={mViewModel.isAllDay}"
            android:text=""
            android:visibility="@{mViewModel.alwaysMode ? View.VISIBLE : View.GONE}"
            app:layout_constraintBottom_toBottomOf="@+id/all_day_bg"
            app:layout_constraintEnd_toEndOf="@+id/all_day_bg"
            app:layout_constraintTop_toTopOf="@+id/all_day_bg" />

9.进行 &&运算

android:visibility="@{(mViewModel.alwaysMode&&mViewModel.isAllDay)? View.VISIBLE : View.GONE}"

10, 布局中能不用方法就不用方法:

比如:我遇到一个需求,服务器返回一个int字段,status,1-8;
1, 7,8 , 这三个转台比较特殊,需要展示红色的提示,其他状态,这个tips应该不显示。

xml中,创建方法需要两个,方法,一个getVisiable ,一个getStatusStr.
如果我创建两个变量一个观察显示,一个观察字符串内容,则可以一个方法搞定。

 public void setStatusAndIsShow(int status) {
        String msg = "";
        switch (status) {
            case 4:
                msg = ResGetUtils.getString(R.string.contract_refuse_tips);
                tips.set(msg);
                isShowTips.set(true);
                break;
            case 5:
                msg = ResGetUtils.getString(R.string.have_not_sign_tips);
                tips.set(msg);
                isShowTips.set(true);
                break;
            case 8:
                msg = ResGetUtils.getString(R.string.contract_roll_back_tips);
                tips.set(msg);
                isShowTips.set(true);
                break;
            case 1:
            case 2:
            case 3:
            case 6:
            case 7:
                isShowTips.set(false);
                break;
        }
    }

遇到的问题

问题一

报错:Attempt to invoke virtual method 'double java.lang.Double.doubleValue()' on a null object reference

解决方案: android:text="@={``+branchInfo.waterUnitPrice}"

问题2 :

****/ data binding error ****msg:Cannot import same alias twice. View in Location{startLine=21, startOffset=8, endLine=21,

可以查看21行,发现:

  <data>

        <variable
            name="onClickListener"
            type="android.view.View.OnClickListener" />
  <import type="android.view.View" />
        <variable
            name="showTimeSelect"
            type="boolean" />

        <variable
            name="viewModel"
            type="com.ttlock.roommaster.room.vm.CheckInViewModel" />

        <import type="android.view.View" />

    </data>

导入了两次view,去掉一个,恢复正常。

问题三:

databinding 不能正常生成,整体爆红:
问题原因:使用了BaseQuickadapterhelper开源第三方。
reycycler中的,item的布局中的的:

  <variable
            name="onClickListener"
            type="android.view.View.OnClickListener" />

去掉! 问题解决!!!

问题四:

android studio 项目正常运行,但是binding爆红:
原因是项目中存在一个,和爆红binding所对应布局,极为相似的布局。
取掉,爆红解决。

问题五:
android studio 项目正常运行,但是binding爆红:

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/color_bg_default"
        tools:context="com.ttlock.roommaster.checkin.RoomSourceInfoActivity">

去掉:

        tools:context="com.ttlock.roommaster.checkin.RoomSourceInfoActivity"

问题六:

Caused by: java.lang.UnsupportedOperationException: Can't convert value at index 7 to dimension

android.view.InflateException: Binary XML file line #148: Can't convert value at index 7 to dimension: type=0x12
Caused by: java.lang.UnsupportedOperationException: Can't convert value at index 7 to dimension:
布局绑定的为题,可以采用注释法,判断问题所在,及将怀疑的布局代码注释掉。观察报错情况。判断位置。经验总结,其他的都有点浪费时间。

最终发现是这段代码引起的:

    <TextView
            android:id="@+id/expand_text"
            android:layout_width="wrap_content"
            android:layout_height="@dimen/list_menu_item_height"
            android:layout_marginStart="@id/expand_collapse_bg"
            app:layout_constraintBottom_toBottomOf="@id/expand_collapse_bg"
            app:layout_constraintEnd_toStartOf="@id/expand_icon"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toStartOf="@id/expand_collapse_bg"
            app:layout_constraintTop_toTopOf="@id/expand_collapse_bg" />
<!--        android:text="@{viewModel.isShowCard==false? @string/expand : @string/collapse}"-->
        <TextView
            android:id="@+id/expand_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:textColor="@color/colorPrimary"
            app:layout_constraintBottom_toBottomOf="@id/expand_collapse_bg"
            app:layout_constraintEnd_toEndOf="@id/expand_collapse_bg"
            app:layout_constraintHorizontal_chainStyle="packed"
            app:layout_constraintStart_toEndOf="@id/expand_text"
            app:layout_constraintTop_toTopOf="@id/expand_collapse_bg" />

为什么呢,懵了。
哭了: android:layout_marginStart="@id/expand_collapse_bg"
marginStart里面跟像素哈。
这里竟然这么写,我脑子进水了。

七,问题databinding 双向绑定EditText ,光标飘忽

输入抖动,就会出现,输入23, 显示32,输入24 ,显示42的情况。

解决方案:

可使用+单项绑定:
binding.edtGasNewData.addTextChangedListener(new EditTextListener(binding.edtGasNewData, (input, viewId) -> {

}));

三. 双向绑定:

1>viewmodel里面:
public SingleLiveEvent<RoomDetailObj> mRoomDetailObj = new SingleLiveEvent<>();
2>布局文件:
data标签里面,传入

 <variable
            name="roomDetail"
            type="com.ttlock.roommaster.room.model.RoomDetailObj" />

布局中引用

   android:text="@={``+roomDetail.waterUnitPrice}"

3>Actvity里面:

   mViewModel.mRoomDetailObj.observe(this, roomDetailObj -> {
            binding.setRoomDetail(roomDetailObj);
        });
tips:我故意用的数字类型,这里数字类型要用这样的写法,才能双向绑定。注意前面的单引号一定要正确。字符串类型的,非常简单:
   android:text="@={roomDetail.roomName}"

超强大的自定义双向绑定:

学习了双向绑定,觉得大获成功,但是一到项目中懵逼了,因为有的时候,我们的逻辑没这么简单,我们输入想绑定的是一个code,展示给用户的 确实一个字符串。这就尴尬了。
自定义绑定来了。

Activity的代码:

 ActivityMainBinding binding;
    public TextView three;
    public Button button;
    MainViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        viewModel=ViewModelProviders.of(this).get(MainViewModel.class);
        binding.setV(viewModel);
        viewModel.num.set(1);
        three = findViewById(R.id.three);
        button = findViewById(R.id.button);


        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, String.valueOf(viewModel.num.get()), Toast.LENGTH_SHORT).show();
            }
        });
    }

Viewmodel代码:

public class MainViewModel extends ViewModel {
    public ObservableInt num =new ObservableInt();

    @InverseMethod("stringToInt")
    public static   String intToString(int oldValue) {
        if (oldValue == 1) {
            return "one";
        } else if (oldValue == 2) {
            return "two";
        } else {
            return "";
        }


    }

    public static   int stringToInt(String oldValue) {
        if (oldValue.equals("one")) {
            return 1;
        } else if (oldValue.equals("two")) {
            return 2;
        } else {
            return 3;
        }
    }
}

布局文件:

<?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="v"
            type="com.example.myapplication.MainViewModel" />

        <import type="com.example.myapplication.MainViewModel" />

    </data>

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

        <EditText
            android:id="@+id/three"
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:layout_marginTop="100dp"
            android:text="@={MainViewModel.intToString(v.num)}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/button"
            android:layout_width="100dp"
            android:layout_height="40dp"
            android:layout_marginTop="100dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/three" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

参考如下:

https://juejin.im/entry/6844903494957154318

各个注解方法,尤其使用与最后一个:
https://juejin.im/entry/6844903494957137927

https://www.zybuluo.com/shark0017/note/256112

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352