【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)

0x1、引言

ViewBinding(视图绑定) 的作用和原理一言以蔽之:

  • 作用代替findViewById 的同时,还能保证 空安全类型安全,且 支持Java
  • 原理 → AGP为模块中的每个XML生成绑定类,本质上还是findViewByid,只是自动生成控件实例,并一一对应;

可以把 ViewBinding 看做 DataBinding 功能的 子集,它有的DataBinding都有,而且还多了 数据绑定

何为数据绑定? 在维基百科中的定义如下:

是将 “提供器” 的数据源与 “消费者” 绑定并使其同步的一种通用技术。通常用两种不同语言的数据/信息源完成,如XML数据绑定。在UI数据绑定中,相同语言但不同逻辑功能的数据与信息对象被绑定在一起(例如Java UI元素到Java对象)。在数据绑定过程中,每个数据更改会由绑定到数据的元素自动反射。术语"数据绑定"也指一个外部数据表示随元素更改产生变化,并且底层数据自动更新以反映此更改。

又长又臭,举个简单例子就秒懂了:

一个存储数量的变量count,一个显示数量的TextView,两者绑定,当修改count的值时,TextView自动刷新。

数据源(Model)更新,绑定视图(View) 自动更新,不用开发仔再去手动setXxx(),道理就这么简单。

这种玩法又叫 单向绑定,还有一种 双向绑定,绑定视图发生改变时,数据源也跟着改变,比如:

点击显示数量的TextView,显示的数量自增1,存储数量的变量也自增1。

互相影响,这就是双向绑定。咳…都是些浅显的概念,具体怎么做?

观察者模式 实现,数据变量与View实例关联,数据变量有更新时,遍历回调关联View实例对应设置值的方法。

自己造轮子,可以,但Duck不必~

Jetpack库中的 DataBinding组件 已经封装好一套了,要做的就是熟读文档,然后大胆使用~

API变化日新月异,建议以官方文档为准《数据绑定库》,本文也是基于此文档展开的学习。


0x2、写个最简单的例子

通过一个超简单的例子来帮助大家了解DataBinding,先有基础认知,再往下学就容易多了。

① 启用DataBinding

DataBinding与AGP捆绑,无需声明这个库的依赖,在模块级别的 build.gradle 添加下述配置启用即可 ( 区分AS版本 )。

apply plugin: 'kotlin-kapt'

android {
    ...
    
    // AS 4.0以下
    dataBinding{
        enabled true
    }
    
    // AS 4.0及以上
    buildFeatures {
        dataBinding true
    }
    
    // 还可以这样写
    buildFeatures.dataBinding = true
} 

② 用DataBinding之前

未使用DataBinding之前,先写布局:




    

    

再写Activity:

class TestActivity : AppCompatActivity() {
    private var mCount: Int = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        findViewById

运行效果如下(点击按钮,计数器自增1):

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第1张图片

司空见惯的常规操作,代码中主动setText()去更新TextView的文本,接着换成DataBinding试试看。

③ 用DataBinding之后

来到布局xml文件,鼠标点到 根布局 LinearLayout,按 Alt + Enter,点击 Convert to data binding layout,自动生成一波DataBinding所需的布局。

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第2张图片

生成后的文件内容:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第3张图片

多了两个标签,接着开始改造,data标签中添加属性,修改TextView的android:text指向属性:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第4张图片

接着到Activity:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第5张图片

运行后,点击加1按钮,计数+1,效果与setText()一致,修改属性值,绑定的TextView文本跟着自动刷新。

看着 灰常简单!接着系统过一波详细用法,读者按需查阅即可~


0x3、详细用法

① xml布局文件

先是必须遵守的铁律:

根结点必须为,只能存在一个和一个直接子View结点。

1) variable (变量标签)

变量的 属性名name不能包含_下划线,否则再kt文件里会找不到变量,有时可能需要 指定自定义类型






    



 
2) data (数据标签)

