是什么
三个问题:
-
Viewbinding
和DataBinding
是什么 -
Viewbinding
和DataBinding
能解决什么问题 - 和传统的使用方式的优缺点是是什么
相信来看这篇文章的同学们,基本上都对Viewbinding
和 DataBinding
有一定的了解,或者比我知道的更多更清楚,所以关于这些问题,这里就不在赘述了。不清楚的,可以先去了解一下。
如标题,我们的目的是对Viewbinding
和 DataBinding
进行封装,在封装之前,我们肯定要先了解它们是如何使用的,那么,就先来看一下它们是如何使用的吧。
怎么用
在 app
的 build.gradle
文件中,添加以下配置:
android {
、、、
defaultConfig {
、、、
buildFeatures{
// viewBinding 支持,
viewBinding = true
// dataBinding 支持
dataBinding = true
}
}
}
ViewBinding
public class TestActivity extends AppCompatActivity {
private ActivityTestBinding activityTestBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1、 通过指定的 ViewBinding (ActivityTestBinding) 的 inflate 方法,来初始化
activityTestBinding = ActivityTestBinding.inflate(getLayoutInflater());
//2、通过 其 getRoot() 方法,来获取 view
setContentView(activityTestBinding.getRoot());
//3、无需 findViewById(), 可以直接对控件进行 操作
activityTestBinding.testTextView.setText("");
}
}
DataBinding
1、修改布局文件
与Viewbinding
不同的是,DataBinding
需要修改布局文件:
<?xml version="1.0" encoding="utf-8"?>
<!--DataBinding 的布局,最外层需要 修改为 <layout> 标签-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- data 是布局需要展示的数据-->
<data>
<variable
name="xxx"
type="xxxx" />
</data>
<!-- 这里就是原来的布局-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/test_textView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="这个一个测试界面"
android:textColor="@android:color/black"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2、Java 代码
public class TestActivity extends AppCompatActivity {
private ActivityTestDataBindingBinding testDataBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1、 通过指定的 DataBindingUtil 来初始化
testDataBinding =
DataBindingUtil.setContentView(this,R.layout.activity_test_data_binding);
//2、setContentView
setContentView(testDataBinding.getRoot());
//3、设置数据
testDataBinding.setTestData(new TestValue());
}
}
通过上面的代码发现,如果我们每写一个界面,都要来一遍 XXX.inflate(getLayoutInflater());
,或者是来一遍DataBindingUtil.setContentView(this,R.layout.xxxx);
,是不是顿时就感觉了蛋疼?而且从代码的美观方面,这些代码看起来也是很冗余的、臃肿的。如果你不觉得,那可以划走了,如果你觉得是这样的,那就继续往下看,接下来,我们就需要对这些代码进行精简。
如何简化用法?
一般想要简化Activity
,都会抽象一个 BaseActivity
,所以,我们同样也需要抽象个 BaseBindingActivity
。
不知道会不会有人问,为啥要叫 BaseBindingActivity
,不要问,问就是 我想把ViewBinding
和 DataBinding
合在一起使用。
1、泛型
通过上面的示例代码可以发现,如果想要在Activity
中使用 ViewBinding
和 DataBinding
,就必须拿到一个根据布局文件生成的一个具体的类,比如上面的:ActivityTestBinding
、ActivityTestDataBindingBinding
。所以,这个名称必须要调用者传入,就会考虑到使用泛型。
创建基类的雏形是这样的:
public abstract class BaseBindingActivity<Binding> extends AppCompatActivity {
protected Binding binding;
}
ps: Binding
是泛型名称,不是类
2、反射
现在,我们已经有了一个泛型的声明 binding
,接下来就是获取 该声明的实例。通过上面的示例代码可以发现,DataBinding
比较简单一些,直接通过 DataBindingUtil
就可以获取到,那么ViewBinding
要怎么来获取呢,从上面的基类中可以看到,我们使用的时候,具体的类型就是我们传入的泛型,那么,我们的第一目的,就是拿到泛型的类型,这里就需要用到反射。
1、获取ViewBinding 类型
参考DataBindingUtil
,我们创建一个类: BindingUtil
,添加以下方法
/**
* 找到 ViewBinding 的 class
*
* @param object obj : activity
* @return Class<? extends ViewBinding>
*/
@SuppressWarnings("unchecked")
private static Class<? extends ViewBinding> findViewBinding(Object object) {
//获取到 BaseBindingActivity
Type type = object.getClass().getGenericSuperclass();
if (type == null) {
return null;
}
// 获取 BaseBindingActivity 的 所包含的泛型参数列表,这里是核心。
Type[] types = ((ParameterizedType) type).getActualTypeArguments();
if (types.length == 0) {
return null;
}
//目前泛型只有一个,所以拿第0 个
return (Class<? extends ViewBinding>) types[0];
}
通过上面的方法,可以获取到 具体的ViewBinding
的 class
对象,第一目的已经达到,然后准备获取具体的 ViewBinding
的实例
2、实例化ViewBinding
通过 本身的 inflate()
方法来创建的。
/**
* 得到 Binding 的实例
*
* @param viewBindingClass Class<? extends ViewBinding>
* @param layoutInflater LayoutInflater
* @param <Binding> Binding
* @return Binding
*/
@SuppressWarnings("unchecked")
private static <Binding> Binding createViewBinding(Class<? extends ViewBinding> viewBindingClass, LayoutInflater layoutInflater) {
try {
Method method = viewBindingClass.getMethod("inflate", LayoutInflater.class);
ViewBinding viewBinding = (ViewBinding) method.invoke(viewBindingClass,
layoutInflater);
return (Binding) viewBinding;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
3、目的实现:
把上面的代码组合起来
class BindingUtil {
/**
* activity 的 创建方式
*
* @param appCompatActivity AppCompatActivity
* @param <Binding> Binding
* @return <Binding> Binding
*/
public static <Binding> Binding createBinding(@NonNull AppCompatActivity
appCompatActivity) {
LayoutInflater layoutInflater = appCompatActivity.getLayoutInflater();
Class<? extends ViewBinding> viewBindClass =
findViewBinding(appCompatActivity);
return createViewBinding(viewBindClass, layoutInflater);
}
}
目前为止,我们的BaseBindingActivity
就变成了这样:
public abstract class BaseBindingActivity<Binding> extends AppCompatActivity {
protected Binding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = BindingUtil.createBinding(this);
if (binding == null) {
throw new NullPointerException("binding is null");
}
setContentView(((ViewBinding) binding).getRoot());
}
}
这时候,我们再把自己的 Activity
修改以下,就是下面的样子了。
public class TestActivity extends BaseBindingActivity<ActivityTestBinding> {
}
怎么样,世界是不是一下子就变得清净了。
等等,别高兴太早,还没完呢,我们还有 DataBinding
的创建没有实现呢,但是,不要着急。一步一步来嘛!
4、DataBinding 的实现
之前说过,DataBinding
只需要通过DataBindingUtil
就可以创建,但是需要获取到布局的id,所以这里添加一个方法getLayoutID()
来获取布局资源 ,所以 BaseBindingActivity
的最终结果是这样的:
public abstract class BaseBindingActivity<Binding> extends AppCompatActivity {
protected Binding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getLayoutID() == 0) {
// 布局资源不存在,认为是 ViewBinding
binding = BindingUtil.createBinding(this);
if (binding == null) {
throw new NullPointerException("binding is null");
}
setContentView(((ViewBinding) binding).getRoot());
} else {
//存在则认为是 DataBinding
binding = (Binding) DataBindingUtil.setContentView(this, getLayoutID());
setContentView(((ViewDataBinding) binding).getRoot());
}
}
public int getLayoutID() {
return 0;
}
}
那么,使用 DataBinding
的 Activity
就是这样的了:
public class TestActivity extends BaseBindingActivity<ActivityTestDataBindingBinding> {
@Override
public int getLayoutID() { return R.layout.activity_test_data_binding; }
}
从代码上面看,只多了一个 getLayoutID()
,也是相当的干净清爽。
优化和内容丰富
综上所述,对 基于 Viewbinding
和 DataBinding
的类,进行基类的封装,还是比较简单。
在上面的基础上,我又扩展了一些接口,以方便代码的分层,避免使用者重写 onCreate()
时,造成代码的堆积。
public interface ICreate {
/**
* 返回页面 布局的 Layout id
* @return @LayoutRes id
*/
@LayoutRes int getLayoutID();
/**
* 初始化 view 的状态,例如 显示、 隐藏、等
*/
void initViewState();
/**
* 设置 view 的 监听事件, 例如 点击、长按、选择、输入监听等
*/
void initViewListener();
/**
* 其他的操作,
* @param savedInstanceState savedInstanceState
*/
void onProcess(@Nullable Bundle savedInstanceState);
}
到此,功能基本上够用了,同理,对于Fragment 的封装也是一样的,具体代码可以参考AndroidMvvm/mvvm
可以下载使用,当然也支持直接依赖 到项目中:
implementation 'com.github.eson-yunfei:AndroidMvvm:0.0.5' //最新版本是 Kotlin
当然,不要忘记在根目录的build.gradle
中 添加以下内容
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}