├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── actors.json │ ├── ic_launcher-web.png │ ├── java │ └── me │ │ └── liangfei │ │ └── databinding │ │ ├── MainActivity.kt │ │ ├── adapters │ │ ├── ActorDetailPagerAdapter.kt │ │ └── ActorListAdapter.kt │ │ ├── core │ │ └── App.kt │ │ ├── data │ │ ├── ActorDao.kt │ │ ├── ActorRepository.kt │ │ ├── AppDatabase.kt │ │ ├── Converters.kt │ │ └── entities │ │ │ ├── Actor.kt │ │ │ ├── ActorWork.kt │ │ │ └── Work.kt │ │ ├── fragments │ │ ├── ActorCollectionFragment.kt │ │ ├── ActorDetailFragment.kt │ │ ├── ActorListFragment.kt │ │ ├── ActorProfileFragment.kt │ │ └── ActorWorksFragment.kt │ │ ├── listener │ │ └── OkListener.java │ │ ├── model │ │ ├── Contact.java │ │ ├── ObservableUser.java │ │ ├── PlainUser.java │ │ └── User.java │ │ ├── sample │ │ ├── BaseActivity.java │ │ ├── attributesetter │ │ │ └── AttributeSettersActivity.java │ │ ├── basic │ │ │ └── BasicActivity.java │ │ ├── collection │ │ │ └── CollectionActivity.java │ │ ├── converter │ │ │ └── ConversionsActivity.java │ │ ├── custombinding │ │ │ └── CustomBindingActivity.java │ │ ├── dynamic │ │ │ ├── DynamicActivity.java │ │ │ └── UserAdapter.java │ │ ├── include │ │ │ └── IncludeActivity.java │ │ ├── observable │ │ │ └── ObservableActivity.java │ │ ├── resource │ │ │ └── ResourceActivity.java │ │ ├── viewid │ │ │ └── ViewWithIDsActivity.java │ │ └── viewstub │ │ │ └── ViewStubActivity.java │ │ ├── utilities │ │ ├── BindingAdapters.kt │ │ ├── Constants.kt │ │ └── InjectorUtils.kt │ │ ├── utils │ │ ├── MyStringUtils.java │ │ ├── Randoms.java │ │ └── ScreenUtils.java │ │ ├── view │ │ └── NameCard.java │ │ ├── viewmodels │ │ ├── ActorViewModel.kt │ │ └── ActorViewModelFactory.kt │ │ └── workers │ │ └── SeedDatabaseWorker.kt │ └── res │ ├── drawable │ ├── error.xml │ ├── ic_add.xml │ ├── ic_favorite.xml │ ├── ic_home.xml │ ├── ic_info.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ └── ic_search.xml │ ├── layout │ ├── activity_attribute_setters.xml │ ├── activity_basic.xml │ ├── activity_collection.xml │ ├── activity_conversions.xml │ ├── activity_custom_binding.xml │ ├── activity_dynamic.xml │ ├── activity_include.xml │ ├── activity_main.xml │ ├── activity_observable.xml │ ├── activity_resource.xml │ ├── activity_view_stub.xml │ ├── activity_view_with_ids.xml │ ├── contact.xml │ ├── fragment_actor_detail.xml │ ├── fragment_actor_list.xml │ ├── fragment_actor_profile.xml │ ├── layout_btn_ok.xml │ ├── layout_input.xml │ ├── list_item_actor.xml │ ├── list_item_add.xml │ ├── name_card.xml │ ├── nav_header.xml │ ├── user.xml │ ├── user_item.xml │ └── view_stub.xml │ ├── menu │ ├── menu_attribute_setters.xml │ ├── menu_conversions.xml │ ├── menu_dynamic.xml │ ├── menu_navigation.xml │ ├── menu_view_stub.xml │ └── menu_view_with_ids.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── navigation │ └── nav_actor.xml │ ├── values-v21 │ └── styles.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── art └── Design.sketch ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | .gradle 29 | /local.properties 30 | .idea/ 31 | .DS_Store 32 | /build 33 | /captures 34 | /infer-out 35 | *.iml 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | android: 3 | components: 4 | # Uncomment the lines below if you want to 5 | # use the latest revision of Android SDK Tools 6 | - platform-tools 7 | - tools 8 | 9 | # The BuildTools version used by your project 10 | - build-tools-23.0.1 11 | 12 | # The SDK version used to compile your project 13 | - android-23 14 | 15 | # Additional components 16 | - extra-google-google_play_services 17 | - extra-google-m2repository 18 | - extra-android-m2repository 19 | - addon-google_apis-google-19 20 | 21 | # Specify at least one system image, 22 | # if you need to run emulator(s) during your tests 23 | - sys-img-armeabi-v7a-android-19 24 | - sys-img-x86-android-17 25 | script: ./gradlew assembleDebug 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 liangfei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 精通 Android Data Binding 2 | 3 | [![Build Status](https://travis-ci.org/LyndonChin/MasteringAndroidDataBinding.svg)](https://travis-ci.org/LyndonChin/MasteringAndroidDataBinding) 4 | 5 | * 更多干货可移步至[个人主页](http://liangfei.me) 6 | * QQ 交流群:**324112728** ,或者[点击链接加入QQ群](http://jq.qq.com/?_wv=1027&k=2CokoRt) 7 | 8 | 9 | 10 | --- 11 | 12 | 官方虽然已经给出了教程 - [Data Binding Guide](https://developer.android.com/tools/data-binding/guide.html) [(中文版 - Data Binding(数据绑定)用户指南)](http://www.jianshu.com/p/b1df61a4df77) ,但是实践之后发现槽点实在太多,于是就有了这个教程,针对每个知识点给出更详实的例子同时也总结了遇到的一些坑,希望对你有所帮助:) 13 | 14 | > 我现在转行做纯前端开发了,写了几个月 React/Vue 之后发现,DataBinding 真是一个伟大的 MVVM 框架,它缩小了 Native 开发和前端开发之间的距离,技术会过时,理念恒久远。 15 | 16 | ## 准备 17 | 18 | 新建一个 Project,建议使用[新版本的 Gradle 插件](build.gradle#L16)(至少要保证插件版本不低于 **1.5.0**): 19 | 20 | ```groovy 21 | classpath 'com.android.tools.build:gradle:3.2.1' 22 | ``` 23 | 24 | 然后修改对应模块(Module)的 [build.gradle](app/build.gradle#L6-L8): 25 | 26 | ```groovy 27 | dataBinding { 28 | enabled true 29 | } 30 | ``` 31 | 32 | ## 基础 33 | 34 | 工程创建完成后,我们通过一个最简单的例子来说明 Data Binding 的基本用法。 35 | 36 | ### 布局文件 37 | 38 | 使用 Data Binding 之后,xml 的布局文件就不再用于单纯地展示 UI 元素,还需要定义 UI 元素用到的变量。所以,它的根节点不再是一个 `ViewGroup`,而是变成了 `layout`,并且新增了一个节点 `data`。 39 | 40 | ```xml 41 | 42 | 43 | 44 | 45 | 46 | .... 47 | 48 | 49 | ``` 50 | 51 | 要实现 MVVM 的 `ViewModel` 就需要把数据(Model)与 UI(View) 进行绑定,`data` 节点的作用就像一个桥梁,搭建了 View 和 Model 之间的通路。 52 | 53 | 我们先在 xml 布局文件的 `data` 节点中声明一个 `variable`,这个变量会为 UI 元素提供数据(例如 `TextView` 的 `android:text`),然后在 Java 代码中把『后台』数据与这个 `variable` 进行绑定。 54 | 55 | 下面我们使用 Data Binding 创建一个展示用户信息的表格。 56 | 57 | ### 数据对象 58 | 59 | 添加一个 POJO 类 - [`User`](app/src/main/java/com/liangfeizc/databinding/model/User.java),非常简单,两个属性以及他们的 getter 和 setter。 60 | 61 | ```java 62 | public class User { 63 | private final String firstName; 64 | private final String lastName; 65 | 66 | public User(String firstName, String lastName) { 67 | this.firstName = firstName; 68 | this.lastName = lastName; 69 | } 70 | 71 | public String getFirstName() { 72 | return firstName; 73 | } 74 | 75 | public String getLastName() { 76 | return lastName; 77 | } 78 | } 79 | ``` 80 | 81 | 稍后,我们会新建一个 `User` 类型的变量,然后把它跟布局文件中声明的变量进行绑定。 82 | 83 | ### 定义 Variable 84 | 85 | 回到布局文件,在 `data` 节点中声明一个 `User` 类型的变量 `user`。 86 | 87 | ```xml 88 | 89 | 90 | 91 | ``` 92 | 93 | 其中 `type` 属性就是我们在 Java 文件中定义的 `User` 类。 94 | 95 | 当然,`data` 节点也支持 `import`,所以上面的代码可以换一种形式来写。 96 | 97 | ```xml 98 | 99 | 100 | 101 | 102 | ``` 103 | 104 | 然后我们刚才在 build.gradle 中添加的那个插件 - `com.android.databinding` 会根据 xml 文件的名称 **Generate** 一个继承自 `ViewDataBinding` 的类。 当然,IDE 中看不到这个文件,需要手动去 build 目录下找。 105 | 106 | 例如,这里 xml 的文件名叫 `activity_basic.xml`,那么生成的类就是 `ActivityBasicBinding`。 107 | 108 | **注意** 109 | 110 | `java.lang.*` 包中的类会被自动导入,可以直接使用,例如要定义一个 `String` 类型的变量: 111 | 112 | ```xml 113 | 114 | ``` 115 | 116 | ### 绑定 Variable 117 | 118 | 修改 [`BasicActivity`](app/src/main/java/com/liangfeizc/databinding/sample/basic/BasicActivity.java#L17-L20) 的 `onCreate` 方法,用 `DatabindingUtil.setContentView()` 来替换掉 `setContentView()`,然后创建一个 `user` 对象,通过 `binding.setUser(user)` 与 `variable` 进行绑定。 119 | 120 | ```java 121 | @Override 122 | protected void onCreate(Bundle savedInstanceState) { 123 | super.onCreate(savedInstanceState); 124 | ActivityBasicBinding binding = DataBindingUtil.setContentView( 125 | this, R.layout.activity_basic); 126 | User user = new User("fei", "Liang"); 127 | binding.setUser(user); 128 | } 129 | ``` 130 | 131 | 除了使用框架自动生成的 `ActivityBasicBinding`,我们也可以通过如下方式自定义类名。 132 | 133 | ```xml 134 | 135 | 136 | ``` 137 | 138 | **注意** 139 | 140 | `ActivityBasicBinding` 类是自动生成的,所有的 `set` 方法也是根据 `variable` 名称生成的。例如,我们定义了两个变量。 141 | 142 | ```xml 143 | 144 | 145 | 146 | 147 | ``` 148 | 149 | 那么就会生成对应的两个 set 方法。 150 | 151 | ```java 152 | setFirstName(String firstName); 153 | setLastName(String lastName); 154 | ``` 155 | 156 | 157 | ### 使用 Variable 158 | 159 | 数据与 Variable 绑定之后,xml 的 UI 元素就可以直接使用了。 160 | 161 | ```xml 162 | 166 | ``` 167 | 168 | 至此,一个简单的数据绑定就完成了,可参考[完整代码](app/src/main/java/com/liangfeizc/databinding/sample/basic/) 169 | 170 | ## 高级用法 171 | 172 | ### 使用类方法 173 | 174 | 首先定义一个静态方法 175 | 176 | ```java 177 | public class MyStringUtils { 178 | public static String capitalize(final String word) { 179 | if (word.length() > 1) { 180 | return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1); 181 | } 182 | return word; 183 | } 184 | } 185 | ``` 186 | 187 | 然后在 xml 的 `data` 节点中导入: 188 | 189 | ```xml 190 | 191 | ``` 192 | 193 | 使用方法与 Java 语法一样: 194 | 195 | ```java 196 | 200 | ``` 201 | 202 | ### 类型别名 203 | 204 | 如果我们在 `data` 节点了导入了两个同名的类怎么办? 205 | 206 | ```xml 207 | 208 | 209 | 210 | ``` 211 | 212 | 这样一来出现了两个 `User` 类,那 `user` 变量要用哪一个呢?不用担心,`import` 还有一个 `alias` 属性。 213 | 214 | ```xml 215 | 216 | 217 | 218 | ``` 219 | 220 | ### Null Coalescing 运算符 221 | 222 | ```java 223 | android:text="@{user.displayName ?? user.lastName}" 224 | ``` 225 | 226 | 就等价于 227 | 228 | ```java 229 | android:text="@{user.displayName != null ? user.displayName : user.lastName}" 230 | ``` 231 | 232 | ### 属性值 233 | 234 | 通过 `@{}` 可以直接把 Java 中定义的属性值赋值给 xml 属性。 235 | 236 | ```xml 237 | 242 | ``` 243 | 244 | ### 使用资源数据 245 | 246 | 这个例子,官方教程有错误,可以参考[Android Data Binder 的一个bug](http://blog.csdn.net/feelang/article/details/46342699),[完整代码在此](app/src/main/res/layout/activity_resource.xml) 247 | 248 | ```xml 249 | 256 | ``` 257 | 258 | ## Observable Binding 259 | 260 | 本来这一节的标题应该叫**双向绑定**,但是很遗憾,现在的 **Data Binding** 暂时支持单向绑定,还没有达到 **Angular.js** 的威力。 261 | 262 | 要实现 Observable Binding,首先得有一个 `implement` 了接口 `android.databinding.Observable` 的类,为了方便,Android 原生提供了已经封装好的一个类 - `BaseObservable`,并且实现了监听器的注册机制。 263 | 264 | 我们可以直接继承 `BaseObservable`。 265 | 266 | ```java 267 | public class ObservableUser extends BaseObservable { 268 | private String firstName; 269 | private String lastName; 270 | 271 | @Bindable 272 | public String getFirstName() { 273 | return firstName; 274 | } 275 | 276 | @Bindable 277 | public String getLastName() { 278 | return lastName; 279 | } 280 | 281 | public void setFirstName(String firstName) { 282 | this.firstName = firstName; 283 | notifyPropertyChanged(BR.firstName); 284 | } 285 | 286 | public void setLastName(String lastName) { 287 | this.lastName = lastName; 288 | notifyPropertyChanged(BR.lastName); 289 | } 290 | } 291 | ``` 292 | 293 | `BR` 是编译阶段生成的一个类,功能与 `R.java` 类似,用 `@Bindable` 标记过 `getter` 方法会在 `BR` 中生成一个 *entry*。 294 | 295 | 通过代码可以看出,当数据发生变化时还是需要手动发出通知。 通过调用 `notifyPropertyChanged(BR.firstName)` 可以通知系统 `BR.firstName` 这个 `entry` 的数据已经发生变化,需要更新 UI。 296 | 297 | 除此之外,还有一种更细粒度的绑定方式,可以具体到成员变量,这种方式无需继承 `BaseObservable`,一个简单的 **POJO** 就可以实现。 298 | 299 | ```java 300 | public class PlainUser { 301 | public final ObservableField firstName = new ObservableField<>(); 302 | public final ObservableField lastName = new ObservableField<>(); 303 | public final ObservableInt age = new ObservableInt(); 304 | } 305 | ``` 306 | 307 | 系统为我们提供了所有的 **primitive type** 所对应的 **Observable**类,例如 `ObservableInt`、`ObservableFloat`、`ObservableBoolean` 等等,还有一个 `ObservableField` 对应着 **reference type**。 308 | 309 | 剩下的数据绑定与前面介绍的方式一样,具体可参考[ObservableActivity](app/src/main/java/com/liangfeizc/databinding/sample/observable/ObservableActivity.java)。 310 | 311 | ## 带 ID 的 View 312 | 313 | **Data Binding** 有效降低了代码的冗余性,甚至完全没有必要再去获取一个 View 实例,但是情况不是绝对的,万一我们真的就需要了呢?不用担心,只要给 View 定义一个 ID,**Data Binding** 就会为我们生成一个对应的 `final` 变量。 314 | 315 | ```xml 316 | 320 | ``` 321 | 322 | 上面代码中定义了一个 ID 为 *firstName** 的 `TextView`,那么它对应的变量就是 323 | 324 | ```java 325 | public final TextView firstName; 326 | ``` 327 | 328 | 具体代码可参考 [ViewWithIDsActivity.java](app/src/main/java/com/liangfeizc/databinding/sample/viewid/ViewWithIDsActivity.java) 329 | 330 | ## ViewStubs 331 | 332 | xml 中的 `ViewStub` 经过 binding 之后会转换成 `ViewStubProxy`, 具体代码可参考 [ViewStubActivity.java](app/src/main/java/com/liangfeizc/databinding/sample/viewstub/ViewStubActivity.java) 333 | 334 | 简单用代码说明一下,xml 文件与之前的代码一样,根节点改为 `layout`,在 `LinearLayout` 中添加一个 `ViewStub`,添加 **ID**。 335 | 336 | ```xml 337 | 338 | 340 | 344 | 345 | 346 | ``` 347 | 348 | 在 Java 代码中获取 `binding` 实例,为 `ViewStubProy` 注册 `ViewStub.OnInflateListener` 事件: 349 | 350 | ```java 351 | binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub); 352 | binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() { 353 | @Override 354 | public void onInflate(ViewStub stub, View inflated) { 355 | ViewStubBinding binding = DataBindingUtil.bind(inflated); 356 | User user = new User("fee", "lang"); 357 | binding.setUser(user); 358 | } 359 | }); 360 | ``` 361 | 362 | ## Dynamic Variables 363 | 364 | 完整代码可以参考 [dynamic](app/src/main/java/com/liangfeizc/databinding/sample/dynamic) 365 | 366 | 以 `RecyclerView` 为例,`Adapter` 的 **DataBinding** 需要动态生成,因此我们可以在 `onCreateViewHolder` 的时候创建这个 **DataBinding**,然后在 `onBindViewHolder` 中获取这个 **DataBinding**。 367 | 368 | ```java 369 | public static class BindingHolder extends RecyclerView.ViewHolder { 370 | private ViewDataBinding binding; 371 | 372 | public BindingHolder(View itemView) { 373 | super(itemView); 374 | } 375 | 376 | public ViewDataBinding getBinding() { 377 | return binding; 378 | } 379 | 380 | public void setBinding(ViewDataBinding binding) { 381 | this.binding = binding; 382 | } 383 | } 384 | 385 | @Override 386 | public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 387 | ViewDataBinding binding = DataBindingUtil.inflate( 388 | LayoutInflater.from(viewGroup.getContext()), 389 | R.layout.list_item, 390 | viewGroup, 391 | false); 392 | BindingHolder holder = new BindingHolder(binding.getRoot()); 393 | holder.setBinding(binding); 394 | return holder; 395 | } 396 | 397 | @Override 398 | public void onBindViewHolder(BindingHolder holder, int position) { 399 | User user = users.get(position); 400 | holder.getBinding().setVariable(BR.user, user); 401 | holder.getBinding().executePendingBindings(); 402 | } 403 | ``` 404 | 405 | 注意此处 `DataBindingUtil` 的用法: 406 | 407 | ```java 408 | ViewDataBinding binding = DataBindingUtil.inflate( 409 | LayoutInflater.from(viewGroup.getContext()), 410 | R.layout.list_item, 411 | viewGroup, 412 | false); 413 | ``` 414 | 415 | --- 416 | 417 | 还有另外一种比较简洁的方式,直接在构造 Holder 时把 `View` 与自动生成的 `XXXBinding` 进行绑定。 418 | 419 | ```java 420 | public class UserAdapter extends RecyclerView.Adapter { 421 | private static final int USER_COUNT = 10; 422 | 423 | @NonNull 424 | private List mUsers; 425 | 426 | public UserAdapter() { 427 | mUsers = new ArrayList<>(10); 428 | for (int i = 0; i < USER_COUNT; i ++) { 429 | User user = new User(RandomNames.nextFirstName(), RandomNames.nextLastName()); 430 | mUsers.add(user); 431 | } 432 | } 433 | 434 | public static class UserHolder extends RecyclerView.ViewHolder { 435 | private UserItemBinding mBinding; 436 | 437 | public UserHolder(View itemView) { 438 | super(itemView); 439 | mBinding = DataBindingUtil.bind(itemView); 440 | } 441 | 442 | public void bind(@NonNull User user) { 443 | mBinding.setUser(user); 444 | } 445 | } 446 | 447 | @Override 448 | public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 449 | View itemView = LayoutInflater.from(viewGroup.getContext()) 450 | .inflate(R.layout.user_item, viewGroup, false); 451 | return new UserHolder(itemView); 452 | } 453 | 454 | @Override 455 | public void onBindViewHolder(UserHolder holder, int position) { 456 | holder.bind(mUsers.get(position)); 457 | } 458 | 459 | @Override 460 | public int getItemCount() { 461 | return mUsers.size(); 462 | } 463 | } 464 | ``` 465 | 466 | ## Attribute setters 467 | 468 | 有了 **Data Binding**,即使属性没有在 `declare-styleable` 中定义,我们也可以通过 xml 进行赋值操作。 469 | 为了演示这个功能,我自定义了一个 View - [NameCard](app/src/main/java/com/liangfeizc/databinding/view/NameCard.java),属性资源 [R.styleable.NameCard](app/src/main/res/values/styles.xml#L8-L10) 中只定义了一个 `age` 属性,其中 `firstName` 和 `lastName` 只有对应的两个 `setter` 方法。 470 | 471 | 只要有 `setter` 方法就可以像下面代码一样赋值: 472 | 473 | ```xml 474 | 482 | ``` 483 | 484 | `onClickListener` 也是同样道理,只不过我们是在 `Activity` 中定义了一个 `Listener`。 485 | 486 | ## 转换器 (Converters) 487 | 488 | > **非常重要** 489 | 490 | > 使用 **Converter** 一定要保证它不会影响到其他的属性,例如这个 `@BindingConversion`- [convertColorToString](app/src/main/java/com/liangfeizc/databinding/sample/converter/ConversionsActivity.java#L50-L63) 就会影响到[android:visibility](app/src/main/res/layout/activity_basic.xml#L76), 因为他们都是都符合从 int 到 int 的转换。 491 | 492 | 493 | 在 xml 中为属性赋值时,如果变量的类型与属性不一致,通过 **DataBinding** 可以进行转换。 494 | 495 | 例如,下面代码中如果要为属性 `android:background` 赋值一个 `int` 型的 color 变量: 496 | 497 | ```xml 498 | 503 | ``` 504 | 505 | 只需要定义一个标记了 `@BindingConversion` 的静态方法即可(*方法的定义位置可以随意*): 506 | 507 | ```java 508 | @BindingConversion 509 | public static ColorDrawable convertColorToDrawable(int color) { 510 | return new ColorDrawable(color); 511 | } 512 | ``` 513 | 514 | 具体代码可参考 [ConversionsActivity.java](app/src/main/java/com/liangfeizc/databinding/sample/converter/ConversionsActivity.java)。 515 | 516 | ## include 517 | 518 | 用法可以参考代码 [IncludeActivity.java](/app/src/main/java/com/liangfeizc/databinding/sample/include/IncludeActivity.java) 519 | 520 | 如果在非根节点的 ViewGroup 中使用 `include` 会导致 crash,已经在 StackOverflow 上提了一个问题[Android Data Binding makes app crash when using include tag in a non-root ViewGroup](http://stackoverflow.com/questions/30887906/android-data-binding-makes-app-crash-when-using-include-tag-in-a-non-root-viewgr),直されたそうですけど。 521 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'androidx.navigation.safeargs' 6 | 7 | android { 8 | compileSdkVersion 28 9 | 10 | dataBinding { 11 | enabled true 12 | } 13 | 14 | defaultConfig { 15 | applicationId "me.liangfei.databinding" 16 | minSdkVersion 16 17 | targetSdkVersion 28 18 | versionCode 1 19 | versionName "1.0" 20 | vectorDrawables.useSupportLibrary = true 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | kapt "androidx.room:room-compiler:$rootProject.roomVersion" 33 | implementation "com.github.bumptech.glide:glide:$rootProject.glideVersion" 34 | annotationProcessor "com.github.bumptech.glide:compiler:$rootProject.glideVersion" 35 | implementation "android.arch.navigation:navigation-fragment-ktx:$rootProject.navigationVersion" 36 | implementation "android.arch.navigation:navigation-ui-ktx:$rootProject.navigationVersion" 37 | implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion" 38 | implementation "androidx.room:room-runtime:$roomVersion" 39 | implementation "androidx.appcompat:appcompat:$rootProject.appCompatVersion" 40 | implementation "androidx.cardview:cardview:$rootProject.cardViewVersion" 41 | implementation "androidx.recyclerview:recyclerview:$rootProject.recyclerViewVersion" 42 | implementation "android.arch.work:work-runtime-ktx:$rootProject.workVersion" 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$rootProject.kotlinVersion" 44 | implementation "com.squareup.picasso:picasso:2.5.2" 45 | implementation "com.liangfeizc:avatarview:0.0.1@aar" 46 | implementation "com.google.android.material:material:$rootProject.materialVersion" 47 | implementation "com.google.code.gson:gson:$rootProject.gsonVersion" 48 | implementation "androidx.viewpager:viewpager:$rootProject.viewPagerVersion" 49 | } 50 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/rufi/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 38 | 39 | 42 | 43 | 46 | 47 | 50 | 51 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/assets/actors.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "堀北真希", 4 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682412118-1a6c540c-282c-4ae6-9153-8f973beacec8.png", 5 | "birthday": "1988/10/6", 6 | "birthplace": "東京都清瀬市" 7 | }, 8 | { 9 | "name": "新垣結衣", 10 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682411981-5c268a9e-88e0-4477-8b02-76d31fc0019b.png", 11 | "birthday": "1988/10/6", 12 | "birthplace": "東京都清瀬市" 13 | }, 14 | { 15 | "name": "戸田恵梨香", 16 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682412003-4b8a231a-91a4-48cd-a14b-5ddbd2a15711.png", 17 | "birthday": "1988/10/6", 18 | "birthplace": "東京都清瀬市" 19 | }, 20 | { 21 | "name": "長澤まさみ", 22 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682412142-48fdfc0e-3e37-42a2-9b18-fdc99b1b6b0d.png", 23 | "birthday": "1988/10/6", 24 | "birthplace": "東京都清瀬市" 25 | }, 26 | { 27 | "name": "北川景子", 28 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682412067-4e258376-4a91-4ff7-80aa-0fb7135906fd.png", 29 | "birthday": "1988/10/6", 30 | "birthplace": "東京都清瀬市" 31 | }, 32 | { 33 | "name": "石原さとみ", 34 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682412088-070d1f2a-b3ec-4562-948e-06439a0268b9.png", 35 | "birthday": "1988/10/6", 36 | "birthplace": "東京都清瀬市" 37 | }, 38 | { 39 | "name": "佐々木希", 40 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682412036-135a2640-d4d0-4628-8ce5-6a194eaee765.png", 41 | "birthday": "1988/10/6", 42 | "birthplace": "東京都清瀬市" 43 | }, 44 | { 45 | "name": "深田恭子", 46 | "avatar": "https://cdn.nlark.com/yuque/0/2019/png/124977/1555682412210-c4d86d94-abbd-4de2-8539-f4183ad05f78.png", 47 | "birthday": "1988/10/6", 48 | "birthplace": "東京都清瀬市" 49 | } 50 | ] -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangfeidotme/MasteringAndroidDataBinding/526a98f68cd116e83047f722b1231f36d37c6400/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.core.view.GravityCompat 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.drawerlayout.widget.DrawerLayout 8 | import androidx.navigation.NavController 9 | import androidx.navigation.Navigation 10 | import androidx.navigation.ui.AppBarConfiguration 11 | import androidx.navigation.ui.navigateUp 12 | import androidx.navigation.ui.setupActionBarWithNavController 13 | import androidx.navigation.ui.setupWithNavController 14 | import me.liangfei.databinding.databinding.ActivityMainBinding 15 | 16 | class MainActivity : AppCompatActivity() { 17 | private lateinit var drawerLayout: DrawerLayout 18 | private lateinit var appBarConfiguration: AppBarConfiguration 19 | private lateinit var navController: NavController 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | 24 | val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, 25 | R.layout.activity_main) 26 | drawerLayout = binding.drawerLayout 27 | 28 | navController = Navigation.findNavController(this, R.id.actor_nav_fragment) 29 | appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout) 30 | 31 | // Set up ActionBar 32 | setSupportActionBar(binding.toolbar) 33 | setupActionBarWithNavController(navController, appBarConfiguration) 34 | 35 | binding.navigationView.setupWithNavController(navController) 36 | } 37 | 38 | override fun onSupportNavigateUp(): Boolean { 39 | return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() 40 | } 41 | 42 | override fun onBackPressed() { 43 | if (drawerLayout.isDrawerOpen(GravityCompat.START)) { 44 | drawerLayout.closeDrawer(GravityCompat.START) 45 | } else { 46 | super.onBackPressed() 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/adapters/ActorDetailPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.adapters 2 | 3 | import androidx.fragment.app.FragmentManager 4 | import androidx.fragment.app.FragmentStatePagerAdapter 5 | import me.liangfei.databinding.fragments.ActorProfileFragment 6 | import me.liangfei.databinding.fragments.ActorWorksFragment 7 | 8 | 9 | class ActorDetailPagerAdapter(fm: FragmentManager, val actorId: Int) : FragmentStatePagerAdapter(fm) { 10 | override fun getItem(position: Int) = when (position) { 11 | 0 -> ActorProfileFragment.newInstance(actorId) 12 | 1 -> ActorWorksFragment() 13 | else -> throw IllegalStateException("no way") 14 | } 15 | 16 | override fun getCount() = 2 17 | 18 | override fun getPageTitle(position: Int) = when (position) { 19 | 0 -> "Profile" 20 | 1 -> "Works" 21 | else -> throw IllegalStateException("no way") 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/adapters/ActorListAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.adapters 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.databinding.DataBindingUtil 7 | import androidx.navigation.findNavController 8 | import androidx.recyclerview.widget.DiffUtil 9 | import androidx.recyclerview.widget.ListAdapter 10 | import androidx.recyclerview.widget.RecyclerView 11 | import me.liangfei.databinding.R 12 | import me.liangfei.databinding.data.entities.Actor 13 | import me.liangfei.databinding.databinding.ListItemActorBinding 14 | import me.liangfei.databinding.databinding.ListItemAddBinding 15 | import me.liangfei.databinding.fragments.ActorCollectionFragmentDirections 16 | 17 | 18 | class ActorListAdapter : ListAdapter(ActorDiffCallback()) { 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = 20 | if (viewType.isTypeAdd()) { 21 | AddViewHolder( 22 | DataBindingUtil.inflate( 23 | LayoutInflater.from(parent.context), 24 | R.layout.list_item_add, parent, false 25 | ) 26 | ) 27 | } else { 28 | ViewHolder( 29 | DataBindingUtil.inflate( 30 | LayoutInflater.from(parent.context), 31 | R.layout.list_item_actor, parent, false 32 | ) 33 | ) 34 | } 35 | 36 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 37 | if (getItemViewType(position).isTypeAdd()) { 38 | (holder as AddViewHolder).bind(View.OnClickListener { 39 | // TODO go to add more actors 40 | }) 41 | } else { 42 | getItem(position).let { actor -> 43 | with(holder as ViewHolder) { 44 | itemView.tag = actor 45 | bind(createOnClickListener(actor.actorId), actor) 46 | } 47 | } 48 | } 49 | } 50 | 51 | private fun createOnClickListener(actorId: Int): View.OnClickListener { 52 | return View.OnClickListener { 53 | val direction = ActorCollectionFragmentDirections.showActorDetail(actorId) 54 | it.findNavController().navigate(direction) 55 | } 56 | } 57 | 58 | override fun getItemViewType(position: Int) = 59 | if (position == itemCount - 1) VIEW_TYPE_ADD else VIEW_TYPE_ACTOR 60 | 61 | 62 | override fun getItemCount() = super.getItemCount() + 1 63 | 64 | class ViewHolder( 65 | private val binding: ListItemActorBinding 66 | ) : RecyclerView.ViewHolder(binding.root) { 67 | fun bind(listener: View.OnClickListener, actor: Actor) { 68 | with(binding) { 69 | clickListener = listener 70 | this.actor = actor 71 | executePendingBindings() 72 | } 73 | } 74 | } 75 | 76 | class AddViewHolder(val binding: ListItemAddBinding) : RecyclerView.ViewHolder(binding.root) { 77 | fun bind(listener: View.OnClickListener) { 78 | binding.onClickListener = listener 79 | } 80 | } 81 | 82 | companion object { 83 | private const val VIEW_TYPE_ACTOR = 0x00 84 | private const val VIEW_TYPE_ADD = 0x01 85 | 86 | fun Int.isTypeAdd() = this == VIEW_TYPE_ADD 87 | } 88 | } 89 | 90 | private class ActorDiffCallback : DiffUtil.ItemCallback() { 91 | override fun areContentsTheSame(oldItem: Actor, newItem: Actor) = 92 | oldItem.name == newItem.name 93 | 94 | override fun areItemsTheSame(oldItem: Actor, newItem: Actor) = 95 | oldItem.actorId == newItem.actorId 96 | 97 | } 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/core/App.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.core 2 | 3 | import android.app.Application 4 | 5 | class App : Application() { 6 | companion object { 7 | val TAG = "DataBinding" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/data/ActorDao.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.data 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import me.liangfei.databinding.data.entities.Actor 9 | 10 | 11 | @Dao 12 | interface ActorDao { 13 | @Query("SELECT id, name, avatar FROM actors") 14 | fun getActors(): LiveData> 15 | 16 | @Query("SELECT * FROM actors WHERE id = :actorId") 17 | fun getActor(actorId: Int): LiveData 18 | 19 | @Insert(onConflict = OnConflictStrategy.REPLACE) 20 | fun insertAll(actors: List) 21 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/data/ActorRepository.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.data 2 | 3 | 4 | class ActorRepository private constructor(private val actorDao: ActorDao) { 5 | fun getActors() = actorDao.getActors() 6 | 7 | fun getActor(actorId: Int) = actorDao.getActor(actorId) 8 | 9 | companion object { 10 | @Volatile private var instance: ActorRepository? = null 11 | 12 | fun getInstance(actorDao: ActorDao) = 13 | instance ?: synchronized(this) { 14 | instance ?: ActorRepository(actorDao).also { instance = it} 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/data/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.data 2 | 3 | import android.content.Context 4 | import androidx.room.* 5 | import androidx.sqlite.db.SupportSQLiteDatabase 6 | import androidx.work.OneTimeWorkRequestBuilder 7 | import androidx.work.WorkManager 8 | import me.liangfei.databinding.data.entities.Actor 9 | import me.liangfei.databinding.utilities.DATABASE_NAME 10 | import me.liangfei.databinding.workers.SeedDatabaseWorker 11 | 12 | @Database(entities = [Actor::class], version = 1, exportSchema = false) 13 | @TypeConverters(Converters::class) 14 | abstract class AppDatabase : RoomDatabase() { 15 | abstract fun actorDao(): ActorDao 16 | 17 | companion object { 18 | @Volatile private var instance: AppDatabase? = null 19 | 20 | fun getInstance(context: Context): AppDatabase { 21 | return instance ?: synchronized(this) { 22 | instance ?: buildDatabase(context).also { instance = it } 23 | } 24 | } 25 | 26 | private fun buildDatabase(context: Context): AppDatabase { 27 | return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) 28 | .addCallback(object : RoomDatabase.Callback() { 29 | override fun onCreate(db: SupportSQLiteDatabase) { 30 | super.onCreate(db) 31 | val request = OneTimeWorkRequestBuilder().build() 32 | WorkManager.getInstance().enqueue(request) 33 | } 34 | }) 35 | .build() 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/data/Converters.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.data 2 | 3 | import androidx.room.TypeConverter 4 | import java.util.* 5 | 6 | 7 | class Converters { 8 | @TypeConverter 9 | fun timestampToDate(value: Long?) = value?.let { Date(it) } 10 | 11 | @TypeConverter 12 | fun dateToTimestamp(date: Date?) = date?.time 13 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/data/entities/Actor.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.data.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.Ignore 6 | import androidx.room.PrimaryKey 7 | import java.util.* 8 | 9 | 10 | @Entity(tableName = "actors") 11 | data class Actor( 12 | val name: String, 13 | val avatar: String, 14 | val birthday: String?, 15 | val realName: String?, 16 | val birthplace: String?, 17 | val office: String? 18 | ) { 19 | @PrimaryKey(autoGenerate = true) 20 | @ColumnInfo(name = "id") 21 | var actorId: Int = 0 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/data/entities/ActorWork.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.data.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import me.liangfei.databinding.data.entities.Actor 7 | import me.liangfei.databinding.data.entities.Work 8 | 9 | @Entity( 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = Actor::class, 13 | parentColumns = ["id"], 14 | childColumns = ["actor_id"] 15 | ), 16 | ForeignKey( 17 | entity = Work::class, 18 | parentColumns = ["id"], 19 | childColumns = ["work_id"] 20 | ) 21 | ], 22 | primaryKeys = ["work_id", "actor_id"], 23 | tableName = "actor_work_bindings" 24 | ) 25 | data class ActorWork( 26 | @ColumnInfo(name = "work_id") 27 | val workId: Int, 28 | @ColumnInfo(name = "actor_id") 29 | val actorId: Int 30 | ) 31 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/data/entities/Work.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.data.entities 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | 7 | @Entity(tableName = "works") 8 | data class Work( 9 | val name: String 10 | ) { 11 | @PrimaryKey(autoGenerate = true) 12 | var id: Int = 0 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/fragments/ActorCollectionFragment.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import androidx.recyclerview.widget.GridLayoutManager 11 | import me.liangfei.databinding.adapters.ActorListAdapter 12 | import me.liangfei.databinding.databinding.FragmentActorListBinding 13 | import me.liangfei.databinding.utilities.InjectorUtils 14 | import me.liangfei.databinding.viewmodels.ActorViewModel 15 | 16 | class ActorCollectionFragment : Fragment() { 17 | private lateinit var viewModel: ActorViewModel 18 | 19 | override fun onCreateView( 20 | inflater: LayoutInflater, 21 | container: ViewGroup?, 22 | savedInstanceState: Bundle? 23 | ): View? { 24 | val binding = FragmentActorListBinding.inflate(inflater, container, false) 25 | 26 | val context = context ?: return binding.root 27 | val factory = InjectorUtils.provideActorViewModelFactory(context) 28 | 29 | viewModel = ViewModelProviders.of(requireActivity(), factory) 30 | .get(ActorViewModel::class.java) 31 | 32 | binding.actorListView.layoutManager = GridLayoutManager(context, 3) 33 | 34 | val adapter = ActorListAdapter() 35 | binding.actorListView.adapter = adapter 36 | subscribeUi(adapter) 37 | 38 | return binding.root 39 | } 40 | 41 | private fun subscribeUi(adapter: ActorListAdapter) { 42 | viewModel.actors.observe(viewLifecycleOwner, Observer { 43 | adapter.submitList(it) 44 | }) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/fragments/ActorDetailFragment.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.navigation.fragment.navArgs 9 | import kotlinx.android.synthetic.main.fragment_actor_detail.* 10 | import me.liangfei.databinding.adapters.ActorDetailPagerAdapter 11 | import me.liangfei.databinding.databinding.FragmentActorDetailBinding 12 | 13 | /** 14 | * Created by LIANG.FEI on 25/1/2019. 15 | */ 16 | class ActorDetailFragment : Fragment() { 17 | private val params by navArgs() 18 | 19 | override fun onCreateView( 20 | inflater: LayoutInflater, 21 | container: ViewGroup?, 22 | savedInstanceState: Bundle? 23 | ): View? { 24 | val binding = FragmentActorDetailBinding.inflate(inflater, container, false) 25 | val context = context ?: return binding.root 26 | 27 | // init view pager 28 | binding.tabLayout.setupWithViewPager(binding.viewPager) 29 | binding.viewPager.adapter = ActorDetailPagerAdapter( 30 | requireFragmentManager(), params.actorId) 31 | 32 | binding.imageUrl = "http://img.mp.itc.cn/upload/20161210/7a14ce89d0e44a8591f0ec7bac09ccb5_th.jpg" 33 | 34 | // TODO ViewPager 加上 tab,一个显示详情,一个展示影视作品,两边都用 RecyclerView。 35 | return binding.root 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/fragments/ActorListFragment.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.fragments 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | 6 | class ActorListFragment : Fragment() { 7 | 8 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/fragments/ActorProfileFragment.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import me.liangfei.databinding.databinding.FragmentActorProfileBinding 11 | import me.liangfei.databinding.utilities.InjectorUtils 12 | import me.liangfei.databinding.viewmodels.ActorViewModel 13 | 14 | 15 | class ActorProfileFragment : Fragment() { 16 | override fun onCreateView( 17 | inflater: LayoutInflater, 18 | container: ViewGroup?, 19 | savedInstanceState: Bundle? 20 | ): View? { 21 | val binding = FragmentActorProfileBinding.inflate( 22 | inflater, container, false 23 | ) 24 | 25 | val context = context ?: return binding.root 26 | val factory = InjectorUtils.provideActorViewModelFactory(context) 27 | val viewModel = ViewModelProviders.of(this, factory) 28 | .get(ActorViewModel::class.java) 29 | 30 | val actorId = arguments?.getInt(ARG_ACTOR_ID) 31 | actorId?.let { 32 | viewModel.actorDetail(actorId).observe(viewLifecycleOwner, Observer { 33 | binding.actor = it 34 | }) 35 | } 36 | 37 | return binding.root 38 | } 39 | 40 | companion object { 41 | private const val ARG_ACTOR_ID = "arg_actor_id" 42 | 43 | fun newInstance(actorId: Int) = ActorProfileFragment().apply { 44 | arguments = Bundle().apply { 45 | putInt(ARG_ACTOR_ID, actorId) 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/fragments/ActorWorksFragment.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | 9 | 10 | class ActorWorksFragment : Fragment() { 11 | override fun onCreateView( 12 | inflater: LayoutInflater, 13 | container: ViewGroup?, 14 | savedInstanceState: Bundle? 15 | ): View? { 16 | return super.onCreateView(inflater, container, savedInstanceState) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/listener/OkListener.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.listener; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * the listener for ok button 7 | * Created by 喵叔catuncle 8 | * 2016/05/31 20:10 9 | */ 10 | public interface OkListener { 11 | 12 | void onClickOk(View view); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/model/Contact.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.model; 2 | 3 | /** 4 | * Created by liangfeizc on 6/2/15. 5 | */ 6 | public class Contact { 7 | private final String mTel; 8 | private final String mAddress; 9 | 10 | public Contact(String tel, String address) { 11 | mTel = tel; 12 | mAddress = address; 13 | } 14 | 15 | public String getTel() { 16 | return mTel; 17 | } 18 | 19 | public String getAddress() { 20 | return mAddress; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/model/ObservableUser.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.model; 2 | 3 | import androidx.databinding.BaseObservable; 4 | import androidx.databinding.Bindable; 5 | 6 | import me.liangfei.databinding.BR; 7 | 8 | /** 9 | * Created by liangfeizc on 6/3/15. 10 | */ 11 | public class ObservableUser extends BaseObservable { 12 | private String mFirstName; 13 | private String mLastName; 14 | 15 | @Bindable 16 | public String getFirstName() { 17 | return mFirstName; 18 | } 19 | 20 | @Bindable 21 | public String getLastName() { 22 | return mLastName; 23 | } 24 | 25 | public void setFirstName(String firstName) { 26 | mFirstName = firstName; 27 | notifyPropertyChanged(BR.firstName); 28 | } 29 | 30 | public void setLastName(String lastName) { 31 | mLastName = lastName; 32 | notifyPropertyChanged(BR.lastName); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/model/PlainUser.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.model; 2 | 3 | import androidx.databinding.ObservableField; 4 | import androidx.databinding.ObservableInt; 5 | 6 | /** 7 | * Created by rufi on 6/3/15. 8 | */ 9 | public class PlainUser { 10 | public final ObservableField firstName = new ObservableField<>(); 11 | public final ObservableField lastName = new ObservableField<>(); 12 | public final ObservableInt age = new ObservableInt(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/model/User.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.model; 2 | 3 | /** 4 | * Created by liangfeizc on 6/2/15. 5 | */ 6 | public class User { 7 | private final String firstName; 8 | private final String lastName; 9 | private String displayName; 10 | private int age; 11 | 12 | public User(String firstName, String lastName) { 13 | this.firstName = firstName; 14 | this.lastName = lastName; 15 | } 16 | 17 | public User(String firstName, String lastName, int age) { 18 | this(firstName, lastName); 19 | this.age = age; 20 | } 21 | 22 | public int getAge() { 23 | return age; 24 | } 25 | 26 | public String getFirstName() { 27 | return firstName; 28 | } 29 | 30 | public String getLastName() { 31 | return lastName; 32 | } 33 | 34 | public String getDisplayName() { 35 | return firstName + " " + lastName; 36 | } 37 | 38 | public boolean isAdult() { 39 | return age >= 18; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.ActionBar; 5 | import androidx.appcompat.app.AppCompatActivity; 6 | import android.view.MenuItem; 7 | 8 | /** 9 | * Created by rufi on 6/9/15. 10 | */ 11 | public class BaseActivity extends AppCompatActivity { 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | ActionBar actionBar = getSupportActionBar(); 17 | if (actionBar != null) { 18 | actionBar.setDisplayHomeAsUpEnabled(true); 19 | actionBar.setDisplayShowHomeEnabled(false); 20 | actionBar.setDisplayShowTitleEnabled(true); 21 | actionBar.setDisplayUseLogoEnabled(false); 22 | } 23 | } 24 | 25 | @Override 26 | public boolean onOptionsItemSelected(MenuItem item) { 27 | switch (item.getItemId()) { 28 | case android.R.id.home: 29 | finish(); 30 | break; 31 | } 32 | return super.onOptionsItemSelected(item); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/attributesetter/AttributeSettersActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.attributesetter; 2 | 3 | import androidx.databinding.BindingAdapter; 4 | import androidx.databinding.DataBindingUtil; 5 | import me.liangfei.databinding.R; 6 | import me.liangfei.databinding.core.App; 7 | import me.liangfei.databinding.sample.BaseActivity; 8 | import me.liangfei.databinding.utils.Randoms; 9 | 10 | import android.graphics.drawable.Drawable; 11 | import android.os.Bundle; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.widget.ImageView; 15 | import android.widget.Toast; 16 | 17 | import me.liangfei.databinding.databinding.ActivityAttributeSettersBinding; 18 | 19 | import com.squareup.picasso.Picasso; 20 | 21 | public class AttributeSettersActivity extends BaseActivity { 22 | private ActivityAttributeSettersBinding mBinding; 23 | 24 | public View.OnClickListener avatarClickListener = new View.OnClickListener() { 25 | @Override 26 | public void onClick(View v) { 27 | Toast.makeText(AttributeSettersActivity.this, "Come on", Toast.LENGTH_SHORT).show(); 28 | mBinding.setImageUrl(Randoms.nextImgUrl()); 29 | } 30 | }; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | 36 | mBinding = DataBindingUtil.setContentView(this, R.layout.activity_attribute_setters); 37 | mBinding.setActivity(this); 38 | mBinding.setImageUrl(Randoms.nextImgUrl()); 39 | } 40 | 41 | @BindingAdapter({"imageUrl", "error"}) 42 | public static void loadImage(ImageView view, String url, Drawable error) { 43 | Log.d(App.Companion.getTAG(), "load image"); 44 | Picasso.with(view.getContext()).load(url).error(error).into(view); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/basic/BasicActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.basic; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import me.liangfei.databinding.R; 5 | import me.liangfei.databinding.model.User; 6 | import me.liangfei.databinding.sample.BaseActivity; 7 | 8 | import android.os.Bundle; 9 | 10 | import me.liangfei.databinding.databinding.ActivityBasicBinding; 11 | 12 | public class BasicActivity extends BaseActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | 18 | ActivityBasicBinding binding = DataBindingUtil.setContentView( 19 | this, R.layout.activity_basic); 20 | User user = new User("fei", "Liang", 27); 21 | binding.setUser(user); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/collection/CollectionActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.collection; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import me.liangfei.databinding.R; 5 | import me.liangfei.databinding.sample.BaseActivity; 6 | 7 | import android.os.Bundle; 8 | import android.util.SparseArray; 9 | 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | import me.liangfei.databinding.databinding.CollectionsBinding; 17 | 18 | public class CollectionActivity extends BaseActivity { 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | CollectionsBinding binding = DataBindingUtil.setContentView( 24 | this, R.layout.activity_collection); 25 | 26 | String[] literals = new String[]{"liang", "fei"}; 27 | 28 | List list = new ArrayList<>(); 29 | SparseArray sparse = new SparseArray<>(2); 30 | 31 | String key = "firstName"; 32 | int index = 0; 33 | 34 | for (int i = 0; i < literals.length; i++) { 35 | list.add(literals[i]); 36 | sparse.put(0, literals[i]); 37 | } 38 | 39 | Map map = new HashMap<>(); 40 | map.put(key, "liang"); 41 | map.put("lastName", "fei"); 42 | 43 | binding.setIndex(index); 44 | binding.setKey(key); 45 | binding.setList(list); 46 | binding.setSparse(sparse); 47 | binding.setMap(map); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/converter/ConversionsActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.converter; 2 | 3 | import androidx.databinding.BindingAdapter; 4 | import androidx.databinding.BindingConversion; 5 | import androidx.databinding.DataBindingUtil; 6 | import androidx.databinding.ObservableBoolean; 7 | import me.liangfei.databinding.R; 8 | import me.liangfei.databinding.sample.BaseActivity; 9 | import me.liangfei.databinding.utils.ScreenUtils; 10 | 11 | import android.graphics.drawable.ColorDrawable; 12 | import android.os.Bundle; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | 16 | import me.liangfei.databinding.databinding.ActivityConversionsBinding; 17 | 18 | public class ConversionsActivity extends BaseActivity { 19 | 20 | private ObservableBoolean mIsError = new ObservableBoolean(); 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | ActivityConversionsBinding binding = 27 | DataBindingUtil.setContentView(this, R.layout.activity_conversions); 28 | 29 | mIsError.set(true); 30 | 31 | binding.setIsError(mIsError); 32 | binding.setHeight(ScreenUtils.dp2px(this, 200)); 33 | 34 | } 35 | 36 | public void toggleIsError(View view) { 37 | mIsError.set(!mIsError.get()); 38 | } 39 | 40 | @BindingConversion 41 | public static ColorDrawable convertColorToDrawable(int color) { 42 | return new ColorDrawable(color); 43 | } 44 | 45 | @BindingAdapter("layout_height") 46 | public static void setLayoutHeight(View view, float height) { 47 | ViewGroup.LayoutParams params = view.getLayoutParams(); 48 | params.height = (int) height; 49 | view.setLayoutParams(params); 50 | } 51 | /** !!! Binding conversion should be forbidden, otherwise it will conflict with 52 | * {@code android:visiblity} attribute. 53 | */ 54 | /* 55 | @BindingConversion 56 | public static int convertColorToString(int color) { 57 | switch (color) { 58 | case Color.RED: 59 | return R.string.red; 60 | case Color.WHITE: 61 | return R.string.white; 62 | } 63 | return R.string.app_name; 64 | }*/ 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/custombinding/CustomBindingActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.custombinding; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import me.liangfei.databinding.R; 5 | import me.liangfei.databinding.model.Contact; 6 | import me.liangfei.databinding.sample.BaseActivity; 7 | 8 | import android.os.Bundle; 9 | 10 | import me.liangfei.databinding.ContractBinding; 11 | 12 | public class CustomBindingActivity extends BaseActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | 18 | ContractBinding binding = DataBindingUtil.setContentView( 19 | this, R.layout.activity_custom_binding); 20 | 21 | Contact contact = new Contact("111", "Japan"); 22 | binding.setContact(contact); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/dynamic/DynamicActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.dynamic; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | 5 | import android.os.Bundle; 6 | 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import me.liangfei.databinding.R; 9 | import me.liangfei.databinding.sample.BaseActivity; 10 | 11 | import me.liangfei.databinding.databinding.ActivityDynamicBinding; 12 | 13 | public class DynamicActivity extends BaseActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | 19 | ActivityDynamicBinding binding = DataBindingUtil.setContentView( 20 | this, R.layout.activity_dynamic); 21 | 22 | binding.recyclerView.setHasFixedSize(true); 23 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); 24 | binding.recyclerView.setAdapter(new UserAdapter()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/dynamic/UserAdapter.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.dynamic; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import androidx.annotation.NonNull; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import me.liangfei.databinding.R; 7 | import me.liangfei.databinding.model.User; 8 | import me.liangfei.databinding.utils.Randoms; 9 | 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import me.liangfei.databinding.databinding.UserItemBinding; 18 | 19 | /** 20 | * Created by rufi on 6/5/15. 21 | */ 22 | public class UserAdapter extends RecyclerView.Adapter { 23 | private static final int USER_COUNT = 10; 24 | 25 | @NonNull 26 | private List mUsers; 27 | 28 | public UserAdapter() { 29 | mUsers = new ArrayList<>(10); 30 | for (int i = 0; i < USER_COUNT; i++) { 31 | User user = new User(Randoms.nextFirstName(), Randoms.nextLastName()); 32 | mUsers.add(user); 33 | } 34 | } 35 | 36 | public static class UserHolder extends RecyclerView.ViewHolder { 37 | private UserItemBinding mBinding; 38 | 39 | public UserHolder(View itemView) { 40 | super(itemView); 41 | mBinding = DataBindingUtil.bind(itemView); 42 | } 43 | 44 | public void bind(@NonNull User user) { 45 | mBinding.setUser(user); 46 | } 47 | } 48 | 49 | @Override 50 | public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 51 | View itemView = LayoutInflater.from(viewGroup.getContext()) 52 | .inflate(R.layout.user_item, viewGroup, false); 53 | return new UserHolder(itemView); 54 | } 55 | 56 | @Override 57 | public void onBindViewHolder(UserHolder holder, int position) { 58 | holder.bind(mUsers.get(position)); 59 | } 60 | 61 | @Override 62 | public int getItemCount() { 63 | return mUsers.size(); 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/include/IncludeActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.include; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import me.liangfei.databinding.R; 5 | import me.liangfei.databinding.listener.OkListener; 6 | import me.liangfei.databinding.model.User; 7 | import me.liangfei.databinding.sample.BaseActivity; 8 | import me.liangfei.databinding.databinding.ActivityIncludeBinding; 9 | 10 | import android.os.Bundle; 11 | import android.text.Editable; 12 | import android.text.TextWatcher; 13 | import android.view.View; 14 | import android.widget.Toast; 15 | 16 | public class IncludeActivity extends BaseActivity implements OkListener { 17 | 18 | private ActivityIncludeBinding binding; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | binding = DataBindingUtil.setContentView( 24 | this, R.layout.activity_include); 25 | 26 | binding.setListener(this); 27 | binding.setOkText("to toast"); 28 | 29 | //in order to get the etName, you must define an id for the include tag. 30 | binding.layoutInput.etName.addTextChangedListener(new TextWatcher() { 31 | @Override 32 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 33 | 34 | } 35 | 36 | @Override 37 | public void onTextChanged(CharSequence s, int start, int before, int count) { 38 | User user = new User(s.toString(), "Liang"); 39 | 40 | binding.setUser(user); 41 | } 42 | 43 | @Override 44 | public void afterTextChanged(Editable s) { 45 | 46 | } 47 | }); 48 | } 49 | 50 | @Override 51 | public void onClickOk(View view) { 52 | Toast.makeText(this, "the btn clicked!", Toast.LENGTH_SHORT).show(); 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/observable/ObservableActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.observable; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import androidx.databinding.ObservableArrayMap; 5 | import me.liangfei.databinding.R; 6 | import me.liangfei.databinding.model.ObservableUser; 7 | import me.liangfei.databinding.model.PlainUser; 8 | import me.liangfei.databinding.sample.BaseActivity; 9 | import me.liangfei.databinding.databinding.ActivityObservableBinding; 10 | 11 | import android.os.Bundle; 12 | import android.view.View; 13 | 14 | public class ObservableActivity extends BaseActivity { 15 | 16 | private ObservableUser user = new ObservableUser(); 17 | private PlainUser plainUser = new PlainUser(); 18 | private ObservableArrayMap mapUser = new ObservableArrayMap<>(); 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | 24 | ActivityObservableBinding binding = DataBindingUtil.setContentView( 25 | this, R.layout.activity_observable); 26 | 27 | setMyName(null); 28 | 29 | binding.setUser(user); 30 | binding.setPlainUser(plainUser); 31 | binding.setMapUser(mapUser); 32 | } 33 | 34 | public void setOtherName(View view) { 35 | user.setFirstName("zhu"); 36 | user.setLastName("chen"); 37 | 38 | plainUser.firstName.set("zhu"); 39 | plainUser.lastName.set("chen"); 40 | plainUser.age.set(27); 41 | 42 | mapUser.put("firstName", "zhu"); 43 | mapUser.put("lastName", "chen"); 44 | mapUser.put("age", 27); 45 | } 46 | 47 | public void setMyName(View view) { 48 | user.setFirstName("liang"); 49 | user.setLastName("fei"); 50 | 51 | plainUser.firstName.set("liang"); 52 | plainUser.lastName.set("fei"); 53 | plainUser.age.set(27); 54 | 55 | mapUser.put("firstName", "liang"); 56 | mapUser.put("lastName", "fei"); 57 | mapUser.put("age", 27); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/resource/ResourceActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.resource; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import me.liangfei.databinding.R; 5 | import me.liangfei.databinding.sample.BaseActivity; 6 | 7 | import android.os.Bundle; 8 | 9 | import me.liangfei.databinding.databinding.ResourceBinding; 10 | 11 | public class ResourceActivity extends BaseActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | ResourceBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_resource); 17 | 18 | binding.setLarge(true); 19 | 20 | binding.setFirstName("liang"); 21 | binding.setLastName("fei"); 22 | 23 | binding.setBananaCount(2); 24 | binding.setOrangeCount(10); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/viewid/ViewWithIDsActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.viewid; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import me.liangfei.databinding.R; 5 | import me.liangfei.databinding.sample.BaseActivity; 6 | import me.liangfei.databinding.databinding.ActivityViewWithIdsBinding; 7 | 8 | import android.os.Bundle; 9 | import android.view.View; 10 | 11 | public class ViewWithIDsActivity extends BaseActivity { 12 | 13 | ActivityViewWithIdsBinding binding; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | binding = DataBindingUtil.setContentView(this, R.layout.activity_view_with_ids); 19 | } 20 | 21 | public void showMyName(View view) { 22 | binding.firstName.setText("liang"); 23 | binding.lastName.setText("fei"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/sample/viewstub/ViewStubActivity.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.sample.viewstub; 2 | 3 | import androidx.databinding.DataBindingUtil; 4 | import me.liangfei.databinding.R; 5 | import me.liangfei.databinding.model.User; 6 | import me.liangfei.databinding.sample.BaseActivity; 7 | 8 | import android.os.Bundle; 9 | import android.view.View; 10 | import android.view.ViewStub; 11 | 12 | import me.liangfei.databinding.databinding.ActivityViewStubBinding; 13 | import me.liangfei.databinding.databinding.ViewStubBinding; 14 | 15 | 16 | public class ViewStubActivity extends BaseActivity { 17 | private ActivityViewStubBinding mBinding; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | mBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub); 24 | mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() { 25 | @Override 26 | public void onInflate(ViewStub stub, View inflated) { 27 | ViewStubBinding binding = DataBindingUtil.bind(inflated); 28 | User user = new User("liang", "fei"); 29 | binding.setUser(user); 30 | } 31 | }); 32 | 33 | } 34 | 35 | 36 | /** 37 | * Don't panic for red error reporting. Just ignore it and run the app. Surprise never ends. 38 | */ 39 | public void inflateViewStub(View view) { 40 | if (!mBinding.viewStub.isInflated()) { 41 | mBinding.viewStub.getViewStub().inflate(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/utilities/BindingAdapters.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.utilities 2 | 3 | import android.graphics.BitmapFactory 4 | import android.widget.ImageView 5 | import androidx.databinding.BindingAdapter 6 | import com.bumptech.glide.Glide 7 | import com.bumptech.glide.request.RequestOptions 8 | 9 | 10 | object BindingAdapters { 11 | @JvmStatic 12 | @BindingAdapter(value = ["url", "isRounded"], requireAll = false) 13 | fun loadImage(imageView: ImageView, url: String?, isRounded: Boolean = false) { 14 | Glide.with(imageView) 15 | .load(url) 16 | .apply( 17 | if (isRounded) RequestOptions.circleCropTransform() 18 | else RequestOptions.noTransformation() 19 | ) 20 | .into(imageView) 21 | } 22 | 23 | @JvmStatic 24 | @BindingAdapter(value = ["avatarFromAssets"]) 25 | fun loadAssetImage(imageView: ImageView, filePath: String) { 26 | with (imageView.context) { 27 | Glide.with(this) 28 | .load(BitmapFactory.decodeStream(assets.open(filePath))) 29 | .apply(RequestOptions.circleCropTransform()) 30 | .into(imageView) 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/utilities/Constants.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.utilities 2 | 3 | 4 | const val DATABASE_NAME = "everbinding-db" 5 | const val ACTOR_DATA_FILENAME = "actors.json" 6 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/utilities/InjectorUtils.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.utilities 2 | 3 | import android.content.Context 4 | import me.liangfei.databinding.data.ActorRepository 5 | import me.liangfei.databinding.data.AppDatabase 6 | import me.liangfei.databinding.viewmodels.ActorViewModelFactory 7 | 8 | 9 | object InjectorUtils { 10 | private fun getActorRepository(context: Context): ActorRepository { 11 | return ActorRepository.getInstance( 12 | AppDatabase.getInstance(context.applicationContext).actorDao()) 13 | } 14 | 15 | fun provideActorViewModelFactory( 16 | context: Context 17 | ): ActorViewModelFactory { 18 | val repository = getActorRepository(context) 19 | return ActorViewModelFactory(repository) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/utils/MyStringUtils.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.utils; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * Created by liangfeizc on 6/2/15. 8 | */ 9 | public class MyStringUtils { 10 | private static Pattern sWordPattern = Pattern.compile("\\w+"); 11 | 12 | public static String capitalizeSentence(final String sentence) { 13 | String result = sentence; 14 | Matcher matcher = sWordPattern.matcher(result); 15 | while (matcher.find()) { 16 | String word = matcher.group(); 17 | result = result.replace(matcher.group(), capitalize(word)); 18 | } 19 | return result; 20 | } 21 | 22 | public static String capitalize(final String word) { 23 | if (word.length() > 1) { 24 | return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1); 25 | } 26 | return word; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/utils/Randoms.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.utils; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Created by liangfeizc on 10/24/15. 7 | */ 8 | public class Randoms { 9 | private static final Random sRandom = new Random(); 10 | 11 | private static final String[] IMG_URLS = new String[]{ 12 | "http://ww2.sinaimg.cn/large/7a8aed7bjw1ex8h4lnq3oj20hs0qoadj.jpg", 13 | "http://pic.meizitu.com/wp-content/uploads/2015a/10/24/01.jpg", 14 | "http://pic.meizitu.com/wp-content/uploads/2015a/10/23/01.jpg", 15 | "http://pic.meizitu.com/wp-content/uploads/2015a/10/18/01.jpg" 16 | }; 17 | 18 | public static String nextImgUrl() { 19 | return IMG_URLS[sRandom.nextInt(IMG_URLS.length)]; 20 | } 21 | 22 | /*****************************/ 23 | 24 | public static final String[] FIRST_NAMES = {"Zhao", "Qian", "Sun", "Li", "Zhou", "Wu"}; 25 | public static final String[] LAST_NAMES = {"Tiedan", "Ritian", "LiangChen"}; 26 | 27 | private static final Random sRandomGenerator = new Random(); 28 | 29 | public static String nextFirstName() { 30 | return FIRST_NAMES[sRandomGenerator.nextInt(FIRST_NAMES.length)]; 31 | } 32 | 33 | public static String nextLastName() { 34 | return LAST_NAMES[sRandomGenerator.nextInt(LAST_NAMES.length)]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/utils/ScreenUtils.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.utils; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Created by liangfeizc on 7/31/15. 7 | */ 8 | public class ScreenUtils { 9 | public static float dp2px(final Context context, final float dpValue) { 10 | return dpValue * context.getResources().getDisplayMetrics().density; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/view/NameCard.java: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.view; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import androidx.annotation.IntRange; 6 | import androidx.annotation.NonNull; 7 | import me.liangfei.databinding.R; 8 | 9 | import android.util.AttributeSet; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | 14 | 15 | /** 16 | * Created by rufi on 6/9/15. 17 | */ 18 | public class NameCard extends LinearLayout { 19 | private int mAge; 20 | 21 | private TextView mFirstName; 22 | private TextView mLastName; 23 | 24 | public NameCard(Context context) { 25 | this(context, null); 26 | } 27 | 28 | public NameCard(Context context, AttributeSet attrs) { 29 | this(context, attrs, 0); 30 | } 31 | 32 | public NameCard(Context context, AttributeSet attrs, int defStyleAttr) { 33 | super(context, attrs, defStyleAttr); 34 | 35 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NameCard); 36 | 37 | try { 38 | mAge = a.getInteger(R.styleable.NameCard_age, 0); 39 | } finally { 40 | a.recycle(); 41 | } 42 | 43 | init(); 44 | } 45 | 46 | private void init() { 47 | inflate(getContext(), R.layout.name_card, this); 48 | mFirstName = (TextView) findViewById(R.id.first_name); 49 | mLastName = (TextView) findViewById(R.id.last_name); 50 | } 51 | 52 | public void setFirstName(@NonNull final String firstName) { 53 | mFirstName.setText(firstName); 54 | } 55 | 56 | public void setLastName(@NonNull final String lastName) { 57 | mLastName.setText(lastName); 58 | } 59 | 60 | public void setAge(@IntRange(from=1) int age) { 61 | mAge = age; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/viewmodels/ActorViewModel.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.viewmodels 2 | 3 | import androidx.lifecycle.ViewModel 4 | import me.liangfei.databinding.data.ActorRepository 5 | 6 | /** 7 | * Created by LIANG.FEI on 25/1/2019. 8 | */ 9 | class ActorViewModel internal constructor(private val actorRepository: ActorRepository) 10 | : ViewModel() { 11 | val actors = actorRepository.getActors() 12 | fun actorDetail(actorId: Int) = actorRepository.getActor(actorId) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/viewmodels/ActorViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.viewmodels 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import me.liangfei.databinding.data.ActorRepository 6 | 7 | 8 | @Suppress("UNCHECKED_CAST") 9 | class ActorViewModelFactory( 10 | private val repository: ActorRepository 11 | ) : ViewModelProvider.NewInstanceFactory() { 12 | override fun create(modelClass: Class): T { 13 | return ActorViewModel(repository) as T 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/me/liangfei/databinding/workers/SeedDatabaseWorker.kt: -------------------------------------------------------------------------------- 1 | package me.liangfei.databinding.workers 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import androidx.work.CoroutineWorker 6 | import androidx.work.WorkerParameters 7 | import com.google.gson.Gson 8 | import com.google.gson.reflect.TypeToken 9 | import com.google.gson.stream.JsonReader 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.coroutineScope 12 | import me.liangfei.databinding.data.entities.Actor 13 | import me.liangfei.databinding.data.AppDatabase 14 | import me.liangfei.databinding.utilities.ACTOR_DATA_FILENAME 15 | 16 | 17 | class SeedDatabaseWorker( 18 | context: Context, 19 | workerParams: WorkerParameters 20 | ) : CoroutineWorker(context, workerParams) { 21 | private val TAG by lazy { SeedDatabaseWorker::class.java.simpleName } 22 | override val coroutineContext = Dispatchers.IO 23 | 24 | override suspend fun doWork(): Result = coroutineScope { 25 | 26 | try { 27 | applicationContext.assets.open(ACTOR_DATA_FILENAME).use { inputStream -> 28 | JsonReader(inputStream.reader()).use { jsonReader -> 29 | val actorType = object : TypeToken>() {}.type 30 | val actorList: List = Gson().fromJson(jsonReader, actorType) 31 | 32 | val database = AppDatabase.getInstance(applicationContext) 33 | database.actorDao().insertAll(actorList) 34 | 35 | Result.success() 36 | } 37 | } 38 | } catch (ex: Exception) { 39 | Log.e(TAG, "Error seeding database", ex) 40 | Result.failure() 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_attribute_setters.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 21 | 22 | 33 | 34 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 55 | 56 | 57 | 58 | 59 | 64 | 65 | 69 | 70 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_collection.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 29 | 33 | 34 | 39 | 43 | 44 | 49 | 53 | 54 | 60 | 61 | 66 | 70 | 71 | 76 | 80 | 81 | 86 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_conversions.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 23 | 24 |