Adding items to ListView, maintaining scroll position and NOT seeing a scroll jump

前端 未结 3 1725
暗喜
暗喜 2021-01-31 04:23

I\'m building an interface similar to the Google Hangouts chat interface. New messages are added to the bottom of the list. Scrolling up to the top of the list will trigger a lo

3条回答
  •  被撕碎了的回忆
    2021-01-31 04:31

    The code suggested by the question author works, but it's dangerous.
    For instance, this condition:

    listView.getFirstVisiblePosition() == positionToSave
    

    may always be true if no items were changed.

    I had some problems with this aproach in a situation where any number of elements were added both above and below the current element. So I came up with a sligtly improved version:

    /* This listener will block any listView redraws utils unlock() is called */
    private class ListViewPredrawListener implements OnPreDrawListener {
    
        private View view;
        private boolean locked;
    
        private ListViewPredrawListener(View view) {
            this.view = view;
        }
    
        public void lock() {
            if (!locked) {
                locked = true;
                view.getViewTreeObserver().addOnPreDrawListener(this);
            }
        }
    
        public void unlock() {
            if (locked) {
                locked = false;
                view.getViewTreeObserver().removeOnPreDrawListener(this);
            }
        }
    
        @Override
        public boolean onPreDraw() {
            return false;
        }
    }
    
    /* Method inside our BaseAdapter */
    private updateList(List newItems) {
        int pos = listView.getFirstVisiblePosition();
        View cell = listView.getChildAt(pos);
        String savedId = adapter.getItemId(pos); // item the user is currently looking at
        savedPositionOffset = cell == null ? 0 : cell.getTop(); // current item top offset
    
        // Now we block listView drawing until after setSelectionFromTop() is called
        final ListViewPredrawListener predrawListener = new ListViewPredrawListener(listView);
        predrawListener.lock();
    
        // We have no idea what changed between items and newItems, the only assumption
        // that we make is that item with savedId is still in the newItems list
        items = newItems; 
        notifyDataSetChanged();
        // or for ArrayAdapter:
        //clear(); 
        //addAll(newItems);
    
        listView.post(new Runnable() {
            @Override
            public void run() {
                // Now we can finally unlock listView drawing
                // Note that this code will always be executed
                predrawListener.unlock();
    
                int newPosition = ...; // Calculate new position based on the savedId
                listView.setSelectionFromTop(newPosition, savedPositionOffset);
            }
        });
    }
    

提交回复
热议问题