Xamarin.Android vs. Native Android - 如何在C#中实现Java的Listener

对于使用Xamarin来开发安卓应用的工程师来说,了解如何将Java代码转换为C#代码是一个非常重要的事情。

最近我正在使用纯的Xamarin.Android项目来实现一些东西,但是我遇到了非常多的问题。

当你想要解决这些问题的时候,通常你在网上找到的都是原生态安卓的一些文档资料以及一些sample的项目。为了解决问题,我就找了一个来自谷歌官方的一个sample程序,是用Java写的。

所以我必须要学会如何转换Java code到C#,否则开发Xamarin.Android的程序会变得非常困难。

难点在哪里?

虽然业界一直有一个说法,Java和C#的语法相似度超过了80%,甚至更多。但是,在我看来还是有很多的不同。

在这篇博客中,我将会用我现在的Xamarin.Android里的一个实现来demo这样一个转换Java的事件处理(也就是俗称的监听器Listener)到C#的事件处理器。

同时,我也会展示给大家看一个非常常见的情景,在我们开发Xamarin.Android的项目时需要经常使用的Java与C#之间的转换。

转换Event Handler

首先我们讲讲Java和C#的事件处理的不同。

我们都知道在Java中,当你要设置一些成员变量或者是实现一些事件接口,你都会调用类似setXXXX的方法。

但是我碰到的问题是,在一个安卓的Acivity中,界面里会有多个一样的元素,他们需要实现的事件接口也一样。

假设我们有如下的安卓界面代码:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<LinearLayout
android:id="@+id/width_input_area"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:layout_marginBottom="@dimen/margin_medium"
android:layout_marginStart="@dimen/margin_large"
android:layout_marginEnd="@dimen/margin_large">

<TextView
android:id="@+id/width_seekbar_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:text="@string/width"
android:textColor="@color/accent"
android:textSize="12sp" />

<LinearLayout
android:id="@+id/width_seekbar_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<SeekBar
android:id="@+id/seek_bar_width"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />

<TextView
android:id="@+id/textview_width"
android:layout_height="wrap_content"
android:layout_width="80dp"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginLeft="@dimen/margin_small"
android:textSize="16sp"
android:gravity="end" />
</LinearLayout>
</LinearLayout>

<LinearLayout
android:id="@+id/weight_input_area"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:layout_marginBottom="@dimen/margin_medium"
android:layout_marginStart="@dimen/margin_large"
android:layout_marginEnd="@dimen/margin_large">

<TextView
android:id="@+id/weight_seekbar_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:text="@string/weight"
android:textColor="@color/accent"
android:textSize="12sp" />

<LinearLayout
android:id="@+id/weight_seekbar_area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<SeekBar
android:id="@+id/seek_bar_weight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />

<TextView
android:id="@+id/textview_weight"
android:layout_height="wrap_content"
android:layout_width="80dp"
android:layout_marginStart="@dimen/margin_small"
android:layout_marginLeft="@dimen/margin_small"
android:textSize="16sp"
android:gravity="end" />
</LinearLayout>
</LinearLayout>

你应该可以在Android designer中看到类似如下的界面:

我们可以看到,这里我们有2个SeekBar并且他们都有相同的行为:当你拖动SeekBar的点的时候,右边数值会相应地发生变化。

因此,我们就是要实现SeekBar的onProgressChanged事件。

实现

在我找到的谷歌的sample项目中,它是像下面这样实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ...

mWidthSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
@override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser){
widthTextView
.setText(String.valueOf(progressToWidth(progress)));
}

@override
public void onStartTrackingTouch(SeekBar seekBar){

}

@override void onStopTrackingTouch(SeekBar seekBar){

}
});
// ...

我们从上面的代码可以看出,在Java或者原生态安卓的开发中,事件都是调用setXXXXListener并且传入一个新的监听器的实例化的方式来生成的。之后,在这个block里面,重写所有你想重写的方法,或者说有一些方法是强制会被要求override的。

但是,在C#中,我们没有这种监听器的机制,但是我们还是可以用事件处理器或者委托来完成一样的事情。在C#中,事件有点类似于属性,绑定在那个对象上。

所以,如果我们要实现上面Java一样效果的代码的话,可以参考如下:

1
2
3
4
5
6
7
8
widthSeekBar.ProgressChanged += WidthSeekBar_ProgressChanged;
widthSeekBar.StartTrackingTouch += (o, e) => { };
widthSeekBar.StopTrackingTouch += (o, e) => { };

private void WidthSeekBar_ProgressChanged(object sender, SeekBar.ProgressChangedEventArgs e)
{
widthTextView.Text = e.Progress.ToString();
}

这样就完成了,非常简单吧?而且如果是用VS来开发的话,VS的智能提示真的非常好用。当你输入到widthSeekBar.ProgressChanged +=的时候,你就可以按Tab键来自动生成一个方法对应这个事件。并且,现在VS已经做到生成的Event前面还加了相应的控件的名字WidthSeekBar,非常智能化。

其他相似的情况

我想在这里分享一个普遍会发生的需要被转换的模式,在Java和C#中。

假设,你声明了一个成员变量在Java的类中,然后每次你想要修改这个变量的值的时候,你会必须要调用setXXXX(),这个方法会在该类中被实现。

但是另一方面,C#只会使用属性来完成一样的效果。。

查看以下Java代码:

1
2
3
4
5
6
7
8
public class Person
{
private String name;
public void setName(String name)
{
this.name = name;
}
}

然后你会这样调用去改变那个变量的值:

1
2
Person p = new Person();
p.setName("Allen");

但是在C#中,我们是这样做的:

1
2
3
4
public class Person
{
public string Name {get;set;}
}

然后这样调用:

1
2
Person p = new Person();
p.Name = "Allen";

这是一个我认为非常非常常见的现象,仅仅是写出来给大家一个参考。

Happy Coding!

透过一个项目理解原生态安卓和Xamarin安卓的开发区别 Broken AVD System Path - AVD Manager无法运行安卓模拟器
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

×