双层嵌套recyclerview的整屏滑动

recyclerview 的 scrollstate 有三种 0 ->  idle    1  -> dragging    2 -> setting
recyclerview 的原始滚动状态变化是
1.拖动松手:  0 -> 1 -> 0
2.滑动松手: 0 -> 1 -> 2 -> 0

要处理 recyclerview 嵌套 recyclerview 的整屏滚动并且关联页码的更新,采取操作:松手时执行 smoothScrollToPosition 方法
即在 onScrollStateChanged 方法中监听:

A. 0 -> 1 说明用户的手触碰到了屏幕,执行以下计算
   int position = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
   preX = rv.getChildAt(0).getX() - position * SCREEN_WIDTH;   -> rv 滑动的初始值

B. 1 -> !1 说明用户松手了,执行以下计算
   int position = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
   float x = rv.getChildAt(0).getX() - position * SCREEN_WIDTH; -> rv 滑动的当前值

    float scrolledX = x - preX; -> 此次滑动距离
    int newPosition = -10; -> newPosition 代表应该把屏幕定位到哪一页
    if(scrolledX < 0){ -> 方向向右
        if(scrolledX <= 0 - scrollDistance2NextPage){-> scrollDistance2NextPage 滑动多远后即视为要进入下一页
           newPosition = position + 1;
        }else {
           newPosition = position;
        }
    }else if(scrolledX > 0){ -> 方向向左
        if(scrolledX < scrollDistance2NextPage){
           newPosition = position + 1;
        }else {
           newPosition = position;
        }
    }

    if(newPosition != -10){
       rv.smoothScrollToPosition(newPosition);
    }

   if(preState != 0 && newState == 0){ -> 滑动结束后更新页码
       updatePageIndex(newPosition);
   }

执行A和B两步即可实现整屏滚动。
然而 updatePageIndex 的操作会在滑动结束后再次触发滑动(原因待查),因此改成以下模式(计算出要滑动到哪一页时就把页码更新)
    if(newPosition != -10){
       rv.smoothScrollToPosition(newPosition);
       updatePageIndex(newPosition); -> 更新页码
    }
