RecyclerView在打开键盘时无法滚动到末尾

66

我在我的应用程序中使用RecyclerView,每当新元素添加到RecyclerView中时,它会使用滚动到最后一个元素的方式。

recyclerView.scrollToPosition(adapter.getCount());

但是,每当键盘打开(因为editTextView),它会调整视图大小,使得recyclerView变小,但无法滚动到最后一个元素。

android:windowSoftInputMode="adjustResize"

我甚至尝试使用下面的代码滚动到最后一个元素,但它没有起作用。

editTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if(hasFocus){
                recyclerView.scrollToPosition(chatAdapter.getItemCount());
            }
        }
    });

我可以尝试使用adjustPan来向上移动视图,但这会隐藏我的工具栏。 请建议任何解决此问题的方法。


在 onFocusChange 中,new handler().postDelay(runnable, 500)。 - tiny sunlight
我在这里回答了我的解决方案:https://dev59.com/uKfja4cB1Zd3GeqPpQZ3#65337149 - Hylke
12个回答

87

您可以使用 recyclerview.addOnLayoutChangeListener() 捕获键盘上升的变化。 如果底部小于 oldBottom,则键盘处于上升状态。

if ( bottom < oldBottom) { 
   recyclerview.postDelayed(new Runnable() { 
       @Override 
       public void run() {
           recyclerview.smoothScrollToPosition(bottomPosition); 
       }
    }, 100);
}

11
不起作用。但是,如果你将“scrollToPosition”更改为“smoothScrollToPosition”,那么它就能正常工作。 - Mukesh Sharma
我不想显示动画,所以我只使用了scrollToPosition...在我的应用中,它可以正常工作。但我不知道为什么在你的情况下无法正常工作。谢谢! - jihoon kim
4
以上的方法都对我不起作用 :( 当键盘弹出时,我仍然无法使回收视图滚动到底部。 - Sandra
4
使用post()方法而不是postDelayed()方法,可以让此代码更加流畅。如果您要尝试,请确保删除100毫秒的参数,因为post()方法只接受可运行对象。 - David Velasquez
我有一个问题,就是当键盘打开时不想滚动。如何防止Recycler View发生意外的滚动? - Mahdi

78

将此内容添加到您的Activity或Fragment中:

    if (Build.VERSION.SDK_INT >= 11) {
        recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v,
                    int left, int top, int right, int bottom,
                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
                if (bottom < oldBottom) {
                    recyclerView.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            recyclerView.smoothScrollToPosition(
                                    recyclerView.getAdapter().getItemCount() - 1);
                        }
                    }, 100);
                }
            }
        });
    }

这给了我在一个相关问题上的线索,我一直在努力解决。谢谢! - ade.akinyede
1
不需要进行SDK检查,因为在新设备的所有新应用程序中都使用了Recyler视图:),感谢您的解决方案! - Ali Obeid

38

对我来说有效

LinearLayoutManager layoutManager = new LinearLayoutManager(context);
layoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(layoutManager);

1
它可以工作。但视图总是从底部开始。有没有什么解决方法可以从顶部开始? - Mani
3
如果您正在寻找不滚动到底部,但在键盘打开后仍然可以保持最后一个项目可见的好解决方案,那么这是一个很好的选择。正是我所需要的,谢谢! - Simon Ninon
是的,我已经找到了我的情况的解决方法,这意味着我需要在打开活动时加载布局。然后它将在屏幕打开之前加载。所以我会通过 recyclerView.smoothScrollToPosition(position); 将其移动到顶部。没有任何闪烁,屏幕将显示recyclerview的第一个元素。 - Mani
5
非常适合聊天消息列表! - Touré Holder
2022年为我工作! - Osdward
正是我在寻找的东西。 - undefined

14

我发现postDelayed是不必要的,而且使用适配器位置并不能处理当回收视图在项目中间滚动时的情况。我用以下代码实现了我想要的外观:

