Узнать, прокручивается ли ListView вниз?


105

Могу ли я узнать, прокручивается ли мой ListView вниз? Под этим я подразумеваю, что последний элемент полностью виден.

Ответы:


171

Отредактировано :

Поскольку я исследовал эту конкретную тему в одном из своих приложений, я могу написать расширенный ответ для будущих читателей этого вопроса.

Внедрите OnScrollListener, установите свои ListView, onScrollListenerи тогда вы сможете все правильно обрабатывать.

Например:

private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);

// ... ... ...

@Override
public void onScroll(AbsListView lw, final int firstVisibleItem,
        final int visibleItemCount, final int totalItemCount)
{

    switch(lw.getId()) 
    {
        case R.id.your_list_id:     

            // Make your calculation stuff here. You have all your
            // needed info from the parameters of this function.

            // Sample calculation to determine if the last 
            // item is fully visible.
            final int lastItem = firstVisibleItem + visibleItemCount;

            if(lastItem == totalItemCount)
            {
                if(preLast!=lastItem)
                {
                    //to avoid multiple calls for last item
                    Log.d("Last", "Last");
                    preLast = lastItem;
                }
            }
    }
}

26
Это не определит, полностью ли виден последний элемент.
fhucho

1
Если в вашем представлении списка есть верхние или нижние колонтитулы, вы также должны это учитывать.
Мартин Маркончини

5
@Wroclai Это не определит, полностью ли виден последний элемент.
Gaurav Arora

Искал какой-то похожий РАБОЧИЙ код .. Сработало !! Большое спасибо!!
Сурадж Дубей

Я не хочу , чтобы начать новый вопрос, но что мне делать , если мой listviewIS stackFromBottom? Я пробовал, if (0 == firstVisibleItem){//listviewtop}но мне постоянно звонят.
shadyinside 05

68

Поздний ответ, но если вы просто хотите проверить, прокручивается ли ваш ListView до конца или нет, без создания прослушивателя событий, вы можете использовать этот оператор if:

if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
    yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
    //It is scrolled all the way down here

}

Сначала он проверяет, отображается ли последняя возможная позиция. Затем он проверяет, совпадает ли нижняя часть последней кнопки с нижней частью ListView. Вы можете сделать что-то подобное, чтобы узнать, полностью ли он находится наверху:

if (yourListView.getFirstVisiblePosition() == 0 &&
    yourListView.getChildAt(0).getTop() >= 0)
{
    //It is scrolled all the way up here

}

4
Спасибо. Просто нужно изменить ... getChildAt (yourListView.getCount () -1) ... на ... getChildAt (yourListView.getChildCount () -1) ...
OferR

@OferR Не уверен, но я думаю, что getChildCount()возвращает представления в группе просмотра, что при повторном использовании представления не совпадает с количеством элементов в адаптере. Однако, поскольку ListView происходит от AdapterView, вы можете использовать его getCount()непосредственно в ListView.
Уильям Т. Маллард

@ Уильям Т. Маллард. Ваше первое предложение правильное, и это именно то, что мы хотим: последний просмотр, показанный в группе. Это необходимо для того, чтобы убедиться, что он полностью виден. (Рассмотрим ListView с 20 строками, но отображаются только последние 8. Мы хотим получить 8-е представление в ViewGroup, а не 20-е, которое не существует)
OferR

обратите внимание, что это решение не будет работать, если вы быстро обновляете представление списка.
белка

19

Как я это сделал:

listView.setOnScrollListener(new AbsListView.OnScrollListener() {

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE 
            && (listView.getLastVisiblePosition() - listView.getHeaderViewsCount() -
            listView.getFooterViewsCount()) >= (adapter.getCount() - 1)) {

        // Now your listview has hit the bottom
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

    }
});

1
Это круто! Спасибо
Shariful Islam


6
public void onScrollStateChanged(AbsListView view, int scrollState)        
{
    if (!view.canScrollList(View.SCROLL_AXIS_VERTICAL) && scrollState == SCROLL_STATE_IDLE)    
    {
        //When List reaches bottom and the list isn't moving (is idle)
    }
}

Это сработало для меня.


1
view.canScrollList, к сожалению, API 19+
ozmank

Работает как шарм
pavaniiitn 04

