美文网首页Android自定义View
自定义RecycleView下拉刷新

自定义RecycleView下拉刷新

作者: digtal_ | 来源:发表于2018-11-12 11:58 被阅读976次

    1.概述

    Android中的下拉刷新是我们很常见的的操作,一般列表页的下拉刷新都是SwipeRefreshLayout嵌套RecycleView完成,其中的原理就是运用了NestedScrolling嵌套滚动机制,具体可参考鸿洋老师的这篇文章https://blog.csdn.net/lmj623565791/article/details/52204039.

    public class SwipeRefreshLayout extends ViewGroup implements NestedScrollingParent,
            NestedScrollingChild {
    
    public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    

    从上可以看到SwipeRefreshLayout 实现了NestedScrollingParent,RecyclerView 实现了NestedScrollingChild2 ,我们用的最多的下拉刷新就是使用了这个机制.

    2.效果展示

    refresh.gif

    3.具体实现

    1. 布局
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
        <com.chinamall21.mobile.study.view.ReFreshParent
            android:id="@+id/refresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.RecyclerView
                android:id="@+id/rv"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
            </android.support.v7.widget.RecyclerView>
    
        </com.chinamall21.mobile.study.view.ReFreshParent>
    </RelativeLayout>
    

    刷新的头部局

    <?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="120dp"
                  android:background="@color/colorPrimary"
                  android:orientation="horizontal">
    
        <FrameLayout
            android:layout_gravity="center_vertical"
            android:layout_width="130dp"
            android:layout_height="wrap_content">
    
            <ImageView
                android:id="@+id/iv_arrow"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="70dp"
                android:src="@mipmap/icon_arrow"/>
    
            <ProgressBar
                android:id="@+id/pb"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="70dp"
                android:indeterminateDrawable="@drawable/progress_drawable"
                android:visibility="invisible"/>
        </FrameLayout>
    
        <LinearLayout
            android:id="@+id/ll_text"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:layout_height="wrap_content"
            android:orientation="vertical">
    
            <TextView
                android:id="@+id/tv_tip"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="下拉刷新"
                android:textColor="#fff"
                android:textSize="18sp"/>
    
            <TextView
                android:id="@+id/tv_time"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="5dp"
                android:textSize="13sp"
                android:text="第一次刷新"
                android:textColor="#fff"/>
        </LinearLayout>
    </LinearLayout>
    
    1. ReFreshParent
    public class ReFreshParent extends ViewGroup implements NestedScrollingParent {
    
        public ReFreshParent(Context context, AttributeSet attrs) {
            super(context, attrs);
            mScroller = new Scroller(context);
            View view = LayoutInflater.from(context).inflate(R.layout.rv_header, this, false);
            mProgress = view.findViewById(R.id.pb);
            mTvTip = view.findViewById(R.id.tv_tip);
            mArrow = view.findViewById(R.id.iv_arrow);
            addView(view);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            mHeader.layout(0, -mHeaderHeight, getWidth(), 0);
            mContent.layout(0, 0, getWidth(), getBottom());
        }
    

    在构造中加载出头部局,然后添加该布局,onLayout方法中将头部局摆放到列表上

    1. 滑动操作
      嵌套滑动过程中滑动事件是由子孩子传给父布局的,这里面就是由RecycleView传过来的滑动事件,在父布局中我们只重写了这二个实现方法:
      /**
         * 父View是否允许嵌套滑动
         *
         * @param child            包含嵌套滑动父类的子View
         * @param target           实现嵌套滑动的子View
         * @param nestedScrollAxes 嵌套滑动方向,水平竖直或都支持
         */
        @Override
        public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
              //当前状态不是正在刷新中当前布局就可以滑动
            return mStatus != STATUS.REFRESHING;
        }
    
      /**
         * 嵌套滑动子View滑动之前的准备工作
         *
         * @param target   实现嵌套滑动的子View
         * @param dx       水平方向上嵌套滑动的子View滑动的总距离
         * @param dy       竖直方向上嵌套滑动的子View滑动的总距离
         * @param consumed consumed[0]水平方向与consumed[1]竖直方向上父View消耗(滑动)的距离
         */
        @Override
        public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
             //上滑头部局逐渐隐藏
            boolean hiddenRefresh = dy > 0 && getScrollY() <= 0;
            //下滑头部局逐渐出现
            boolean showRefresh = dy < 0 && getScrollY() >= -mHeaderHeight && !ViewCompat.canScrollVertically(target, -1);
    
            if (getScrollY() >= -mHeaderHeight / 2) {
                mStatus = STATUS.PULLREFRESH;
            } else {
                mStatus = STATUS.RELEASEREFRESH;
            }
            refreshByStatus(mStatus);
    
            if (hiddenRefresh || showRefresh) {
                scrollBy(0, dy);
                consumed[1] = dy;
            }
        }
    
    
    • onStartNestedScroll方法中,如果当前状态不是正在刷新中就允许父布局可以可以接收滑动事件.
    • onNestedPreScroll方法中,上滑头部局逐渐隐藏或下滑头部局逐渐出现这二种状态下就调用scrollBy方法滑动父布局来隐藏或显示头部局.
      public boolean onTouch(View v, MotionEvent event) {
           if (event.getAction() == MotionEvent.ACTION_UP) {
               if (getScrollY() <= -mHeaderHeight / 2) {
               mStatus = STATUS.REFRESHING;
               refreshByStatus(mStatus);
               } else {
                 scrollTo(0, 0);
               }
           }
               return false;
       }
    
    • 在手抬起的时候根据头部局的位置来判断是否刷新
    • 根据状态来动态改变头部局的展示

    4. 父布局完整代码:

    public class ReFreshParent extends LinearLayout implements NestedScrollingParent {
        private boolean isMeasured;
        //刷新的头部局
        private View mHeader;
        //RecycleView列表页
        private RecyclerView mContent;
        //头部局的高度
        private int mHeaderHeight;
        private Scroller mScroller;
        //进度条
        private ProgressBar mProgress;
        //刷新提示
        private TextView mTvTip;
        //指示箭头
        private ImageView mArrow;
        //上次刷新的时间
        private long mLastTime;
        private TextView mTvTime;
        private ValueAnimator mUpAnim;
        private ValueAnimator mDownAnim;
    
        private enum STATUS {
            //刷新中,下拉刷新,释放刷新
            REFRESHING, PULLREFRESH, RELEASEREFRESH
        }
    
        //当前刷新状态
        private STATUS mStatus = STATUS.PULLREFRESH;
        private STATUS mLastStatus;
    
    
        public ReFreshParent(Context context) {
            this(context, null);
        }
    
        public ReFreshParent(Context context, AttributeSet attrs) {
            super(context, attrs);
            mScroller = new Scroller(context);
            View view = LayoutInflater.from(context).inflate(R.layout.rv_header, this, false);
            mProgress = view.findViewById(R.id.pb);
            mTvTip = view.findViewById(R.id.tv_tip);
            mArrow = view.findViewById(R.id.iv_arrow);
            mTvTime = view.findViewById(R.id.tv_time);
    
            addView(view);
    
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            mHeader.layout(0, -mHeaderHeight, getWidth(), 0);
            mContent.layout(0, 0, getWidth(), getBottom());
            LogUtils.LogE("getBottom =" + getBottom());
        }
    
        @Override
        public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) {
            //当前状态不是正在刷新中当前布局就可以滑动
            return mStatus != STATUS.REFRESHING;
    
        }
    
        @Override
        public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
            //上滑头部局逐渐隐藏
            boolean hiddenRefresh = dy > 0 && getScrollY() <= 0;
            //下滑头部局逐渐出现
            boolean showRefresh = dy < 0 && getScrollY() >= -mHeaderHeight && !ViewCompat.canScrollVertically(target, -1);
    
            if (getScrollY() >= -mHeaderHeight / 2) {
                mStatus = STATUS.PULLREFRESH;
            } else {
                mStatus = STATUS.RELEASEREFRESH;
            }
            refreshByStatus(mStatus);
    
            if (hiddenRefresh || showRefresh) {
                scrollBy(0, dy);
                consumed[1] = dy;
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if (!isMeasured) {
                mHeader = getChildAt(0);
                mContent = (RecyclerView) getChildAt(1);
                measureChildren(widthMeasureSpec, heightMeasureSpec);
                //获取头部局的高度
                mHeaderHeight = mHeader.getMeasuredHeight();
                isMeasured = true;
                //recycleView手抬起时的监听
                mContent.setOnTouchListener(new OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        if (event.getAction() == MotionEvent.ACTION_UP) {
    
                            if (getScrollY() <= -mHeaderHeight / 2) {
                                mStatus = STATUS.REFRESHING;
                                refreshByStatus(mStatus);
                            } else {
                                scrollTo(0, 0);
                            }
                        }
                        return false;
                    }
                });
    
                mDownAnim = ValueAnimator.ofFloat(180, 360);
                mDownAnim.setDuration(500);
                mDownAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
    
                        mArrow.setRotation((Float) animation.getAnimatedValue());
                    }
                });
    
                mUpAnim = ValueAnimator.ofFloat(0, 180);
                mUpAnim.setDuration(500);
                mUpAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        mArrow.setRotation((Float) animation.getAnimatedValue());
                    }
                });
            }
        }
    
        /**
         * 根据状态去改变
         */
        private void refreshByStatus(STATUS status) {
            if (mLastStatus != status) {
                switch (status) {
                    case PULLREFRESH:
                        if (mLastStatus != null) {            
                            mTvTip.setText("下拉刷新");
                            mDownAnim.start();
                        }
    
                        break;
                    case REFRESHING:      
                        mTvTip.setText("正在刷新");
                        mProgress.setVisibility(VISIBLE);
                        mArrow.setVisibility(INVISIBLE);
                        mLastTime = System.currentTimeMillis();
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                mProgress.setVisibility(INVISIBLE);
                                mArrow.setVisibility(VISIBLE);
                                scrollTo(0, 0);
                                mStatus = STATUS.PULLREFRESH;
                                mLastStatus = null;
                                mTvTime.setText("上次刷新时间:" + formartTime(mLastTime));
                                //刷新完毕
                                if (mRefreshCompleteListener != null)
                                    mRefreshCompleteListener.refreshed();
                            }
                        }, 1500);
                        break;
    
                    case RELEASEREFRESH:    
                        mTvTip.setText("释放刷新");
                        mUpAnim.start();
                        break;
                }
                mLastStatus = status;
            }
    
        }
    
        private String formartTime(long time) {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time));
        }
    
        @Override
        public void scrollTo(int x, int y) {
            if (y <= -mHeaderHeight) {
                y = -mHeaderHeight;
            } else if (y >= 0) {
                y = 0;
            }
            super.scrollTo(x, y);
        }
    
        @Override
        public void computeScroll() {
            if (mScroller.computeScrollOffset()) {
                scrollTo(0, mScroller.getCurrY());
                invalidate();
            }
        }
    
        public interface RefreshCompleteListener {
            void refreshed();
        }
    
        private RefreshCompleteListener mRefreshCompleteListener;
    
        public void setRefreshCompleteListener(RefreshCompleteListener refreshCompleteListener) {
            mRefreshCompleteListener = refreshCompleteListener;
        }
    }
    

    项目地址:https://github.com/digtal/recycleview-study

    相关文章

      网友评论

        本文标题:自定义RecycleView下拉刷新

        本文链接:https://www.haomeiwen.com/subject/cvboxqtx.html