recycler.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
    if (bottom < oldBottom) {
        messageRecycler.scrollBy(0, oldBottom - bottom);
    }
})

当你滚动到底部时,这个方法可以正常工作,但如果你滚动到顶部并关闭键盘,它将无法正常工作。 - Lucas P.

8

虽然这是一个老问题,但我今天遇到了这个问题,并发现以上的方法都不起作用。这是我的解决方案:

    recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v,
                                   int left, int top, int right, int bottom,
                                   int oldLeft, int oldTop, int oldRight, int oldBottom) {
            if (bottom < oldBottom) {
                final int lastAdapterItem = mAdapter.getItemCount() - 1;
                recyclerView.post(new Runnable() {
                  @Override
                  public void run() {
                    int recyclerViewPositionOffset = -1000000;
                    View bottomView = linearLayoutManager.findViewByPosition(lastAdapterItem);
                    if (bottomView != null) {
                       recyclerViewPositionOffset = 0 - bottomView.getHeight();
                     }
                     linearLayoutManager.scrollToPositionWithOffset(lastAdapterItem, recyclerViewPositionOffset);
                  }
                });
              }
         });
   }

6

这个有效。

private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private LinearLayoutManager mManager;

...

mManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mManager);

(initialize and set adapter.)

mRecyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        if (bottom < oldBottom) scrollToBottom();
    }
});

private void scrollToBottom() {
    mManager.smoothScrollToPosition(mRecyclerView, null, mAdapter.getItemCount());
}

谢谢,这对我很有帮助:rvMessageDetail.apply { (layoutManager as LinearLayoutManager).smoothScrollToPosition(this, null, position) } - Ho Luong

3
        recyclerView.smoothScrollToPosition(recyclerView.getAdapter().getItemCount());

1
它在支持库版本27.0.1中正常工作。
清单中没有需要设置的内容。
val currentScrollPosition = 0

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)

            currentScrollPosition = recyclerView.computeVerticalScrollOffset() + recyclerView.computeVerticalScrollExtent()
        }

        override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) { }
    })

    storyList.addOnLayoutChangeListener { view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
        if (bottom < oldBottom) {
            if (currentScrollPosition >= recyclerView.computeVerticalScrollRange()) {
                recyclerVIew.post {
                    recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_NEVER
                    recyclerView.smoothScrollBy(0, recyclerView.computeVerticalScrollRange() - recyclerView.computeVerticalScrollOffset() + recyclerView.computeVerticalScrollExtent())
                }
            }
        } else {
            recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_ALWAYS
        }
    }

1

当您打开聊天时,您将在回收视图的末尾获得最后一条消息:

layoutManager.setStackFromEnd(true); 
recyclerView.setLayoutManager(layoutManager);

以下代码将根据键盘进行调整!

    recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
        @Override
        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
            if ( bottom <= oldBottom) { 
                recyclerview.postDelayed(new Runnable() { 
                    @Override 
                    public void run() {
                        recyclerview.smoothScrollToPosition(bottom); 
                    }
                }, 100);
         }
     }
    });

0
你可以使用 addOnItemTouchListener 来查看滚动的变化:
private Boolean scrollListerActivate = true;

mRecyclerViewTop.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if(scrollListerActivate == true) {
            scrollListTopManager();
        }
    }
});

要滚动到最后(或其他)位置,您应该使用scrollListTopManager方法:

public void scrollListTopManager() {
    int position = 0;
    int itemCount = mRecyclerViewTop.getAdapter().getItemCount();
    position = itemCount - 1;
    
    mRecyclerViewTop.scrollToPosition(position);
}

如果你想要手动滚动,你应该使用 addOnItemTouchListener 来停止使用 scrollListTopManager 方法:

mRecyclerViewTop.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent 
    motionEvent) {
        scrollListerActivate = false;
        return false;
    }
    @Override
    public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) 
    {}
            
    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean b) {}
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接