这篇文章主要介绍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下拉刷新效果”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。