【发布时间】:2011-07-04 15:59:10
【问题描述】:
我可以知道我的 ListView 是否滚动到底部吗?我的意思是最后一项是完全可见的。
【问题讨论】:
我可以知道我的 ListView 是否滚动到底部吗?我的意思是最后一项是完全可见的。
【问题讨论】:
已编辑:
由于我一直在我的一个应用程序中研究这个特定主题,我可以为这个问题的未来读者写一个扩展答案。
实现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 是 stackFromBottom 我该怎么办?我试过if (0 == firstVisibleItem){//listviewtop},但它被反复调用。
大意是:
if (getListView().getLastVisiblePosition() == (adapter.items.size() - 1))
【讨论】:
迟到的答案,但如果您只是想检查您的 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,您可以直接在 ListView 上使用getCount()。
这会将您的列表向下滚动到最后一个条目。
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);
【讨论】:
处理滚动非常痛苦,检测它何时完成并且它确实位于列表的底部(而不是可见屏幕的底部),并且仅触发我的服务一次,以从网络获取数据。但是它现在工作正常。代码如下,方便遇到同样情况的人。
注意:我必须将与适配器相关的代码移动到 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,只要最后一个项目在视图中,即使向后滚动也会再次触发服务。
【讨论】:
为了扩展上述答案之一,这是我必须做的才能让它完全工作。 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());
}
【讨论】:
当列表到达最后时调用您的列表,如果发生错误,则不会再次调用 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),您的列表向上滚动并返回到末尾时,这将再次调用列表视图的末尾。
【讨论】:
这可以是
@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");
}
}
【讨论】:
我找到了一种非常好的方式来自动加载下一页集,这种方式不需要您自己的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;
}
}
【讨论】:
我还不能发表评论,因为我没有足够的声誉,但在 @Ali Imran 和 @Wroclai 的回答中,我认为缺少一些东西。使用那段代码,一旦您更新 preLast,它将永远不会再次执行 Log。 在我的具体问题中,我想在每次滚动到底部时执行一些操作,但是一旦 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”,您现在可以在每次再次滚动到底部时执行代码(在本例中为日志)。
【讨论】:
我去了:
@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
}
}
【讨论】:
我的做法:
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) {
}
});
【讨论】:
canScrollVertically(int direction) 适用于所有视图,并且似乎按照您的要求执行,与大多数其他答案相比,代码更少。插入一个正数,如果结果为假,你就在底部。
即:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
【讨论】:
我找到了一种更好的方法来检测listview滚动到底部,首先通过这个检测scoll end
Implementation of onScrollListener to detect the end of scrolling in a 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! ***/
}
}
最后结合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");
}
}
}
};
【讨论】:
在方法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
}
}
【讨论】:
非常感谢 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;
}
}
}
【讨论】:
你需要给你的listView添加一个空的xml页脚资源,并检测这个页脚是否可见。
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);
}
【讨论】:
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)
}
}
这对我有用。
【讨论】:
如果您在列表视图的最后一项的视图上设置标签,稍后您可以使用该标签检索视图,如果视图为空,那是因为视图不再加载。像这样:
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;
}
}
对于滚动方向:
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
}
}
}
【讨论】:
如果您在列表视图中使用自定义适配器(大多数人都这样做!)这里给出了一个漂亮的解决方案!
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");
}
}
【讨论】: