Реализация onScrollListener для обнаружения конца прокрутки в ListView

У меня есть ListView, отображающий некоторые элементы. Я хотел бы выполнить некоторую операцию над элементами, которые в настоящее время отображаются в видимой части ListView, в зависимости от того, как прокручивается ListView; поэтому я подумал реализовать OnScrollListener из ListView. Согласно ссылке API Android, метод onScroll «будет вызываться после завершения прокрутки». Мне кажется, это то, что мне нужно, так как после завершения прокрутки я выполняю свои действия над ListView (метод onScroll возвращает индекс первого отображаемого элемента и количество отображаемых элементов).

Но после реализации я вижу из LogCat, что метод onScroll запускается не только после завершения прокрутки, но и запускается для каждого нового элемента, который входит в отображаемое представление, от начала до конца прокрутки. Это не то поведение, которого я ожидаю и которое мне нужно. Вместо этого другой метод слушателя (onScrollStateChanged) не предоставляет информацию об элементах, отображаемых в настоящее время в ListView.

Итак, кто-нибудь знает, как использовать эту пару методов, чтобы определить конец прокрутки и получить информацию об отображаемых элементах? Несоответствие между ссылкой API и фактическим поведением метода меня немного смутило. Заранее спасибо.

P.S.: Я видел несколько подобных тем, но ничто не помогает мне понять, как все это работает..!


person e-cal    schedule 15.06.2011    source источник
comment
попробуйте это, я думаю, это поможет полностью github.com/commonsguy/cwac-endless вы можете использовать бесконечный адаптер.   -  person Jabbir Basha    schedule 05.11.2012


Ответы (7)


В конце концов я нашел решение, не очень элегантное, но которое сработало для меня; выяснив, что метод onScroll вызывается для каждого шага прокрутки, а не просто вызывается в конце прокрутки, и что onScrollStateChanged на самом деле вызывается только после завершения прокрутки, я делаю что-то вроде этого:

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! ***/
    }
}

Практически каждый раз, когда ListView прокручивается, я сохраняю данные о первом видимом элементе и количестве видимых элементов (метод onScroll); когда состояние прокрутки изменяется (onScrollStateChanged), я сохраняю состояние и вызываю другой метод, который на самом деле понимает, была ли прокрутка и завершена ли она. Таким образом, у меня также есть данные о видимых элементах, которые мне нужны. Может быть, не чистый, но работает!

С Уважением

person e-cal    schedule 17.06.2011
comment
Я использовал что-то подобное, что очень хорошо сработало в моем случае. Просто, но эффективно: public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } public void onScrollStateChanged(AbsListView view, int scrollState) { isScrollCompleted(scrollState); } private void isScrollCompleted(int currentScrollState) { if (currentScrollState == OnScrollListener.SCROLL_STATE_IDLE) { mAdapter.notifyDataSetChanged(); } } - person Abandoned Cart; 04.09.2012
comment
Как я могу показать 10 элементов за раз, а затем загрузить еще 10 элементов, используя ваш код. Пожалуйста помоги!!! - person android_developer; 28.11.2013
comment
Не нужно currentFirstVisibleItem - person Pratik Butani; 05.05.2014
comment
Большое спасибо... это так элегантно - person Pratik Butani; 05.05.2014
comment
currentFirstVisibleItem можно сравнить с 0, чтобы обнаружить прокрутку вверх. - person Artem Mostyaev; 19.02.2016

Уменьшить это поведение сложно, и мне потребовалось довольно много времени, чтобы довести его до совершенства. Суть проблемы в том, что слушателей прокрутки самих по себе недостаточно для обнаружения «остановки прокрутки» (включая кнопки направления/трекбол), насколько я могу судить. В итоге я сделал комбинацию вещей, которая работает именно так, как я ожидал.

