RecyclerView.Adapter.notifyItemMoved(0,1) scrolls screen

后端 未结 4 1580
旧巷少年郎
旧巷少年郎 2021-01-02 03:00

I have a RecyclerView managed by a LinearlayoutManager, if I swap item 1 with 0 and then call mAdapter.notifyItemMoved(0,1), the moving animation causes the screen to scroll

相关标签:
4条回答
  • 2021-01-02 03:24

    Call scrollToPosition(0) after moving items. Unfortunately, i assume, LinearLayoutManager tries to keep first item stable, which moves so it moves the list with it.

    0 讨论(0)
  • 2021-01-02 03:32

    Translate @Andreas Wenger's answer to kotlin:

    val firstPos = manager.findFirstCompletelyVisibleItemPosition()
    var offsetTop = 0
    if (firstPos >= 0) {
        val firstView = manager.findViewByPosition(firstPos)!!
        offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView)
    }
    
    // apply changes
    adapter.notify...
    
    if (firstPos >= 0) {
        manager.scrollToPositionWithOffset(firstPos, offsetTop)
    }
    

    In my case, the view can have a top margin, which also needs to be counted in the offset, otherwise the recyclerview will not scroll to the intended position. To do so, just write:

    val topMargin = (firstView.layoutParams as? MarginLayoutParams)?.topMargin ?: 0
    offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView) - topMargin
    

    Even easier if you have ktx dependency in your project:

    offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView) - firstView.marginTop
    
    0 讨论(0)
  • 2021-01-02 03:33

    Sadly the workaround presented by yigit scrolls the RecyclerView to the top. This is the best workaround I found till now:

    // figure out the position of the first visible item
    int firstPos = manager.findFirstCompletelyVisibleItemPosition();
    int offsetTop = 0;
    if(firstPos >= 0) {
        View firstView = manager.findViewByPosition(firstPos);
        offsetTop = manager.getDecoratedTop(firstView) - manager.getTopDecorationHeight(firstView);
    }
    
    // apply changes
    adapter.notify...
    
    // reapply the saved position
    if(firstPos >= 0) {
        manager.scrollToPositionWithOffset(firstPos, offsetTop);
    }
    
    0 讨论(0)
  • 2021-01-02 03:41

    LinearLayoutManager has done this for you in LinearLayoutManager.prepareForDrop.

    All you need to provide is the moving (old) View and the target (new) View.

    layoutManager.prepareForDrop(oldView, targetView, -1, -1)
    // the numbers, x and y don't matter to LinearLayoutManager's implementation of prepareForDrop
    

    It's an "unofficial" API because it states in the source

    // This method is only intended to be called (and should only ever be called) by
    // ItemTouchHelper.
    public void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y) {
        ...
    }
    

    But it still works and does exactly what the other answers say, doing all the offset calculations accounting for layout direction for you.

    This is actually the same method that is called by LinearLayoutManager when used by an ItemTouchHelper to account for this dreadful bug.

    0 讨论(0)
提交回复
热议问题