有2种刷新方式
开始之前,请在gradle加入RecyclerView组件
implementation 'com.android.support:design:28.0.0'
因为之前参考过很多资料,在使用RecyclerView下拉刷新的时候,网上大多数方法都是在RecyclerView中添加首尾元素达到刷新的目的,但是这样需要写多个xml布局以及修改adapter,感觉使用起来很不方便,于是在研究如何脱离adapter来实现下拉刷新。
有一个难点:需要解决滑动冲突的问题。
这里我使用外部拦截法来解决冲突,即在RecyclerView外层需要写一层viewgroup,然后重写layout的onInterceptTouchEvent,所有事件都会从这里经过。但是需要注意的是,DOWN事件不能拦截,因为拦截了就会致使RecyclerView接收不到任何事件。
但是,需要注意的是,如果指定layout进行处理事件,那么这一次的事件就不会再经过layout的onInterceptTouchEvent,而是直接调用layout的onTouchEvent。
详细的我在代码中进行了注解,不明白的可以评论,我回及时回复您,也很欢迎和我讨论。
layout代码,继承自LinearLayout
/**
* author: siney
* Date: 2019/3/3
* description: 使用外部拦截法解决滑动冲突
*/
public class RefreshLoadLayout extends LinearLayout {
private static final String TAG = "RefreshLoadLayout";
private static final String REFRESH = "refreshing";
private static final String LOAD = "loading";
private static final String BOTH = "both";
public static final int MOVING = 1, UP = 2;
public interface OnChangeListener{
void headerChange(RefreshLoadLayout layout, int nowH, int maxH, int action);
void footerChange(RefreshLoadLayout layout, int nowH, int maxH, int action);
}
public RefreshLoadLayout(Context context) {
super(context);
}
public RefreshLoadLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RefreshLoadLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initParameters(attrs);
}
private void initParameters(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.RefreshLoadLayout);
headerId = a.getResourceId(R.styleable.RefreshLoadLayout_header, -1);
footerId = a.getResourceId(R.styleable.RefreshLoadLayout_footer, -1);
mode = a.getString(R.styleable.RefreshLoadLayout_mode);
duration = a.getInt(R.styleable.RefreshLoadLayout_duration, 500);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if(!isFirst){
isFirst = true;
initChildView();
}
}
private void initChildView() {
if(headerId == -1 && footerId == -1){
Log.e(TAG, "Your have to set at least one resource Id");
return;
}
for(int i = 0;i < getChildCount();i++){
View child = getChildAt(i);
if(child.getId() == headerId){
header = child;
headerMaxH = child.getMeasuredHeight();
headerNowH = 0;
setViewHeight(header, 0);
}else if(child.getId() == footerId){
footer = child;
footerMaxH = child.getMeasuredHeight();
setViewHeight(footer, 0);
}else if(child instanceof RecyclerView){
recyclerView = (RecyclerView) child;
}
}
}
private void setViewHeight(View v, int height) {
ViewGroup.LayoutParams lp = v.getLayoutParams();
if(v == header){
if(height > headerMaxH)headerNowH = headerMaxH;
else if(height < 0)headerNowH = 0;
else headerNowH = height;
lp.height = headerNowH;
}else if(v == footer){
if(height > footerMaxH)footerNowH = footerMaxH;
else if(height < 0)footerNowH = 0;
else footerNowH = height;
lp.height = footerNowH;
}
v.setLayoutParams(lp);
}
private void animClose(final View v, int nowH) {
ValueAnimator animator = ValueAnimator.ofInt(nowH, 0);
if(interpolator != null)
animator.setInterpolator(interpolator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int vv = (int) animation.getAnimatedValue();
setViewHeight(v, vv);
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isAnimFinish = true;
}
});
animator.setDuration(duration);
animator.start();
}
private int headerId, footerId;
private String mode;
private boolean isFirst, isAnimFinish = true;
private int headerNowH, headerMaxH, footerNowH, footerMaxH;
private View header, footer;
private RecyclerView recyclerView;
private int lastY, duration;
private OnChangeListener listener;
private Interpolator interpolator;
@Override
public boolean onTouchEvent(MotionEvent e) {
if(!isAnimFinish)
return true;
int y = (int) e.getRawY();
switch (e.getAction()){
case MotionEvent.ACTION_MOVE:
handle(e);
break;
case MotionEvent.ACTION_UP:
if(listener != null){
if(headerNowH > 0){
isAnimFinish = false;
listener.headerChange(this, headerNowH, headerMaxH, UP);
}else if(footerNowH > 0){
isAnimFinish = false;
listener.footerChange(this, footerNowH, footerMaxH, UP);
}
}
break;
}
lastY = y;
return true;//如果本layout拦截了,那么处理结束就已经消费结束
}
private void handle(MotionEvent e) {
int y = (int) e.getRawY();
if(REFRESH.equals(mode)){
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if(manager instanceof LinearLayoutManager)
((LinearLayoutManager)manager).setStackFromEnd(false);
if(headerNowH == 0 && (y - lastY) < 0 || (y - lastY) > 0 && recyclerView.canScrollVertically(-1))
recyclerView.onTouchEvent(e);
else if(!recyclerView.canScrollVertically(-1)){
if(listener != null)
listener.headerChange(this, headerNowH, headerMaxH, MOVING);
setViewHeight(header, headerNowH + y - lastY);
}
}else if(LOAD.equals(mode)){
if(footerNowH == 0 && (y - lastY) > 0 || (y - lastY) < 0 && recyclerView.canScrollVertically(1))
recyclerView.onTouchEvent(e);
else if(recyclerView.canScrollVertically(-1) && !recyclerView.canScrollVertically(1)){
if(listener != null)
listener.headerChange(this, footerNowH, footerMaxH, MOVING);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if(manager instanceof LinearLayoutManager)
((LinearLayoutManager)manager).setStackFromEnd(true);
setViewHeight(footer, footerNowH + lastY - y);
}
}else if(BOTH.equals(mode)){
if((y - lastY) > 0 && !recyclerView.canScrollVertically(-1)){//如果下拉,并且不能下滑了,说明到头
((LinearLayoutManager)recyclerView.getLayoutManager()).setStackFromEnd(false);
if(listener != null)
listener.headerChange(this, headerNowH, headerMaxH, MOVING);
setViewHeight(header, headerNowH + y - lastY);
}else if((y - lastY) < 0 && !recyclerView.canScrollVertically(1)
&& recyclerView.canScrollVertically(-1) ){//如果上拉,并且不能上滑了,说明到底
((LinearLayoutManager)recyclerView.getLayoutManager()).setStackFromEnd(true);
if(listener != null)
listener.headerChange(this, footerNowH, footerMaxH, MOVING);
setViewHeight(footer, footerNowH + lastY - y);
}else{//其余情况在这里处理
if(headerNowH > 0){
if(listener != null)
listener.headerChange(this, headerNowH, headerMaxH, MOVING);
setViewHeight(header, headerNowH + y - lastY);
}else if(footerNowH > 0){
if(listener != null)
listener.headerChange(this, footerNowH, footerMaxH, MOVING);
setViewHeight(footer, footerNowH + lastY - y);
}else{
recyclerView.onTouchEvent(e);
}
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
boolean intercepted = false;
int y = (int) e.getRawY();
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if(!isAnimFinish){
intercepted = true;
}else if(REFRESH.equals(mode)){
if(!recyclerView.canScrollVertically(-1) && (y - lastY) > 0)
intercepted = true;
}else if(LOAD.equals(mode)){
if(!recyclerView.canScrollVertically(1) && (lastY - y) > 0 && recyclerView.canScrollVertically(-1))
intercepted = true;
}else if(BOTH.equals(mode)){
if(!recyclerView.canScrollVertically(-1) && (y - lastY) > 0 ||
(recyclerView.canScrollVertically(-1) && !recyclerView.canScrollVertically(1) && (lastY - y) > 0))
intercepted = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
lastY = y;
return intercepted;
}
//完成加载后finish操作
public void finish(){
if(headerNowH >0){
animClose(header, headerNowH);
}else if(footerNowH > 0){
animClose(footer, footerNowH);
}
}
public OnChangeListener getListener() {
return listener;
}
public void setListener(OnChangeListener listener) {
this.listener = listener;
}
public Interpolator getInterpolator() {
return interpolator;
}
public void setInterpolator(Interpolator interpolator) {
this.interpolator = interpolator;
}
}
xml布局文件
mode:表示滑动模式,有refreshing、loading、both,3种模式,第一种只有刷新,第二种只有加载更多,第三种两者都有。header和footer分别表示下拉刷新和上拉加载更多的资源布局id。
Activity
public class MainActivity extends AppCompatActivity {
private RecyclerView my;
private RefreshLoadLayout layout;
private TextView header, footer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
my = findViewById(R.id.my);
layout = findViewById(R.id.layout);
header = findViewById(R.id.header);
footer = findViewById(R.id.footer);
final LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
MyAdapter adapter = new MyAdapter();
my.setLayoutManager(manager);
my.setAdapter(adapter);
initListener();
}
private void initListener() {
layout.setListener(new RefreshLoadLayout.OnChangeListener() {
@Override
public void headerChange(final RefreshLoadLayout layout, int nowH, int maxH, int action) {
if(action == RefreshLoadLayout.UP){
header.setText("刷新中.......");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
header.setText("加载完成");
layout.finish();
}
}, 2000);
}
}
@Override
public void footerChange(final RefreshLoadLayout layout, int nowH, int maxH, int action) {
// Log.e("TAG", "footer "+nowH+" "+" "+action);
if(action == RefreshLoadLayout.UP){
footer.setText("加载中.......");
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
footer.setText("加载完成");
layout.finish();
}
}, 2000);
}
}
});
}
}
对于 list滚动时也可以触发刷新,即刷新与滚动可以兼容(不包括fling)这种情况,只需要继承RecycleView,或者设置OnTouchListener。
代码如下:
@Override
public boolean onTouchEvent(MotionEvent e) {
int y = (int) e.getRawY();
switch (e.getAction()){
case MotionEvent.ACTION_MOVE:
if(!canScrollVertically(-1) && (y - lastY) > 0 || !canScrollVertically(1) && (lastY - y) > 0){
Log.e("TAG", "MyRecyclerView onTouchEvent 【MOVE】 给上一级");
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
return super.onTouchEvent(e);
}
demo可以看我的github