Android打造 ListView GridView等 通用的下拉重新整理 上拉自動載入的元件
阿新 • • 發佈:2018-11-13
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
前言
下拉重新整理元件在開發中使用率是非常高的,基本上聯網的APP都會採用這種方式。對於開發效率而言,使用獲得大家認可的開源庫必然是效率最高的,但是不重複發明輪子的前提是你得自己知道輪子是怎麼發明出來的,並且自己能夠實現這些功能。否則只是知道其原理,並沒有去實踐那也就是紙上談兵了。做程式猿,動手做才會遇到真正的問題,否則就只是自以為是的認為自己懂了。今天這篇文章就是以自己重複發明輪子這個出發點而來的,通過實現經典、使用率較高的元件來提高自己的認識。下面我們就一起來學習吧。
整體佈局結構
圖1 圖2
原理都雖然簡單,但是實現起來卻也是會有很多小麻煩。這裡沒有采用通過設定onTouchListener的方法,因此使用這個方式在下拉的時候依然會出現ListView的最頂部的"HOLD"檢視,不太爽。這種實現方法也蠻簡單的,具體看郭神的部落格 Android下拉重新整理完全解析,教你如何一分鐘實現下拉重新整理功能。
下拉重新整理基本原理
基本原理就是在使用者滑動螢幕上的元件時,在onInterceptTouchEvent方法中判斷是否到了ContentView (這裡我們以ListView為例來說明)的最頂端,如果到了最頂端且使用者還繼續向下滑,那麼會攔截觸控事件避免它分發到ListView,即在onInterceptTouchEvent中返回true ( 不太清楚的可以參考資料如下 : Android Touch事件分發過程。 ),這樣就將觸控事件分發到了onTouchEvent函式中,我們對於使用者觸控事件的處理邏輯主要都在這個函式中。如果該函式返回false,那麼觸控事件則會分發給其Child View,這裡的這個Child View就是ListView了,當返回false時使用者滑動螢幕時就會滾動ListView。 /* * 在適當的時候攔截觸控事件,這裡指的適當的時候是當mContentView滑動到頂部,並且是下拉時攔截觸控事件,否則不攔截,交給其child * view 來處理。 * @see * android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onTouchEvent will be called and we do the actual * scrolling there. */ final int action = MotionEventCompat.getActionMasked(ev); // Always handle the case of the touch gesture being complete. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Do not intercept touch event, let the child handle it return false; } switch (action) { case MotionEvent.ACTION_DOWN: mYDown = (int) ev.getRawY(); break; case MotionEvent.ACTION_MOVE: // int yDistance = (int) ev.getRawY() - mYDown; mYDistance = (int) ev.getRawY() - mYDown; showStatus(mCurrentStatus); Log.d(VIEW_LOG_TAG, "%%% isBottom : " + isBottom() + ", isTop : " + isTop() + ", mYDistance : " + mYDistance); // 如果拉到了頂部, 並且是下拉,則攔截觸控事件,從而轉到onTouchEvent來處理下拉重新整理事件 if ((isTop() && mYDistance > 0) || (mYDistance > 0 && mCurrentStatus == STATUS_REFRESHING)) { return true; } break; } // Do not intercept touch event, let the child handle it return false; }
首先我們在ACTION_DOWN事件中記錄使用者按下的觸控點的Y軸座標mYDown,然後在ACTION_MOVE中再次獲取Y軸的座標,計算出兩者之間的差值。如果滑動的差值大於mTouchSlop則繼續進行處理,mTouchSlop為判斷一個觸控滑動事件是否有效的的最小閥值,如果小於這個閥值我們認為這個觸控滑動事件無效,例如手抖了一下,距離很短,因此我們忽略類似的事件。
在有效的滑動距離之內,我們判斷當前元件的狀態,如果不是正在重新整理的狀態,那麼我們根據當前ListView的paddingTop的高度來設定不同的值,paddingTop如果高度大於ListView高度的70%,那麼我們將當前狀態設定為“釋放可重新整理”狀態,即STATUS_RELEASE_TO_REFRESH狀態;反之,我們設定狀態為“繼續下拉”狀態,即“STATUS_PULL_TO_REFRESH”。通過這個paddingTop高度我們來規定當使用者下拉距離到一定的距離後才出發重新整理操作,否則視為無效下拉。然而不管這個時候是什麼狀態,我們都會修改Header的padding Top屬性,從而達到拉伸header的效果。
當狀態為“釋放可重新整理”時,我們擡起手指,會出發ACTION_UP事件,此時我們在該事件中進行下拉重新整理操作。onTouchEvent程式碼如下 :
/* * 在這裡處理觸控事件以達到下拉重新整理或者上拉自動載入的問題 * @see android.view.View#onTouchEvent(android.view.MotionEvent) */ @Override public boolean onTouchEvent(MotionEvent event) { Log.d(VIEW_LOG_TAG, "@@@ onTouchEvent : action = " + event.getAction()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mYDown = (int) event.getRawY(); Log.d(VIEW_LOG_TAG, "#### ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(VIEW_LOG_TAG, "#### ACTION_MOVE"); int currentY = (int) event.getRawY(); mYDistance = currentY - mYDown; // 高度大於header view的高度才可以重新整理 if (mYDistance >= mTouchSlop) { if (mCurrentStatus != STATUS_REFRESHING) { // if (mHeaderView.getPaddingTop() > mHeaderViewHeight * 0.7f) { mCurrentStatus = STATUS_RELEASE_TO_REFRESH; mTipsTextView.setText(R.string.pull_to_refresh_release_label); } else { mCurrentStatus = STATUS_PULL_TO_REFRESH; mTipsTextView.setText(R.string.pull_to_refresh_pull_label); } } rotateHeaderArrow(); int scaleHeight = (int) (mYDistance * 0.8f);// 去了滑動距離的80%,減小靈敏度而已 // Y軸的滑動距離小於螢幕高度4分之一時才會拉伸header,反之保持不變 if (scaleHeight <= mScrHeight / 4) { adjustHeaderPadding(scaleHeight); } } break; case MotionEvent.ACTION_UP: // 下拉重新整理的具體操作 doRefresh(); break; default: break; } return true; }
擡起手指時出發的重新整理操作,程式碼如下:
/** * 執行重新整理操作 */ private final void doRefresh() { if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) { // 設定狀態為正在重新整理狀態 mCurrentStatus = STATUS_REFRESHING; mArrowImageView.clearAnimation(); // 隱藏header中的箭頭圖示 mArrowImageView.setVisibility(View.GONE); // 設定header中的進度條可見 mHeaderProgressBar.setVisibility(View.VISIBLE); // 設定一些文字 mTimeTextView.setText(R.string.pull_to_refresh_update_time_label); SimpleDateFormat sdf = new SimpleDateFormat(); mTimeTextView.append(sdf.format(new Date())); // mTipsTextView.setText(R.string.pull_to_refresh_refreshing_label); // 執行回撥 if (mPullRefreshListener != null) { mPullRefreshListener.onRefresh(); } // 使headview 正常顯示, 直到呼叫了refreshComplete後再隱藏 new HeaderViewHideTask().execute(0); } else { // 隱藏header view adjustHeaderPadding(-mHeaderViewHeight); } }
在重新整理狀態時,header正常顯示,即此時的padding top需要設定為0,我們使用一個非同步任務來逐步修改padding top的值,使得header從拉伸效果逐步、平滑的恢復原始的大小。使用者呼叫refreshComplete()函式後,即重新整理完成後,再逐步調整listview的padding top將其隱藏。至此,整個下拉重新整理過程結束。
滑動到底部自動載入
滑動到底部自動載入相對來說要簡單得多,我們也是以ContentView是ListView的情況來說明。原理就是監聽ListView ( 即 ContentView )的的滾動事件,因此如果ContentView的型別不支援滾動事件,則不能實現該功能。listview符合要求,因此其能實現自動載入。我們在onScroll函式中判斷listview是否到了最後一項,如果到了最後一項,那麼顯示出footer,並且開始載入。當用戶呼叫loadMoreComplete函式時代表載入結束。此時隱藏footer,整個過程結束。 /* * 滾動事件,實現滑動到底部時上拉載入更多 * @see android.widget.AbsListView.OnScrollListener#onScroll(android.widget. * AbsListView, int, int, int) */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { Log.d(VIEW_LOG_TAG, "&&& mYDistance = " + mYDistance); if (mFooterView == null || mYDistance >= 0 || mCurrentStatus == STATUS_LOADING || mCurrentStatus == STATUS_REFRESHING) { return; } loadmore(); } /** * 下拉到底部時載入更多 */ private void loadmore() { if (isShowFooterView() && mLoadMoreListener != null) { mFooterTextView.setText(R.string.pull_to_refresh_refreshing_label); mFooterProgressBar.setVisibility(View.VISIBLE); adjustFooterPadding(0); mCurrentStatus = STATUS_LOADING; mLoadMoreListener.onLoadMore(); } }
其中loadmore函式中呼叫的isShowFooterView函式就是用來判斷是否到了最底部的,程式碼如下 :
/* * 下拉到listview 最後一項時則返回true, 將出發自動載入 * @see com.uit.pullrefresh.base.PullRefreshBase#isShowFooterView() */ @Override protected boolean isShowFooterView() { if (mContentView == null || mContentView.getAdapter() == null) { return false; } return mContentView.getLastVisiblePosition() == mContentView.getAdapter().getCount() - 1; }
OK,至此整個核心的過程介紹完畢了。
效果圖
ListView
TextView下拉重新整理