热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android实现RecyclerView下拉刷新效果

这篇文章主要为大家详细介绍了Android实现RecyclerView下拉刷新效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文为大家分享了Android实现RecyclerView下拉刷新效果的具体代码,供大家参考,具体内容如下

思路

  • RealPullRefreshView继承了一个LinearLayout
  • 里面放置了一个刷新头布局,将其margin_top设置为负的刷新头的高度的
  • 再添加一个RecyclerView
  • 触摸事件分发机制,当在特定条件下让RealPullRefreshView拦截触摸事件,否则的话,不拦截,让RecyclerView自己去处理触摸事件
  • 在手指下拉时,定义好不同的状态STATE,在不同状态下,处理不同的显示,这里讲不同状态下的刷新头如何显示,抽象为一个接口,用户可以实现这个接口,自定义刷新头的布局和动画
  • 加载更多的功能是利用RecyclerView的多type布局实现的
  • 难点在于触摸事件的拦截,和认真处理各种滑动的问题

使用

xml

 


这是headerview

<&#63;xml version="1.0" encoding="utf-8"&#63;>



  
  



代码

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 more=new ArrayList();
        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();


  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 本文介绍如何使用布局文件在Android应用中排列多行TextView和Button,使其占据屏幕的特定比例,并提供示例代码以帮助理解和实现。 ... [详细]
  • 国内BI工具迎战国际巨头Tableau,稳步崛起
    尽管商业智能(BI)工具在中国的普及程度尚不及国际市场,但近年来,随着本土企业的持续创新和市场推广,国内主流BI工具正逐渐崭露头角。面对国际品牌如Tableau的强大竞争,国内BI工具通过不断优化产品和技术,赢得了越来越多用户的认可。 ... [详细]
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
  • 将Web服务部署到Tomcat
    本文介绍了如何在JDeveloper 12c中创建一个Java项目,并将其打包为Web服务,然后部署到Tomcat服务器。内容涵盖从项目创建、编写Web服务代码、配置相关XML文件到最终的本地部署和验证。 ... [详细]
  • XNA 3.0 游戏编程:从 XML 文件加载数据
    本文介绍如何在 XNA 3.0 游戏项目中从 XML 文件加载数据。我们将探讨如何将 XML 数据序列化为二进制文件,并通过内容管道加载到游戏中。此外,还会涉及自定义类型读取器和写入器的实现。 ... [详细]
  • 本文介绍如何在 Unity 的 XML 配置文件中,将参数传递给自定义生命周期管理器的构造函数。我们将详细探讨 CustomLifetimeManager 类的实现及其配置方法。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.xmlbeans.SchemaType 类的 getBaseEnumType() 方法,提供了多个代码示例,并解释了其在不同场景下的使用方法。 ... [详细]
  • 本文详细介绍了如何解决MyBatis中常见的BindingException错误,提供了多种排查和修复方法,确保Mapper接口与XML文件的正确配置。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 本文探讨了在通过 API 端点调用时,使用猫鼬(Mongoose)的 findOne 方法总是返回 null 的问题,并提供了详细的解决方案和建议。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
author-avatar
mobiledu2502858787
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有