它有个属性class,可以自定义DataBinding生成的类名及路径 (一般不需要):







 
3) @{}表达式

支持下述运算符和关键字:

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <;)
  • instanceof
  • 分组运算符 ()
  • 文字 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:

使用示例如下:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}' 

不支持关键字及操作:this、super、new、显式泛型调用。

null合并运算符(??):如果左边不为Null,取左边,否则取右边,示例如下:

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


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

属性引用:表达式中可以引用类的属性,如:

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

空安全:DataBinding生成的代码会 自动检查null值并避免出现空指针异常

如user为null,会为user.lastName分配默认null值,如果引用user.age,age为int,分配默认值0。

View引用:可以通过ID引用布局中其他的View,会将ID转换为 驼峰式大小写,示例如下:


    
 

集合:可以使用[]运算符访问集合,如Array、List、Map等,示例如下:


    
    
    
    
    
    
    
    



android:text="@{list[index]}"
android:text="@{sparse[index]}"
android:text="@{map[key]}"


android:text="@{map.key}" 

:变量的元素类型type的值不能包含 ‘<’ 字符,直接 List 这样写会引起XML语法错误。需要对 ‘<’ 做下 转义,即 <

字符串

可以用 单引号(‘’) 包裹特征值,这样就可以在表达式中使用双引号了,示例如下:

android:text='@{map["firstName"]}' 

可以用双引号扩住特征值,然后用 反单引号(``) 将字符串括起来,示例如下:

android:text="@{map[`firstName`]}" 

还支持用 + 号拼接字符串哦~

资源

表达式中引用应用资源,示例如下:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}" 

还支持格式化字符串及复数的参数传入,示例如下:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}" 

还可以把属性引用和View引用作为资源参数进行传递,示例如下:

android:text="@{@string/example_resource(user.lastName, exampleText.text)}"


android:text="@{@plurals/orange(orangeCount, orangeCount)}" 

某些资源需要显式类型求值,如下表所示:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第6张图片

4) 事件处理

事件属性名一般由 监听器方法名称确定,如:View.OnClickListeneronClick()android:onClick。但存在特例,如下表:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第7张图片

另外,可以使用 方法引用监听器绑定 来进行事件处理,两者的代码示例如下:

// 方法引用
class MyHandlers {
    fun onClickFriend(view: View) { ... }
}



   
       
       
   
   
       
   


// 监听器绑定
class Presenter {
    fun onSaveClick(task: Task){}
}



    
        
        
    
    
            

不难看出区别,引用方法需要和监听器的参数一致,而监听器绑定更加灵活,可在运行时动态运行lambda表达式,参数无需一致。

上述忽略了onClick(View)的View参数,如果后面的lambda表达式有用到的话,可以定义 命名参数,示例如下:

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}

 

  • 如果监听事件的返回类型不为Void,lambda表达式也要返回相同类型的值
  • 监听器表达式这种写法功能强大,可以使代码更易阅读,但不建议写太复杂的表达式,本末倒置,反而使得布局难以阅读和维护。

变量

变量类型在编译时会进行检查,如果不同配置(如横向或纵向)有不同的布局文件,变量会合并到一起。所以这些布局文件的变量定义不要存在冲突!(如同样的变量类型不一致)

系统会根据需要生成名为 context 的特殊变量,用于绑定表达式,它的值是根视图的 getContext() 获取到的Context对象。如果另外定义了同名变量会覆盖。

包含

在include其他布局时,有时需要把变量值传递过去,可以通过 bind:变量名 进行传递,要求两个布局文件拥有同一个变量。示例如下:


...




...
android:text="@{user}" 

注:不支持include作为merge元素的直接子元素,如这样的布局是编译不通过的:



   
       
   
   
       
       
   
 


② 可观察的数据对象

