阅读XRecyclerView源码一

学习目标

熟悉并掌握 XRecyclerView 实现下拉刷新的过程

效果图

概述

XRecyclerView这个开源项目,可以帮我们轻松的实现 RecyclerView 下拉刷新和上拉加载更多的功能,而且使用步骤和普通的 RecyclerView 一样,而且配合了 AVLoadingIndicatorView 可以方便的实现多种加载动画效果。

接下来试着拆下这个轮子,熟悉和学习它的实现过程。可以先从自己关注的功能来分步骤的阅读源码。这篇文章我是先从 RecyclerView 的下拉刷新功能实现开始阅读和说明。

源码解析

示例图1

如上图所示,下拉刷新最重要的类是 ArrowRefreshHeader ,图中还有一个 SimpleViewSwitcher 类,它主要就是通过对外暴露的 setView() 方法将 AVLoadingIndicatorView 控件设置进去,如果对 AVLoadingIndicatorView 的动画样式进行改变,设置好后可以重新再调用这个方法进行覆盖之前的设置。

不过在 SimpleViewSwitcher 类中有一点就是我在仿照源码在 onMeasure() 方法里面对子 view 进行测量,然后重新根据子 view 的测量结果设置宽高的时候,发现 AVLoadingIndicatorView 这个控件的 大小总是就那么点大,在布局文件 listview_header 中再怎么改变它的大小都没效果,后来我还是用了系统默认的大小作为容器的宽高。

// 测量子 View 大小,设置 ViewGroup 宽高
    // 里面就放了一个 AVLoadingIndicatorView 的子 View
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

//        int childCount = this.getChildCount();
//        int maxHeight = 0;
//        int maxWidth = 0;
//        for (int i = 0; i < childCount; i++) {
//            View child = this.getChildAt(i);
//            this.measureChild(child,widthMeasureSpec,heightMeasureSpec);
//
//            maxHeight = child.getMeasuredHeight();
//            maxWidth = child.getMeasuredWidth();
//        }
        setMeasuredDimension(widthMeasureSpec,heightMeasureSpec);
    }

上图中 ArrowRefreshHeader 类中只是列出了几个重要的方法。下面会重点说下这几个方法。

在初始化中,注意设置下拉刷新布局的高度为 0

// 初始情况,设置下拉刷新view高度为0
        mContainer = (LinearLayout) LayoutInflater.from(getContext()).inflate(
                R.layout.listview_header, null);
        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        lp.setMargins(0, 0, 0, 0);
        this.setLayoutParams(lp);
        this.setPadding(0, 0, 0, 0);

        addView(mContainer, new LayoutParams(LayoutParams.MATCH_PARENT, 0));
        setGravity(Gravity.BOTTOM);

初始化 AVLoadingIndicatorView 控件,并加入 SimpleViewSwitcher 容器中

 //init the progress view
        mProgressBar = (SimpleViewSwitcher)findViewById(R.id.listview_header_progressbar);
        AVLoadingIndicatorView progressView = new  AVLoadingIndicatorView(getContext());
        progressView.setIndicatorColor(0xffB5B5B5);
        progressView.setIndicatorId(ProgressStyle.BallSpinFadeLoader);
        mProgressBar.setView(progressView);

setState() 方法

setState(int state) 方法中,根据设置的状态 state 与之前的状态 mState 对比,进行下一步操作。

if (state == mState) return ;

        if (state == STATE_REFRESHING) {    // 显示进度
            mArrowImageView.clearAnimation();
            mArrowImageView.setVisibility(View.INVISIBLE);
            mProgressBar.setVisibility(View.VISIBLE);
            smoothScrollTo(mMeasuredHeight);
        } else if(state == STATE_DONE) {
            mArrowImageView.setVisibility(View.INVISIBLE);
            mProgressBar.setVisibility(View.INVISIBLE);
        } else {    // 显示箭头图片
            mArrowImageView.setVisibility(View.VISIBLE);
            mProgressBar.setVisibility(View.INVISIBLE);
        }

        switch(state){
            case STATE_NORMAL:
                if (mState == STATE_RELEASE_TO_REFRESH) {
                    mArrowImageView.startAnimation(mRotateDownAnim);
                }
                if (mState == STATE_REFRESHING) {
                    mArrowImageView.clearAnimation();
                }
                mStatusTextView.setText(R.string.listview_header_hint_normal);
                break;
            case STATE_RELEASE_TO_REFRESH:
                if (mState != STATE_RELEASE_TO_REFRESH) {
                    mArrowImageView.clearAnimation();
                    mArrowImageView.startAnimation(mRotateUpAnim);
                    mStatusTextView.setText(R.string.listview_header_hint_release);
                }
                break;
            case     STATE_REFRESHING:
                mStatusTextView.setText(R.string.refreshing);
                break;
            case    STATE_DONE:
                mStatusTextView.setText(R.string.refresh_done);
                break;
            default:
        }

        mState = state;

