Réponses:
Modifié :
Depuis que j'ai enquêté sur ce sujet particulier dans l'une de mes applications, je peux écrire une réponse détaillée pour les futurs lecteurs de cette question.
Mettre en œuvre un OnScrollListener
, définissez vos ListView
« s onScrollListener
et vous devriez être en mesure de gérer les choses correctement.
Par exemple:
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
c'est le cas stackFromBottom
? J'ai essayé if (0 == firstVisibleItem){//listviewtop}
mais cela est appelé à plusieurs reprises.
Réponse tardive, mais si vous souhaitez simplement vérifier si votre ListView défile complètement ou non, sans créer d'écouteur d'événement, vous pouvez utiliser cette instruction if:
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
//It is scrolled all the way down here
}
Il vérifie d'abord si la dernière position possible est en vue. Ensuite, il vérifie si le bas du dernier bouton s'aligne avec le bas de ListView. Vous pouvez faire quelque chose de similaire pour savoir si c'est tout en haut:
if (yourListView.getFirstVisiblePosition() == 0 &&
yourListView.getChildAt(0).getTop() >= 0)
{
//It is scrolled all the way up here
}
getChildCount()
renvoie les vues dans le groupe de vues, qui avec le recyclage des vues n'est pas le même que le nombre d'éléments dans l'adaptateur. Cependant, puisque ListView descend d'AdapterView, vous pouvez l'utiliser getCount()
directement sur ListView.
La façon dont je l'ai fait:
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) {
}
});
Quelque chose à l'effet de:
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)
}
}
Cela a fonctionné pour moi.
Cela peut être
@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");
}
}
C'était assez pénible de gérer le défilement, de détecter quand il est terminé et il se trouve effectivement en bas de la liste (pas en bas de l'écran visible), et ne déclenche mon service qu'une seule fois, pour récupérer des données sur le web. Cependant, cela fonctionne bien maintenant. Le code est le suivant pour le bénéfice de toute personne confrontée à la même situation.
REMARQUE: J'ai dû déplacer le code associé à mon adaptateur dans onViewCreated au lieu de onCreate et détecter le défilement principalement comme ceci:
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);
}
}
Ici, RideListSimpleCursorAdapter.REACHED_THE_END est une variable supplémentaire dans mon SimpleCustomAdapter qui est définie comme ceci:
if (position == getCount() - 1) {
REACHED_THE_END = true;
} else {
REACHED_THE_END = false;
}
Ce n'est que lorsque ces deux conditions sont réunies que cela signifie que je suis effectivement au bas de la liste et que mon service ne fonctionnera qu'une seule fois. Si je n'attrape pas le REACHED_THE_END, même le défilement vers l'arrière déclenche à nouveau le service, tant que le dernier élément est visible.
canScrollVertically(int direction)
fonctionne pour toutes les vues et semble faire ce que vous avez demandé, avec moins de code que la plupart des autres réponses. Branchez un nombre positif, et si le résultat est faux, vous êtes en bas.
c'est à dire:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
Pour développer un peu l'une des réponses ci-dessus, voici ce que j'ai dû faire pour qu'elle fonctionne complètement. Il semble y avoir environ 6dp de remplissage intégré à l'intérieur de ListViews, et onScroll () était appelé lorsque la liste était vide. Cela gère ces deux choses. Il pourrait probablement être un peu optimisé, mais il est écrit plus pour plus de clarté.
Remarque: j'ai essayé plusieurs techniques de conversion de dp en pixels, et celle de dp2px () a été la meilleure.
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());
}
Je ne peux pas encore faire de commentaire car je n'ai pas assez de réputation, mais dans la réponse de @Ali Imran et @Wroclai, je pense qu'il manque quelque chose. Avec ce morceau de code, une fois que vous mettez à jour preLast, il n'exécutera plus jamais le journal. Dans mon problème spécifique, je veux exécuter une opération à chaque fois que je défile vers le bas, mais une fois que preLast est mis à jour vers LastItem, cette opération n'est plus jamais exécutée.
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;
}
}
Avec cet «autre», vous êtes maintenant en mesure d'exécuter votre code (Log, dans ce cas) chaque fois que vous faites défiler à nouveau vers le bas.
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
}
}
}
Pour que votre liste soit appelée lorsque la liste arrive en dernier et si une erreur se produit, cela n'appellera pas à nouveau la vue endoflist . Ce code aidera également ce scénario.
@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;
}
où LIST_UP_THRESHOLD_VALUE peut être n'importe quelle valeur entière (j'ai utilisé 5) où votre liste est défilée vers le haut et en revenant à la fin, cela appellera à nouveau la fin de la vue de liste.
J'ai trouvé un très bon moyen de charger automatiquement l'ensemble de pages suivant d'une manière qui ne nécessite pas la vôtre ScrollView
(comme l'exige la réponse acceptée).
Sur ParseQueryAdapter, il existe une méthode appelée getNextPageView
qui est là pour vous permettre de fournir votre propre vue personnalisée qui apparaît à la fin de la liste lorsqu'il y a plus de données à charger afin qu'elle ne se déclenche que lorsque vous avez atteint la fin de votre ensemble de pages actuel (c'est la vue "charger plus .." par défaut). Cette méthode est seulement appelé quand il y a plus de données à charger il est un excellent endroit pour appeler loadNextPage();
cette façon , l'adaptateur fait tout le travail pour vous pour déterminer lorsque de nouvelles données doivent être chargées et il ne sera pas appelé à tous si vous avez atteint la fin de l'ensemble de données.
public class YourAdapter extends ParseQueryAdapter<ParseObject> {
..
@Override
public View getNextPageView(View v, ViewGroup parent) {
loadNextPage();
return super.getNextPageView(v, parent);
}
}
Ensuite, dans votre activité / fragment, il vous suffit de définir l'adaptateur et les nouvelles données seront automatiquement mises à jour pour vous comme par magie.
adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);
Pour détecter si le dernier élément est entièrement visible , vous pouvez simplement ajouter un calcul au bas du dernier élément visible de la vue 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;
}
}
J'y suis allé avec:
@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
}
}
Dans la méthode getView()
(d'une BaseAdapter
classe dérivée), on peut vérifier si la position de la vue courante est égale à la liste des éléments dans le Adapter
. Si tel est le cas, cela signifie que nous avons atteint la fin / le bas de la liste:
@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
}
}
Je trouve un meilleur moyen de détecter le défilement de la liste en bas, d'abord détecter la fin du scoll par cette
implémentation de onScrollListener pour détecter la fin du défilement dans un 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! ***/
}
}
combine enfin la réponse de 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");
}
}
}
};
Un grand merci aux affiches dans stackoverflow! J'ai combiné quelques idées et créé un écouteur de classe pour les activités et les fragments (donc ce code est plus réutilisable, rendant le code plus rapide à écrire et beaucoup plus propre).
Tout ce que vous avez à faire lorsque vous avez ma classe est d'implémenter l'interface (et bien sûr de créer une méthode pour cela) qui est déclarée dans ma classe et de créer un objet de cette classe en passant des arguments.
/**
* 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;
}
}
}
Vous devez ajouter une ressource de pied de page XML vide à votre listView et détecter si ce pied de page est visible.
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;
}
Ensuite, dans votre listView, vous faites cela
@
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);
}
Si vous définissez une balise sur une vue du dernier élément de la vue de liste, plus tard vous pourrez récupérer la vue avec la balise, si la vue est nulle c'est parce que la vue n'est plus chargée. Comme ça:
private class YourAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) {
if (cursor.isLast()) {
viewInYourList.setTag("last");
}
else{
viewInYourList.setTag("notLast");
}
}
}
puis si vous avez besoin de savoir si le dernier élément est chargé
View last = yourListView.findViewWithTag("last");
if (last != null) {
// do what you want to do
}
Janwilx72 a raison, mais le sdk minimum est 21, donc je crée cette méthode :
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;
}
}
pour 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{}
Peut-être en retard, juste pour les retardataires。
Si vous utilisez un adaptateur personnalisé avec votre liste (la plupart des gens le font!), Une belle solution est donnée ici!
https://stackoverflow.com/a/55350409/1845404
La méthode getView de l'adaptateur détecte le moment où la liste a fait défiler jusqu'au dernier élément. Il ajoute également une correction pour les rares fois où une position antérieure est appelée même après que l'adaptateur a déjà rendu la dernière vue.
Je l'ai fait et travaille pour moi:
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");
}
}
Cela fera défiler votre liste jusqu'à la dernière entrée.
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);