DataBinding中可观察的数据对象有三种不同类型:字段集合对象,通过数据绑定,数据对象可在数据发生更改时通知其他对象,即监听器。

1) 可观察字段

示例如下:

class User {
    val firstName = ObservableField()
    val lastName = ObservableField()
    val age = ObservableInt()
}

// 访问字段值,使用set()、get() 访问器方法
user.firstName = "Google"
val age = user.age 

除了ObservableInt类外还有这些基本类型:

ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、ObservableParcelable

:AS 3.1及更高版本允许使用LiveData对象替换可观察字段。

2) 可观察集合

示例如下:

ObservableArrayMap().apply {
    put("firstName", "Google")
    put("lastName", "Inc.")
    put("age", 17)
}

// 布局中通过字符串key找到值

    
    



ObservableArrayList().apply {
    add("Google")
    add("Inc.")
    add(17)
}

// 布局中通过索引访问列表

    
    

 

3) 可观察对象

可以自行实现 Observable 接口,但更建议使用DataBinding提供的 BaseObservable

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第8张图片

实现Observable接口,线程安全,使用 PropertyChangeRegistry 来执行 OnPropertyChangedCallback

使用代码示例如下:

class User : BaseObservable() {

    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }

    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}

// 附:Java中的写法
private static class User extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        return this.lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
} 

流程:getter设置Bindable注解 + setter中调用notifyPropertyChanged()

问:上面的BR哪来的

DataBinding会在模块包中生成名为 BR 的类,该类包含数据绑定的资源ID。在编译期,Bindable 注释会在BR类文件中生成一个条目。如果数据类的父类没办法更改,Observable接口可以使用 PropertyChangeRegistry 对象实现。


③ 生成的绑定类

生成的绑定类都是继承的 ViewDataBinding,类名基于布局名称,采用 Pascal命名法 进行转换并添加Binding 后缀,如 activity_main.xmlActivityMainBinding

1) 创建绑定对象

直接点开 DataBindingUtil 类,可以看到里面提供的多种绑定相关的方法:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第9张图片

如果可以 预知绑定类型,如ActivityMainBinding,也可以直接用ActivityMainBinding.bind()来绑定~

2) 带ID的View

DataBinding会对布局中拥有ID的每个View在绑定类中创建不可变字段。

3) 变量

DataBinding会为布局中声明的每个变量生成getter、setter方法。

4) ViewStub

占位置,惰性加载,当ViewStub被inflate或setVisible可见,它会从视图层次结构消失,如果想绑定里面的View,需要在监听 OnInflateListener,在此完成绑定

5) 即时绑定

当可变或可观察对象发生更改时,绑定会按照计划在下一帧之前发生更改。如果需要立即执行绑定,强制执行,可 executePendingBindings(),但要注意,此方法必须运行在UI线程

6) 高级绑定

动态变量,有时系统并不知道特定的绑定类,但仍需指定绑定值,如RecyclerView.Adapter,示例如下:

class BindingHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
    lateinit var binding: ViewDataBinding
}

class MyAdapter(data: List) : RecyclerView.Adapter() {
    private var mData = data

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder {
        // 核心代码
        val binding: ViewDataBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_layout,
            parent,
            false
        )
        val holder = BindingHolder(binding.root)
        holder.binding = binding
        return holder
    }

    override fun onBindViewHolder(holder: BindingHolder, position: Int) {
        val user = mData[position]
        holder.binding.setVariable(BR.mUser, user)
    }

    override fun getItemCount() = mData.size
} 


④ 绑定适配器

1) 自动选择方法

属性搜索对应方法,不会考虑命名空间,只考虑 属性名称 和 **类型**,如:

android:text="@{user.name} 

如果user.getName()的返回值为String,查找接受String参数的setText()方法,所以表达式返回正确的类型很重要,必要时你还可以根据需要进行类型转换。

2) 指定自定义方法名

