DataBinding → 数据绑定 (使用篇)

DataBinding → 数据绑定 (使用篇)_第1张图片

/   今日科技快讯   /

华为近日发布2022年第一季度经营业绩,实现销售收入1310亿元人民币,同比下降13.9%。华为第一季度净利润率同比下降6.8个百分点,至4.3%。华为轮值董事长胡厚崑表示:“整体经营结果符合预期,消费者业务受到较大影响,ICT基础设施业务实现稳定增长。此外,公司在研发上加大投入,以保持持续创新的能力,为客户创造价值。”

/   作者简介   /

明天就是五一长假了,祝大家节日愉快,我们节后再见!

本篇文章来自coder_pig的投稿,文章主要分享了DataBinding的使用,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

coder_pig的博客地址:

https://juejin.cn/user/4142615541321928/posts

/   引言   /

DataBinding → 数据绑定 (使用篇)_第2张图片

放羊一个月,继续回来学穿Jetpack,带来第三个组件 DataBinding (数据绑定)。在前面的章节 《【Jetpack】学穿:ViewBinding → 视图绑定》 (https://juejin.cn/post/7067532076223823903)剥源码的时候就有看到 DataBinding 相关的代码。

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不必~

62c47f7b451c75278b02d975afc5959a.png

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

API变化日新月异,建议以官方文档为准《数据绑定库》(https://developer.android.com/topic/libraries/data-binding?hl=zh-cn),本文也是基于此文档展开的学习。

/   最简单的例子   /

通过一个超简单的例子来帮助大家了解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):

DataBinding → 数据绑定 (使用篇)_第3张图片

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

用DataBinding之后

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

DataBinding → 数据绑定 (使用篇)_第4张图片

生成后的文件内容:

DataBinding → 数据绑定 (使用篇)_第5张图片

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

DataBinding → 数据绑定 (使用篇)_第6张图片

接着到Activity:

DataBinding → 数据绑定 (使用篇)_第7张图片

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

67e5f1f950fbb3d2d4851e7e72297c11.png

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

/   详细用法   /

xml布局文件

先是必须遵守的铁律:

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

  • variable (变量标签)

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










  • data (数据标签)

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







  • @{}表达式

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

  • 算术运算符 + - / * %

  • 字符串连接运算符 +

  • 逻辑运算符 && ||

  • 二元运算符 & | ^

  • 一元运算符 + - ! ~

  • 移位运算符 >> >>> <<

  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <)

  • 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)}"

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

DataBinding → 数据绑定 (使用篇)_第8张图片

  • 事件处理

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

DataBinding → 数据绑定 (使用篇)_第9张图片

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

// 方法引用
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中可观察的数据对象有三种不同类型:字段、集合和对象,通过数据绑定,数据对象可在数据发生更改时通知其他对象,即监听器。

  • 可观察字段
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对象替换可观察字段。


  • 可观察集合
ObservableArrayMap().apply {
    put("firstName", "Google")
    put("lastName", "Inc.")
    put("age", 17)
}

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

    
    "/>



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

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

    
    "/>

  • 可观察对象

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

DataBinding → 数据绑定 (使用篇)_第10张图片

实现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.xml → ActivityMainBinding。

  • 创建绑定对象

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

DataBinding → 数据绑定 (使用篇)_第11张图片

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

  • 带ID的View

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

  • 变量

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

  • ViewStub

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

  • 即时绑定

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

  • 高级绑定

动态变量,有时系统并不知道特定的绑定类,但仍需指定绑定值,如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
}

绑定适配器

  • 自动选择方法

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

android:text="@{user.name}

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

  • 指定自定义方法名

使用 @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
  • 提供自定义逻辑

有些属性需要自定义逻辑,可以使用 @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定义的属性名相同会冲突报错;

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

  • 对象转换

自动转换对象:绑定表达式返回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中内置支持双向绑定的类如下图所示:

DataBinding → 数据绑定 (使用篇)_第12张图片

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

  • 自定义属性的双向绑定

官方例子:对名为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 修饰的方法。

  • 转换器

绑定到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组件配合)。

/   解决Drawable复用   /

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

  • 没有固定的设计规范,不同的设计师有不同的颜色、圆角大小倾向;

  • 祖传代码,每个接盘开发仔,都有自己一套命名规则,有些文件内容一样,就是名字不一样;

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

DataBinding → 数据绑定 (使用篇)_第13张图片

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

8bcb2d0e2d4ef3cc1dd89314591d3988.png

写个脚本扫描下项目中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)

运行结果如下:

DataBinding → 数据绑定 (使用篇)_第14张图片

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

DataBinding → 数据绑定 (使用篇)_第15张图片

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

1. 自定义View

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

实现示例:Silhouette(https://github.com/FreddyChen/Silhouette)

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

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

实现示例:《干掉shape,手动构建GradientDrwable》(https://juejin.cn/post/7082321786720747527)

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

实现示例:BackgroundLibrary原理解读:《无需自定义View,彻底解放shape,selector吧》(https://juejin.cn/post/6844903676973170702)

第二种思路的实现相比第一种侵入性低多了,接着看看用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(https://github.com/whataa/noDrawable)

DataBinding → 数据绑定 (使用篇)_第16张图片

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

DataBinding → 数据绑定 (使用篇)_第17张图片

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

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

f0fcc781913afebb5c936d83a9c2b72b.png

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

/   总结   /

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

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

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

在微软工作365天,还你一个我眼中更加真实的微软

深度剖析Android IPC原理

欢迎关注我的公众号

学习技术或投稿

0aebed2c2353ba57c1d4055ce941c2b0.png

DataBinding → 数据绑定 (使用篇)_第18张图片

长按上图,识别图中二维码即可关注

你可能感兴趣的:(java,android,移动开发,python,gwt)