android開發中最經常使用的就是列表組件,如ListView,recycleView,用到它們感受就會涉及到數據更新,分頁加載。java
最開始的時候,刷新組件我是在技術羣裏頭找了一個被人綁定好的庫,是綁定的github上一個星星不少的java原生組件。可是demo很簡單,對於當時小白的我懵逼了,不曉得咋個用,並且一直以爲banding的庫總感受有問題,就想着直接找一個java的庫翻譯成C#版本的。功夫不負苦心人,在csdn上找到了一篇 http://blog.csdn.net/zhongkejingwang/article/details/38868463android
寫的很詳細,翻譯起來也省了很多力,也很感謝原做者。git
忽然想起一句話,叫作咱們不生產代碼,只是代碼的搬運工!github
這裏貼上我翻譯好的其中幾個很重要的組件的代碼:web
1.PullToRefreshLayoutasync
using System; using System.Threading.Tasks; using Android.Content; using Android.OS; using Android.Util; using Android.Views; using Android.Views.Animations; using Android.Widget; using CNBlog.Droid.Utils; namespace CNBlog.Droid.PullableView { public class PullToRefreshLayout : RelativeLayout { // 初始狀態 private const int initStatus = 0; // 釋放刷新 private const int releaseToRefresh = 1; // 正在刷新 private const int refreshing = 2; // 釋放加載 private const int releaseToLoad = 3; // 正在加載 private const int loading = 4; // 操做完畢 private const int complete = 5; // 當前狀態 private int currentStatus = 0; // 刷新回調接口 private OnRefreshListener mListener; // 刷新成功 private const int succeed = 0; // 刷新失敗 private const int failed = 1; // 按下Y座標,上一個事件點Y座標 private float downY, lastY; // 下拉的距離。注意:pullDownY和pullUpY不可能同時不爲0 private float pullDownY = 0; // 上拉的距離 private float pullUpY = 0; // 釋放刷新的距離 private float refreshDist = 200; // 釋放加載的距離 private float loadmoreDist = 200; private UIScheduling uScheduling; // 回滾速度 private float moveSpeed = 8; // 第一次執行佈局 private bool isLayout = false; // 在刷新過程當中滑動操做 private bool isTouch = false; // 手指滑動距離與下拉頭的滑動距離比,中間會隨正切函數變化 private float radio = 2; // 下拉箭頭的轉180°動畫 private RotateAnimation rotateAnimation; // 均勻旋轉動畫 private RotateAnimation refreshingAnimation; // 下拉頭 private View refreshView; // 下拉的箭頭 public View pullView; // 正在刷新的圖標 private View refreshingView; // 刷新結果圖標 private View refreshStateImageView; // 刷新結果:成功或失敗 private TextView refreshStateTextView; // 上拉頭 private View loadmoreView; // 上拉的箭頭 public View pullUpView; // 正在加載的圖標 private View loadingView; // 加載結果圖標 private View loadStateImageView; // 加載結果:成功或失敗 private TextView loadStateTextView; //請求加載錯誤View private View errorView; // 實現了Pullable接口的View private View pullableView; // 過濾多點觸碰 private int mEvents; // 這兩個變量用來控制pull的方向,若是不加控制,當狀況知足可上拉又可下拉時無法下拉 private bool canPullDown = true; private bool canPullUp = true; private Context mContext; private Handler updateUIHandler; public void setOnRefreshListener(OnRefreshListener listener) { mListener = listener; } public PullToRefreshLayout(Context context) : base(context) { initView(context); } public PullToRefreshLayout(Context context, IAttributeSet attrs) : base(context, attrs) { initView(context); } public PullToRefreshLayout(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle) { initView(context); } private void initView(Context context) { mContext = context; updateUIHandler = new Handler((Message msg) => { // 回彈速度隨下拉距離moveDeltaY增大而增大 moveSpeed = (float)(8 + 5 * Math.Tan(Math.PI / 2 / MeasuredHeight * (pullDownY + Math.Abs(pullUpY)))); if (!isTouch) { // 正在刷新,且沒有往上推的話則懸停,顯示"正在刷新..." if (currentStatus == refreshing && pullDownY <= refreshDist) { pullDownY = refreshDist; uScheduling.Cancel(); } else if (currentStatus == loading && -pullUpY <= loadmoreDist) { pullUpY = -loadmoreDist; uScheduling.Cancel(); } } if (pullDownY > 0) pullDownY -= moveSpeed; else if (pullUpY < 0) pullUpY += moveSpeed; if (pullDownY < 0) { // 已完成回彈 pullDownY = 0; pullView.ClearAnimation(); // 隱藏下拉頭時有可能還在刷新,只有當前狀態不是正在刷新時才改變狀態 if (currentStatus != refreshing && currentStatus != loading) changeStatus(initStatus); uScheduling.Cancel(); RequestLayout(); } if (pullUpY > 0) { // 已完成回彈 pullUpY = 0; pullUpView.ClearAnimation(); // 隱藏上拉頭時有可能還在刷新,只有當前狀態不是正在刷新時才改變狀態 if (currentStatus != refreshing && currentStatus != loading) changeStatus(initStatus); uScheduling.Cancel(); RequestLayout(); } // 刷新佈局,會自動調用onLayout RequestLayout(); // 沒有拖拉或者回彈完成 if (pullDownY + Math.Abs(pullUpY) == 0) { uScheduling.Cancel(); } }); uScheduling = new UIScheduling(updateUIHandler); rotateAnimation = (RotateAnimation)AnimationUtils.LoadAnimation( context, Resource.Animator.reverse_anim); refreshingAnimation = (RotateAnimation)AnimationUtils.LoadAnimation( context, Resource.Animator.rotating); // 添加勻速轉動動畫 LinearInterpolator lir = new LinearInterpolator(); rotateAnimation.Interpolator = lir; refreshingAnimation.Interpolator = lir; } private void initView() { // 初始化下拉布局 pullView = refreshView.FindViewById<View>(Resource.Id.pull_icon); refreshStateTextView = refreshView.FindViewById<TextView>(Resource.Id.state_tv); refreshingView = refreshView.FindViewById<View>(Resource.Id.refreshing_icon); refreshStateImageView = refreshView.FindViewById<View>(Resource.Id.state_iv); // 初始化上拉布局 pullUpView = loadmoreView.FindViewById<View>(Resource.Id.pullup_icon); loadStateTextView = loadmoreView.FindViewById<TextView>(Resource.Id.loadstate_tv); loadingView = loadmoreView.FindViewById<View>(Resource.Id.loading_icon); loadStateImageView = loadmoreView.FindViewById<View>(Resource.Id.loadstate_iv); } /// <summary> /// 完成刷新操做,顯示刷新結果。注意:刷新完成後必定要調用這個方法 /// </summary> /// <param name="refreshResult">succeed表明成功, failed表明失敗</param> public void refreshFinish(int refreshResult) { refreshingView.ClearAnimation(); refreshingView.Visibility = ViewStates.Gone; switch (refreshResult) { case 0: // 刷新成功 refreshStateImageView.Visibility = ViewStates.Visible; refreshStateTextView.Text = "刷新成功"; refreshStateImageView.SetBackgroundResource(Resource.Mipmap.refresh_succeed); break; case 1: default: // 刷新失敗 refreshStateImageView.Visibility = ViewStates.Visible; refreshStateTextView.Text = "刷新失敗"; refreshStateImageView.SetBackgroundResource(Resource.Mipmap.refresh_failed); break; } if (pullDownY > 0) { // 刷新結果停留1秒 new Handler((Message msg) => { changeStatus(complete); hide(); }).SendEmptyMessageDelayed(0, 1000); } else { changeStatus(complete); hide(); } } /// <summary> /// 加載完畢,顯示加載結果。注意:刷新完成後必定要調用這個方法 /// </summary> /// <param name="refreshResult">succeed表明成功, failed表明失敗</param> public void loadmoreFinish(int refreshResult) { loadingView.ClearAnimation(); loadingView.Visibility = ViewStates.Gone; switch (refreshResult) { case 0: // 加載成功 loadStateImageView.Visibility = ViewStates.Visible; loadStateTextView.Text = "加載成功"; loadStateImageView.SetBackgroundResource(Resource.Mipmap.load_succeed); break; case 1: default: // 加載失敗 loadStateImageView.Visibility = ViewStates.Visible; loadStateTextView.Text = "加載失敗"; loadStateImageView.SetBackgroundResource(Resource.Mipmap.load_failed); pullableView.Visibility = ViewStates.Gone; break; } if (pullUpY < 0) { // 刷新結果停留1秒 new Handler((Message msg) => { changeStatus(complete); hide(); }).SendEmptyMessageDelayed(0, 1000); } else { changeStatus(complete); hide(); } } /// <summary> /// 改變界面佈局狀態 /// </summary> /// <param name="status"></param> private void changeStatus(int status) { currentStatus = status; Log.Debug("status:", status.ToString()); switch (currentStatus) { case 0: // 下拉布局初始狀態 refreshStateImageView.Visibility = ViewStates.Gone; refreshStateTextView.Text = Context.GetString(Resource.String.pull_to_refresh); pullView.ClearAnimation(); pullView.Visibility = ViewStates.Visible; // 上拉布局初始狀態 loadStateImageView.Visibility = ViewStates.Gone; loadStateTextView.Text = Context.GetString(Resource.String.pullup_to_load); pullUpView.ClearAnimation(); pullUpView.Visibility = ViewStates.Visible; break; case 1: // 釋放刷新狀態 refreshStateTextView.Text = Context.GetString(Resource.String.release_to_refresh); pullView.StartAnimation(rotateAnimation); break; case 2: // 正在刷新狀態 pullView.ClearAnimation(); refreshingView.Visibility = ViewStates.Visible; pullView.Visibility = ViewStates.Invisible; refreshingView.StartAnimation(refreshingAnimation); refreshStateTextView.Text = Context.GetString(Resource.String.refreshing); break; case 3: // 釋放加載狀態 loadStateTextView.Text = Context.GetString(Resource.String.release_to_load); pullUpView.StartAnimation(rotateAnimation); break; case 4: // 正在加載狀態 pullUpView.ClearAnimation(); loadingView.Visibility = ViewStates.Visible; pullUpView.Visibility = ViewStates.Invisible; loadingView.StartAnimation(refreshingAnimation); loadStateTextView.Text = Context.GetString(Resource.String.loading); break; case 5: // 刷新或加載完畢,啥都不作 break; } } /// <summary> /// 不限制上拉或下拉 /// </summary> private void releasePull() { canPullDown = true; canPullUp = true; } /// <summary> /// 由父控件決定是否分發事件,防止事件衝突 /// </summary> /// <param name="e"></param> /// <returns></returns> public override bool DispatchTouchEvent(MotionEvent e) { switch (e.ActionMasked) { case MotionEventActions.Down: downY = e.GetY(); lastY = downY; uScheduling.Cancel(); mEvents = 0; releasePull(); break; case MotionEventActions.PointerDown: case MotionEventActions.PointerUp: // 過濾多點觸碰 mEvents = -1; break; case MotionEventActions.Move: if (mEvents == 0) { if (pullDownY > 0 || (((Pullable)pullableView).canPullDown() && canPullDown && currentStatus != loading)) { // 能夠下拉,正在加載時不能下拉 // 對實際滑動距離作縮小,形成用力拉的感受 pullDownY = pullDownY + (e.GetY() - lastY) / radio; if (pullDownY < 0) { pullDownY = 0; canPullDown = false; canPullUp = true; } if (pullDownY > this.MeasuredHeight) pullDownY = this.MeasuredHeight; if (currentStatus == refreshing) { // 正在刷新的時候觸摸移動 isTouch = true; } } else if (pullUpY < 0 || (((Pullable)pullableView).canPullUp() && canPullUp && currentStatus != refreshing)) { // 能夠上拉,正在刷新時不能上拉 pullUpY = pullUpY + (e.GetY() - lastY) / radio; if (pullUpY > 0) { pullUpY = 0; canPullDown = true; canPullUp = false; } if (pullUpY < -this.MeasuredHeight) pullUpY = -this.MeasuredHeight; if (currentStatus == loading) { // 正在加載的時候觸摸移動 isTouch = true; } } else releasePull(); } else mEvents = 0; lastY = e.GetY(); // 根據下拉距離改變比例 radio = (float)(2 + 2 * Math.Tan(Math.PI / 2 / this.MeasuredHeight * (pullDownY + Math.Abs(pullUpY)))); if (pullDownY > 0 || pullUpY < 0) RequestLayout(); if (pullDownY > 0) { if (pullDownY <= refreshDist && (currentStatus == releaseToRefresh || currentStatus == complete)) { // 若是下拉距離沒達到刷新的距離且當前狀態是釋放刷新,改變狀態爲下拉刷新 changeStatus(initStatus); } if (pullDownY >= refreshDist && currentStatus == initStatus) { // 若是下拉距離達到刷新的距離且當前狀態是初始狀態刷新,改變狀態爲釋放刷新 changeStatus(releaseToRefresh); } } else if (pullUpY < 0) { // 下面是判斷上拉加載的,同上,注意pullUpY是負值 if (-pullUpY <= loadmoreDist && (currentStatus == releaseToLoad || currentStatus == complete) && mListener.CanLoadMore() ) { changeStatus(initStatus); } // 上拉操做 if (-pullUpY >= loadmoreDist && currentStatus == initStatus && mListener.CanLoadMore() ) { changeStatus(releaseToLoad); } } // 由於刷新和加載操做不能同時進行,因此pullDownY和pullUpY不會同時不爲0,所以這裏用(pullDownY + // Math.Abs(pullUpY))就能夠不對當前狀態做區分了 if ((pullDownY + Math.Abs(pullUpY)) > 8) { // 防止下拉過程當中誤觸發長按事件和點擊事件 e.Action = MotionEventActions.Cancel; } break; case MotionEventActions.Up: if (pullDownY > refreshDist || -pullUpY > loadmoreDist) // 正在刷新時往下拉(正在加載時往上拉),釋放後下拉頭(上拉頭)不隱藏 { isTouch = false; } if (currentStatus == releaseToRefresh) { changeStatus(refreshing); // 刷新操做 if (mListener != null) mListener.onRefresh(this); } else if (currentStatus == releaseToLoad) { changeStatus(loading); // 加載操做 if (mListener != null) mListener.onLoadMore(this); } hide(); break; default: break; } base.DispatchTouchEvent(e); return true; } protected override void OnLayout(bool changed, int l, int t, int r, int b) { if (!isLayout) { // 這裏是第一次進來的時候作一些初始化 refreshView = GetChildAt(0); pullableView = GetChildAt(1); loadmoreView = GetChildAt(2); isLayout = true; initView(); refreshDist = ((ViewGroup)refreshView).GetChildAt(0) .MeasuredHeight; loadmoreDist = ((ViewGroup)loadmoreView).GetChildAt(0) .MeasuredHeight; } // 改變子控件的佈局,這裏直接用(pullDownY + pullUpY)做爲偏移量,這樣就能夠不對當前狀態做區分 refreshView.Layout(0, (int)(pullDownY + pullUpY) - refreshView.MeasuredHeight, refreshView.MeasuredWidth, (int)(pullDownY + pullUpY)); pullableView.Layout(0, (int)(pullDownY + pullUpY), pullableView.MeasuredWidth, (int)(pullDownY + pullUpY) + pullableView.MeasuredHeight); loadmoreView.Layout(0, (int)(pullDownY + pullUpY) + pullableView.MeasuredHeight, loadmoreView.MeasuredWidth, (int)(pullDownY + pullUpY) + pullableView.MeasuredHeight + loadmoreView.MeasuredHeight); } /// <summary> /// 隱藏刷新UI界面 /// </summary> private void hide() { uScheduling.Schedule(5); } public async Task AutoRefresh() { while (pullDownY < 4 / 3 * refreshDist) { pullDownY += moveSpeed; if (pullDownY > refreshDist) changeStatus(releaseToRefresh); RequestLayout(); await Task.Delay(20); } changeStatus(refreshing); if(mListener!=null) mListener.onRefresh(this); hide(); } } }
2.PullableListViewide
而後接下來就是一個自定義實現的ListView函數
using System; using Android.Content; using Android.Util; using Android.Views; using Android.Widget; namespace CNBlog.Droid.PullableView { public class PullableListView :ListView,Pullable { private View errorLayout; public PullableListView(Context context) :base(context) { } public PullableListView(Context context, IAttributeSet attrs) :base(context,attrs) { } public PullableListView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs,defStyle) { } public bool canPullDown() { if (Count == 0) { // 沒有item的時候也能夠下拉刷新 return true; } else if (FirstVisiblePosition == 0 && GetChildAt(0).Top >= 0) { // 滑到ListView的頂部了 return true; } else return false; } public bool canPullUp() { if (Count == 0) { // 沒有item的時候也能夠上拉加載 return true; } else if (LastVisiblePosition == (Count - 1)) { // 滑到底部了 if (GetChildAt(LastVisiblePosition - FirstVisiblePosition) != null && GetChildAt( LastVisiblePosition - FirstVisiblePosition).Bottom <= MeasuredHeight) return true; } return false; } public void SetErrorLayout() { if (errorLayout == null) errorLayout = LayoutInflater.From(Context).Inflate(Resource.Layout.error_page, null); RemoveHeaderView(errorLayout); AddHeaderView(errorLayout,null,false); SetHeaderDividersEnabled(false); } } }
3.Pullable佈局
namespace CNBlog.Droid.PullableView { /// <summary> /// 如需擴展其它View,實現該接口便可 /// </summary> public interface Pullable { /// <summary> /// 判斷是否能夠下拉,若是不須要下拉功能能夠直接return false /// </summary> /// <returns></returns> bool canPullDown(); /// <summary> /// 判斷是否能夠上拉,若是不須要上拉功能能夠直接return false /// </summary> /// <returns></returns> bool canPullUp(); } }
4.OnRefreshListener動畫
using System.Threading.Tasks; namespace CNBlog.Droid.PullableView { public interface OnRefreshListener { /// <summary> /// 刷新操做 /// </summary> /// <param name="pullToRefreshLayout"></param> Task onRefresh(PullToRefreshLayout pullToRefreshLayout); /// <summary> /// 加載更多 /// </summary> /// <param name="pullToRefreshLayout"></param> Task onLoadMore(PullToRefreshLayout pullToRefreshLayout); /// <summary> /// 是否能夠加載更多 /// </summary> /// <returns><c>true</c>, if load more was caned, <c>false</c> otherwise.</returns> bool CanLoadMore(); } }
代碼搬運工,註釋都是如出一轍的
仍是大概講解下,第 一個PullToRefreshLayout刷新組件,關鍵代碼在於重寫的 DispatchTouchEvent 函數。
dispatchTouchEvent是用來處理觸摸事件分發,關於事件分發機制是咋樣的,這個就複雜了,畢竟我也是菜逼,估計也講不明白。
這兒的左右大概就是監聽手指觸摸事件,經過監聽手指上拉以及下拉做相應的操做。
第二個PullableListView 繼承自ListView以及實現了接口Pullable,Pullable裏的canPullDown(),canPullUp() 用於DispatchTouchEvent 觸發時判斷當前View是否能夠上拉加載更多,以及下拉加載更多。
而自定義的ListView經過當前Item所在位置判斷是否能夠是否能夠上拉加載更多,以及下拉加載更多。
而OnRefreshListener定義了刷新操做以及加載更多 兩個接口。
順便再貼上一塊兒實現的WebView
using System; using Android.Content; using Android.Util; using Android.Webkit; namespace CNBlog.Droid.PullableView { public class PullableWebView :WebView,Pullable { public PullableWebView(Context context) :base(context) { } public PullableWebView(Context context, IAttributeSet attrs) :base(context,attrs) { } public PullableWebView(Context context, IAttributeSet attrs, int defStyle) :base(context,attrs,defStyle) { } public bool canPullDown() { if (ScrollY == 0) return true; else return false; } public bool canPullUp() { //if (ScrollY >=ContentHeight * Scale // - MeasuredHeight) // return true; //else return false; } } }
這個就很簡單了,由於webview我不須要上拉加載更多,因此只須要判斷當前滾動條在什麼位置,若是ScrollY=0便可下拉刷新。
這裏上傳一個目前已打包好的APK文件,讓各位看官老爺體驗下,若是以爲醜陋,渣的話,請各位輕點兒噴,畢竟我仍是個孩子啊。
有什麼好的建議的話,歡迎你們提出。
網盤地址:http://pan.baidu.com/s/1sl0Eu1f
下章貼出源碼哈,待我稍稍整理下,我想這也是各位看官最關心的事兒吧。