目 錄(本篇字數:2120)
為之前的自定義View添加DrawerLayout(側拉抽屜),為自定義View系列畫上完美句號。
-
介紹
繼續我上篇文章的內容:一步步實現ListView的Item側滑刪除菜單效果,仿QQ的聊天頁面側滑刪除,這篇我將給ListView加上上拉刷新、下拉加載的動畫效果。
其實,這篇內容和上篇內容用到的原理、邏輯、思路及實現等基本都類似。所謂一通百通啊,真的是這樣,你只要掌握自定義View的一些套路,其實也不是很難嘛。
主要解決問題(ListView 與下拉刷新、上拉加載的滑動沖突)
先來看看我實現的效果,首先是上拉刷新的效果:
- 效果圖
-
實現思路和代碼
那么看這樣實現,如果你沒做過的話,是不是覺得這個很復雜呢?其實並不然。首先,依然是我們的布局,布局分上、中、下三部分。上為上拉刷新內容、中為ListView、下為下拉加載內容。只要你清楚了這樣的布局,那么實現起來輕輕松松啊,有沒有?
-
布局文件
看一下我們的布局文件:
<listview.example.x.slidelistview.RefreshLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="180dp"
android:background="@android:color/holo_red_dark"
android:gravity="center_horizontal">
<ProgressBar
android:id="@+id/refresh_progress"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="bottom|right"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/tv_refresh_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="32dp"
android:layout_marginTop="8dp"
android:textColor="@android:color/white" />
<ImageView
android:id="@+id/iv_refreshing"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center_horizontal|bottom"
android:src="@drawable/ic_flight_black_24dp" />
</FrameLayout>
<ListView
android:id="@+id/lv_contact"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@android:color/holo_orange_dark"
android:gravity="center">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<ProgressBar
android:id="@+id/load_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_load_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="正在加載"
android:textColor="@android:color/white" />
</LinearLayout>
</RelativeLayout>
</listview.example.x.slidelistview.RefreshLayout>
布局里的內容元素我就不做多的說明了,也沒什么好說明的。我們看最外層這個控件,是我自定義的繼承FrameLayout的一個RefreshLayout類。為什么用FrameLayout?我在上篇文章已經做了說明了,不清楚的依然可以在上面推薦鏈接點進去查看。首先,我們將這三個家伙進行布局,當然是從上到下的那種。來看看代碼:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mHeaderView.layout(0, -mHeaderHeight, mHeaderWidth, 0);
mContentView.layout(0, 0, mContentWidth, mContentHeight);
mFooterView.layout(0, mContentHeight, mFooterWidth, mContentHeight + mFooterHeight);
}
-
下拉刷新實現
這就完成了我從上至下的布局。既然,我們把它布局到了屏幕上方,顯然是看不見的。現在只能通過手指將它滑動下來顯示,那么我們在touch事件做滑動處理,來看看代碼。
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
startY = y;
break;
case MotionEvent.ACTION_MOVE:
if (isTop) {
if (firstDownTag == 0) {
/**
* 如果是第一次的話,因為事件傳遞原因
* onInterceptTouchEvent()執行了 ACTION_DOWN事件
* 標記了startY的值(這個值也許非常大,是根據手指按下的y坐標來定的)
* 關鍵是onTouchEvent的ACTION_DOWN無法得到執行,所以 scrollTo(0, disY);將直接移動到startY的位置
* 效果就是導致第一次向下拉,瞬間移動了非常多
*/
firstDownTag++;
} else {
final float dy = y - startY;
int disY = (int) (getScrollY() - dy);
if (-disY <= 0) {
disY = 0;
}
if (-disY < mHeaderHeight) {
scrollTo(0, disY);
mRefreshProgress.setVisibility(INVISIBLE);
if (-disY < mRefreshHeight) {
tvRefreshText.setText("准備起飛");
startRefreshIcon();
} else {
tvRefreshText.setText("加速中");
stopRefreshIcon();
}
}
}
}
startX = x;
startY = y;
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
if (-getScrollY() > mRefreshHeight) {
startRefreshing();
} else {
stopRefreshing();
}
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
isIntercept = false;
upX = x;
upY = y;
break;
case MotionEvent.ACTION_MOVE:
if (isTop) {
if (upY - y < 0) {
isIntercept = true;
} else if (y - upY < 0) {
isIntercept = false;
}
}
break;
case MotionEvent.ACTION_UP:
upY = 0;
upX = 0;
break;
}
return isIntercept;
}
這里有一個大坑我們得爬,就是在RefreshLayout不攔截事件的時候,它默認會分發事件給ListView,導致ListView把touch事件給消費了,所以不攔截的情況下,盡管你怎么往下拉,它始終是拉不出來的。哈哈,那么解決方法就是我們攔截它。但是攔截總是有條件的,這個條件有兩點:
1、ListView的子項在第一個,也就是到達最頂部。
2、如果在ListView到達頂部前提下,手指還繼續往下滑動,那么就是下拉刷新的動作了,在此時攔截它。
上面代碼就是做了這兩件事情,還有就是滑動動畫。當然,這得在我們RefreshLayout中實現對ListView的滑動監聽的接口,判斷是否處於頂部和底部:。還有一個就是我們的飛機動畫了,這比較簡單了。
-
上拉加載實現
既然說完了下拉刷新,下面我們來看看上拉加載動畫吧。
- 上拉加載效果
其實,上拉加載只是和我們的下拉刷新方向相反的。既然我們已經實現了下拉刷新,那么上拉加載還不是手到擒來嘛。因為我們前面已經處理過了事件沖突,所以可以一路向前,通暢無阻。
我們看一下關鍵代碼,最主要的還是我們的touch事件的代碼,添加上拉加載的邏輯代碼,其他都非常簡單了:
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = x;
startY = y;
break;
case MotionEvent.ACTION_MOVE:
if (isTop) {
if (firstDownTag == 0) {
/**
* 如果是第一次的話,因為事件傳遞原因
* onInterceptTouchEvent()執行了 ACTION_DOWN事件
* 標記了startY的值(這個值也許非常大,是根據手指按下的y坐標來定的)
* 關鍵是onTouchEvent的ACTION_DOWN無法得到執行,所以 scrollTo(0, disY);將直接移動到startY的位置
* 效果就是導致第一次向下拉,瞬間移動了非常多
*/
firstDownTag++;
} else {
final float dy = y - startY;
int disY = (int) (getScrollY() - dy);
if (-disY <= 0) {
disY = 0;
}
if (-disY < mHeaderHeight) {
scrollTo(0, disY);
mRefreshProgress.setVisibility(INVISIBLE);
if (-disY < mRefreshHeight) {
tvRefreshText.setText("准備起飛");
startRefreshIcon();
} else {
tvRefreshText.setText("加速中");
stopRefreshIcon();
}
}
}
} else if (isBottom) {/** 在ListView底部,繼續上拉 **/
final float dy = y - startY;
int disY = (int) (getScrollY() - dy);
if (disY < 0) {
disY = 0;
ivLoadingIcon.setVisibility(VISIBLE);
mLoadingProgress.setVisibility(INVISIBLE);
} else if (disY >= mLoadingHeight) {
disY = mLoadingHeight + 5;
}
scrollTo(getScrollX(), disY);
// if (dy < 0) {
// startLoadingIcon();
// } else {
// stopLoadingIcon();
// }
}
startX = x;
startY = y;
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
if (isTop) {
if (-getScrollY() > mRefreshHeight) {
startRefreshing();
} else {
stopRefreshing();
}
} else if (isBottom) {
if (getScrollY() > mLoadingHeight) {
startLoading();
} else {
stopLoading();
}
}
break;
}
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
isIntercept = false;
upX = downX = x;
upY = downY = y;
break;
case MotionEvent.ACTION_MOVE:
if (isTop) {
/** 下拉刷新攔截 **/
if (upY - y < 0) {
isIntercept = true;
} else if (y - upY < 0) {
isIntercept = false;
}
} else if (isBottom) {
/** 上拉加載攔截 **/
if (y - downY < 0) {
isIntercept = true;
} else if (y - downY > 0) {
isIntercept = false;
}
}
break;
case MotionEvent.ACTION_UP:
downX = upY = 0;
downX = upX = 0;
break;
}
return isIntercept;
}
-
飛機轉頭和動畫代碼
private void stopRefreshing() {
mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY());
/**
* ListView子項移動到第一個
*/
mListView.setSelection(0);
invalidate();
}
private void startRefreshing() {
mScroller.startScroll(getScrollX(), getScrollY(), 0, -mRefreshHeight - getScrollY());
tvRefreshText.setText("起飛咯~");
mRefreshProgress.setVisibility(VISIBLE);
startIconAnimation();
invalidate();
/**
* 模擬刷新完成,延遲關閉
*/
handler.postDelayed(() -> stopRefreshing(), 2000);
}
private void startLoading() {
mScroller.startScroll(getScrollX(), getScrollY(), 0, mFooterHeight - getScrollY());
ivLoadingIcon.setVisibility(INVISIBLE);
mLoadingProgress.setVisibility(VISIBLE);
invalidate();
handler.postDelayed(() -> stopLoading(), 1500);
}
private void stopLoading() {
mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY(),1500);
ivLoadingIcon.setVisibility(VISIBLE);
mLoadingProgress.setVisibility(INVISIBLE);
ivLoadingIcon.setPivotX(ivLoadingIcon.getWidth() / 2);
ivLoadingIcon.setPivotY(ivLoadingIcon.getHeight() / 2);
ivLoadingIcon.setRotation(180);
invalidate();
}
private void startIconAnimation() {
TranslateAnimation animation = new TranslateAnimation(0, 0,
getScaleY(), -mRefreshHeight);
animation.setFillAfter(false);
animation.setDuration(2000);
ivRefreshIcon.startAnimation(animation);
}
private void startRefreshIcon() {
ivRefreshIcon.setPivotX(ivRefreshIcon.getWidth() / 2);
ivRefreshIcon.setPivotY(ivRefreshIcon.getHeight() / 2);
ivRefreshIcon.setRotation(180);
}
private void stopRefreshIcon() {
ivRefreshIcon.setPivotX(ivRefreshIcon.getWidth() / 2);
ivRefreshIcon.setPivotY(ivRefreshIcon.getHeight() / 2);
ivRefreshIcon.setRotation(360);
}
那么,我們整個下拉刷新、上拉加載的最終效果:
博文續篇
為之前的自定義View添加DrawerLayout(側拉抽屜),為自定義View系列畫上完美句號。
©原文鏈接:https://blog.csdn.net/smile_Running/article/details/81950872
@作者博客:_Xu2WeI
@更多博文:查看作者的更多博文