4

Это может быть

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // TODO Auto-generated method stub

                if (scrollState == 2)
                    flag = true;
                Log.i("Scroll State", "" + scrollState);
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                // TODO Auto-generated method stub
                if ((visibleItemCount == (totalItemCount - firstVisibleItem))
                        && flag) {
                    flag = false;



                    Log.i("Scroll", "Ended");
                }
            }

3

Было довольно болезненно иметь дело с прокруткой, определяя, когда она завершена, и она действительно находится внизу списка (а не внизу видимого экрана) и запускает мою службу только один раз, чтобы получить данные из Интернета. Однако сейчас он работает нормально. Код приведен ниже для всех, кто сталкивается с такой же ситуацией.

ПРИМЕЧАНИЕ. Мне пришлось переместить код, связанный с адаптером, в onViewCreated вместо onCreate и обнаруживать прокрутку в основном следующим образом:

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (getListView().getLastVisiblePosition() == (adapter.getCount() - 1))
        if (RideListSimpleCursorAdapter.REACHED_THE_END) {
            Log.v(TAG, "Loading more data");
            RideListSimpleCursorAdapter.REACHED_THE_END = false;
            Intent intent = new Intent(getActivity().getApplicationContext(), FindRideService.class);
            getActivity().getApplicationContext().startService(intent);
        }
}

Здесь RideListSimpleCursorAdapter.REACHED_THE_END - дополнительная переменная в моем SimpleCustomAdapter, которая задается следующим образом:

if (position == getCount() - 1) {
      REACHED_THE_END = true;
    } else {
      REACHED_THE_END = false;
    }

Только когда оба этих условия соблюдаются, это означает, что я действительно нахожусь в конце списка и что моя служба будет запущена только один раз. Если я не улавливаю REACHED_THE_END, даже прокрутка назад запускает службу снова, пока последний элемент находится в поле зрения.


Я избавился от else, потому что это доставляло мне проблемы, но в целом хороший ответ
Майк Баглио-младший.

3

canScrollVertically(int direction)работает для всех представлений и, кажется, делает то, что вы просили, с меньшим количеством кода, чем большинство других ответов. Вставьте положительное число, и если результат окажется ложным, вы окажетесь внизу.

то есть:

if (!yourView.canScrollVertically(1)) { //you've reached bottom }


2

Чтобы немного расширить один из приведенных выше ответов, это то, что мне пришлось сделать, чтобы он работал полностью. Кажется, что внутри ListViews есть около 6dp встроенного заполнения, а onScroll () вызывается, когда список был пуст. Это обрабатывает обе эти вещи. Возможно, его можно было бы немного оптимизировать, но написано больше для ясности.

Боковое примечание: я пробовал несколько разных методов преобразования dp в пиксель, и этот dp2px () оказался лучшим.

myListView.setOnScrollListener(new OnScrollListener() {
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if (visibleItemCount > 0) {
            boolean atStart = true;
            boolean atEnd = true;

            View firstView = view.getChildAt(0);
            if ((firstVisibleItem > 0) ||
                    ((firstVisibleItem == 0) && (firstView.getTop() < (dp2px(6) - 1)))) {
                // not at start
                atStart = false;
            }

            int lastVisibleItem = firstVisibleItem + visibleItemCount;
            View lastView = view.getChildAt(visibleItemCount - 1);
            if ((lastVisibleItem < totalItemCount) ||
                    ((lastVisibleItem == totalItemCount) &&
                            ((view.getHeight() - (dp2px(6) - 1)) < lastView.getBottom()))
                    ) {
                        // not at end
                    atEnd = false;
                }

            // now use atStart and atEnd to do whatever you need to do
            // ...
        }
    }
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }
});

