📱 Android | 基础的事件处理(DataBinding的使用)

入门Android应用开发,第二步当然是要处理一些基本事件啦。📱

准备

对于Android的一些基本事件的处理,一般的做法都是使用findViewById获取xml布局上的控件,然后转化为对应的控件类。有了控件对象,我们就可以通过各种方法获取控件中的内容或者响应控件中的不同的事件。

当然,有着各种各样的框架可以替代findViewById,以更方便更优雅的方式写代码,比如ButterKnife

但是这种做法把所有的逻辑、数据和视图的处理都放在了Activity里面,如果项目变大了,这种写法就会变得十分不可维护,因此衍生了一系列的 MVC、MVP、MVVM 的架构。

通过这个项目,来尝试一下Google官方的推荐DataBinding

DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常

官方宣称这个框架的性能比findViewById还强。

DataBinding并不需要什么依赖库,并且最低可以支持到Android 1.3,可谓是十分强大了,只需要改动一下

文件位置:app\build.gradle

1
2
3
4
5
6
android {
...
dataBinding {
enabled = true
}
}

把数据绑定选项开启就可以了

Model

首先,我们要先为这个页面建立一个Model

1
2
3
4
5
6
7
8
9
10
11
12
public class SearchModel extends BaseObservable {
private String searchKey;
private int searchType;

public SearchModel(int type) {
searchKey = "";
searchType = type;
}

...

}

在这个Model里面,有两个变量,一个是搜索框内的文本searchKey,另一个是搜索的类型。

然后还需要为他加上一般的getXXsetXX的基本函数,以便外部可以访问并且绑定这两个变量。

Handler

有了数据绑定,当然还需要一个事件绑定,这里用一个Handler类来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SearchHandler {
private Context mContext;
private ActivitySearchBinding mSearchBinding;

public SearchHandler(Context context, ActivitySearchBinding searchBinding) {
mContext = context;
mSearchBinding = searchBinding;
}

public void onClickSearch(View view) {
...
}

public void onSelectType(RadioGroup group, int checkedId) {
...
}
}

在这个类里面实现了两个事件,分别对应Button的点击事件和RadioGroup的选择事件。

XML

DataBinding强大的地方就在于可以通过修改XML文件,指定控件绑定的事件或者变量。

首先,将原来的布局外面套上一层Layout

然后把上面写好的ModelHandler引入这个布局当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<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="searchModel"
type="cn.zhenly.experimentone.model.SearchModel" />

<variable
name="searchHandler"
type="cn.zhenly.experimentone.handler.SearchHandler" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".context.SearchActivity">
...
</android.support.constraint.ConstraintLayout>
</layout>

然后就可以将这些变量或者事件绑定到指定的控件上面了.

下面是其中一个控件绑定的例子

1
2
3
4
5
6
 <RadioGroup
...
android:checkedButton="@={searchModel.searchType}"
android:onCheckedChanged="@{searchHandler::onSelectType}">
...
</RadioGroup>

其中数据通过@={class.data}对数据进行双向绑定,其中的=是双向绑定。

然后事件通过@{class::function}绑定事件,也可以通过 Lambda 表达式@{()=>{class.function()}} 进行绑定。

Activity

绑定到一系列东西之后,自然要把这些东西都联系起来,这里就需要在Activity里面来处理了。

1
2
3
4
5
6
7
8
9
10
11
12
public class SearchActivity extends AppCompatActivity {
ActivitySearchBinding mSearchBinding;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSearchBinding = DataBindingUtil.setContentView(this, R.layout.activity_search);
SearchModel search = new SearchModel(R.id.radio_button_image);
mSearchBinding.setSearchModel(search);
mSearchBinding.setSearchHandler(new SearchHandler(this, mSearchBinding));
}
}

使用DataBindingUtillayoutActivity绑定在一起,然后为他设置好ModelHandler

这样就把他们联系起来了。

获取信息

那在事件处理中,我们如何获取绑定好的数据呢?

这个很简单,由于我们的Handler里面已经在初始化的时候绑定了指定的binding类,我们只需要从里面get就可以了

1
SearchModel searchData = mSearchBinding.getSearchModel();

然后就可以很简单地获取到绑定好的数据了。比起findViewById要方便得多,尤其是当项目庞大的时候。

Toast 与 AlertDialog

这次的实验中主要使用到就是这两个类,用于显示Toast和对话框。

他们的用法也比较简单。

Toast甚至只需要一行代码就可以实现了

1
Toast.makeText(mContext, "搜索内容不能为空", Toast.LENGTH_LONG).show();

mContext为当前活动的上下文,在handler初始化的时候就已经绑定了

Toast.LENGTH_LONGToast显示停留的时间。

AlertDialog就需要借助AlertDialog.Builder来建立

下面是一个简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle("提示");
builder.setCancelable(true);
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(mContext, "对话框“确定”按钮被点击", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(mContext, "对话框“取消”按钮被点击", Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
});
builder.setMessage("搜索失败");
builder.show();

通过各种set方法为对话框设置标题、内容、按钮以及其绑定的回调事件。

最后通过show()使其显示出来。

Screenshot_20180921-103758_ExperimentOne

奇怪的东西

虽然使用来是比较方便,一路上也没有遇到什么问题,但是却发现了一个奇怪的现象。

奇怪的 ID

如果布局文件中Buttonid的字典序大于RadioGroupid,那么上面绑定在RadioGrouponChechChange事件onSelectType就会被莫名其妙地在界面加载的时候触发两次。也就是说,在应用打开的时候,会有两次Toast弹出,显示图片被选中

然而把他们的 ID 的字典序调换过来就没有了这个现象了,这已经是科学不能解释的事情了,大概涉及到玄学领域了。

通过查看DataBindIng自动生成的类ActivitySearchBindingImpl,发现了这样一个现象,就是这两个事件的绑定的顺序是和他们的id的字典序是有关系的。

奇怪的绑定

然后我尝试用回调 Lambda 的方式去掉直接绑定的方式绑定buttononClick事件,然后一开始的触发两次居然再次消失了。这大概和绑定的顺序已经方法有关,但是都是不可预测的玄学东西。

Model 与 Handler

通过在互联网的搜索,发现了stackoverflow| Android Data binding issue Binding adapter call twice上一个一个类似的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private OnRebindCallback<ActivityMainBinding> delayRebindCallback =
new OnRebindCallback<ActivityMainBinding>() {
@Override
public boolean onPreBind(ActivityMainBinding binding) {
return false;
}
};

// ... and then after creating the binding ...
binding.addOnRebindCallback(delayRebindCallback);
// In your onResponse (assuming it is on the UI thread):

binding.removeOnRebindCallback(delayRebindCallback);
binding.setUserinfo(img);
binding.executePendingBindings();

然后我在绑定Model之后立刻binding.executePendingBindings(),然后再绑定handler事件,然后那个现象就消失了。然后我就可以猜测,这个现象是因为UI的多线程导致的。

因为model的绑定会导致onCheckChange事件触发, 如果handler的绑定在model之前,那么就会触发那个事件,而绑定的顺序和方式可能也会导致不同的绑定顺序。

触发两次

那为什么会触发两次呢?

Android-为什么 RadioGroup.onCheckedChanged() 会调用多次?

这个应该就和这个事件的内部实现有关了,因为一开始的初始值是-1,然后代码中对其赋值。然后就发生了一系列的化学反应,导致触发了两次。

总结

通过简单的事件处理,来探索了Google最新的框架,虽然遇到一些奇奇怪怪的东西,但是总体上体验还是不错的,比起传统的方式,这种架构的确是更加优美了。

土豪通道
0%