Antworten:
Bearbeitet :
Da ich mich in einer meiner Bewerbungen mit diesem speziellen Thema befasst habe, kann ich eine erweiterte Antwort für zukünftige Leser dieser Frage schreiben.
Implementieren Sie eine OnScrollListener
, stellen Sie Ihre ListView
ein onScrollListener
und dann sollten Sie in der Lage sein, die Dinge richtig zu handhaben.
Beispielsweise:
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
ist stackFromBottom
? Ich habe es versucht, if (0 == firstVisibleItem){//listviewtop}
aber das wird immer wieder aufgerufen.
Späte Antwort, aber wenn Sie einfach überprüfen möchten, ob Ihre ListView ganz nach unten gescrollt ist oder nicht, ohne einen Ereignis-Listener zu erstellen, können Sie diese if-Anweisung verwenden:
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
//It is scrolled all the way down here
}
Zunächst wird geprüft, ob die zuletzt mögliche Position sichtbar ist. Anschließend wird überprüft, ob der untere Rand der letzten Schaltfläche mit dem unteren Rand der ListView übereinstimmt. Sie können etwas Ähnliches tun, um zu wissen, ob es ganz oben ist:
if (yourListView.getFirstVisiblePosition() == 0 &&
yourListView.getChildAt(0).getTop() >= 0)
{
//It is scrolled all the way up here
}
getChildCount()
die Ansichten in der Ansichtsgruppe werden zurückgegeben, was beim Ansichtsrecycling nicht mit der Anzahl der Elemente im Adapter übereinstimmt. Da ListView jedoch von AdapterView abstammt, können Sie es getCount()
direkt in ListView verwenden.
So wie ich es gemacht habe:
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) {
}
});
Etwas zu bewirken von:
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)
}
}
Das hat bei mir funktioniert.
Das kann sein
@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");
}
}
Es war ziemlich schmerzhaft, mit dem Scrollen umzugehen, zu erkennen, wann es fertig ist, und es befindet sich tatsächlich am Ende der Liste (nicht am unteren Rand des sichtbaren Bildschirms) und löst meinen Dienst nur einmal aus, um Daten aus dem Web abzurufen. Allerdings funktioniert es jetzt gut. Der Code lautet wie folgt für alle, die sich der gleichen Situation gegenübersehen.
HINWEIS: Ich musste meinen adapterbezogenen Code in onViewCreated anstelle von onCreate verschieben und das Scrollen hauptsächlich so erkennen:
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);
}
}
Hier ist RideListSimpleCursorAdapter.REACHED_THE_END eine zusätzliche Variable in meinem SimpleCustomAdapter, die wie folgt festgelegt ist:
if (position == getCount() - 1) {
REACHED_THE_END = true;
} else {
REACHED_THE_END = false;
}
Nur wenn beide Bedingungen erfüllt sind, bedeutet dies, dass ich tatsächlich ganz unten auf der Liste stehe und mein Dienst nur einmal ausgeführt wird. Wenn ich REACHED_THE_END nicht abfange, wird der Dienst auch beim Zurückblättern erneut ausgelöst, solange das letzte Element angezeigt wird.
canScrollVertically(int direction)
funktioniert für alle Ansichten und scheint das zu tun, was Sie gefragt haben, mit weniger Code als die meisten anderen Antworten. Geben Sie eine positive Zahl ein, und wenn das Ergebnis falsch ist, sind Sie ganz unten.
dh:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
Um eine der oben genannten Antworten ein wenig zu erweitern, musste ich Folgendes tun, damit sie vollständig funktioniert. In ListViews scheint ein integrierter Abstand von ca. 6 dp vorhanden zu sein, und onScroll () wurde aufgerufen, als die Liste leer war. Dies behandelt beide Dinge. Es könnte wahrscheinlich ein bisschen optimiert werden, ist aber aus Gründen der Klarheit mehr geschrieben.
Randnotiz: Ich habe verschiedene Konvertierungstechniken von dp zu Pixel ausprobiert, und diese dp2px () war die beste.
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());
}
Ich kann noch keinen Kommentar abgeben, weil ich nicht genug Ruf habe, aber in der Antwort von @Ali Imran und @Wroclai denke ich, dass etwas fehlt. Mit diesem Code wird das Protokoll nach dem Aktualisieren von preLast nie wieder ausgeführt. In meinem speziellen Problem möchte ich jedes Mal, wenn ich nach unten scrolle, eine Operation ausführen. Sobald preLast jedoch auf LastItem aktualisiert wurde, wird diese Operation nie wieder ausgeführt.
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;
}
}}
Mit diesem "else" können Sie jetzt Ihren Code (in diesem Fall Log) jedes Mal ausführen, wenn Sie erneut nach unten scrollen.
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
}
}
}
Wenn Ihre Liste aufgerufen wird, wenn die Liste das letzte Mal erreicht ist und ein Fehler auftritt, wird die Endoflist-Ansicht nicht erneut aufgerufen . Dieser Code hilft auch in diesem Szenario.
@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;
}
Dabei kann LIST_UP_THRESHOLD_VALUE ein beliebiger ganzzahliger Wert sein (ich habe 5 verwendet), bei dem Ihre Liste nach oben gescrollt wird. Wenn Sie zum Ende zurückkehren, wird das Ende der Listenansicht erneut aufgerufen.
Ich habe eine sehr gute Möglichkeit gefunden, das nächste Seitenset automatisch so zu laden, dass es kein eigenes erfordert ScrollView
(wie es die akzeptierte Antwort erfordert).
In ParseQueryAdapter gibt es eine Methode namens getNextPageView
, mit der Sie Ihre eigene benutzerdefinierte Ansicht bereitstellen können, die am Ende der Liste angezeigt wird, wenn mehr Daten geladen werden müssen, sodass sie erst ausgelöst wird, wenn Sie das Ende Ihres aktuellen Seitensatzes erreicht haben (Dies ist standardmäßig die Ansicht "Mehr laden ..."). Diese Methode wird nur aufgerufen, wenn mehr Daten geladen werden müssen. Daher ist dies ein großartiger Ort zum Aufrufen. Auf loadNextPage();
diese Weise erledigt der Adapter die ganze harte Arbeit für Sie, um zu bestimmen, wann neue Daten geladen werden sollen, und wird bei Bedarf überhaupt nicht aufgerufen hat das Ende des Datensatzes erreicht.
public class YourAdapter extends ParseQueryAdapter<ParseObject> {
..
@Override
public View getNextPageView(View v, ViewGroup parent) {
loadNextPage();
return super.getNextPageView(v, parent);
}
}
Dann müssen Sie in Ihrer Aktivität / Ihrem Fragment nur noch den Adapter einstellen, und neue Daten werden automatisch wie von Zauberhand für Sie aktualisiert.
adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);
Um festzustellen, ob das letzte Element vollständig sichtbar ist , können Sie einfach eine Berechnung am unteren sichtbaren Element der Ansicht unten hinzufügen 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;
}
}
Ich ging mit:
@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
}
}
In der Methode getView()
(einer BaseAdapter
abgeleiteten Klasse) kann überprüft werden, ob die Position der aktuellen Ansicht der Liste der Elemente in der entspricht Adapter
. Wenn dies der Fall ist, bedeutet dies, dass wir das Ende / Ende der Liste erreicht haben:
@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
}
}
Ich finde einen besseren Weg, um das Bildlaufende der Listenansicht unten zu erkennen. Erkennen Sie zuerst das Ende des Bildlaufs durch diese
Implementierung von onScrollListener, um das Ende des Bildlaufs in einer Listenansicht zu erkennen
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! ***/
}
}
Kombiniere endlich Martijns Antwort
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");
}
}
}
};
Vielen Dank an die Poster im Stackoverflow! Ich habe einige Ideen kombiniert und einen Klassen-Listener für Aktivitäten und Fragmente erstellt (daher ist dieser Code wiederverwendbarer, sodass Code schneller geschrieben und viel sauberer wird).
Alles, was Sie tun müssen, wenn Sie meine Klasse erhalten haben, ist, die in meiner Klasse deklarierte Schnittstelle (und natürlich die Erstellungsmethode dafür) zu implementieren und ein Objekt dieser Klasse zu erstellen, das Argumente übergibt.
/**
* 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;
}
}
}
Sie müssen Ihrer listView eine leere XML-Fußzeilenressource hinzufügen und feststellen, ob diese Fußzeile sichtbar ist.
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;
}
Dann tun Sie dies in Ihrem listView-Scroll-Listener
@
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);
}
Wenn Sie ein Tag für eine Ansicht des letzten Elements der Listenansicht festlegen, können Sie später die Ansicht mit dem Tag abrufen. Wenn die Ansicht null ist, liegt dies daran, dass die Ansicht nicht mehr geladen wird. So was:
private class YourAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) {
if (cursor.isLast()) {
viewInYourList.setTag("last");
}
else{
viewInYourList.setTag("notLast");
}
}
}
dann, wenn Sie wissen müssen, ob der letzte Artikel geladen ist
View last = yourListView.findViewWithTag("last");
if (last != null) {
// do what you want to do
}
Janwilx72 ist richtig, aber es ist min sdk ist 21, also erstelle ich diese Methode:
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;
}
}
für 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{}
Vielleicht spät, nur für Spätankömmlinge。
Wenn Sie einen benutzerdefinierten Adapter mit Ihrer Listenansicht verwenden (die meisten Leute tun dies!), Wird hier eine schöne Lösung angegeben!
https://stackoverflow.com/a/55350409/1845404
Die getView-Methode des Adapters erkennt, wenn die Liste zum letzten Element gescrollt wurde. Außerdem werden Korrekturen für die seltenen Fälle hinzugefügt, in denen eine frühere Position aufgerufen wird, selbst nachdem der Adapter die letzte Ansicht bereits gerendert hat.
Ich habe das gemacht und arbeite für mich:
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");
}
}
Dadurch wird Ihre Liste bis zum letzten Eintrag nach unten gescrollt.
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);