Узнайте, если ListView прокручивается в нижней части?
могу ли я узнать, если мой ListView прокручивается вниз? Под этим я подразумеваю, что последний элемент полностью виден.
22 ответа:
редактировать:
так как я исследовал в этой конкретной теме в одном из моих приложений, я могу написать расширенный ответ для будущих читателей этого вопроса.
реализовать
OnScrollListener
, установитьListView
' sonScrollListener
и тогда вы должны быть в состоянии справиться с вещами правильно.например:
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 полностью или нет, без создания прослушивателя событий, вы можете использовать этот оператор 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 }
Как я это сделал:
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"); } }
было довольно больно иметь дело с прокруткой, обнаруживая, когда она закончена, и она действительно находится в нижней части списка (а не в нижней части видимого экрана), и запускает мой сервис только один раз, чтобы получить данные из интернета. Однако сейчас он работает нормально. Код выглядит следующим образом в интересах всех, кто сталкивается с такой же ситуацией.
Примечание: мне пришлось переместить код, связанный с адаптером, в 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, даже прокрутка назад снова запускает службу, пока последний элемент находится в поле зрения.
canScrollVertically(int direction)
работает для всех представлений и, кажется, делает то, что вы просили, с меньшим кодом, чем большинство других ответов. Подключите положительное число, и если результат будет ложным, вы находитесь внизу.ie:
if (!yourView.canScrollVertically(1)) { //you've reached bottom }
чтобы немного расширить один из приведенных выше ответов, это то, что я должен был сделать, чтобы заставить его работать полностью. Там, кажется, около 6dp встроенного заполнения внутри ListViews, и 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()); }
Я пока не могу комментировать, потому что у меня недостаточно репутации, но в ответе @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" вы теперь можете выполнить свой код (Лог, в данном случае) каждый раз, когда вы прокручиваете вниз еще раз.
для вашего списка, чтобы вызвать, когда список достигнет последнего, и если произойдет ошибка, то это не вызовет 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
(как и требуется принятый ответ).On 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);
чтобы определить, является ли последний элемент полностью видны, вы можете просто добавить расчет на нижней части последнего видимого элемента представления по
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()
(of aBaseAdapter
-производный класс) можно проверить, если позиция текущего представления равна списку элементов в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 для обнаружения конца прокрутки в ListViewpublic 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"); } } } };
большое спасибо плакатам в 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; } } }
вам нужно добавить пустой ресурс нижнего колонтитула 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); }
Если вы установите тег на представление последнего элемента 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 }
Janwilx72 прав,но это min 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{}
может быть, поздно, только для опоздавших。
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 } } }