使用 @BindingMethods 注解一个类 (接口也可以),相当于一个容器,内部参数是一个 @BindingMethod 数组。一般用不到它,绝大部分的属性DataBinding都已经使用命名惯例实现了。用法示例如下:

@BindingMethods(value = [
    BindingMethod(type = ImageView::class, attribute = "android:tint", method = "setImageTintList"),
    BindingMethod(type = ImageView::class, attribute = "android:xxx", method = "setAaaXxx")
])
class ImageBindingAdapter 

3) 提供自定义逻辑

有些属性需要自定义逻辑,可以使用 @BindingAdapter 注解来自定义setter的,如:android:paddingLeft没有关联的setter,而是提供了setPadding(left, top, right, bottom) 。示例如下:

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
            view.getPaddingTop(),
            view.getPaddingRight(),
            view.getPaddingBottom())
} 

注意参数类型:与属性关联的View类型 + 与属性绑定表达式中接受的类型

还可以定义接收多个属性的适配器,示例如下:

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
} 

在布局中使用适配器,示例如下:

 

如果ImageView同时使用了imageUrl、error,且前者是String,后者是Drawable,就会调用适配器。

如果你希望设置了任意属性就调用适配器,可以将适配器的 requireAll 设置为 false,示例如下:

@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
    if (url == null) {
        imageView.setImageDrawable(placeholder);
    } else {
        MyImageLoader.loadInto(imageView, url, placeholder);
    }
} 

  • requireAll设置为false,没填写的属性将为null,需要做好非空判断!
  • 上述写法命名空间可以随意,写xxx:imageUrl也是可以的,但如果定义了如android:imageUrl就只能用这个命名空间;
  • 自定义的绑定适配器和默认数据绑定适配器冲突,会使用自定义的绑定适配器;
  • @BindingMethod属性名和@BindingAdapter定义的属性名相同会冲突报错;

官方文档还贴出了更复杂一点的示例,读者感兴趣自己看吧,懒得搬运了~

4) 对象转换

自动转换对象:绑定表达式返回Object时,会选择用于设置属性值的方法,会自动转换为所选方法的参数类型。

自定义转换:某些情况下,需要在特定类型间自定义转换,如 android:background 需要 Drawable 但指定color传入的值却是整数。

 

每当需要Drawable且返回整数时,int都应转换为ColorDrawable,可以使用 @BindingConversion 注解静态方法来完成这个转换。

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color) 

注:绑定表达式提供的值类型要保持一致,不能在同一个表达式中使用不同类型,如:

 

⑤ 双向数据绑定

单向数据绑定时,可为属性设置值,并在事件监听器中更新属性:

 

双向数据绑定为上述过程提供了一种快捷方式:

 

相比起普通的@{}多了个**=**,可接收属性的数据更改并同时监听用户更新,对应属性还得做下更改:

class LoginViewModel : BaseObservable {
    // val data = ...

    @Bindable
    fun getRememberMe(): Boolean {
        return data.rememberMe
    }

    fun setRememberMe(value: Boolean) {
        // 避免死循环
        if (data.rememberMe != value) {
            data.rememberMe = value

            // 对变化做出反应
            saveData()

            // 更新观察者
            notifyPropertyChanged(BR.remember_me)
        }
    }
} 

由于可绑定属性的 getter 方法称为 getRememberMe(),因此属性的相应 setter 方法会自动使用名称 setRememberMe()

双向绑定 存在一个很大的问题 死循环,数据变化触发视图变化,视图变化又会触发数据变化,一直循环,所以 需要对变化前后的数据进行判断,有变动才更新。

DataBinding中内置支持双向绑定的类如下图所示:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第10张图片

表中没有的属性,想用双向绑定,就得自己实现 @BindingAdapter 注解了。

1) 自定义属性的双向绑定

官方例子:对名为MyView的自定义View中,对其"time"属性启用双向绑定,流程如下:

// 1、使用@BindingAdapter修饰setter方法
@BindingAdapter("time")
@JvmStatic fun setTime(view: MyView, newValue: Time) {
    // 新旧值对比,避免死循环
    if (view.time != newValue) {
        view.time = newValue
    }
}

// 2、使用@InverseBindingAdapter修饰getter方法
@InverseBindingAdapter("time")
@JvmStatic fun getTime(view: MyView) : Time {
    return view.getTime()
} 

DataBinding知道 数据更改时要执行的操作(@BindingAdapter注解修饰的方法),还知道 View属性发生改变时要调用的内容(InverseBindingListener),但不知道属性何时被修改,所以还要给View设置监听器,将@BindingAdapter注解也加到监听器方法上:

// 3、View上设置监听器,可以是自定义的,也可以是通用事件,如焦点丢失或文本修改
@BindingAdapter("app:timeAttrChanged")
@JvmStatic fun setListeners(
        view: MyView,
        attrChange: InverseBindingListener
) {
    // Set a listener for click, focus, touch, etc.
    attrChange.onChange() // 通知数据更新 
} 

监听器中包含一个 InverseBindingListener 可用它告知DataBinding,属性已更改,可以开始调用 @InverseBindingAdapter 修饰的方法。

2) 转换器

绑定到View的变量需要设置格式、转换或更改后才能显示,可以定义转换器对象来设置格式。

如果使用到双向表达式,还得使用反向转换器,以告知DataBinding如何将用户提供的字符串转换回后备数据类型。示例如下:

object Converter {
    // 添加注解修饰反向转换器。
    @InverseMethod("stringToDate")
    @JvmStatic fun dateToString(
        view: EditText, oldValue: Long,
        value: Long
    ): String {
        // Converts long to String.
    }

    @JvmStatic fun stringToDate(
        view: EditText, oldValue: String,
        value: String
    ): Long {
        // Converts String to long.
    }
} 

纸上得来终觉浅,绝知此事要躬行,大概的用法就过到这里,后续实践过程遇到问题再来补充 (如和其他Jetpack组件配合)。


0x4、妙用DataBinding——解决Drawable复用

Android日常开发中,有一项令我们头大的"小事" → drawable.xml文件的维护,怎么说?

  • 没有固定的设计规范,不同的设计师有不同的颜色、圆角大小倾向;
  • 祖传代码,每个接盘开发仔,都有自己一套命名规则,有些文件内容一样,就是名字不一样;

来来来,看看公司项目中drawable的这些命名:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第11张图片

谁看了不头皮发麻啊,还维护个XX,最好的维护就是不维护,上来就新建:

写个脚本扫描下项目中drawable.xml的文件个数 (基于Python):

import os

def search_all_drawable(path):
    global drawable_count
    os.chdir(path)
    items = os.listdir(os.curdir)
    for item in items:
        contact_path = os.path.join(path, item)
        // 判断文件夹、路径包含\drawable\的文件、不满足条件的文件
        if os.path.isdir(contact_path):
            print("[-]", contact_path)
            search_all_drawable(contact_path)
        elif contact_path.find(drawable_sep) != -1:
            if contact_path.endswith(".xml"):
                print("[+]", contact_path)
                drawable_count += 1
        else:
            print('[!]', contact_path)
            pass

if __name__ == '__main__':
    drawable_sep = os.path.sep + "drawable" + os.path.sep
    drawable_count = 0
    search_all_drawable(r"D:\Code\Android\项目路径")
    print("检索到drawable.xml文件共计:%d 个" % drawable_count) 

运行结果如下:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第12张图片

817个,一个300多字节,算3个1KB好了,如果能全部干掉的话,能减少273KB的体积,APK瘦身新技能get√

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第13张图片

① 笔者已知干掉drawable.xml的两种思路

  1. 自定义View

思路:将drawable.xml中的常用属性作为控件的自定义属性,在内部动态生成Drawable作为控件的背景。

