Android實現(xiàn)RecyclerView下拉刷新效果
更新時間:2017年07月15日 14:13:53 作者:Android_Study_OK
這篇文章主要為大家詳細介紹了Android實現(xiàn)RecyclerView下拉刷新效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
本文為大家分享了Android實現(xiàn)RecyclerView下拉刷新效果的具體代碼,供大家參考,具體內(nèi)容如下
思路
- RealPullRefreshView繼承了一個LinearLayout
- 里面放置了一個刷新頭布局,將其margin_top設(shè)置為負的刷新頭的高度的
- 再添加一個RecyclerView
- 觸摸事件分發(fā)機制,當在特定條件下讓RealPullRefreshView攔截觸摸事件,否則的話,不攔截,讓RecyclerView自己去處理觸摸事件
- 在手指下拉時,定義好不同的狀態(tài)STATE,在不同狀態(tài)下,處理不同的顯示,這里講不同狀態(tài)下的刷新頭如何顯示,抽象為一個接口,用戶可以實現(xiàn)這個接口,自定義刷新頭的布局和動畫
- 加載更多的功能是利用RecyclerView的多type布局實現(xiàn)的
- 難點在于觸摸事件的攔截,和認真處理各種滑動的問題
使用
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("新數(shù)據(jù)"+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,他會馬上重走一次構(gòu)造方法,再次獲取一次,才能獲得正確的值 // 如果第一次獲取的值為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布局,會導(dǎo)致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); // } // 以下代碼主要是為了設(shè)置頭布局的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的話,會默認使用這個 // 用戶可以實現(xiàn)OnPullShowViewListener接口,去實現(xiàn)自己想要的顯示效果 mOnPullShowViewListener = new ImplOnPullShowViewListener(this); setLoadMore(); } private void setLoadMore() { // 當目前的可見條目是所有數(shù)據(jù)的最后一個時,開始加載新的數(shù)據(jù) 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]; } // ******************這里說明什么規(guī)則下,攔截,其余代碼不要動了,其余代碼指的是處理滑動沖突的代碼*************** if (firstCompletelyVisibleItemPosition == 0 && deltaY > 0 && Math.abs(deltaY) > Math.abs(deltaX)) {//拉倒最頂部,繼續(xù)往下拉,將拉出頭布局,要父布局攔截 intercepted = true; } else if (getScrollY() < 0) {//表示頭布局已經(jīng)向下拉出來,頭布局已經(jīng)顯示了,要父布局攔截 intercepted = true; } else if (deltaY < 0) { intercepted = false;//不要父布局攔截了 } else { intercepted = false;//不要父布局攔截了 } // ******************什么規(guī)則下,攔截*************** 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) { //防止在正在刷新狀態(tài)下,上拉出空白 } else if (getScrollY() <= 0 && getScrollY() > -refreshHeadviewHeight * 5) { // 最多下拉到頭布局高度5倍的距離 scrollBy(0, -deltaY / 2); } if (getScrollY() > -refreshHeadviewHeight && STATE != REFRESHING) {//頭布局顯示不全時,為下拉刷新PULL_DOWN_REFRESH狀態(tài) STATE = PULL_DOWN_REFRESH; if (mOnPullShowViewListener != null) { mOnPullShowViewListener.onPullDownRefreshState(getScrollY(), refreshHeadviewHeight, deltaY); } } if (getScrollY() < -refreshHeadviewHeight && STATE != REFRESHING) {//頭布局完全顯示時,為釋放刷新RELEASE_REFRESH狀態(tài) STATE = RELEASE_REFRESH; if (mOnPullShowViewListener != null) { mOnPullShowViewListener.onReleaseRefreshState(getScrollY(), deltaY); } } break; } case MotionEvent.ACTION_UP: { final int scrollY = getScrollY(); //松手時,根據(jù)所處的狀態(tài),讓布局滑動到不同的地方,做不同的操作 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; } /** * 當用戶使用完下拉刷新回調(diào)時,需要調(diào)用此方法,將頭不去隱藏,將STATE恢復(fù) */ public void refreshFinish() { smoothScrollBy(0, 0 - getScrollY()); getRecyclerView().getAdapter().notifyDataSetChanged(); STATE = DEFAULT; if (mOnPullShowViewListener != null) { mOnPullShowViewListener.onDefaultState(); } Toast.makeText(getContext(), "刷新成功!", Toast.LENGTH_SHORT).show(); } /** * 當用戶使用完加載更多后回調(diào)時,需要調(diào)用此方法,將STATE恢復(fù) */ public void loadMreFinish() { getRecyclerView().getAdapter().notifyDataSetChanged(); STATE = DEFAULT; Toast.makeText(getContext(), "加載成功了!", Toast.LENGTH_SHORT).show(); } /** * 在500毫秒內(nèi)平滑地滾動多少像素點 * * @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(); } // *************** // ***************** /** * 回調(diào)接口 */ public interface OnPullListener { /** * 當下拉刷新正在刷新時,這時候可以去請求數(shù)據(jù),記得最后調(diào)用refreshFinish()復(fù)位 */ void onRefresh(); /** * 當加載更多時 */ void onLoadMore(); } /** * 回調(diào)接口,可以通過下面的回調(diào),自定義各種狀態(tài)下的顯示效果 * 可以根據(jù)下拉距離scrollY設(shè)計動畫效果 */ 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(); /** * 默認狀態(tài)時,頁面顯示效果,主要是為了復(fù)位各種狀態(tài) */ void onDefaultState(); } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
您可能感興趣的文章:
- Android使用RecyclerView實現(xiàn)今日頭條頻道管理功能
- Android利用RecyclerView編寫聊天界面
- Android RecyclerView實現(xiàn)水平、垂直方向分割線
- Android RecyclerView實現(xiàn)數(shù)據(jù)列表展示效果
- Android RecyclerView顯示Item布局不一致解決辦法
- Android中使用RecyclerView實現(xiàn)下拉刷新和上拉加載
- Android中RecyclerView布局代替GridView實現(xiàn)類似支付寶的界面
- Android中RecyclerView實現(xiàn)橫向滑動代碼
- Android RecyclerView實現(xiàn)下拉刷新和上拉加載
- Android RecyclerView使用方法詳解
相關(guān)文章
微信瀏覽器彈出框滑動時頁面跟著滑動的實現(xiàn)代碼(兼容Android和IOS端)
小編在做微信開發(fā)的時候遇到微信瀏覽器彈出框滑動時頁面跟著滑動的效果,下面把關(guān)鍵代碼分享給大家,需要的朋友參考下2016-11-11android教程之把自己的應(yīng)用加入到系統(tǒng)分享中
在Android系統(tǒng)中打開相冊中的某張圖片, 點擊右上角的分享按鈕會彈出分享列表, 把自己的應(yīng)用加入到里面來,下面是設(shè)置方法2014-02-02Jetpack?Compose?實現(xiàn)一個圖片選擇框架功能
這篇文章主要介紹了Jetpack?Compose?實現(xiàn)一個圖片選擇框架,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06Android Map新用法:MapFragment應(yīng)用介紹
MapView ,MapActivity 這種的局限在于,必須要繼承MapActivity,否則無法使用MapView,但是,MapFragment 這種的局限在于,必須要安裝Google Play Service ,也就是說必須是原生rom。而且sdk要在12以上2013-01-01Android位圖(圖片)加載引入的內(nèi)存溢出問題詳細解析
Android在加載大背景圖或者大量圖片時,常常致使內(nèi)存溢出,下面這篇文章主要給大家介紹了關(guān)于Android位圖(圖片)加載引入的內(nèi)存溢出問題的相關(guān)資料,需要的朋友可以參考下2022-12-12