下拉刷新功能在安卓和iOS中非常常见,一般实现这样的功能都是直接使用第三方的库,网上能找到很多这样的开源库。然而在Xamarin. Android中要实现一个好用的下拉刷新功能却不是很容易,在网上找了几个Xamarin.Android的下拉刷新控件,都不是很满意,所以想重新绑定一个java写的下拉刷新控件。在网上找了几个这样的开源库,通过对比发现android-pull-to-refresh实现的功能比较多,实现的效果也比较满意。
Android-Pull-To-Refresh项目地址:https://github.com/naver/android-pull-to-refresh
该库包含如下功能点:
- 支持顶部下拉刷新和顶部上拉刷新(可以同时启用这两个功能)
- Android2.3以上设备支持滚动
- 支持以下控件
- ListView
- ExpandableListView
- GridView
- WebView
- ScrollView
- HorizontalScrollView
- ViewPager
- 支持检测列表是否滚动到最末尾
- 支持ListFragment
- 支持很多自定义选项(1.自定义正在加载界面,可以修改图标和文字 2.支持多个提示图标,平滑滚动时间间隔设置等)
其他详细说明请到该项目的网站查看。
本文主要包含以下五个部分
下面开始进行绑定操作,要能够进行绑定,首先需要将java项目编译为jar文件,我是通过fat-jar插件生成pulltorefresh.jar文件的,通过其他方式生成也是可以的。
只选中项目的output即可
接下来创建Android Binding项目,将生成的jar文件添加到项目中,生成类型选择embeddedjar,生成的版本选择2.3,由于该项目没有引用其他项目,所以不需要进行其他设置,项目的结构图如下:
接下来编译项目,编译时VS给出了如下的错误提示:
error CS0060: Inconsistent accessibility: base class 'Com.Handmark.Pulltorefresh.Library.PullToRefreshListView.InternalListView' is less accessible than class 'Com.Handmark.Pulltorefresh.Library.PullToRefreshListView.InternalListViewSDK9'
error CS0102: The type 'Com.Handmark.Pulltorefresh.Library.PullToRefreshBase' already contains a definition for 'Mode'
error CS0102: The type 'Com.Handmark.Pulltorefresh.Library.PullToRefreshBase' already contains a definition for 'State'
根据错误提示可以看出:
第一个错误是由于子类方法的可访问性比父类的高,双击错误提示可以看到InternalListView类是protected修饰的,而子类InternalListViewSDK9是public修饰,在c#是不允许的,那么我们可以通过metadata.xml配置类型修饰符,使他们的修饰符统一,我这里将InternalListView类的修饰符修改public,代码如下:
<attr path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshListView.InternalListView']" name="visibility">public</attr>
第二个和第三个错误的提示为已经包含了Mode和State的定义,通过查看java的源代码发现是由以下原因引起的:
PullToRefreshBase类里面定义了两个枚举Mode和State,并且定义了getMode、getState、setMode、setState方法,java里的get和set方法会被转换为c#里的属性,生成的代码如下:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
在C#中,这样的代码是无法通过编译的,因为属性的名称和类型的名称一样,所以必须进行修改才行,我们可以将枚举的名称分别修改为PullToRefreshMode和PullToRefreshState.我们在metadata.xml里添加如下代码:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
再次编译,发现还是报错,一共报了10个错误:
第一个错误是由于访问修饰符不统一造成的,通过添加如下代码可以解决:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
剩下的9个问题都是类似的,未实现某个接口或者抽象类的某个方法,双击其中的错误提示,发现有6个方法都是实现了,但编译的时候还是提示未实现方法。通过查看java源代码与生成的c#代码,找到了原因,java源代码里面有一个泛型类PullToRefreshBase<V entends View>,该类里面有一个抽象的泛型方法protected abstract T createRefreshableView(Context context, AttributeSet attrs);,转换为c#代码后泛型抽象方法的返回值变为了Java.Lang.Object,而实际上应该是生成一个泛型类,然后生成一个泛型抽象方法,可见转换程序还不是太完善。此处的修改方式为:将子类的对应方法的返回值修改为Java.Lang.Object,代码如下:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
剩下3个方法,在生成的C#代码里确实没找到,那就是没进行转换或者转换时报错了,此时我们需要查看对应的java源代码,分别找到报错的方法InternalListView.setEmptyView
InternalExpandableListView.setEmptyView
InternalGridView.setEmptyView
这3个方法里的代码都很简单,都只有一句代码
PullToRefreshListView.this.setEmptyView(emptyView);
PullToRefreshExpandableListView.this.setEmptyView(emptyView);
PullToRefreshGridView.this.setEmptyView(emptyView);
这3句代码都非常类似,都是”ClassName.this.MethodName”,在java里,只有内部类里可以这样写,该代码的作用是访问外部类的实例方法。 由于c#里面没有” ClassName.this.MethodName”的写法,所以猜想可能是这个原因导致了转换失败,那么我们就只有修改java源代码进行测试了。去掉this,换成同等效果的写法,修改方法如下,3个类的修改方法类似,这里就只写一个:
1. 在InternalListView类增加一个PullToRefreshListView类型的字段 _view
2. 在InternalListView类的构造函数添加一个PullToRefreshListView类型的参数view,并在构造函数内部给新增的字段赋值,使用view的值
3. 修改setEmptyView方法的代码,改为_view. setEmptyView(emptyView)
4. 增加一个get方法,返回刚才新增的_view字段
5. 修改引用的代码,传入对应的参数
修改完成后重新导出jar文件,替换为vs项目中的对应jar文件,然后重新编译,编译之后还是报同样的错误,这样就证明我们的猜想不正确,那么到底是什么原因导致转换失败内?我开始试了一些其他方法,都没有成功编译,最后发现InternalListView以及InternalListViewSDK9都是内部类(嵌套到PullToRefreshListView里面的,另外两个两个类也是内部类),就是这个内部类导致了错误,我们把对应的6个内部类(每个类里面2个内部类,一个InternalXXX一个InternalXXXSDK9)改为普通类,再次导出jar,再次编译,编译通过了,我们现在来测试一下绑定的库能否正常工作。
新建一个测试项目PullToRefresh.Sample,添加相关资源及引用,并将原项目的java代码翻译为c#代码,翻译的时候有以下两个地方需要注意:
1. 实现java接口的类要继承自Java.Lang.Object,否则需要自己实现IJavaObject,而自己实现的IJavaObject很有可能无法正常工作,具体信息可以参考:http://developer.xamarin.com/guides/android/advanced_topics/java_integration_overview/working_with_jni/#Implementing_Interfaces
2. C#的内部类是无法直接访问外部类的实例成员的,所以需要对其中的内部类做调整
3. 向集合里添加数据使用mAdapter.Insert方法,直接向List集合添加界面不会显示数据
翻译后的C#代码如下:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
代码调整完成后进行编译,发现编译的时候又报错了,报错信息里很多乱码,不过可以看到几个关键字“OnSmoothScrollFinishedListener”,所以猜想可能是“OnSmoothScrollFinishedListener”这个接口可能有问题,我们返回到java源代码查看。
在java源代码里的“PullToRefreshBase”类里搜索“OnSmoothScrollFinishedListener”,
找到接口的定义:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
我们发现接口是static且是默认的修饰符,我们试一试将修饰符改为public,重新导出jar,再次编译,发现能够通过了,现在我们来看看功能是否正常
运行的时候报错了,提示找不到资源,原来我们绑定java库时忘记打包资源了,我们将资源文件一起打包,然后重新编译.打包资源文件时需要注意以下问题:
- jar文件和资源文件都打包到一个zip文件中,zip压缩包的目录结构如下:
再次编译并运行,终于正常运行了。
由于最近在使用Mvvmcross,所以也写一个Mvvmcross的例子,下面以ListView为例,实现一个MvxPullToRefreshListView。代码如下:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
使用MvxPullToRefreshListView的关键代码如下:
FirstViewModel.cs
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
FirstView.axml代码如下:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
user_item.xaml的代码如下:
![](http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
运行的效果和上面一样,就不上图了。
C#要使用java的jar不容易啊,会出现各种各样的问题,感觉比直接在java里使用麻烦很多很多,需要耐心的解决这些问题,并且可能需要修改java代码。不过使用Xamarin的好处是,逻辑代码可以完全重用,并且编写代码的效率比直接用java要高。