实现示例:Silhouette

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第14张图片

就是对常用到drawable的控件进行自定义封装,可以,但侵入式太强了,不好应用到其他任意控件。有些第三方控件可能还得走drawable.xml的老路。

  1. 代码自动生成Drawable赋值给控件

一种实现方法:手动构建GradientDrawable,配合扩展函数、扩展属性等语法特性,动态设置。

通过下面这样的代码动态设置

mBinding.goMeetingBtn.shape = corner(17) +
    stroke(5, "#ff0000") +
    gradient(GradientDrawable.Orientation.RIGHT_LEFT, "#00ff00", "#0000ff") 

还可以再折腾下,弄成更简洁的DSL形式,感兴趣可以参考 drawable.dsl 自行实现。

另一种实现方法:为LayoutInflater添加自定义LayoutInflater.Factory,解析添加的自定义属性,并生成系统提供的GradientDrawable、RippleDrawable、StateListDrawable。

实现示例:BackgroundLibrary

第二种思路的实现相比第一种侵入性低多了,接着看看用DataBinding怎么做~

② 用DataBinding干掉drawable.xml的思路

上面说过,可以通过 @BindingAdapter 注解为属性提供自定义逻辑。

我们要做的就是抽取drawable.xml中的常用属性,定下属性命名规则,如:drawable_solidColor,然后编写Drawable创建及设置的逻辑,示例如下:

@BindingAdapter(value = {
        "drawable_solidColor",
        "drawable_radius",
}, requireAll = false)
public static void setViewBackground(View v, int color, int radius) {
    GradientDrawable drawable = new GradientDrawable();
    drawable.setColor(color);
    drawable.setCornerRadius(radius);
    view.setBackground(drawable);
} 

接着就可以在符合DataBinding规则的xml中使用了:


    
 

原理还是非常简单的,就是把常用的属性抠出来比较麻烦,有轮子直接扒:noDrawable

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第15张图片

不想另外依赖库的话,直接Copy这两个文件就好:

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第16张图片

还可以根据自己的需求添加属性,或进行其他扩展,这种方案也比较简单。不过也有局限性,需要对应的 页面用上DataBinding,否则不会生效。

这些方案对于新项目还好,旧项目的话,想一上来就干掉所有drawable.xml,不太现实,重复的工作量太大了。可以先保证新开发的页面不再使用drawable.xml,后续改动到的页面逐步去掉drawable.xml,当剩余drawable.xml量级比较少时再批量修改。没有最好的方案,只有最适合的方案。

2333,想批量干掉也不是不可以,写脚本就好,毕竟都是重复操作,BackgroundLibrary那个方案比较好入手,文件文本替换。大概的思路:定义一个类存每个Drawable对应属性的值,然后遍历所有drawable.xml,查找用到@drawable/xxx的xml文件,定位到对应标签,删掉原来的语句,补上BackgroundLibrary里定义的属性和值。另外,如果Java/Kotlin代码中动态用到了drawable.xml还得单独处理下。


0x5、小结

本节系统地过了一下DataBinding的用法,还送了一个DataBinding解决Drawable复用的案例,相信读者看完应该能够放心大胆地用上DataBinding了。

使用过程遇到的问题及解决方案欢迎在评论区反馈,笔者自己也会记录补充下,原理篇先欠着,后续有时间再填,就酱,谢谢~


文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

需要的小伙伴私信【学习】我免费分享给你,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)

直接点击文末小卡片可以领取哦!

Android学习PDF+架构视频+面试文档+源码笔记

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第17张图片

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第18张图片

  • Android学习的系统对应视频

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第19张图片

  • Android进阶的系统对应学习资料

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第20张图片

  • Android BAT大厂面试题(有解析)

【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)_第21张图片

领取地址:

点击下方卡片免费领取

你可能感兴趣的:(Android开发,技术提升,jetpack,java,ui,xml,c#)