Я решил, что лучший способ сделать это — расширить ListView и переопределить несколько методов:

  ....
  @Override
  public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN || keyCode == KeyEvent.KEYCODE_DPAD_UP) {
      startWait();
    }
    return super.onKeyUp(keyCode, event);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
      stopWait();
    }
    if (event.getAction() == MotionEvent.ACTION_UP ||
        event.getAction() == MotionEvent.ACTION_CANCEL) {
      startWait();
    }
    return super.onTouchEvent(event);
  }

  private OnScrollListener customScrollListener = new OnScrollListener() {
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
      int totalItemCount) {
      stopWait();
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
      if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
        startWait();
      } else {
        stopWait();
      }
    }
  };

  //All this waiting could be improved, but that's the idea
  private Thread waitThread = null;
  private int waitCount  = Integer.MIN_VALUE;

  public void stopWait() {
    waitCount = Integer.MIN_VALUE;
  }

  public synchronized void startWait() {
    waitCount = 0;

    if (waitThread != null) {
      return;
    }

    waitThread = new Thread(new Runnable() {
    @Override
    public void run() {
    try {
      for (; waitCount.get() < 10; waitCount++) {
        Thread.sleep(50);
      }

        //Kick it back to the UI thread.
        view.post(theRunnableWithYourOnScrollStopCode); // HERE IS WHERE YOU DO WHATEVER YOU WANTED TO DO ON STOP
      } catch (InterruptedException e) {
      } finally  {
        waitThread = null;
      }
    }
  });
  waitThread.start();
}

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

person dmon    schedule 15.06.2011
comment
Спасибо, ваш ответ мне очень помогает, но сейчас он слишком сложен для моей цели; Я закончил с другим решением, которое я публикую ниже... - person e-cal; 17.06.2011
comment
@dmon Вы имеете в виду, что theRunnableWithYourOnScrollStopCode - это место, где я могу разместить свою функцию: например, загрузить данные или сделать веб-запрос? Спасибо! - person Alston; 04.07.2014

Анализируя предыдущие ответы, нашел свой:

В onViewCreated, onCreate или где бы вы ни брали свой список:

ListView list = (ListView) view.findViewById(R.id.[your id]);
    list.setOnScrollListener(this);

Затем заполните методы слушателя:

public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
 // yes, just leave it empty
}

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

private void isScrollCompleted(AbsListView view, int scrollState) {
    if (!view.canScrollVertically(1) && this.currentScrollState == SCROLL_STATE_IDLE) {
        // do work
    }
}

Кажется, делает то, что должен: определяет конец списка без тонны кода и модификаций адаптера.

person Nikola-L_F    schedule 25.12.2015

Попробуй это...

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

    int position = firstVisibleItem+visibleItemCount;
    int limit = totalItemCount;

    // Check if bottom has been reached
    if (position >= limit && totalItemCount > 0) {
         //scroll end reached
          //Write your code here
    }
}
person user4813506    schedule 21.04.2015

Просто более полезное представление ответа e-cal

Реализация прослушивателя:

public abstract class OnScrollFinishListener implements AbsListView.OnScrollListener {

    int mCurrentScrollState;
    int mCurrentVisibleItemCount;

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mCurrentScrollState = scrollState;
        if (isScrollCompleted()) {
            onScrollFinished();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mCurrentVisibleItemCount = visibleItemCount;
    }

    private boolean isScrollCompleted() {
        return mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE;
    }

    protected abstract void onScrollFinished();
}

Использование:

    listView.setOnScrollListener(new OnScrollFinishListener() {
         @Override
         protected void onScrollFinished() {
             // do whatever you need here!
         }
    });
person Oleksandr    schedule 02.10.2015

Есть еще одно простое решение. Поместите прослушиватель в адаптер списка и в getview проверьте, если (позиция == totalItemCount элементов в массиве для отображения), если да, я вызываю свой прослушиватель из адаптера и в прослушивателе запустите вашу операцию, что бы это ни было

person Ajith Memana    schedule 18.04.2013
comment
Это без учета виртуализации. Вы подумаете, что достигли конца, прежде чем последний элемент станет видимым. - person siger; 20.09.2014

У меня возникло много проблем при попытке выяснить, находится ли список внизу OnScrollListener, лучшее решение, которое я нашел, - это проверить метод getView адаптера, если я в настоящее время показываю последнюю строку:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ActivityView view;
        if (convertView==null)
            view = (ActivityView)inflater.inflate(R.layout.activity_item, null);
        else
            view = (ActivityView)convertView;

       view.showRow(activitiesList.get(position),position,controller,this);
        if(position == activitiesList.size() - 1)
        {
            Log.i("BOTTOM","reached");
        }
        return view;
    }

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

ваше здоровье.

person Gal Rom    schedule 06.05.2015