Xamarin. Android实现下拉刷新功能
PS:发现文章被其他网站或者博客抓取后发表为原创了,给图片加了个水印
下拉刷新功能在安卓和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.支持多个提示图标,平滑滚动时间间隔设置等)
其他详细说明请到该项目的网站查看。
本文主要包含以下五个部分
一、Jar文件的生成
下面开始进行绑定操作,要能够进行绑定,首先需要将java项目编译为jar文件,我是通过fat-jar插件生成pulltorefresh.jar文件的,通过其他方式生成也是可以的。
只选中项目的output即可
二、PullToRefresh.dll的生成
接下来创建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#里的属性,生成的代码如下:
public class PullToRefreshBase{
public enum Mode{}
public enum State{}
public Mode Mode{get;set;}
public State State{get;set;}
}
在C#中,这样的代码是无法通过编译的,因为属性的名称和类型的名称一样,所以必须进行修改才行,我们可以将枚举的名称分别修改为PullToRefreshMode和PullToRefreshState.我们在metadata.xml里添加如下代码:
<attr path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshBase.Mode']"
name="managedName">
PullToRefreshMode
</attr> <attr path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshBase.State']"
name="managedName">
PullToRefreshState
</attr>
再次编译,发现还是报错,一共报了10个错误:
第一个错误是由于访问修饰符不统一造成的,通过添加如下代码可以解决:
<attr path="/api/package[@name='com.handmark.pulltorefresh.library.internal']/class[@name='RotateLoadingLayout']/method[@name='onLoadingDrawableSet' and count(parameter)=1 and parameter[1][@type='android.graphics.drawable.Drawable']]"
name="visibility">protected</attr>
剩下的9个问题都是类似的,未实现某个接口或者抽象类的某个方法,双击其中的错误提示,发现有6个方法都是实现了,但编译的时候还是提示未实现方法。通过查看java源代码与生成的c#代码,找到了原因,java源代码里面有一个泛型类PullToRefreshBase<V entends View>,该类里面有一个抽象的泛型方法protected abstract T createRefreshableView(Context context, AttributeSet attrs);,转换为c#代码后泛型抽象方法的返回值变为了Java.Lang.Object,而实际上应该是生成一个泛型类,然后生成一个泛型抽象方法,可见转换程序还不是太完善。此处的修改方式为:将子类的对应方法的返回值修改为Java.Lang.Object,代码如下:
<attr
path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshScrollView']/method[@name='createRefreshableView']"
name="managedReturn"
>Java.Lang.Object</attr> <attr
path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshExpandableListView']/method[@name='createRefreshableView']"
name="managedReturn"
>Java.Lang.Object</attr> <attr
path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshGridView']/method[@name='createRefreshableView']"
name="managedReturn"
>Java.Lang.Object</attr> <attr
path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshHorizontalScrollView']/method[@name='createRefreshableView']"
name="managedReturn"
>Java.Lang.Object</attr> <attr
path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshListView']/method[@name='createRefreshableView']"
name="managedReturn"
>Java.Lang.Object</attr> <attr
path="/api/package[@name='com.handmark.pulltorefresh.library']/class[@name='PullToRefreshWebView']/method[@name='createRefreshableView']"
name="managedReturn"
>Java.Lang.Object</attr>
剩下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的使用
新建一个测试项目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#代码如下:
[Activity(Label = "PullToRefresh.Sample", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity, PullToRefreshBase.IOnRefreshListener, PullToRefreshBase.IOnLastItemVisibleListener
{
private List<string> mListItems;
private PullToRefreshListView mPullRefreshListView;
private ArrayAdapter<string> mAdapter;
private string[] mStrings = { "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
"Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
"Allgauer Emmentaler", "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
"Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", "Aisy Cendre",
"Allgauer Emmentaler" }; protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle); // Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main); mPullRefreshListView = FindViewById<PullToRefreshListView>(Resource.Id.pull_refresh_list);
var actualListView = (ListView)mPullRefreshListView.RefreshableView;
mListItems=new List<string>(mStrings);
mAdapter=new ArrayAdapter<string>(this,Android.Resource.Layout.SimpleListItem1,mListItems); var soundListener = new SoundPullEventListener (this);
soundListener.AddSoundEvent(PullToRefreshBase.PullToRefreshState.PullToRefresh, Resource.Raw.pull_event);
soundListener.AddSoundEvent(PullToRefreshBase.PullToRefreshState.Reset, Resource.Raw.reset_sound);
soundListener.AddSoundEvent(PullToRefreshBase.PullToRefreshState.Refreshing, Resource.Raw.refreshing_sound); mPullRefreshListView.SetOnPullEventListener(soundListener);
mPullRefreshListView.SetOnRefreshListener(this);
mPullRefreshListView.SetOnLastItemVisibleListener(this); actualListView.Adapter = mAdapter;
} private class GetDataTask : AsyncTask<Java.Lang.Void, Java.Lang.Void, string[]>
{
private readonly MainActivity _mainActivity; public GetDataTask(MainActivity mainActivity)
{
_mainActivity = mainActivity;
} protected override string[] RunInBackground(params Java.Lang.Void[] @params)
{
try
{
Thread.Sleep();
}
catch (InterruptedException)
{
} return _mainActivity.mStrings;
} protected override void OnPostExecute(Object result)
{
_mainActivity. mAdapter.Insert("added after refresh:" + DateTime.Now.ToString("t"),);
_mainActivity.mAdapter.NotifyDataSetChanged(); _mainActivity.mPullRefreshListView.OnRefreshComplete(); base.OnPostExecute(result);
}
} public void OnRefresh(PullToRefreshBase p0)
{
p0.GetLoadingLayoutProxy(true,true).SetLastUpdatedLabel(string.Format("上次更新:{0:t}",DateTime.Now));
new GetDataTask(this).Execute();
} public void OnLastItemVisible()
{
Toast.MakeText(this,"End of List", ToastLength.Short).Show();
}
}
代码调整完成后进行编译,发现编译的时候又报错了,报错信息里很多乱码,不过可以看到几个关键字“OnSmoothScrollFinishedListener”,所以猜想可能是“OnSmoothScrollFinishedListener”这个接口可能有问题,我们返回到java源代码查看。
在java源代码里的“PullToRefreshBase”类里搜索“OnSmoothScrollFinishedListener”,
找到接口的定义:
static interface OnSmoothScrollFinishedListener {
void onSmoothScrollFinished();
}
我们发现接口是static且是默认的修饰符,我们试一试将修饰符改为public,重新导出jar,再次编译,发现能够通过了,现在我们来看看功能是否正常
运行的时候报错了,提示找不到资源,原来我们绑定java库时忘记打包资源了,我们将资源文件一起打包,然后重新编译.打包资源文件时需要注意以下问题:
- jar文件和资源文件都打包到一个zip文件中,zip压缩包的目录结构如下:
再次编译并运行,终于正常运行了。
四、MvvmCross中使用PullToRefresh
由于最近在使用Mvvmcross,所以也写一个Mvvmcross的例子,下面以ListView为例,实现一个MvxPullToRefreshListView。代码如下:
public class MvxPullToRefreshListView:PullToRefreshListView,PullToRefreshBase.IOnRefreshListener
{
public MvxPullToRefreshListView(Context context, IAttributeSet attrs)
: this(context, attrs, new MvxAdapter(context))
{
} public MvxPullToRefreshListView(Context context, IAttributeSet attrs, IMvxAdapter adapter)
: base(context, attrs)
{
Mode = PullToRefreshMode.Both;
if (adapter == null)
return; var itemTemplateId = MvxAttributeHelpers.ReadListItemTemplateId(context, attrs);
adapter.ItemTemplateId = itemTemplateId;
var lv = (ListView) RefreshableView;
lv.Adapter = adapter;
base.SetOnRefreshListener(this);
} public IMvxAdapter Adapter
{
get
{
var v = ((ListView)RefreshableView);
var adapter=((HeaderViewListAdapter) v.Adapter).WrappedAdapter as IMvxAdapter;
return adapter;
}
set
{
var existing = Adapter;
if (existing == value)
return; if (value != null && existing != null)
{
value.ItemsSource = existing.ItemsSource;
value.ItemTemplateId = existing.ItemTemplateId;
}
var v = ((ListView)base.RefreshableView);
v.Adapter = value;
}
} [MvxSetToNullAfterBinding]
public IEnumerable ItemsSource
{
get { return Adapter.ItemsSource; }
set { Adapter.ItemsSource = value; }
} public bool IsLoading
{
get { return Refreshing; }
set
{
if (!value)
{
OnRefreshComplete();
}
}
} public int ItemTemplateId
{
get { return Adapter.ItemTemplateId; }
set { Adapter.ItemTemplateId = value; }
} private ICommand _itemClick;
public new ICommand ItemClick
{
get { return _itemClick; }
set { _itemClick = value; if (_itemClick != null) EnsureItemClickOverloaded(); }
} private bool _itemClickOverloaded = false;
private void EnsureItemClickOverloaded()
{
if (_itemClickOverloaded)
return; _itemClickOverloaded = true;
var v = ((ListView)base.RefreshableView);
v.ItemClick += (sender, args) => ExecuteCommandOnItem(this.ItemClick, args.Position);
} private ICommand _itemLongClick;
public new ICommand ItemLongClick
{
get { return _itemLongClick; }
set { _itemLongClick = value; if (_itemLongClick != null) EnsureItemLongClickOverloaded(); }
} private bool _itemLongClickOverloaded = false; private void EnsureItemLongClickOverloaded()
{
if (_itemLongClickOverloaded)
return; _itemLongClickOverloaded = true;
var v = ((ListView)base.RefreshableView);
v.ItemLongClick += (sender, args) => ExecuteCommandOnItem(this.ItemLongClick, args.Position);
} protected virtual void ExecuteCommandOnItem(ICommand command, int position)
{
if (command == null)
return; var item = Adapter.GetRawItem(position);
if (item == null)
return; if (!command.CanExecute(item))
return; command.Execute(item);
} public ICommand RefreshCommand { get; set; } #region IOnRefreshListener Members public void OnRefresh(PullToRefreshBase p0)
{
var lastUpdatedLabel = string.Format("上次更新:{0:T}", DateTime.Now);
p0.LoadingLayoutProxy.SetLastUpdatedLabel(lastUpdatedLabel);
if (RefreshCommand != null)
{
if (RefreshCommand.CanExecute(null))
{
RefreshCommand.Execute(null);
}
}
} #endregion
}
使用MvxPullToRefreshListView的关键代码如下:
FirstViewModel.cs
/// <summary>
/// The _refresh command
/// </summary>
private MvxCommand _refreshCommand;
/// <summary>
/// Gets the refresh command.
/// </summary>
/// <value>The refresh command.</value>
public System.Windows.Input.ICommand RefreshCommand
{
get
{
_refreshCommand = _refreshCommand ?? new MvxCommand(async()=>await DoRefreshCommand());
return _refreshCommand;
}
} /// <summary>
/// Does the refresh command.
/// </summary>
/// <returns>Task.</returns>
private async Task DoRefreshCommand()
{
Users = await LoadDataAsync();
} /// <summary>
/// load data as an asynchronous operation.
/// </summary>
/// <returns>Task{List{UserInfo}}.</returns>
private async Task<List<UserInfo>> LoadDataAsync()
{
IsLoading = true;
await Task.Delay(SleepMilliSeconds);
var r = new Random();
var count = r.Next(, ); return await Task.Run(() =>
{
var list = new List<UserInfo>();
for (int i = ; i < count; i++)
{
list.Insert(, new UserInfo
{
FirstName = "FirstName" + DateTime.Now.ToString("HH:mm:ss fff") + "__" + i,
LastName = "LastName" + DateTime.Now.ToString("T") + "__" + i + r.Next(),
});
}
IsLoading = false; return list;
});
}
FirstView.axml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<pulltorefreshsample.droid.views.MvxPullToRefreshListView
android:id="@+id/pull_refresh_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000"
android:divider="#FF0C79E9"
android:dividerHeight="1dp"
android:fadingEdge="none"
android:fastScrollEnabled="false"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
android:smoothScrollbar="true"
local:MvxItemTemplate="@layout/user_item"
local:MvxBind="ItemsSource Users;RefreshCommand RefreshCommand;IsLoading IsLoading"
/>
</LinearLayout>
user_item.xaml的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
local:MvxBind="Text FirstName"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView1" />
<TextView
local:MvxBind="Text LastName"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView2" />
</LinearLayout>
运行的效果和上面一样,就不上图了。
五、总结
C#要使用java的jar不容易啊,会出现各种各样的问题,感觉比直接在java里使用麻烦很多很多,需要耐心的解决这些问题,并且可能需要修改java代码,如果没有源代码,可能会比较麻烦。不过使用Xamarin的好处是,逻辑代码可以完全重用,并且编写代码的效率比直接用java要高。
Xamarin. Android实现下拉刷新功能的更多相关文章
- [转]Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能
版权声明:本文出自郭霖的博客,转载必须注明出处. 转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9255575 最近项目中需要用到L ...
- Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能 (转)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9255575 最 近项目中需要用到ListView下拉刷新的功能,一开始想图省事,在 ...
- android ListView上拉加载更多 下拉刷新功能实现(采用pull-to-refresh)
Android实现上拉加载更多功能以及下拉刷新功能, 采用了目前比较火的PullToRefresh,他是目前实现比较好的下拉刷新的类库. 目前他支持的控件有:ListView, ExpandableL ...
- Android StaggeredGrid 加下拉刷新功能 PullToRefresh
https://github.com/etsy/AndroidStaggeredGrid 用的github上面提供瀑布流,继承于abslistview,回收机制不错,并且提供了OnScrollLis ...
- Android 高仿微信(QQ)滑动弹出编辑、删除菜单效果,增加下拉刷新功能
不可否认,微信.QQ列表的滑动删除.编辑功能着实很经典(从IOS那边模仿过来的),然.Android这边,对列表的操作,其实大多还停留上下文菜单来实现. Android如何实现list item的滑动 ...
- Android自定义下拉刷新
网上的下拉刷新功能很多,不过基本上都是隐藏header的,而项目里面需要只隐藏部分的header,类似QQ好友动态的效果,修改了一些现有的,最后有很多问题,所以就自己自定义了一个,逻辑也很简单,首先就 ...
- Android智能下拉刷新加载框架—看这些就够了
一些值得学习的几个下拉刷新上拉加载开源库 Android智能下拉刷新框架-SmartRefreshLayout 支持所有的 View(AbsListView.RecyclerView.WebView. ...
- 利用Swiperefreshlayout实现下拉刷新功能的技术探讨
在常见的APP中通常有着下拉页面从而达到刷新页面的功能,这种看似简单的功能有着花样繁多的实现方式.而利用Swiperefreshlayout实现下拉刷新功能则是其中比较简明扼要的一种. 一般来说,在竖 ...
- 使用google自带包实现下拉刷新功能
android 实现下拉刷新有非常多开源的源代码能够用 比方 :PullToRefreshListView 使用起来也非常方便 如今还能够直接使用google libs以下的 android-sup ...
随机推荐
- 0-1背包问题蛮力法求解(c++版本)
// 0.1背包求解.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #define ...
- Oracle 的基本操作符
!= 不等于 select empno,ename,job from scott.emp where job!='manager' ^= 不等于 select empno,ename,job from ...
- 水印第三版 ~ 变态水印(这次用Magick.NET来实现,附需求分析和源码)
技能 汇总:http://www.cnblogs.com/dunitian/p/4822808.html#skill 以前的水印,只是简单走起,用的是原生态的方法.现在各种变态水印,于是就不再用原生态 ...
- javascript单元测试框架mochajs详解
关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...
- Xamarin+Prism开发详解一:PCL跨平台类库与Profile的关系
在[Xamarin+Prism小试牛刀:定制跨平台Outlook邮箱应用]中提到过以下错误,不知道大伙还记得不: 无法安装程序包"Microsoft.Identity.Client 1.0. ...
- UML图中经常用到几种的关系图例
学习这个东西挺奇怪的,时间一长就容易忘记,或者记不清楚.今天看到一些UML图的关系,发现有些出入了,索性就写下来,以后再忘记的时候过来看看. 在UML的类图中,常见的有以下几种关系: 继承(Gener ...
- Html 制作相册
本文主要讲述采用Html5+jQuery+CSS 制作相册的小小记录. 主要功能点: Html5进行布局 调用jQuery(借用官网的一句话:The Write Less, Do More)极大的简化 ...
- phpexcel读取输出操作
//读取 <?php header("Content-Type:text/html;charset=utf-8"); include 'Classes/PHPExcel.ph ...
- How to accept Track changes in Microsoft Word 2010?
"Track changes" is wonderful and remarkable tool of Microsoft Word 2010. The feature allow ...
- springMvc的日期转换之二
方式一:使用@InitBinder注解实现日期转换 前台页面: 后台打印: 方式二:处理多种日期格式类型之间的转换 采用方式:由于binder.registerCustomEditor(Date.cl ...