至此整屏滑动的触摸滑动解决。然而还有遥控器的整屏滑动。
给 内层 recyclerview 的 item 设置 onkeylistener,onKey 逻辑如下

        int innerItemPosition = (int) v.getTag(); -> 内层 recyclerview item 的 tag 是它在父 recyclerview 的 position
        if(event.getKeyCode() == KeyEvent.KEYCODE_PAGE_UP){
            prePage(innerItemPosition);
            return true;
        }else if(event.getKeyCode() == KeyEvent.KEYCODE_PAGE_DOWN){
            nextPage(innerItemPosition);
            return true;
        }else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT){
            boolean atLeft = innerItemPosition % SceneInfo.COL == 0;
            if(atLeft){
            int rowIndex = innerItemPosition / SceneInfo.COL;
            prePage(rowIndex * SceneInfo.COL + SceneInfo.COL - 1);
            return true;
        }else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT){
            boolean atRight = (innerItemPosition + 1) % SceneInfo.COL == 0;
            if(atRight){
            int rowIndex = innerItemPosition / SceneInfo.COL;
            nextPage(rowIndex * SceneInfo.COL);
            return true;
        }
然后再 prePage 和 nextPage 方法中执行如下逻辑,即可实现遥控器的整屏滑动
   rv.smoothScrollToPosition(position + 1);
   updatePageIndex(position + 1);

再次然而,使用遥控器按以上逻辑翻页后,下一页的焦点不尽如人意,还需要专门调整一下焦点:在 onScrollStateChanged 方法中执行如下逻辑
    if(changingPageByKey && state == 2 && newState == 0){-> 遥控器翻页结束时
        changingPageByKey = false;
        requestFocusAfterChangePage(); -> 在此方法中为目标item请求焦点
    }

这里有个很严重的问题是:smoothScrollToPosition 结束后,
recyclerview.getChildAt(0) 与 ((LinearLayoutManager)recyclerview.getAdapter()).findFirstVisibleItemPosition()代表的子 recyclerview 不是同一个!!!
所以在 requestFocusAfterChangePage 方法中这样查找子 recyclerview :
    int aimPosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
    int childCount = rv.getChildCount();
    RecyclerView itemRv = null;
    for (int i = 0; i < childCount; i++) {
        Object obj = rv.getChildAt(i).getTag(); -> 外层 recyclerview 的 adapter 给 子recyclerview绑定的tag
        if(obj instanceof Integer){
            if(aimPosition == (int)obj){
               itemRv = (RecyclerView) rv.getChildAt(i);
            }
        }
    }

完整代码如下:



    @BindView(R.id.rv)
    RecyclerView rv;

    @BindView(R.id.pageIndexTv)
    TextView pageIndexTv;

    private long lastKeyDownTime = 0;
    private final long keyGapTime = 100;//间隔100毫秒响应按键
    private boolean changingPageByKey = false;
    private int nextItemPosition = 0;
    private final int scrollDistance2NextPage = 50;

    public void updatePageIndex(int page) {
        curPage = page;
        int totalPage = getTotalPage();
        if(totalPage <= 0){
            pageIndexTv.setVisibility(View.GONE);
        }else {
            pageIndexTv.setVisibility(View.VISIBLE);
            CommonUtil.loadPageIndexText(pageIndexTv, curPage + 1, totalPage);
        }
    }

    @Override
    protected void initEventAndData() {
        curClass = getArguments().getInt("curClass");
        manage = getArguments().getBoolean("manage");

        refreshData();
        initRv();

        updatePageIndex(0);
    }

    private int getTotalPage(){
        int totalPage = (rawDataHolder.size() - 1) / SceneInfo.PAGE_SIZE + 1;
        if(rawDataHolder.size() == 0){
            totalPage = 0;
        }
        return totalPage;
    }

    private void refreshData(){
        ...
    }

    private void initRv() {
        LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
        layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        rv.setLayoutManager(layoutManager);
        ...
        rv.setAdapter(new CrossRvAdapter(getContext(), rawDataHolder, manage, new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {

                if(event.getAction() == KeyEvent.ACTION_DOWN){

                    long now = System.currentTimeMillis();
                    if(now - lastKeyDownTime < keyGapTime){
                        return true;
                    }
                    lastKeyDownTime = now;

                    int innerItemPosition = (int) v.getTag();
                    if(event.getKeyCode() == KeyEvent.KEYCODE_PAGE_UP){
                        prePage(innerItemPosition);
                        return true;
                    }else if(event.getKeyCode() == KeyEvent.KEYCODE_PAGE_DOWN){
                        nextPage(innerItemPosition);
                        return true;
                    }else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT){
                        boolean atLeft = innerItemPosition % SceneInfo.COL == 0;
                        if(atLeft){
                            int rowIndex = innerItemPosition / SceneInfo.COL;
                            prePage(rowIndex * SceneInfo.COL + SceneInfo.COL - 1);
                            return true;
                        }
                    }else if(event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT){
                        boolean atRight = (innerItemPosition + 1) % SceneInfo.COL == 0;
                        if(atRight){
                            int rowIndex = innerItemPosition / SceneInfo.COL;
                            nextPage(rowIndex * SceneInfo.COL);
                            return true;
                        }
                    }
                }

                return false;
            }
        }));
        rv.addOnScrollListener( new CrossRvScrollListener());
    }

    private void prePage(int nextInnerItemPosition){

        if(changingPageByKey){//处于遥控器翻页状态中时不处理
            return;
        }

        int position = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
        if(position == 0){
            return;
        }

        changingPageByKey = true;
        nextItemPosition = nextInnerItemPosition;
        rv.smoothScrollToPosition(position - 1);
        updatePageIndex(position - 1);

    }

    private void nextPage(int nextInnerItemPosition){

        if(changingPageByKey){//处于遥控器翻页状态中时不处理
            return;
        }

        int position = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
        if(position == rv.getAdapter().getItemCount() - 1){
            return;
        }

        changingPageByKey = true;
        nextItemPosition = nextInnerItemPosition;
        rv.smoothScrollToPosition(position + 1);
        updatePageIndex(position + 1);

    }

    private void requestFocusAfterChangePage(){
        int nextInnerItemPosition = nextItemPosition;

        if(rv == null || rv.getChildCount() == 0){
            return;
        }

        int aimPosition = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
        int childCount = rv.getChildCount();
        RecyclerView itemRv = null;
        for (int i = 0; i < childCount; i++) {
            Object obj = rv.getChildAt(i).getTag();
            if(obj instanceof Integer){
                if(aimPosition == (int)obj){
                    itemRv = (RecyclerView) rv.getChildAt(i);
                }
            }
        }
        if(itemRv == null){
            return;
        }

        int itemRvChild = itemRv.getChildCount();
        if(itemRvChild == 0){
            return;
        }

        if(itemRvChild > nextInnerItemPosition){
            itemRv.getChildAt(nextInnerItemPosition).requestFocus();
        }else{
            itemRv.getChildAt(itemRvChild - 1).requestFocus();
        }
    }

    class CrossRvScrollListener extends RecyclerView.OnScrollListener {

        int preScrollState = 0;
        float preX = 0;
        final int SCREEN_WIDTH = 1280;

        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);

            int state = preScrollState;
            preScrollState = newState;

            if(newState == RecyclerView.SCROLL_STATE_DRAGGING){
                changingPageByKey = false;
            }

            if(state == RecyclerView.SCROLL_STATE_IDLE && newState == RecyclerView.SCROLL_STATE_DRAGGING){
                int position = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
                preX = rv.getChildAt(0).getX() - position * SCREEN_WIDTH;
            }else if(state == RecyclerView.SCROLL_STATE_DRAGGING && newState != RecyclerView.SCROLL_STATE_DRAGGING){
                int position = ((LinearLayoutManager)rv.getLayoutManager()).findFirstVisibleItemPosition();
                float x = rv.getChildAt(0).getX() - position * SCREEN_WIDTH;

                float scrolledX = x - preX;
                int newPosition = -10;
                if(scrolledX < 0){//向右
                    if(scrolledX <= 0 - scrollDistance2NextPage){
                        newPosition = position + 1;
                    }else {
                        newPosition = position;
                    }
                }if(scrolledX > 0){//向左
                    if(scrolledX < scrollDistance2NextPage){
                        newPosition = position + 1;
                    }else {
                        newPosition = position;
                    }
                }

                if(newPosition != -10){
                    rv.smoothScrollToPosition(newPosition);
                    updatePageIndex(newPosition);
                }

            }

            if(changingPageByKey && state == RecyclerView.SCROLL_STATE_SETTLING && newState == RecyclerView.SCROLL_STATE_IDLE){
                changingPageByKey = false;
                requestFocusAfterChangePage();
            }

        }

    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值