程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> Xamarin. Android實現下拉刷新功能,xamarinandroid

Xamarin. Android實現下拉刷新功能,xamarinandroid

編輯:C#入門知識

Xamarin. Android實現下拉刷新功能,xamarinandroid


下拉刷新功能在安卓和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文件的生成

二、PullToRefresh.dll的生成

三、PullToRefresh的使用

四、MvvmCross中使用PullToRefresh

五、總結

 

一、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;} } View Code

在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> View Code

再次編譯,發現還是報錯,一共報了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> View Code

剩下的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> View Code

剩下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(4000); } catch (InterruptedException) { } return _mainActivity.mStrings; } protected override void OnPostExecute(Object result) { _mainActivity. mAdapter.Insert("added after refresh:" + DateTime.Now.ToString("t"),0); _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(); } } View Code

代碼調整完成後進行編譯,發現編譯的時候又報錯了,報錯信息裡很多亂碼,不過可以看到幾個關鍵字“OnSmoothScrollFinishedListener”,所以猜想可能是“OnSmoothScrollFinishedListener”這個接口可能有問題,我們返回到java源代碼查看。

在java源代碼裡的“PullToRefreshBase”類裡搜索“OnSmoothScrollFinishedListener”,

找到接口的定義:

static interface OnSmoothScrollFinishedListener { void onSmoothScrollFinished(); } View Code

我們發現接口是static且是默認的修飾符,我們試一試將修飾符改為public,重新導出jar,再次編譯,發現能夠通過了,現在我們來看看功能是否正常

運行的時候報錯了,提示找不到資源,原來我們綁定java庫時忘記打包資源了,我們將資源文件一起打包,然後重新編譯.打包資源文件時需要注意以下問題:

    2.詳細信息請參考: http://developer.xamarin.com/guides/android/advanced_topics/java_integration_overview/binding_a_java_library_(.jar)/

再次編譯並運行,終於正常運行了。

四、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 } View Code

使用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(1, 6); return await Task.Run(() => { var list = new List<UserInfo>(); for (int i = 0; i < count; i++) { list.Insert(0, 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; }); } View Code

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> View Code

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> View Code

運行的效果和上面一樣,就不上圖了。

五、總結

C#要使用java的jar不容易啊,會出現各種各樣的問題,感覺比直接在java裡使用麻煩很多很多,需要耐心的解決這些問題,並且可能需要修改java代碼。不過使用Xamarin的好處是,邏輯代碼可以完全重用,並且編寫代碼的效率比直接用java要高。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved