手写RecyclerView下拉刷新

思路

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

使用

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();


    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值