private int dp2px(int dp) {
    return (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}

2

Я пока не могу комментировать, потому что у меня недостаточно репутации, но в ответах @Ali Imran и @Wroclai я думаю, что чего-то не хватает. С этим фрагментом кода, как только вы обновите preLast, он никогда больше не выполнит журнал. В моей конкретной проблеме я хочу выполнять некоторую операцию каждый раз, когда прокручиваю вниз, но как только preLast обновляется до LastItem, эта операция больше не выполняется.

private int preLast;
// Initialization stuff.
yourListView.setOnScrollListener(this);

// ... ... ...

@Override
public void onScroll(AbsListView lw, final int firstVisibleItem,
                 final int visibleItemCount, final int totalItemCount) {

switch(lw.getId()) {
    case android.R.id.list:     

        // Make your calculation stuff here. You have all your
        // needed info from the parameters of this function.

        // Sample calculation to determine if the last 
        // item is fully visible.
         final int lastItem = firstVisibleItem + visibleItemCount;
       if(lastItem == totalItemCount) {
          if(preLast!=lastItem){ //to avoid multiple calls for last item
            Log.d("Last", "Last");
            preLast = lastItem;
          }
       } else {
            preLast = lastItem;
}

}

С этим «else» теперь вы можете выполнять свой код (в данном случае Log) каждый раз, когда вы снова прокручиваете вниз.


2
public void onScroll(AbsListView view, int firstVisibleItem,
                         int visibleItemCount, int totalItemCount) {
        int lastindex = view.getLastVisiblePosition() + 1;

        if (lastindex == totalItemCount) { //showing last row
            if ((view.getChildAt(visibleItemCount - 1)).getTop() == view.getHeight()) {
                //Last row fully visible
            }
        }
    }

1

Чтобы ваш список вызывал, когда список дойдет до последнего, и если произойдет ошибка, то это не вызовет endoflistview снова . Этот код также поможет в этом сценарии.

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
        int visibleItemCount, int totalItemCount) {
    final int lastPosition = firstVisibleItem + visibleItemCount;
    if (lastPosition == totalItemCount) {
        if (previousLastPosition != lastPosition) { 

            //APPLY YOUR LOGIC HERE
        }
        previousLastPosition = lastPosition;
    }
    else if(lastPosition < previousLastPosition - LIST_UP_THRESHOLD_VALUE){
        resetLastIndex();
    }
}

public void resetLastIndex(){
    previousLastPosition = 0;
}

где LIST_UP_THRESHOLD_VALUE может быть любым целочисленным значением (я использовал 5), где ваш список прокручивается вверх, и при возврате к концу это снова вызовет конец представления списка.


1

Я нашел очень хороший способ автоматически загрузить следующий набор страниц, не требующий вашей собственной ScrollView(как того требует принятый ответ).

В ParseQueryAdapter есть метод, getNextPageViewкоторый позволяет вам предоставить свое собственное пользовательское представление, которое появляется в конце списка, когда есть больше данных для загрузки, поэтому он срабатывает только тогда, когда вы достигли конца текущего набора страниц. (по умолчанию это режим «Загрузить еще ..»). Этот метод вызывается только тогда, когда есть больше данных для загрузки, поэтому это отличное место для вызова. loadNextPage(); Таким образом, адаптер выполняет за вас всю тяжелую работу по определению, когда должны быть загружены новые данные, и он не будет вызываться вообще, если у вас есть достиг конца набора данных.

public class YourAdapter extends ParseQueryAdapter<ParseObject> {

..

@Override
public View getNextPageView(View v, ViewGroup parent) {
   loadNextPage();
   return super.getNextPageView(v, parent);
  }

}

Затем внутри вашей активности / фрагмента вам просто нужно установить адаптер, и новые данные будут автоматически обновляться для вас, как по волшебству.

adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);

1

Чтобы определить, является ли последний элемент полностью видимым , вы можете просто добавить расчет в нижней части последнего видимого элемента представления с помощью lastItem.getBottom().

yourListView.setOnScrollListener(this);   

@Override
public void onScroll(AbsListView view, final int firstVisibleItem,
                 final int visibleItemCount, final int totalItemCount) {

    int vH = view.getHeight();
    int topPos = view.getChildAt(0).getTop();
    int bottomPos = view.getChildAt(visibleItemCount - 1).getBottom();

    switch(view.getId()) {
        case R.id.your_list_view_id:
            if(firstVisibleItem == 0 && topPos == 0) {
                //TODO things to do when the list view scroll to the top
            }

            if(firstVisibleItem + visibleItemCount == totalItemCount 
                && vH >= bottomPos) {
                //TODO things to do when the list view scroll to the bottom
            }
            break;
    }
}

1

Я пошел с:

