編輯:關於Android編程
下拉刷新組件在開發中使用率是非常高的,基本上聯網的APP都會采用這種方式。對於開發效率而言,使用獲得大家認可的開源庫必然是效率最高的,但是不重復發明輪子的前提是你得自己知道輪子是怎麼發明出來的,並且自己能夠實現這些功能。否則只是知道其原理,並沒有去實踐那也就是紙上談兵了。做程序猿,動手做才會遇到真正的問題,否則就只是自以為是的認為自己懂了。今天這篇文章就是以自己重復發明輪子這個出發點而來的,通過實現經典、使用率較高的組件來提高自己的認識。下面我們就一起來學習吧。

圖1 圖2
該組件整體以豎直方向的LinearLayout為根視圖,分別是Header、ContentView、Foooter, 從上到下依次排列下來,其中ContentView的寬高都為match_parent,footer和header的寬、高分別為match_parent、wrap_content,原始效果如圖1;在Header、Foooter初始時都會通過設置padding隱藏掉,如圖2中的Header設置paddingTop為負的Header的高度值,同理Footer也通過設置paddingBottom為Footer的負的Footer高度來達到隱藏的效果,所以只有 ContentView區域顯示出來。當用戶下拉到頂端,並且繼續下拉時觸發下拉刷新操作;當用戶上拉到底部, 並且繼續上拉時觸發加載更多的操作。
原理都雖然簡單,但是實現起來卻也是會有很多小麻煩。這裡沒有采用通過設置onTouchListener的方法,因此使用這個方式在下拉的時候依然會出現ListView的最頂部的"HOLD"視圖,不太爽。這種實現方法也蠻簡單的,具體看郭神的博客 Android下拉刷新完全解析,教你如何一分鐘實現下拉刷新功能。
/*
* 在適當的時候攔截觸摸事件,這裡指的適當的時候是當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將其隱藏。至此,整個下拉刷新過程結束。 /*
* 滾動事件,實現滑動到底部時上拉加載更多
* @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下拉刷新

猛擊這裡!
Android Studio 1.0 (穩定版) 完全攻略
這篇博文中主要從以下幾點進行敘述: 1、Android Studio安裝與使用 2、Android Studio特
Android NDK環境搭建與簡單實例
一、NDK與JNI簡介 NDK全稱為native development kit本地語言(C&C++)開發包。而對應的是經常接觸的Android-SDK
淺析Android App的相對布局RelativeLayout
一、什麼是相對布局相對布局是另外一種控件擺放的方式相對布局是通過指定當前控件與兄弟控件或者父控件之間的相對位置,從而達到相對的位置二、為什麼要使用相對布局相對於線性布局u
android 百度地圖離線地圖功能
最近做了一個百度地圖離線地圖的功能,雖然功能實現了,但過程中也碰到了一些問題。首先,看看效果圖吧。 1、離線地圖相關APIAPI地址:http://wiki.lbsyu