在ListView中实现onScrollListener以便检测滚动是否到达底部

42
我有一个显示一些项目的ListView。我想对当前在可见部分的ListView上执行一些操作,具体取决于ListView的滚动方式。因此,我打算实现ListView的OnScrollListener。
根据Android API参考,onScroll方法“将在滚动完成后调用”。这对我来说似乎就是我需要的,因为一旦滚动完成,我就可以对ListView执行我的操作(onScroll方法返回第一个显示项的索引和显示的项数)。
但是一旦实现,我从LogCat中看到onScroll方法不仅在滚动完成后触发,而且会为每个进入显示视图的新项触发,从滚动开始到结束。这不是我期望的行为,也不是我需要的行为。监听器的另一个方法(onScrollStateChanged)则无法提供有关ListView中当前显示的项目的信息。
那么,有人知道如何使用这对方法来检测滚动的结束并获取有关显示项目的信息吗?API参考和方法的实际行为之间的不一致使我有点困惑。
先谢谢了。
注:我看到了一些类似的话题,但没有任何帮助我理解整个过程...!

尝试一下这个,我认为它会很有帮助 https://github.com/commonsguy/cwac-endless 你可以使用无限适配器。 - Jabbir Basha
9个回答

54

最终我找到了一种解决方案,虽然不太优雅但对我有用。我发现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)时,我会保存状态并调用另一个方法来判断是否已经滚动以及是否已完成。通过这种方式,我也有了所需的可见项数据。

可能不够简洁但有效!

谢谢


1
我在我的项目中使用了类似的东西,效果非常好。简单而有效: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(); } } - Abandoned Cart
我该如何使用您的代码一次显示10个项目,然后再加载另外10个项目。请帮忙!!! - android_developer
不需要 currentFirstVisibleItem - Pratik Butani
非常感谢您...这太优雅了。 - Pratik Butani
currentFirstVisibleItem 可以与 0 进行比较,以检测滚动到顶部。 - Artem Mostyaev

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

  if (list.getLastVisiblePosition() == list.getAdapter().getCount() - 1
      && list.getChildAt(list.getChildCount() - 1).getBottom() <= list.getHeight()) {
    // Scroll end reached
    // Write your code here
  }
}

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

            if((firstVisibleItem + visibleItemCount) ==  totalItemCount)
            {
                Log.i("Info", "Scroll Bottom" );
            }
        }});

6
为了实现这种行为是比较棘手的,我花了相当长时间去完善。问题的核心在于,单独使用滚动监听器并不足以检测到“滚动停止”(包括方向按钮/轨迹球),据我所知。最终,我采取了一些组合方法,达到了我期望的效果。
我想到的最好的方法是扩展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。我认为这种实现很好,因为它不会立即触发“事件”,而是等待一段时间,直到完全停止滚动。

谢谢,你的回答对我很有帮助,但现在对我的目的来说有点太棘手了;我已经找到了另一种解决方案,我会在下面发布... - e-cal
@dmon 你的意思是说 theRunnableWithYourOnScrollStopCode 是我可以放置函数的地方:比如加载数据或发起网络请求吗?谢谢! - Alston

4
分析之前的答案,发现以下方法:
在onViewCreated、onCreate或任何你获取listview的地方:
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
    }
}

看起来它做到了应该做到的事情:检测列表结束而不需要大量代码和适配器的修改。


3
试试这个...
@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
    }
}

3

请提供更有用的e-cal's answer表示

监听器实现:

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!
         }
    });

0

当我尝试从OnScrollListener中找出List是否在底部时,遇到了很多问题。我发现最好的解决方案是从适配器的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;
    }

在那里,我可以轻松创建一个接口来通知Fragment/Activity我已经到达底部。

谢谢。


0

还有一种简单的解决方案。在listview的适配器中添加一个监听器,在getview中检查是否(position == 要显示的数组中的总项目数),如果是,我会从适配器中调用我的监听器,并在监听器中运行您想要的操作。


这并没有考虑虚拟化。你会认为已经到达了末尾,但实际上最后一个项目还没有显示出来。 - siger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接