找出ListView是否滚动到底部?


105

我可以确定ListView是否滚动到底部吗?我的意思是说最后一项是完全可见的。

Answers:


171

编辑

由于我已经在我的一个应用程序中研究了这个特定主题,因此我可以为这个问题的未来读者写一个扩展的答案。

实施OnScrollListener,设置您ListViewonScrollListener,然后您应该能够正确处理。

例如:

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 2011年

1
如果列表视图中有页眉或页脚,则也必须考虑到这一点。
Martin Marconcini 2013年

5
@Wroclai这不会检测到最后一项是否完全可见。
Gaurav Arora

我在寻找一些类似的工作代码。非常感谢!!
Suraj Dubey 2014年

我不想提出一个新问题,但是如果我listview是我该怎么办stackFromBottom?我试过了,if (0 == firstVisibleItem){//listviewtop}但是一再被打电话。
shadyinside 2014年

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 2012年

@OferR不确定,但是我认为会getChildCount()返回视图组中的视图,而视图回收与适配器中的项数不同。但是,由于ListView源自AdapterView,因此可以getCount()直接在ListView上使用。
威廉·T·马拉德2014年

@ William T. Mallard您的第一句话是对的,这正是我们想要的:该组中显示的最后一个视图。这是为了验证它是完全可见的。(考虑一个有20行的ListView,但仅显示最后8行。我们希望在ViewGroup中获得第8个View,而不是不存在的第20个)
OferR 2014年

请注意,如果您要快速更新列表视图,则此解决方案将不起作用。
松鼠

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
这很棒!谢谢
伊斯兰教徒伊斯兰教'18


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

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

处理滚动,检测何时完成以及确实位于列表的底部(而不是可见屏幕的底部),这非常痛苦,并且仅触发一次我的服务即可从Web上获取数据。但是,现在工作正常。该代码如下,以使面临相同情况的任何人受益。

注意:我必须将与适配器相关的代码移到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,只要最后一个项目在视野中,即使向后滚动也能再次触发该服务。


我摆脱了其他问题,因为这给我带来了麻烦,但总体而言是个不错的答案
Mike Baglio Jr.

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,它将永远不会再次执行Log。在我的特定问题中,我希望每次滚动到底部时都执行一些操作,但是一旦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

我找到了一种更好的方法来检测listview滚动结束的底部,首先通过此
onScrollListener的实现来检测scoll结束,以检测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

您需要向您的listView添加一个空的xml页脚资源,并检测此页脚是否可见。

    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

如果在listview的最后一个项目的视图上设置标签,则以后可以使用标签检索该视图,如果该视图为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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.