onMove() 方法

通过 onMove(float delta) 方法,根据当前显示的下拉刷新 view 显示的高度 getVisibleHeight 和传入的手指滑动的距离 delta (乘以 DRAG_RATE 滑动速率后的值) ,设置下拉刷新 view 的显示高度和显示状态

 @Override
    public void onMove(float delta) {
        if(getVisibleHeight() > 0 || delta > 0) {
            setVisibleHeight((int) delta + getVisibleHeight());
            if (mState <= STATE_RELEASE_TO_REFRESH) { // 未处于刷新状态,更新箭头
                if (getVisibleHeight() > mMeasuredHeight) {
                    setState(STATE_RELEASE_TO_REFRESH);
                }else {
                    setState(STATE_NORMAL);
                }
            }
        }
    }

releaseAction() 方法

releaseAction() 方法中,同样根据当前显示的下拉刷新 view 显示的高度,设置刷新状态或下拉刷新 view 的慢慢出现或消失的效果

@Override
    public boolean releaseAction() {
        boolean isOnRefresh = false;
        int height = getVisibleHeight();
        if (height == 0) // not visible.
            isOnRefresh = false;

        if(getVisibleHeight() > mMeasuredHeight &&  mState < STATE_REFRESHING){
            setState(STATE_REFRESHING);
            isOnRefresh = true;
        }
        // refreshing and header isn't shown fully. do nothing.
        if (mState == STATE_REFRESHING && height <=  mMeasuredHeight) {
            //return;
        }
        if (mState != STATE_REFRESHING) {
            smoothScrollTo(0); // view 慢慢消失
        }

        if (mState == STATE_REFRESHING) {
            int destHeight = mMeasuredHeight;
            smoothScrollTo(destHeight); // view 慢慢回到容器固定的高度
        }

        return isOnRefresh;
    }

这里面“慢慢”动画效果的实现是

private void smoothScrollTo(int destHeight) {
        ValueAnimator animator = ValueAnimator.ofInt(getVisibleHeight(), destHeight);
        animator.setDuration(300).start();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation)
            {
                setVisibleHeight((int) animation.getAnimatedValue());
            }
        });
        animator.start();
    }

关键的 XRecyclerView

示例图2

因为 XRecyclerView 支持添加多个自定义 header view,这里使用两个集合来分别存储自定义的 header view 和 对于的 viewType,对外暴露一个添加的方法

 // 对外暴露添加头布局的方法
    public void addHeaderView(View view) {
        mHeaderTypes.add(HEADER_INIT_INDEX + mHeaderViews.size()); // 从初始位置 10002 开始
        mHeaderViews.add(view);
        if (mWrapAdapter != null) {
            mWrapAdapter.notifyDataSetChanged(); // // 通知观察者 adapter 的数据发生变化
        }
    }

header view 添加进集合以后,我们需要在 adapter 中进行显示设置。

WrapAdapter

首先我们要把传入的 adapter 进行封装,所以

 // 定义 recyclerview 的 adapter
        private RecyclerView.Adapter adapter;

        // 构造方法传入的仍然是 recyclerview 的 adpater
        public WrapAdapter(Adapter adapter) {
            this.adapter = adapter;
        }

        // 获得封装前的 recyclerview 的 adapter
        public RecyclerView.Adapter getOriginalAdapter() {return this.adapter;}

因为这篇文章主要是介绍下拉刷新功能的实现,所以

// 根据 position 判断当前 item 样式
        public boolean isHeader(int position) {
            return position >= 1 && position < mHeaderViews.size()  + 1;
        }
        public boolean isRefreshHeader(int position) {
            return position == 0;
        }

        // 获取头布局数量
        public int getHeadersCount() {return mHeaderViews.size();}

getItemViewType() 方法中

// 不同 position 设置 itemType
        @Override
        public int getItemViewType(int position) {
            int dataPosition = position - (getHeadersCount() + 1);
            if (isRefreshHeader(position)) {
                return TYPE_REFRESH_HEADER;
            }
            if (isHeader(position)) {
                position = position - 1;
                return mHeaderTypes.get(position);
            }
            int adapterCount;
            if (adapter != null) {
                adapterCount = adapter.getItemCount();
                if(dataPosition < adapterCount) {
                    int type = adapter.getItemViewType(dataPosition);
                    if (isReservedItemViewType(type)) {
                        throw new IllegalStateException("XRecyclerView require itemViewType in adapter should be less than 10000 " );
                    }
                    return type;
                 }
            }
            return 0;
        }

