透过一个项目理解原生态安卓和Xamarin安卓的开发区别

我在写上一篇博客:Xamarin.Android vs. Native Android - How you implement Java Listener in C#的时候有一些错误。那篇博客的内容是关于如果你在参考一个原生态安卓的项目,并且想把它转换为Xamarin.Android项目,有一些语言特性的转换需要了解。我主要是从事件处理的角度写的那篇博客。

那篇博客我还会继续留着,因为它的做法并没有问题,只不过不是最佳实践。大家可以对比阅读这篇来看一下更好的一个solution。

背景

最近我正在学习Xamarin官方文档生成的pdf文件,关于如何开发Xamarin.Android应用的。我读到了Downloadable Fonts这个topic,使用这个方案要比将字体打包进apk文件有更多的好处。

如果你想了解什么是安卓的Downloadable Fonts,你可以查看这个文档:Downloadable Fonts

在上面的链接中,你会看到Google有一个官方的sample来阐述这个功能的使用方式。

链接:android-DownloadableFonts

由于我在学习Xamarin.Android,所以我想直接去用Xamarin.Android实现一个一模一样的sample,作为一个练习。因为平时用Xamarin.Forms会更多,Xamarin.Android会用得相对比较少。

之后噩梦就发生了,我发现如果去参照Google的sample想直接翻译成Xamarin.Android基于C#的代码,还是非常困难的,尽管这两个语言的相似度非常高。

解救了我的是我找到了Xamarin官方出的一个Xamarin.Android的sample,做的事情和我想做的一模一样。

链接:Xamarin.Android DownloadableFonts Sample

我查看了它的代码,发现实现得非常完美。所以,你在这篇博客中会看到的是,我会用这两个项目中的一些实例来展示Java和C#的一些不同的特性,也是基于这些特性的不同,我们在开发Xamarin.Android的时候才需要做一些思路的转换。

我在比较这两个项目时发现了什么?

在开发的时候,永远不会有一个正确答案,条条大路通罗马,每个功能的实现都可以有不同的代码实现方式。每种语言也有每种语言的特性。但是在这个Xamarin.Android的项目中,至少在我看来作者是使用非常多的非常好的一些做法,在转换Google官方sample的时候。

我可以想到有如下几点可以分享给大家:

  • 界面(UI)
  • Java中的字段 vs. C#中的属性
  • Java中的监听器(Listener)* vs. C#中的事件处理器(Event Handler)

前提准备

如果你要参考一些原生态安卓项目来开发Xamarin的项目,第一个并且也是唯一一个你要确保的事情就是你将所有的需要的类库都已经添加进你的项目,包括安卓的support类库等等。

界面(UI)

如果你查看了这两个项目中的界面layout文件,你会发现,UI不会是你需要担心的问题。

因为Xamarin.Android的相应的类库都是原样封装了安卓的控件,所以你需要做的只是copy paste那个项目中的UI代码到你自己的Xamairin.Android项目中即可。Xamarin.Android也是使用一样的类库,包括support v7中的AppCompat等等。

我在写页面的时候碰到了VS的Android designer renderer的错误,原因是那个项目的UI是基于AppCompat的。我必须要把项目中的base Theme更改到兼容AppCompat的才可以。这个我计划也写一篇博客来记录,这里就不多赘述了。

Java中的字段 vs. C#中的属性

C#相对Java有一个很大的提升就是引入了Property属性的概念。你永远不可能在编程的时候不使用字段,所以了解这个区别还是很有帮助的。

假设你现在在Java中要声明了一个字段,每一次你要对这个字段做赋值或者值的更改,你都需要去调用那个对象的setXXXX()方法。

另一方面,C#总是使用Property来达到同样的目的。

看一下如下Java代码:

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

基于上述的类,你会这样来给Personname字段赋值。

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

但是在C#中,你会这样定义这个类:

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

然后这样来更改name字段的值:

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

这是一个非常常见的模式,在你转换Java到C#的过程中。

Java中的监听器(Listener)* vs. C#中的事件处理器(Event Handler)

在一个安卓的Activity中有多个一样的控件是很正常的事情,比如说SeekBar, TextView等等。

在这个Downloadable Fonts项目中,有3个SeekBar并且他们是相同的behavior:当你拖动这个SeekBar的条的时候,跟在他们后边的那个数值会相应发生变化。

