TRecyclerView-实现RecycleView的下拉更新,上滑加载

1. 前言
SwipeRefreshLayout是google出的一个支持下拉更新,上滑加载的控件,有如下两个问题:
1)上滑加载数据需要我们自己实现;
2)loading动画会随着手势向下移动,其实好多情况下我们可能需要固定顶部的loading动画。

针对上面第一个问题,SuperSwipeRefreshLayout是基于SwipeRefreshLayout,实现了上滑的数据加载部分,并对不同的控件,如ListView,RecyclerView等做了很好的封装,项目github地址: SuperSwipeRefreshLayout-Demo

SuperSwipeRefreshLayout里面没有设置loading动画是否随手势移动的API,因此没有解决第二个问题。TRecyclerView为解决上面两个问题,处理了上滑数据加载的逻辑,同时固定loading动画在顶部位置,下面主要讲讲TRecyclerView的具体实现方式。

2. TRecyclerView
1) TRecyclerView 的结构
TRecycleView是一个FrameLayout主要包括两部分,Header View和RecycleView,而RecycleView的View类型大体分为两部分:Normal View和Footer View。

TRecyclerView中有一个TRecyclerAdapter,是用来加载RecyclerView的Item View,是TRecyclerView中真正加载数据的Adapter,其中包括两大类的数据类型,即正常的Normal View和Header View,Normal View是通过RecyclerView.Adapter来加载,就是我们需要写的Adapter。

TRecyclerView的结构图

2)TRecyclerView的初始化
下面结合TRecyclerView的结构图,我们看看具体的代码实现,首先是TRecycleView的构造方法:

public TRecyclerView(Context context) {
        super(context);
        init(context);
    }

private void init(Context ctx) {
        mCtx = ctx;
        mTouchSlop = ViewConfiguration.get(mCtx).getScaledTouchSlop();
        initView();
    }

    private void initView() {
          //创建一个Header View
        createHeaderView();
        addTargetView();
        linearLayoutManager = new LinearLayoutManager(mCtx);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mRecyclerView.setVerticalScrollBarEnabled(true);
        initListener();

    }

    //Header View是一个RelativeLayout,同时往Header View里面添加一个自己的Header,我们可以自定义样式
    private void createHeaderView() {
        mHeaderContainer = new RelativeLayout(mCtx);
        mHeaderHeight = (int)mCtx.getResources().getDimension(R.dimen.header_height);
        mTipHeight =  (int)mCtx.getResources().getDimension(R.dimen.header_tip_height);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mHeaderHeight);
        params.gravity = Gravity.TOP;
        addView(mHeaderContainer, params);
        setHeaderView();
    }

    //设置自己的Header
    private void setHeaderView(){
        mHeaderHolder = new HeaderHolder(mCtx);
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, mHeaderHeight);
        mHeaderContainer.addView(mHeaderHolder.getHeaderView(), params);
        mHeaderHolder.setAnimListener(mAnimListener);

    }

   //添加一个RecycleView,因为TRecycleView是一个FrameLayout,因此,RecycleView是处在在Header View的上面
    private  void addTargetView(){
        mRecyclerView = new RecyclerView(mCtx);
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        addView(mRecyclerView, params);
    }

3)TRecyclerAdapter的实现

我们知道TRecyclerView中真正加载数据的Adapter是TRecyclerAdapter,我们看看TRecyclerView设置RecyclerView.Adapter的API,代码如下:

  public void setAdapter(RecyclerView.Adapter adapter){
        adapter.registerAdapterDataObserver(mDataObserver);
        mTAdapter = new TRecyclerAdapter(mCtx, adapter);
        mRecyclerView.setAdapter(mTAdapter);

    }

我们给RecyclerView.Adapter注册了一个观察者,调用RecyclerView.Adapter的数据更新方法时,会通知TRecyclerAdapter去更新数据数据,代码如下:

private RecyclerView.AdapterDataObserver mDataObserver = new RecyclerView.AdapterDataObserver() {
        @Override
        public void onChanged() {
            mTAdapter.notifyDataSetChanged();
        }


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

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

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

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

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

再看看TRecyclerAdapter的onCreateViewHolderonBindViewHolder方法的实现。

onCreateViewHolder方法:

public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return buildHolder(parent, viewType);
    }


private RecyclerView.ViewHolder buildHolder(ViewGroup parent, int viewType) {
        RecyclerView.ViewHolder holder = null;
        switch (viewType) {
            case ITEM_TYPE_FOOTER:
                //Footer View的类型
                holder = new BaseViewHolder(mFooterHolder.getFooterView());
                break;
            default:
              //Normal View 的类型
                holder = mAdapter.onCreateViewHolder(parent, viewType);
                break;
        }
        return holder;
    }


@Override
    public int getItemViewType(int position) {
        if (isFooter(position)) {
            //底部View
            return ITEM_TYPE_FOOTER;
        } else {
            return mAdapter.getItemViewType(position);
        }
    }

onBindViewHolder方法:


//如果是Footer View类型,则直接返回,否则调用mAdapter的onBindViewHolder方法
@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (isFooter(position)) {
            return;
        }
        initData(holder, position);
    }


   private void initData(RecyclerView.ViewHolder holder, final int position) {
        final int type = getItemViewType(position);
        if (type != ITEM_TYPE_FOOTER) {
            mAdapter.onBindViewHolder(holder, position);
        }

    }

