介紹html
實現思路和代碼java
佈局文件android
下拉刷新實現ide
上拉加載實現佈局
飛機轉頭和動畫代碼post
博文續篇動畫
爲以前的自定義View添加DrawerLayout(側拉抽屜),爲自定義View系列畫上完美句號。spa
繼續我上篇文章的內容:一步步實現ListView的Item側滑刪除菜單效果,仿QQ的聊天頁面側滑刪除,這篇我將給ListView加上上拉刷新、下拉加載的動畫效果。.net
其實,這篇內容和上篇內容用到的原理、邏輯、思路及實現等基本都相似。所謂一通百通啊,真的是這樣,你只要掌握自定義View的一些套路,其實也不是很難嘛。code
主要解決問題(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事件給消費了,因此不攔截的狀況下,儘管你怎麼往下拉,它始終是拉不出來的。哈哈,那麼解決方法就是咱們攔截它。可是攔截老是有條件的,這個條件有兩點:
一、ListView的子項在第一個,也就是到達最頂部。
二、若是在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); }
那麼,咱們整個下拉刷新、上拉加載的最終效果:
©原文連接:https://blog.csdn.net/smile_Running/article/details/81950872
@做者博客:_Xu2WeI
@更多博文:查看做者的更多博文