效果截图如下:

在Java的代码里,是这样实现的:

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
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
public void onStopTrackingTouch(SeekBar seekBar) {
}
});

mWeightSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
weightTextView
.setText(String.valueOf(progressToWeight(progress)));
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});

mItalicSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromuser) {
italicTextView
.setText(String.valueOf(progressToItalic(progress)));
}

@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}

@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});

当然你也可以看我之前的博客,也是一个解决方案。但是,这里我会介绍一个更好的解决方案,如下:

  1. MainActivity这个类中建立一个内部类(Inner Class),取名为SeekBarListenerImpl。之所以使用内部类是因为这个监听器只是为了这个页面的3个SeekBar服务的,放到其他类中也没有任何意义的。并且这个内部类只有在MainAcitivity被实例化的时候会被创建出来,也省了资源。

    代码如下:

    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
    class SeekBarListenerImpl: Object, SeekBar.IOnSeekBarChangeListener
    {
    public enum SeekBarType
    {
    WIDTH, WEIGHT, ITALIC
    }

    public SeekBarType mSeekBarType { get; set; }
    public TextView mTextView { get; set; }
    public MainActivity mActivity { get; set; }

    public void OnProgressChanged(SeekBar seekBar, int progress, bool fromUser)
    {
    var progressText = "";
    switch (mSeekBarType)
    {
    case SeekBarType.WIDTH:
    progressText = String.ValueOf(mActivity.ProgressToWidth(progress));
    break;
    case SeekBarType.WEIGHT:
    progressText = String.ValueOf(mActivity.ProgressToWeight(progress));
    break;
    case SeekBarType.ITALIC:
    progressText = String.ValueOf(mActivity.ProgressToItalic(progress));
    break;
    }
    mTextView.Text = progressText;
    }

    public void OnStartTrackingTouch(SeekBar seekBar)
    {
    // No Op
    }
    public void OnStopTrackingTouch(SeekBar seekBar)
    {
    // No Op
    }
    }

从这里我们可以看出,我们在这个类中已经定义了3种SeekBar的不同Type,所以我们已经将这个Acitvity中的3个SeekBar的使用都实现了。那么如何使用呢?

  1. 我们可以这样使用上面这个类的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    var widthSeekBarListener = new SeekBarListenerImpl
    {
    mActivity = this,
    mTextView = widthTextView,
    mSeekBarType = SeekBarListenerImpl.SeekBarType.WIDTH
    };
    WidthSeekBar.SetOnSeekBarChangeListener(widthSeekBarListener);

    var weightSeekBarListener = new SeekBarListenerImpl
    {
    mActivity = this,
    mTextView = widthTextView,
    mSeekBarType = SeekBarListenerImpl.SeekBarType.WEIGHT
    };
    WidthSeekBar.SetOnSeekBarChangeListener(weightSeekBarListener);

    var italicSeekBarListener = new SeekBarListenerImpl
    {
    mActivity = this,
    mTextView = widthTextView,
    mSeekBarType = SeekBarListenerImpl.SeekBarType.ITALIC
    };
    WidthSeekBar.SetOnSeekBarChangeListener(italicSeekBarListener);

相比之前的博客,我们可以看出这个方法代码更加简短,高效,已经易于后期维护。

额外的观点

我还希望说一个使用内部类的一个时机。在实现Downloadable Fonts这个项目的时候,为了调用下载字体的API,我们必须要调用这个API的Acitivity或者是相应的类是继承自FontsContractCompat.FontRequestCallBack并且重写其中的2个方法。

但是,如果我们要在MainAcitivity中做的话,会发现这个类已经继承于AppCompatActivity了,所以是没有办法继承其他类了。

所以在这个sample中,作者使用了内部类来解决这个问题。

但是在我看的Xamarin的官方的技术资料中,它推荐了一种方式是实现一个FontDownloadHelper类,继承自FontsContractCompat.FontRequestCallBack,来作为一个工具类来使用。

两者都可以解决这个问题,但是要看具体的开发需求是什么,微软官方的工具类的实现可变性比较小,但是比较通用,相反在一个Activity中的实现就可以针对该Activity的需求来自己定义。

感谢阅读!

Xamarin.Android vs. Native Android - 如何在C#中实现Java的Listener
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

×