@Override
public void onScroll(AbsListView listView, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
    if(totalItemCount - 1 == favoriteContactsListView.getLastVisiblePosition())
    {
        int pos = totalItemCount - favoriteContactsListView.getFirstVisiblePosition() - 1;
        View last_item = favoriteContactsListView.getChildAt(pos);

        //do stuff
    }
}

1

В методе getView()(производного от BaseAdapterкласса) можно проверить, совпадает ли позиция текущего представления со списком элементов в Adapter. Если это так, то это означает, что мы достигли конца / конца списка:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // ...

    // detect if the adapter (of the ListView/GridView) has reached the end
    if (position == getCount() - 1) {
        // ... end of list reached
    }
}

0

Я нахожу лучший способ определить конец прокрутки списка внизу, сначала обнаружите конец прокрутки с помощью этой
реализации onScrollListener, чтобы обнаружить конец прокрутки в ListView

 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    this.currentFirstVisibleItem = firstVisibleItem;
    this.currentVisibleItemCount = visibleItemCount;
}

public void onScrollStateChanged(AbsListView view, int scrollState) {
    this.currentScrollState = scrollState;
    this.isScrollCompleted();
 }

private void isScrollCompleted() {
    if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
        /*** In this way I detect if there's been a scroll which has completed ***/
        /*** do the work! ***/
    }
}

наконец объедините ответ Мартейна

OnScrollListener onScrollListener_listview = new OnScrollListener() {       

        private int currentScrollState;
        private int currentVisibleItemCount;

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            // TODO Auto-generated method stub

            this.currentScrollState = scrollState;
            this.isScrollCompleted();
        }

        @Override
        public void onScroll(AbsListView lw, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            // TODO Auto-generated method stub
            this.currentVisibleItemCount = visibleItemCount;

        }

        private void isScrollCompleted() {
            if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
                /*** In this way I detect if there's been a scroll which has completed ***/
                /*** do the work! ***/

                if (listview.getLastVisiblePosition() == listview.getAdapter().getCount() - 1
                        && listview.getChildAt(listview.getChildCount() - 1).getBottom() <= listview.getHeight()) {
                    // It is scrolled all the way down here
                    Log.d("henrytest", "hit bottom");
                }


            }
        }

    };

0

Большое спасибо постерам в stackoverflow! Я объединил некоторые идеи и создал прослушиватель классов для действий и фрагментов (так что этот код более многоразовый, что делает код быстрее писать и намного чище).

Все, что вам нужно сделать, когда вы получили мой класс, - это реализовать интерфейс (и, конечно, создать для него метод), который объявлен в моем классе, и создать объект этого класса, передавая аргументы.

/**
* Listener for getting call when ListView gets scrolled to bottom
*/
public class ListViewScrolledToBottomListener implements AbsListView.OnScrollListener {

ListViewScrolledToBottomCallback scrolledToBottomCallback;

private int currentFirstVisibleItem;
private int currentVisibleItemCount;
private int totalItemCount;
private int currentScrollState;

public interface ListViewScrolledToBottomCallback {
    public void onScrolledToBottom();
}

public ListViewScrolledToBottomListener(Fragment fragment, ListView listView) {
    try {
        scrolledToBottomCallback = (ListViewScrolledToBottomCallback) fragment;
        listView.setOnScrollListener(this);
    } catch (ClassCastException e) {
        throw new ClassCastException(fragment.toString()
                + " must implement ListViewScrolledToBottomCallback");
    }
}

public ListViewScrolledToBottomListener(Activity activity, ListView listView) {
    try {
        scrolledToBottomCallback = (ListViewScrolledToBottomCallback) activity;
        listView.setOnScrollListener(this);
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString()
                + " must implement ListViewScrolledToBottomCallback");
    }
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
    this.currentFirstVisibleItem = firstVisibleItem;
    this.currentVisibleItemCount = visibleItemCount;
    this.totalItemCount = totalItemCount;
}

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
    this.currentScrollState = scrollState;
    if (isScrollCompleted()) {
        if (isScrolledToBottom()) {
            scrolledToBottomCallback.onScrolledToBottom();
        }
    }
}

private boolean isScrollCompleted() {
    if (this.currentVisibleItemCount > 0 && this.currentScrollState == SCROLL_STATE_IDLE) {
        return true;
    } else {
        return false;
    }
}

