Respostas:
Editado :
Como estive investigando esse assunto específico em uma de minhas aplicações, posso escrever uma resposta extensa para futuros leitores dessa questão.
Implementar um OnScrollListener
, definir as suas ListView
's onScrollListener
e então você deve ser capaz de lidar com as coisas corretamente.
Por exemplo:
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
for stackFromBottom
? Eu tentei, if (0 == firstVisibleItem){//listviewtop}
mas está sendo chamado repetidamente.
Resposta tardia, mas se você simplesmente deseja verificar se o ListView está rolado para baixo ou não, sem criar um ouvinte de evento, você pode usar esta instrução if:
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
//It is scrolled all the way down here
}
Primeiro, ele verifica se a última posição possível está à vista. Em seguida, ele verifica se a parte inferior do último botão está alinhada com a parte inferior do ListView. Você pode fazer algo semelhante para saber se está no topo:
if (yourListView.getFirstVisiblePosition() == 0 &&
yourListView.getChildAt(0).getTop() >= 0)
{
//It is scrolled all the way up here
}
getChildCount()
retorna as visualizações no grupo de visualizações, que com a reciclagem de visualizações não é o mesmo que o número de itens no adaptador. No entanto, como ListView desce de AdapterView, você pode usar getCount()
diretamente no ListView.
Do jeito que eu fiz:
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) {
}
});
Algo no sentido 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)
}
}
Isso funcionou para mim.
Isso pode ser
@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");
}
}
Foi muito doloroso lidar com a rolagem, detectar quando ela terminou e realmente está no final da lista (não na parte inferior da tela visível), e aciona meu serviço apenas uma vez, para buscar dados da web. No entanto, está funcionando bem agora. O código é o seguinte para o benefício de qualquer pessoa que enfrente a mesma situação.
NOTA: Tive de mover meu código relacionado ao adaptador para onViewCreated em vez de onCreate e detectar a rolagem principalmente assim:
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);
}
}
Aqui, RideListSimpleCursorAdapter.REACHED_THE_END é uma variável adicional em meu SimpleCustomAdapter que é definida assim:
if (position == getCount() - 1) {
REACHED_THE_END = true;
} else {
REACHED_THE_END = false;
}
Somente quando essas duas condições se encontram, isso significa que estou realmente no final da lista e que meu serviço será executado apenas uma vez. Se eu não pegar o REACHED_THE_END, mesmo rolar para trás aciona o serviço novamente, desde que o último item esteja à vista.
canScrollVertically(int direction)
funciona para todas as visualizações e parece fazer o que você pediu, com menos código do que a maioria das outras respostas. Insira um número positivo e, se o resultado for falso, você está na parte inferior.
ie:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
Para expandir um pouco sobre uma das respostas acima, isso é o que eu tive que fazer para que funcionasse completamente. Parece haver cerca de 6 dp de preenchimento integrado dentro de ListViews, e onScroll () estava sendo chamado quando a lista estava vazia. Isso lida com ambas as coisas. Provavelmente poderia ser um pouco otimizado, mas foi escrito mais para maior clareza.
Nota lateral: Eu tentei várias técnicas diferentes de conversão de dp em pixel, e esta dp2px () tem sido a melhor.
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());
}
Não posso comentar ainda porque não tenho reputação suficiente, mas na resposta de @Ali Imran e @Wroclai acho que falta algo. Com esse pedaço de código, uma vez que você atualize o preLast, ele nunca mais executará o Log. No meu problema específico, desejo executar alguma operação toda vez que rolar para o final, mas depois que preLast for atualizado para LastItem, essa operação nunca será executada novamente.
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;
}
}
Com esse "outro", agora você pode executar seu código (Log, neste caso) toda vez que rolar para o final novamente.
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
}
}
}
Para que sua lista chame quando a lista chegar pela última vez e se ocorrer um erro, isso não chamará o endoflistview novamente . Este código também ajudará neste cenário.
@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;
}
onde o LIST_UP_THRESHOLD_VALUE pode ser qualquer valor inteiro (eu usei 5) onde sua lista é rolada para cima e ao retornar ao final, isso chamará o fim da exibição de lista novamente.
Eu descobri uma maneira muito boa de carregar automaticamente o próximo conjunto de páginas de uma maneira que não exija a sua ScrollView
(como a resposta aceita exige).
Em ParseQueryAdapter, há um método chamado getNextPageView
que permite que você forneça sua própria visualização personalizada que aparece no final da lista quando há mais dados para carregar, de modo que só será acionado quando você atingir o final do conjunto de páginas atual (é a visualização "carregar mais .." por padrão). Este método só é chamado quando há mais dados para carregar, por isso é um ótimo lugar para chamar. loadNextPage();
Desta forma, o adaptador faz todo o trabalho duro para você determinar quando os novos dados devem ser carregados e não será chamado se você tiver chegou ao fim do conjunto de dados.
public class YourAdapter extends ParseQueryAdapter<ParseObject> {
..
@Override
public View getNextPageView(View v, ViewGroup parent) {
loadNextPage();
return super.getNextPageView(v, parent);
}
}
Então dentro de sua atividade / fragmento você só precisa configurar o adaptador e novos dados serão atualizados automaticamente para você como mágica.
adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);
Para detectar se o último item está totalmente visível , você pode simplesmente adicionar cálculos na parte inferior do último item visível da visualização por 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;
}
}
Eu fui com:
@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
}
}
No método getView()
(de uma BaseAdapter
classe derivada) pode-se verificar se a posição da visão atual é igual à lista de itens na Adapter
. Se for esse o caso, significa que chegamos ao fim / fim da lista:
@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
}
}
Eu acho uma maneira melhor de detectar o fim da rolagem do listview na parte inferior, primeiro detecte o fim do scoll por esta
Implementação de onScrollListener para detectar o fim da rolagem em um 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! ***/
}
}
finalmente combinar a resposta 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");
}
}
}
};
Muito obrigado aos pôsteres no stackoverflow! Combinei algumas ideias e criei um ouvinte de classe para atividades e fragmentos (portanto, esse código é mais reutilizável, tornando o código mais rápido de escrever e muito mais limpo).
Tudo que você precisa fazer quando você tem minha classe é implementar a interface (e claro, criar um método para ela) que está declarada na minha classe e criar um objeto dessa classe passando argumentos.
/**
* 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;
}
}
}
Você precisa adicionar um recurso de rodapé xml vazio ao listView e detectar se esse rodapé está visível.
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;
}
Então, em seu listener de rolagem listView você faz isso
@
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);
}
Se você definir uma tag em uma visão do último item da visão de lista, mais tarde você pode recuperar a visão com a marca, se a visão for nula é porque a visão não está mais carregada. Como isso:
private class YourAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) {
if (cursor.isLast()) {
viewInYourList.setTag("last");
}
else{
viewInYourList.setTag("notLast");
}
}
}
então, se você precisa saber se o último item foi carregado
View last = yourListView.findViewWithTag("last");
if (last != null) {
// do what you want to do
}
Janwilx72 está certo, mas é min sdk é 21, então eu crio este método:
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;
}
}
para 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{}
Talvez tarde, apenas para retardatários。
Se você estiver usando um adaptador personalizado com seu listview (a maioria das pessoas usa!), Uma bela solução é dada aqui!
https://stackoverflow.com/a/55350409/1845404
O método getView do adaptador detecta quando a lista foi rolada para o último item. Ele também adiciona correção para os raros momentos em que alguma posição anterior é chamada, mesmo depois que o adaptador já renderizou a última visualização.
Eu fiz isso e funciona para mim:
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");
}
}
Isso rolará para baixo na sua lista até a última entrada.
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);