您好,登錄后才能下訂單哦!
這篇文章主要介紹Android如何實現RecyclerView下拉刷新效果,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
具體內容如下
思路
RealPullRefreshView繼承了一個LinearLayout
里面放置了一個刷新頭布局,將其margin_top設置為負的刷新頭的高度的
再添加一個RecyclerView
觸摸事件分發機制,當在特定條件下讓RealPullRefreshView攔截觸摸事件,否則的話,不攔截,讓RecyclerView自己去處理觸摸事件
在手指下拉時,定義好不同的狀態STATE,在不同狀態下,處理不同的顯示,這里講不同狀態下的刷新頭如何顯示,抽象為一個接口,用戶可以實現這個接口,自定義刷新頭的布局和動畫
加載更多的功能是利用RecyclerView的多type布局實現的
難點在于觸摸事件的攔截,和認真處理各種滑動的問題
使用
xml
<com.example.apple.quickdemo.realview.view.RealPullRefreshView android:id="@+id/real_pull_refresh_view" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" app:refresh_header_view="@layout/headerview"/>
這是headerview
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp" android:background="#ff0" android:orientation="horizontal" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:background="@color/colorAccent" android:visibility="visible" android:id="@+id/tv" android:gravity="center" android:text="下拉刷新" android:textSize="21sp"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher" android:id="@+id/iv" /> </LinearLayout>
代碼
mRealPullRefreshView.setLayoutManager(mLayoutManager); mRealPullRefreshView.setAdapter(mMyAdapte); //用戶可以自定義自己的刷新頭布局和動畫 //mRealPullRefreshView.setOnPullShowViewListener(new GifOnPullShowViewListerner(mRealPullRefreshView)); mRealPullRefreshView.setOnPullListener(new RealPullRefreshView.OnPullListener() { @Override public void onRefresh() { mHandler.postDelayed(new Runnable() { @Override public void run() { mBodies.add(0, new Body("新數據"+i++,100)); mRealPullRefreshView.refreshFinish(); } }, 3000); } @Override public void onLoadMore() { final List<Body> more=new ArrayList<Body>(); mHandler.postDelayed(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { more.add(new Body("more+++"+i,100)); } mBodies.addAll(more); mRealPullRefreshView.loadMreFinish(); } }, 1500); } });
自定義刷新頭布局和動畫
package com.example.apple.quickdemo.realview.show; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.example.apple.quickdemo.R; import com.example.apple.quickdemo.realview.view.RealPullRefreshView; /** * Created by apple on 2017/7/9. */ public class ImplOnPullShowViewListener implements RealPullRefreshView.OnPullShowViewListener { private TextView mTv; private ImageView mIv; private ObjectAnimator mAni; View mHeaderView; public ImplOnPullShowViewListener(RealPullRefreshView realPullRefreshView) { mHeaderView = realPullRefreshView.getRefreshHeaderView(); mTv = (TextView) mHeaderView.findViewById(R.id.tv); mIv = (ImageView) mHeaderView.findViewById(R.id.iv); mAni = ObjectAnimator.ofFloat(mIv, "rotation", -15, 15).setDuration(300); mAni.setRepeatCount(ValueAnimator.INFINITE); mAni.setRepeatMode(ValueAnimator.REVERSE); } @Override public void onPullDownRefreshState(int scrollY, int headviewHeight,int deltaY) { mTv.setText("下拉刷新"); float f = -((float) scrollY / (float) headviewHeight); Log.e("tag", f+ ""); Log.e("tag", -scrollY + "scrollY"); mIv.setScaleX(f); mIv.setScaleY(f); } @Override public void onReleaseRefreshState(int scrollY, int deltaY) { mTv.setText("松手刷新"); } @Override public void onRefreshingState() { mTv.setText("正在刷新"); mIv.setScaleX(1.0f); mIv.setScaleY(1.0f); mAni.start(); } @Override public void onDefaultState() { if (mAni.isRunning()){ mAni.end(); mIv.setRotation(0); } } }
源碼
package com.example.apple.quickdemo.realview.view; import android.content.Context; import android.content.res.TypedArray; import android.support.annotation.Nullable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.Scroller; import android.widget.Toast; import com.example.apple.quickdemo.R; import com.example.apple.quickdemo.realview.show.ImplOnPullShowViewListener; import static android.content.ContentValues.TAG; /** * Created by apple on 2017/7/7. * 下拉刷新 */ public class RealPullRefreshView extends LinearLayout { private int mTouchSlop; // 分別記錄上次滑動的坐標 private int mLastX = 0; private int mLastY = 0; // 分別記錄上次滑動的坐標(onInterceptTouchEnvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; private RecyclerView.Adapter mAdapter; public RecyclerView getRecyclerView() { return mRecyclerView; } private RecyclerView mRecyclerView; private int DEFAULT = 0; private final int PULL_DOWN_REFRESH = 1; private final int RELEASE_REFRESH = 2; private final int REFRESHING = 3; private final int LOAD_MORE = 4; private int STATE = DEFAULT; private int rfreshHeaderWidth; private int refreshHeadviewHeight; private OnPullListener mOnPullListener; private View mRefreshHeaderView; private RecyclerView.LayoutManager mLayoutManager; int refreshHeadviewId; public void setLayoutManager(RecyclerView.LayoutManager manager) { this.mLayoutManager = manager; mRecyclerView.setLayoutManager(mLayoutManager); } public void setAdapter(RecyclerView.Adapter adapter) { this.mAdapter = adapter; mRecyclerView.setAdapter(mAdapter); } public View getRefreshHeaderView() { return mRefreshHeaderView; } public void setOnPullShowViewListener(OnPullShowViewListener onPullShowViewListener) { mOnPullShowViewListener = onPullShowViewListener; } private OnPullShowViewListener mOnPullShowViewListener; public void setOnPullListener(OnPullListener onPullListener) { mOnPullListener = onPullListener; } public RealPullRefreshView(Context context) { super(context); initView(context); } public RealPullRefreshView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initAttrs(context, attrs); // ★★★★★一個坑initAttrs方法里的typedArray去獲取屬性時,第一次獲取的屬性全是0,他會馬上重走一次構造方法,再次獲取一次,才能獲得正確的值 // 如果第一次獲取的值為0,則不去initView if (refreshHeadviewId != 0) { initView(context); } } public RealPullRefreshView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttrs(context, attrs); if (refreshHeadviewId != 0) { initView(context); } } private void initAttrs(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RealPullRefreshView); try { refreshHeadviewId = typedArray.getResourceId(R.styleable.RealPullRefreshView_refresh_header_view, 0); } finally { typedArray.recycle(); } } private void initView(Context context) { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); // 添加headerview // ★ ★ ★ ★ ★ 注意不要用這個方法inflate布局,會導致layout的所有屬性失效,height、width、margin // 原因見 http://blog.csdn.net/zhaokaiqiang1992/article/details/36006467 // ★ ★ ★ ★ ★ mRefreshHeaderView = mInflater.inflate(R.layout.headerview, null); mRefreshHeaderView = LayoutInflater.from(context).inflate(refreshHeadviewId, this, false); addView(mRefreshHeaderView); // } // 以下代碼主要是為了設置頭布局的marginTop值為-headerviewHeight // 注意必須等到一小會才會得到正確的頭布局寬高 postDelayed(new Runnable() { @Override public void run() { Log.e("q11", refreshHeadviewHeight + "qqqqqqqqqq " + mRefreshHeaderView.getHeight()); rfreshHeaderWidth = mRefreshHeaderView.getWidth(); refreshHeadviewHeight = mRefreshHeaderView.getHeight(); MarginLayoutParams lp = new LinearLayout.LayoutParams(rfreshHeaderWidth, refreshHeadviewHeight); lp.setMargins(0, -refreshHeadviewHeight, 0, 0); mRefreshHeaderView.setLayoutParams(lp); } }, 100); // 添加RecyclerView mRecyclerView = new RecyclerView(context); addView(mRecyclerView, LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); // 這里我提供了一個默認的顯示效果,如果用戶不使用mRealPullRefreshView.setOnPullShowViewListener的話,會默認使用這個 // 用戶可以實現OnPullShowViewListener接口,去實現自己想要的顯示效果 mOnPullShowViewListener = new ImplOnPullShowViewListener(this); setLoadMore(); } private void setLoadMore() { // 當目前的可見條目是所有數據的最后一個時,開始加載新的數據 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); int lastCompletelyVisibleItemPosition = -1; if (mLayoutManager instanceof LinearLayoutManager) { LinearLayoutManager manager = (LinearLayoutManager) mLayoutManager; lastCompletelyVisibleItemPosition = manager.findLastVisibleItemPosition(); } else if (mLayoutManager instanceof GridLayoutManager) { GridLayoutManager manager = (GridLayoutManager) mLayoutManager; lastCompletelyVisibleItemPosition = manager.findLastVisibleItemPosition(); } else if (mLayoutManager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) mLayoutManager; lastCompletelyVisibleItemPosition = manager.findLastVisibleItemPositions(new int[manager.getSpanCount()])[0]; } if (lastCompletelyVisibleItemPosition + 1 == mAdapter.getItemCount()) { if (mOnPullListener != null && STATE == DEFAULT) { STATE = LOAD_MORE; mOnPullListener.onLoadMore(); } } } }); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; /*if (STATE!=DEFAULT||STATE!=REFRESHING){ if (!mScroller.isFinished()) { mScroller.abortAnimation(); }}*/ break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; int firstCompletelyVisibleItemPosition = -1; if (mLayoutManager instanceof LinearLayoutManager) { LinearLayoutManager manager = (LinearLayoutManager) mLayoutManager; firstCompletelyVisibleItemPosition = manager.findFirstCompletelyVisibleItemPosition(); } else if (mLayoutManager instanceof GridLayoutManager) { GridLayoutManager manager = (GridLayoutManager) mLayoutManager; firstCompletelyVisibleItemPosition = manager.findFirstCompletelyVisibleItemPosition(); } else if (mLayoutManager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager manager = (StaggeredGridLayoutManager) mLayoutManager; firstCompletelyVisibleItemPosition = manager.findFirstCompletelyVisibleItemPositions(new int[manager.getSpanCount()])[0]; } // ******************這里說明什么規則下,攔截,其余代碼不要動了,其余代碼指的是處理滑動沖突的代碼*************** if (firstCompletelyVisibleItemPosition == 0 && deltaY > 0 && Math.abs(deltaY) > Math.abs(deltaX)) {//拉倒最頂部,繼續往下拉,將拉出頭布局,要父布局攔截 intercepted = true; } else if (getScrollY() < 0) {//表示頭布局已經向下拉出來,頭布局已經顯示了,要父布局攔截 intercepted = true; } else if (deltaY < 0) { intercepted = false;//不要父布局攔截了 } else { intercepted = false;//不要父布局攔截了 } // ******************什么規則下,攔截*************** break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } Log.d(TAG, "intercepted=" + intercepted); mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } /** * 下面不同布局,不同的滑動需求 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (getScrollY() > 0) { //防止在正在刷新狀態下,上拉出空白 } else if (getScrollY() <= 0 && getScrollY() > -refreshHeadviewHeight * 5) { // 最多下拉到頭布局高度5倍的距離 scrollBy(0, -deltaY / 2); } if (getScrollY() > -refreshHeadviewHeight && STATE != REFRESHING) {//頭布局顯示不全時,為下拉刷新PULL_DOWN_REFRESH狀態 STATE = PULL_DOWN_REFRESH; if (mOnPullShowViewListener != null) { mOnPullShowViewListener.onPullDownRefreshState(getScrollY(), refreshHeadviewHeight, deltaY); } } if (getScrollY() < -refreshHeadviewHeight && STATE != REFRESHING) {//頭布局完全顯示時,為釋放刷新RELEASE_REFRESH狀態 STATE = RELEASE_REFRESH; if (mOnPullShowViewListener != null) { mOnPullShowViewListener.onReleaseRefreshState(getScrollY(), deltaY); } } break; } case MotionEvent.ACTION_UP: { final int scrollY = getScrollY(); //松手時,根據所處的狀態,讓布局滑動到不同的地方,做不同的操作 switch (STATE) { case PULL_DOWN_REFRESH: STATE = DEFAULT; //頭布局沒有完全顯示,完全隱藏頭布局 smoothScrollBy(0, -scrollY); break; case RELEASE_REFRESH: STATE = REFRESHING; smoothScrollBy(0, -refreshHeadviewHeight - scrollY); if (mOnPullShowViewListener != null) { mOnPullShowViewListener.onRefreshingState(); } if (mOnPullListener != null) { mOnPullListener.onRefresh(); } break; case REFRESHING: if (getScrollY() < -refreshHeadviewHeight) { smoothScrollBy(0, -refreshHeadviewHeight - scrollY); } else { smoothScrollBy(0, -scrollY); } break; } mVelocityTracker.clear(); break; } default: break; } mLastX = x; mLastY = y; return true; } /** * 當用戶使用完下拉刷新回調時,需要調用此方法,將頭不去隱藏,將STATE恢復 */ public void refreshFinish() { smoothScrollBy(0, 0 - getScrollY()); getRecyclerView().getAdapter().notifyDataSetChanged(); STATE = DEFAULT; if (mOnPullShowViewListener != null) { mOnPullShowViewListener.onDefaultState(); } Toast.makeText(getContext(), "刷新成功!", Toast.LENGTH_SHORT).show(); } /** * 當用戶使用完加載更多后回調時,需要調用此方法,將STATE恢復 */ public void loadMreFinish() { getRecyclerView().getAdapter().notifyDataSetChanged(); STATE = DEFAULT; Toast.makeText(getContext(), "加載成功了!", Toast.LENGTH_SHORT).show(); } /** * 在500毫秒內平滑地滾動多少像素點 * * @param dx * @param dy */ private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(0, getScrollY(), 0, dy, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } /** * 釋放資源 */ @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); } // *************** // ***************** /** * 回調接口 */ public interface OnPullListener { /** * 當下拉刷新正在刷新時,這時候可以去請求數據,記得最后調用refreshFinish()復位 */ void onRefresh(); /** * 當加載更多時 */ void onLoadMore(); } /** * 回調接口,可以通過下面的回調,自定義各種狀態下的顯示效果 * 可以根據下拉距離scrollY設計動畫效果 */ public interface OnPullShowViewListener { /** * 當處于下拉刷新時,頭布局顯示效果 * * @param scrollY 下拉的距離 * @param headviewHeight 頭布局高度 * @param deltaY moveY-lastMoveY,正值為向下拉 */ void onPullDownRefreshState(int scrollY, int headviewHeight, int deltaY); /** * 當處于松手刷新時,頭布局顯示效果 * * @param scrollY 下拉的距離 * @param deltaY moveY-lastMoveY,正值為向下拉 */ void onReleaseRefreshState(int scrollY, int deltaY); /** * 正在刷新時,頁面的顯示效果 */ void onRefreshingState(); /** * 默認狀態時,頁面顯示效果,主要是為了復位各種狀態 */ void onDefaultState(); } }
以上是“Android如何實現RecyclerView下拉刷新效果”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。