private boolean isScrolledToBottom() {
    System.out.println("First:" + currentFirstVisibleItem);
    System.out.println("Current count:" + currentVisibleItemCount);
    System.out.println("Total count:" + totalItemCount);
    int lastItem = currentFirstVisibleItem + currentVisibleItemCount;
    if (lastItem == totalItemCount) {
        return true;
    } else {
        return false;
    }
}
}

0

Вам нужно добавить пустой ресурс нижнего колонтитула xml в свой listView и определить, отображается ли этот нижний колонтитул.

    private View listViewFooter;
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_newsfeed, container, false);

        listView = (CardListView) rootView.findViewById(R.id.newsfeed_list);
        footer = inflater.inflate(R.layout.newsfeed_listview_footer, null);
        listView.addFooterView(footer);

        return rootView;
    }

Затем в вашем слушателе прокрутки listView вы делаете это

@
Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
  if (firstVisibleItem == 0) {
    mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.TOP);
    mSwipyRefreshLayout.setEnabled(true);
  } else if (firstVisibleItem + visibleItemCount == totalItemCount) //If last row is visible. In this case, the last row is the footer.
  {
    if (footer != null) //footer is a variable referencing the footer view of the ListView. You need to initialize this onCreate
    {
      if (listView.getHeight() == footer.getBottom()) { //Check if the whole footer is visible.
        mSwipyRefreshLayout.setDirection(SwipyRefreshLayoutDirection.BOTTOM);
        mSwipyRefreshLayout.setEnabled(true);
      }
    }
  } else
    mSwipyRefreshLayout.setEnabled(false);
}


0

Если вы установите тег для представления последнего элемента списка, позже вы можете получить представление с тегом, если представление имеет значение null, это потому, что представление больше не загружается. Как это:

private class YourAdapter extends CursorAdapter {
    public void bindView(View view, Context context, Cursor cursor) {

         if (cursor.isLast()) {
            viewInYourList.setTag("last");
         }
         else{
            viewInYourList.setTag("notLast");
         }

    }
}

затем, если вам нужно знать, загружен ли последний элемент

View last = yourListView.findViewWithTag("last");
if (last != null) {               
   // do what you want to do
}

0

Janwilx72 прав, но его минимальный SDK равен 21, поэтому я создаю этот метод :

private boolean canScrollList(@ScrollOrientation int direction, AbsListView listView) {
    final int childCount = listView.getChildCount();
    if (childCount == 0) {
        return false;
    }

    final int firstPos = listView.getFirstVisiblePosition();
    final int paddingBottom = listView.getListPaddingBottom();
    final int paddingTop = listView.getListPaddingTop();
    if (direction > 0) {
        final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
        final int lastPos    = firstPos + childCount;
        return lastPos < listView.getChildCount() || lastBottom > listView.getHeight() - paddingBottom;
    } else {
        final int firstTop = listView.getChildAt(0).getTop();
        return firstPos > 0 || firstTop < paddingTop;
    }
}

для ScrollOrientation:

protected static final int SCROLL_UP = -1;
protected static final int SCROLL_DOWN = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({SCROLL_UP, SCROLL_DOWN})
protected @interface Scroll_Orientation{}

Может, поздно, только для опоздавших。


0

Если вы используете специальный адаптер со своим списком (большинство людей так делают!), Здесь дается прекрасное решение!

https://stackoverflow.com/a/55350409/1845404

Метод getView адаптера определяет, когда список был прокручен до последнего элемента. Он также добавляет коррекцию для тех редких случаев, когда вызывается некоторая более ранняя позиция даже после того, как адаптер уже отрендерил последнее представление.


0

Я так и сделал, и у меня работает:

private void YourListView_Scrolled(object sender, ScrolledEventArgs e)
        {
                double itemheight = YourListView.RowHeight;
                double fullHeight = YourListView.Count * itemheight;
                double ViewHeight = YourListView.Height;

                if ((fullHeight - e.ScrollY) < ViewHeight )
                {
                    DisplayAlert("Reached", "We got to the end", "OK");
                }
}

-5

Это прокрутит ваш список до последней записи.

ListView listView = new ListView(this);
listView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
listView.setStackFromBottom(true);
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.