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

前端 未结 3 1724
暗喜
暗喜 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<Item> 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);
            }
        });
    }
    
    0 讨论(0)
  • 2021-01-31 04:38

    As I said in my comment, a OnPreDrawlistener could be another option to solve the problem. The idea of using the listener is to skip showing the ListView between the two states(after adding the data and after setting the selection to the right position). In the OnPreDrawListener(set with listViewReference.getViewTreeObserver().addOnPreDrawListener(listener);) you'll check the current visible position of the ListView and test it against the position which the ListView should show. If those don't match then make the listener's method return false to skip the frame and set the selection on the ListView to the right position. Setting the proper selection will trigger the draw listener again, this time the positions will match, in which case you'd unregister the OnPreDrawlistener and return true.

    0 讨论(0)
  • 2021-01-31 04:46

    I was breaking up my head until I found a solution similar to this. Before adding a set of items you have to save top distance of the firstVisible item and after adding the items do setSelectionFromTop().

    Here is the code:

    // save index and top position
    int index = mList.getFirstVisiblePosition();
    View v = mList.getChildAt(0);
    int top = (v == null) ? 0 : v.getTop();
    
    // for (Item item : items){
        mListAdapter.add(item);
    }
    
    // restore index and top position
    mList.setSelectionFromTop(index, top);
    

    It works without any jump for me with a list of about 500 items :)

    I took this code from this SO post: Retaining position in ListView after calling notifyDataSetChanged

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