RecyclerView 配有LinerLayoutManagerGridLayoutManagerStaggeredGridLayoutManager,所以还要考虑到 item 的跨度问题

// 判断 LayoutManager 类型,对 item 跨度进行设置
        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
            final RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
            if (manager instanceof GridLayoutManager) {
                ((GridLayoutManager) manager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        return (isRefreshHeader(position) || isHeader(position)) ?
                                ((GridLayoutManager) manager).getSpanCount() : 1;
                    }
                });
            }
            adapter.onAttachedToRecyclerView(recyclerView);
        }

还重写了一堆方法,主要是把方法的调用者都用 RecyclerView 的 adapter,而不是封装的 mWrapAdapter,而在外面操作的都是 mWrapAdapter。为了这层封装替换的东西挺多啊。

我们对传入的 adapter 进行封装

// 对传入的 adapter 进行封装
    @Override
    public void setAdapter(Adapter adapter) {
        mWrapAdapter = new WrapAdapter(adapter); // 进行封装
        super.setAdapter(mWrapAdapter); // 设置封装的 adapter
        adapter.registerAdapterDataObserver(mDataObserver); // 注册新的封装的 adapter 的数据观察者,可具体操作用的 mWrapAdapter
        mDataObserver.onChanged(); // 通知 adapter 变化
    }

    // 避免用户自己调用 getAdapter() 获得的是 WrapAdapter,引起 ClassCastException
    @Override
    public Adapter getAdapter() {
        if (mWrapAdapter != null) {
            return mWrapAdapter.getOriginalAdapter(); // 返回封装前的 adapter
        } else {
            return null;
        }
    }

处理下拉刷新的功能主要是要重写 onTouchEvenet() 方法,处理在手指滑动状态改变时的相关操作。

// 处理下拉刷新
    // 涉及到滑动需要重写 onTouchEvenet() 方法,特别是针对 MotionEvent.ACTION_MOVE 处理
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        if (mLastY == -1) { // 初始状态,没被赋值,也就是滑动起始
            mLastY = e.getRawY(); // 该方法是获取点击事件相对整个屏幕顶边的 y 轴坐标,即点击事件距离整个屏幕顶边的距离
        }
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastY = e.getRawY(); //
                break;
            case MotionEvent.ACTION_MOVE:
                float deltaY = e.getRawY() - mLastY; // 差值即为手指滑动的距离
                mLastY = e.getRawY(); // mLastY 值随着手指的变化动态变化
                if (isOnTop()) {
                    mRefreshHeader.onMove(deltaY / DRAG_RATE); // 下拉刷新头布局随手指上下移动而出现/消失
                    if (mRefreshHeader.getVisibleHeight() > 0 && // 判断下拉刷新头布局可见高度,状态在正在刷新之前
                            mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
                        return false;
                    }
                }
                break;
            default: // 没有按下,没有滑动,手放开
                mLastY = -1; // reset
                if (isOnTop()) {
                    if (mRefreshHeader.releaseAction()) { // 判断下拉刷新头布局可见高度是否到了显示刷新的高度
                        if (mLoadingListener != null) {
                            mLoadingListener.onRefresh(); // 调用刷新动作
                        }
                    }
                }
                break;
        }
        return super.onTouchEvent(e);
    }

DataObserver
DataObserver 继承 RecyclerView.AdapterDataObserver,重写 onChanged() 方法,在该内部类里的所有方法的调用都使用封装的 mWrapAdapter 进行调用。

@Override
        public void onChanged() {
            super.onChanged();
            if (mWrapAdapter != null) {
                mWrapAdapter.notifyDataSetChanged();
            }
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
//            super.onItemRangeChanged(positionStart, itemCount);
            mWrapAdapter.notifyItemRangeChanged(positionStart,itemCount);
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
//            super.onItemRangeChanged(positionStart, itemCount, payload);
            mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
//            super.onItemRangeInserted(positionStart, itemCount);
            mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
//            super.onItemRangeRemoved(positionStart, itemCount);
            mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
//            super.onItemRangeMoved(fromPosition, toPosition, itemCount);
            mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
        }

总结

通过学习 XRecyclerView ,我们知道 它的下拉刷新通过配合 AVLoadingIndicatorView ,自定义 ViewGroup,处理下拉刷新布局不同状态的具体变化。在自定义的 XRecyclerView 中通过两个集合存储要添加的头布局和对应的 itemType,对传入的 adapter 中进行二次封装,在封装的 WrapAdapter 中重写 onAttachedToRecyclerView() 针对 RecyclerView 的 GridLayoutManager 进行 item 跨度的设置,防止下拉刷新布局和头布局出现变形。调用 onTouchEvenet() 方法获得手指滑动的距离,设置 ArrowRefreshHeader 的显示和状态设置。

Github例子源码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值