4)TRecyclerView下拉更新数据

TRecycleView下拉超过一定的高度,这个高度是Header View的高度,松开手后开始加载数据,下面看看TRecyclerView的onInterceptTouchEvent方法和onTouchEvent方法。

onInterceptTouchEvent(MotionEvent ev) 方法:

ublic boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
         //如果RecyclerView能够向下或者向上滚动,则不拦截事件,事件交给RecyclerView处理
        if(isUnIntercept()){
            return  false;
        }
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mIsDrag = false;
                mInitY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float y = ev.getY();
                //如果TRecyclerView下拉超过一定距离,则认为TRecyclerView是在被拖拽,同时设置mIsDrag标志为true
                if(y-mInitY >= mTouchSlop && !mIsDrag){
                    mIsDrag = true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIsDrag = false;
                break;
            default:
                break;
        }
        return mIsDrag;
    }

onTouchEvent(MotionEvent event) 方法:

public boolean onTouchEvent(MotionEvent event) {
       if(isUnIntercept()){
           return false;
       }

       float dist = 0f;
       switch (event.getAction()) {
           case MotionEvent.ACTION_DOWN:
               mIsDrag = false;
               break;
           case MotionEvent.ACTION_MOVE:
               float y = event.getY();
               dist = (y - mInitY)* TRecycleViewConst.PULL_DRAG_RATE;
               if(dist > 0){
                   //下拉移动TRecyclerView
                   mRecyclerView.setTranslationY(getY() + dist);
               }
               if(mIsDrag){
                   //下拉距离超过Header View的高度,则松手后可以刷新数据
                   if(mPullRefresh != null){
                       mPullRefresh.pullRefreshEnable(dist >= mHeaderHeight);
                   }
               }
               break;
           case MotionEvent.ACTION_UP:
           case MotionEvent.ACTION_CANCEL:
               dist = (event.getY() - mInitY) * TRecycleViewConst.PULL_DRAG_RATE;
               if(mIsDrag){
               //松手后,如果下拉距离超过Header View的高度,则执行回弹到HeaderView位置的动画,动画结束后,调用刷新数据的方法;否则执行回弹到初始位置的动画
                    if(dist >= mHeaderHeight){
                        animToHeader();
                    }else{
                        animToStart();
                    }
               }
               mIsDrag = false;
               break;
       }
       return true;
   }

animToHeader方法:

private void animToHeader(){
        ObjectAnimator animator = ObjectAnimator.ofFloat(mRecyclerView,"translationY",  mHeaderHeight);
        animator.addListener(mToHeaderListener);
        animator.setDuration(AnimDurConst.ANIM_TO_HEADER_DUR);
        animator.start();

    }



private Animator.AnimatorListener mToHeaderListener = new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            //回弹动画结束后,开始调用pullRefresh的回调,在这里面可以加载网络数据
            if(mPullRefresh != null){
                mRefresh = true;
                mPullRefresh.pullRefresh();
            }
        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    };

5)TRecyclerView上滑加载数据
上面讲了TRecyclerView下拉更新数据的过程,下面讲讲TRecyclerView上滑加载数据的实现。

看上面的结构图,我们知道Footer View并不是直接作为TRecyclerView的一个View,而是RecyclerView的一个Item View,因此,当RecyclerView上滑到最后一个Item View,即Footer View可见时,我们可以处理上滑加载数据的逻辑,代码的实现如下:

private void initListener(){
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                //如果RecyclerView的Scroll State是IDLE,我们判断下RecyclerView是否已经滑动到底部,如果是则执行loadMore方法回调
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    if (targetInBottom()) {
                      if(mPushRefresh != null){
                          mLoadMore = true;
                          mPushRefresh.loadMore();
                      }
                    }
                }

            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
    }

//滑动到底部,且最后一个元素可见,则认为到达底部
private boolean targetInBottom() {
        if (targetInTop()) {
            return false;
        }
        RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        int count = mRecyclerView.getAdapter().getItemCount();
        if (layoutManager instanceof LinearLayoutManager && count > 0) {
            LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
            if (linearLayoutManager.findLastVisibleItemPosition() == count - 1) {
                return true;
            }
        } 
        return false;
    }

3. TRecyclerView实例

我们自个写了一个NewsRecyclerAdapter,通过TRecyclerView实行了数据的下拉更新,上滑加载的逻辑。

下拉更新数据:
pull_down_refresh_data

上滑加载数据:

slide_up_to_refresh_data

TRecyclerView项目地址TRecyclerView

4. 总结

TRecyclerView还存如下一些问题,后续会优化。

1)TRecyclerView中的RecyclerView没有滚动条,这是因为我们直接new RecyclerView,RecyclerView的一些初始化方法没有执行到。

解法方法:RecyclerView通过inflate的方式去加载一个xml文件。

2)TRecyclerView在下拉加载数据的时候,可以再次下拉刷新数据,这个后续会优化。

3)TRecyclerView有一些业务逻辑的耦合,如tips的显示动画。

TRecyclerView项目地址TRecyclerView

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值