前言
这篇文章主要讲解JetPack中的DataBinding组件的使用。
简单实用
解决问题:当界面被改写的时候,Activity中的控件代码也必须被改写。为了避免这个问题的发生,使用了databinding。
- Build.gradle配置项目使用dataBinding功能
defaultConfig{
...
dataBinding{
enabled true
}
...
}
- 将布局文件转换成dataBinding适配的布局文件
- 反向绑定(数据回绑到界面上)
- 在xml中的标签声明变化的数据。
- 通过@{}绑定变量和函数@{()->函数名称}
<layout>
<data>
<!-- 声明变量
name:变量名称
type:变量类型-->
<variable
name="jetViewModel"
type="com.martin.jetmo.jetpack.JetViewModel" />
<!-- 导包-->
<import type="com.dustess.baselib.common.assist.Check" />
</data>
<!--
使用时需要将文件语法
字符串:@{字符串内容},在引用时变量时,必须要有getXXX的函数,否则编译报错。
调用函数:@{()->调用的函数}
-->
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(jetViewModel.likeNumber)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.196" />
<Button
android:id="@+id/button"
android:layout_width="87dp"
android:layout_height="36dp"
android:text="+1"
android:onClick="@{()->jetViewModel.add()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<layout>
- 正向绑定
setContentView
改写为dataBindingUtil.setContentView(R.layout.xxxx)
返回一个AndroidDataBinding类型,这里变量起为databinding。findViewbyId()
被替换为databinding.textView(AndroidDataBinding类型.id名称)。- 在Activitiy中使用databinding.setData()和databinding.setLifecyclerOwner()开启双向绑定。
代码示例
public class JetPackActivity extends AppCompatActivity {
/**
* 创建JetModel
*/
private JetViewModel mJetViewModel;
/**
* 当将布局自动转换为databinding的xml就会自动创建此类
*/
private ActivityMainBinding mainBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mJetViewModel = ViewModelProviders.of(this).get(JetViewModel.class);
//根据在布局中声明变量自动生成
mainBinding.setJetViewModel(mJetViewModel);
mainBinding.setLifecycleOwner(this);
}
}
双向绑定
官网中《双向绑定》文章中提到。若用户在使用EditText
输入文本时,通过dataBinding
可以将直接将监听器直接在XML中进行绑定。
AppCompatEditText
<layout>
<data>
<variable
name="afterChangeListener"
type="androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged" />
</data>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/idSearchEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
...
android:afterTextChanged="@={afterChangeListener}"/>
</layout>
@={} 表示法接收属性的数据更改并同时监听用户更新,其中重要的是包含“=”符号。
上面的TextViewBindingAdapter为Android中内置的。
接下来我们来看下代码的编写。
//将xml申明的afterChangeListener对象在代码中进行复制。
binding.afterChangeListener = TextViewBindingAdapter.AfterTextChanged {
//do something....
}
这样就达到了,我们想要的效果了。
DataBindingUtil的常用的代码片段
在项目中我们会集成一些常用的代码片段,提升开发效率:
BaseBindingActivity
abstract class BaseBindingMVPActivity<T : ViewDataBinding> : BaseInputMVPActivity() {
lateinit var binding: T
override fun getLayoutId(): Int {
binding = DataBindingUtil.setContentView(this, bindingLayoutId) as T
return 0
}
abstract val bindingLayoutId: Int
}
BaseBindingFragment
abstract class BaseBindingMVPFragment<TDB : ViewDataBinding> : BaseMVPLoadFragment(){
lateinit var binding: TDB
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
return binding.root
}
}
BaseBindingViewHolder
class BaseBindViewHolder(view: View) : BaseViewHolder(view) {
var binding: ViewDataBinding? = null
var presenter: BasePresenter<*>? = null
init {
try {
binding = DataBindingUtil.bind(view)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
布局绑定资源字符串的方法
关键方法@{@string/bu_cost_learning_time(time)}
使用用例
资源字符串
<string name="bu_cost_learning_time">累计学习时长:%1$s</string>
资源布局
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="time"
type="com.lang.String" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@{@string/bu_cost_learning_time(time)}"
android:textColor="@color/textColor595959"
android:textSize="13sp"
app:layout_constraintStart_toStartOf="@+id/idDescriptionTv"
app:layout_constraintTop_toBottomOf="@+id/idDescriptionTv"
tools:text="@string/bu_cost_learning_time"/>
</layout>
进阶点:布局控件使用自定义属性
使用案例1:自定义防止多次点击属性app:singleClick
databing可以将自定义属性,并且使用在对应的布局中,如:点击事件我们可以在布局中导入,并且绑定到按钮中进行触发。下面讲解使用案例
创建扩展类DataBindingAdapter.kt
/**
* 短时间内响应一次点击
*/
//定义布局中引用的属性名称
@BindingAdapter("singleClick")
//实现细节
fun bindSingleClick(view: View, onClickListener: View.OnClickListener) {
//OnSingleClickListener:是自己再想买里面实现的防双点击的按钮
view.setOnClickListener(object : OnSingleClickListener() {
override fun onSingleClick(v: View) {
onClickListener.onClick(v)
}
})
}
在布局中使用app:singleClick属性
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="listener"
type="android.view.View.OnClickListener" />
</data>
<TextView
android:id="@+id/idTitle"
android:layout_width="match_parent"
android:layout_height="@dimen/def_tab_height"
android:background="@color/white"
android:gravity="center_vertical"
android:text="双点击测试"
android:textSize="@dimen/font_size_def_title"
android:textStyle="bold"
app:singleClick="@{listener}"
app:layout_constraintTop_toTopOf="parent" />
</layout>
使用案例2:自定义图片加载属性app:imageUrl
我们自定义app:imageUrl
属性,在xml中使用后,可以直接执行bindImageUrl
函数代码段
扩展工具类:ImageDataBindingAdapter.kt
//自定义属性:app:imageUrl、app:roundRadius、app:placeImage
@BindingAdapter(value = ["app:imageUrl", "app:roundRadius", "app:placeImage"], requireAll = false)
//具体实现细节
fun bindImageUrl(imageView: ImageView, url: String?, roundRadius: Int = 0, placeImage: Drawable?) {
val drawable = placeImage?:imageView.context.getDrawable(R.drawable.def_place)
GlideUtils.getInstance().loadContextRoundBitmap(imageView.context, Check.checkReplace(url),
imageView, drawable, drawable, DensityUtils.dip2px(imageView.context, roundRadius.toFloat()))
}
使用扩展属性app:imageUrl
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="imgUrl"
type="com.lang.String" />
</data>
<ImageView
android:id="@+id/imagUrlTestIv"
android:layout_width="@dimen/dp_36"
android:layout_height="@dimen/dp_36"
android:layout_marginStart="@dimen/dp_8"
app:imageUrl="@{imgUrl}"/>
</layout>