如何用C#实现Java中的类构造函数中嵌入了Callback的场景

背景

我最近做了一个case,是将一个安卓的aar的包绑定到Xamarin.Android的项目中去,这个aar的包是一个UI控件,既可以是DatePicker,也可以是TimerPicker。
我帮助客户生成了binding的dll并且成功绑定到安卓的项目中去。但是,当我在要使用这个类库的时候,遇到了一个代码实现上的问题。

问题出在哪里?

有时候在安卓的开发中,经常会碰到这种情况:对于一些UI控件,或者是自定义的UI控件,我们经常会在实例化这个控件的时候,就给它设置一个回调函数(Callback)来决定当使用这个控件的时候,需要做一些什么事情。在Java中,这个实现非常方便,我们将Callback的实现传入构造函数中,每次实例化都定义它的行为即可。

但是在我的case中,由于这个控件既可以是一个DatePicker(只选日期),也可以是一个TimerPicker(选择日期+时间)。并且它们的callback实现也不同,一个是写入选择之后写入的TextView不同,另外是一个处理传入时间的字符串的函数的参数传递也不同。

Java的实现

在这个安卓的类库中,首先会有一个接口,定义了这个Callback需要实现的一个event,如下:

1
2
3
4
// Callback Interface after time selected
public interface Callback {
void onTimeSelected(long timestamp);
}

下面是这个自定义控件的构造函数之一:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Instantitate the DatePicker
*
* @param context Activity Context
* @param callback Callback after select date
* @param beginDateStr Date String, format: yyyy-MM-dd HH:mm
* @param endDateStr Date String, format: yyyy-MM-dd HH:mm
*/

public CustomDatePicker(Context context, Callback callback, String beginDateStr, String endDateStr)
{
// ...
}

接下来是在MainActivity.java文件中调用这个类库的实现。我们会有两个init的方法会构造两个不同的实例出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void initDatePicker()
{
// ....
mDatePicker = new CustomDatePicker(this, new CustomDatePicker.Callback(){
@override
public void onTimeSelected(long timestamp) {
mTvSelectedDate.setText(DateFormatUtils.long2Str(timestamp, false));
}
}, beginTimestamp, endTimestamp);
//...
}

private void initTimerPicker()
{
// ...
mTimerPicker = new CustomDatePicker(this, new CustomDatePicker.Callback(){
@override
public void onTimeSelected(long timestamp){
mTvSelectedTime.setText(DateFormatUtils.long2Str(timestamp, true));
}
}, beginTimestamp, endTimestamp);
// ...
}

从上面的代码中可以看到,对了日期选择器,我们是传入mTvSelectedDate这个TextView中,而且对于方法DateFormatUtils.long2Str的第二个参数,我们传递的是false。

然而,对于日期时间选择器,我们是传入mTvSelectedTime这个TextView中,而且对于方法DateFormatUtils.long2Str的第二个参数,我们传递的是true。

但是在Xamarin.Android中,我们是要使用C#来做编程的。所以这样的实现在C#中是不可能的,因为C#中是不可能临时重写一个接口的方法然后传递到构造函数中的。

解决方案

在C#中,我们都知道,要实现某个接口,我们必须有一个类,继承自这个接口,然后实现其中的方法。

如果我们查看这个binding library生成的dll,会发现确实有这样一个接口:

Interface in dll

根据现在的CustomDatePicker的构造函数,我们必须传递一个Callback的实例给到它,并且要求其中的实现是和Java中一样的,可以cover到那两种情况,实现不同的onTimeSelected事件。

我从我的上一篇博客中找到了一些灵感,所以我决定写一个新的Callback类,并且在里面做一些灵活的实现。我会设计一个构造函数,其中会构造出需要更改值的TextView,并且会有两个属性来判定当前这个callback是用于DatePicker的还是TimerPicker的。

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Callback : Java.Lang.Object, CustomDatePicker.ICallback
{
public bool mIsDatePicker { get; set; }
public bool mIsTimerPicker { get; set; }
public TextView mTextView { get; set; }

// Constructor
public Callback(TextView textView, bool isDatePicker, bool isTimerPicker)
{
mTextView = textView;
mIsDatePicker = isDatePicker;
mIsTimerPicker = isTimerPicker;
}

// Implement the interface method
public void OnTimeSelected(long p0)
{
if (mIsDatePicker)
{
mTextView.Text = DateFormatUtils.Long2Str(p0, false);
}
else if (mIsTimerPicker)
{
mTextView.Text = DateFormatUtils.Long2Str(p0, true);
}
}
}

下面是在MainActivity.cs文件中的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void InitDatePicker()
{
// ...
mDatePicker = new CustomDatePicker(this, new Callback(mTvSelectedDate, true, false), beginTimestamp, endTimestamp);
// ...
}

private void InitTimerPicker()
{
// ...
mTimerPicker = new CustomDatePicker(this, new Callback(mTvSelectedTime, false, true), beginTimestamp, endTimestamp);
// ...
}

从上述代码可以看出,我们实现了和Java中相同的功能,并且这个类是绝对可以再拓展的,如果将来类库有什么变化,我们也可以做相应的变化。

总结

Java总是会有一些特性是很难被转化为C#的,但是我们不用害怕这些,因为我们总是有办法找到一个workaround来实现相同的功能的。

最后,祝大家编程愉快!

Xamarin.Forms自定义Navbar的后退图标和文字 透过一个项目理解原生态安卓和Xamarin安卓的开发区别
You need to set install_url to use ShareThis. Please set it in _config.yml.

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×