找出ListView是否滚动到底部?
我可以找出我的ListView是否滚动到底部? 我的意思是,最后一个项目是完全可见的。
编辑 :
因为我在一个应用程序中一直在研究这个特定的主题,所以我可以为这个问题的未来读者写一个扩展的答案。
实现一个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; } } } }
迟到的答案,但如果你只是想检查你的ListView是否一直向下滚动,而不创build一个事件监听器,你可以使用这个if语句:
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 && yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight()) { //It is scrolled all the way down here }
首先检查最后一个可能的位置是否在视野中。 然后检查最后一个button的底部是否与ListView的底部alignment。 你可以做类似的事情,知道它是否在顶部:
if (yourListView.getFirstVisiblePosition() == 0 && yourListView.getChildAt(0).getTop() >= 0) { //It is scrolled all the way up here }
一些事情的影响:
if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
我这样做的方式:
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) { } });
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) } }
这对我有效。
这可以
@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"); } }
处理滚动,检测何时完成,确实位于列表的底部(而不是可见屏幕的底部),以及触发我的服务一次,以便从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中的一个附加variables,它是这样设置的:
if (position == getCount() - 1) { REACHED_THE_END = true; } else { REACHED_THE_END = false; }
只有当这两个条件相遇时,这就意味着我确实在名单的底部,而且我的服务只会运行一次。 如果我不赶上REACHED_THE_END,只要最后一个项目在视图中,即使向后滚动,也会再次触发服务。
canScrollVertically(int direction)
适用于所有视图,并且似乎按照您的要求进行操作,代码less于大多数其他答案。 插入一个正数,如果结果是错误的,那么你在底部。
即:
if (!yourView.canScrollVertically(1)) { //you've reached bottom }
为了扩大上面的答案之一,这是我必须做的,以完成它的工作。 在ListViews中似乎有大约6dp的内置填充,onScroll()在列表为空时被调用。 这处理这两件事情。 它可能会稍微优化一下,但为了清晰起见,这些内容会更加清晰。
附注:我已经尝试了几种不同的像素转换技术,这个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()); }
我还不能评论,因为我没有足够的声誉,但在@Ali Imran和@Wroclai的答案我觉得缺less一些东西。 有了这段代码,一旦你更新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; }
}
有了这个“其他”,你现在可以执行你的代码(在这种情况下,日志),每次你再次滚动到底部。
当列表到达最后,如果错误发生,那么这个列表不会再次调用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),其中列表向上滚动,在返回结束时,这将再次调用列表视图的结束。
我发现一个非常好的方式来自动加载下一页的方式,不需要自己的ScrollView
(如接受的答案要求)。
在ParseQueryAdapter上,有一个名为getNextPageView
的方法,它允许您在需要加载更多数据时提供自己的自定义视图,以便在您到达当前页面的末尾时触发设置(默认是“加载更多”视图)。 这个方法只有在有更多的数据要加载的时候调用,所以这是一个调用loadNextPage();
的好地方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);
要检测最后一个项目是否完全可见 ,您可以通过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; } }
我跟着去了
@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 } }
在方法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 } }
我发现一个更好的方法来检测listview滚动结束底部,首先检测到scoll结束
实现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! ***/ } }
最后结合Martijn的答案
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"); } } } };
非常感谢海报在stackoverflow! 我结合了一些想法,并创build了活动和片段的类监听器(所以这个代码更加可重用,使得代码写得更快,更干净)。
当你得到我的类时,你所要做的就是实现在我的类中声明的接口(当然也包括它的创build方法),并创build这个类的对象来传递参数。
/** * 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; } } }
你需要添加一个空的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); }
如果您在列表视图的最后一项的视图上设置了一个标签,稍后您可以使用标签检索视图,如果视图为空,则是因为视图不再被加载。 喜欢这个:
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 }
Janwilx72是正确的,但是最小sdk是21,所以我创build这个方法:
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{}
也许迟到了,只是为了latecommers。
这会将您的列表向下滚动到最后一个条目。
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);