Ответы:
Отредактировано :
Поскольку я исследовал эту конкретную тему в одном из своих приложений, я могу написать расширенный ответ для будущих читателей этого вопроса.
Внедрите 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
IS stackFromBottom
? Я пробовал, if (0 == firstVisibleItem){//listviewtop}
но мне постоянно звонят.
Поздний ответ, но если вы просто хотите проверить, прокручивается ли ваш 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
}
getChildCount()
возвращает представления в группе просмотра, что при повторном использовании представления не совпадает с количеством элементов в адаптере. Однако, поскольку ListView происходит от AdapterView, вы можете использовать его getCount()
непосредственно в ListView.
Как я это сделал:
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) {
}
});
Что-то вроде:
if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
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)
работает для всех представлений и, кажется, делает то, что вы просили, с меньшим количеством кода, чем большинство других ответов. Вставьте положительное число, и если результат окажется ложным, вы окажетесь внизу.
то есть:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
Чтобы немного расширить один из приведенных выше ответов, это то, что мне пришлось сделать, чтобы он работал полностью. Кажется, что внутри 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());
}
Я пока не могу комментировать, потому что у меня недостаточно репутации, но в ответах @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» теперь вы можете выполнять свой код (в данном случае Log) каждый раз, когда вы снова прокручиваете вниз.
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
}
}
}
Чтобы ваш список вызывал, когда список дойдет до последнего, и если произойдет ошибка, то это не вызовет 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();
Таким образом, адаптер выполняет за вас всю тяжелую работу по определению, когда должны быть загружены новые данные, и он не будет вызываться вообще, если у вас есть достиг конца набора данных.
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
}
}
Я нахожу лучший способ определить конец прокрутки списка внизу, сначала обнаружите конец прокрутки с помощью этой
реализации 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! ***/
}
}
наконец объедините ответ Мартейна
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);
}
Если вы установите тег для представления последнего элемента списка, позже вы можете получить представление с тегом, если представление имеет значение 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 прав, но его минимальный 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{}
Может, поздно, только для опоздавших。
Если вы используете специальный адаптер со своим списком (большинство людей так делают!), Здесь дается прекрасное решение!
https://stackoverflow.com/a/55350409/1845404
Метод getView адаптера определяет, когда список был прокручен до последнего элемента. Он также добавляет коррекцию для тех редких случаев, когда вызывается некоторая более ранняя позиция даже после того, как адаптер уже отрендерил последнее представление.
Я так и сделал, и у меня работает:
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");
}
}
Это прокрутит ваш список до последней записи.
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);