Android實現(xiàn)RecyclerView下拉刷新效果
更新時間:2017年07月15日 14:13:53 作者:Android_Study_OK
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)RecyclerView下拉刷新效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
本文為大家分享了Android實現(xiàn)RecyclerView下拉刷新效果的具體代碼,供大家參考,具體內(nèi)容如下
思路
- RealPullRefreshView繼承了一個LinearLayout
- 里面放置了一個刷新頭布局,將其margin_top設(shè)置為負(fù)的刷新頭的高度的
- 再添加一個RecyclerView
- 觸摸事件分發(fā)機制,當(dāng)在特定條件下讓RealPullRefreshView攔截觸摸事件,否則的話,不攔截,讓RecyclerView自己去處理觸摸事件
- 在手指下拉時,定義好不同的狀態(tài)STATE,在不同狀態(tài)下,處理不同的顯示,這里講不同狀態(tài)下的刷新頭如何顯示,抽象為一個接口,用戶可以實現(xiàn)這個接口,自定義刷新頭的布局和動畫
- 加載更多的功能是利用RecyclerView的多type布局實現(xiàn)的
- 難點在于觸摸事件的攔截,和認(rè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;
// 分別記錄上次滑動的坐標(biāo)
private int mLastX = 0;
private int mLastY = 0;
// 分別記錄上次滑動的坐標(biāo)(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);
// 這里我提供了一個默認(rèn)的顯示效果,如果用戶不使用mRealPullRefreshView.setOnPullShowViewListener的話,會默認(rèn)使用這個
// 用戶可以實現(xiàn)OnPullShowViewListener接口,去實現(xiàn)自己想要的顯示效果
mOnPullShowViewListener = new ImplOnPullShowViewListener(this);
setLoadMore();
}
private void setLoadMore() {
// 當(dāng)目前的可見條目是所有數(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ù)往下拉,將拉出頭布局,要父布局?jǐn)r截
intercepted = true;
} else if (getScrollY() < 0) {//表示頭布局已經(jīng)向下拉出來,頭布局已經(jīng)顯示了,要父布局?jǐn)r截
intercepted = true;
} else if (deltaY < 0) {
intercepted = false;//不要父布局?jǐn)r截了
} else {
intercepted = false;//不要父布局?jǐn)r截了
}
// ******************什么規(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;
}
/**
* 當(dāng)用戶使用完下拉刷新回調(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();
}
/**
* 當(dāng)用戶使用完加載更多后回調(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 {
/**
* 當(dāng)下拉刷新正在刷新時,這時候可以去請求數(shù)據(jù),記得最后調(diào)用refreshFinish()復(fù)位
*/
void onRefresh();
/**
* 當(dāng)加載更多時
*/
void onLoadMore();
}
/**
* 回調(diào)接口,可以通過下面的回調(diào),自定義各種狀態(tài)下的顯示效果
* 可以根據(jù)下拉距離scrollY設(shè)計動畫效果
*/
public interface OnPullShowViewListener {
/**
* 當(dāng)處于下拉刷新時,頭布局顯示效果
*
* @param scrollY 下拉的距離
* @param headviewHeight 頭布局高度
* @param deltaY moveY-lastMoveY,正值為向下拉
*/
void onPullDownRefreshState(int scrollY, int headviewHeight, int deltaY);
/**
* 當(dāng)處于松手刷新時,頭布局顯示效果
*
* @param scrollY 下拉的距離
* @param deltaY moveY-lastMoveY,正值為向下拉
*/
void onReleaseRefreshState(int scrollY, int deltaY);
/**
* 正在刷新時,頁面的顯示效果
*/
void onRefreshingState();
/**
* 默認(rèn)狀態(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-11
android教程之把自己的應(yīng)用加入到系統(tǒng)分享中
在Android系統(tǒng)中打開相冊中的某張圖片, 點擊右上角的分享按鈕會彈出分享列表, 把自己的應(yīng)用加入到里面來,下面是設(shè)置方法2014-02-02
Jetpack?Compose?實現(xiàn)一個圖片選擇框架功能
這篇文章主要介紹了Jetpack?Compose?實現(xiàn)一個圖片選擇框架,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
Android Map新用法:MapFragment應(yīng)用介紹
MapView ,MapActivity 這種的局限在于,必須要繼承MapActivity,否則無法使用MapView,但是,MapFragment 這種的局限在于,必須要安裝Google Play Service ,也就是說必須是原生rom。而且sdk要在12以上2013-01-01
Android位圖(圖片)加載引入的內(nèi)存溢出問題詳細(xì)解析
Android在加載大背景圖或者大量圖片時,常常致使內(nèi)存溢出,下面這篇文章主要給大家介紹了關(guān)于Android位圖(圖片)加載引入的內(nèi)存溢出問題的相關(guān)資料,需要的朋友可以參考下2022-12-12

