Câu trả lời:
Đã chỉnh sửa :
Vì tôi đã tìm hiểu về chủ đề cụ thể này trong một trong các ứng dụng của mình, tôi có thể viết một câu trả lời mở rộng cho những độc giả trong tương lai của câu hỏi này.
Thực hiện một OnScrollListener
, thiết lập của bạn ListView
's onScrollListener
và sau đó bạn sẽ có thể xử lý mọi thứ một cách chính xác.
Ví dụ:
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
là câu hỏi của tôi stackFromBottom
? Tôi đã thử if (0 == firstVisibleItem){//listviewtop}
nhưng nó được gọi liên tục.
Câu trả lời muộn, nhưng nếu bạn chỉ muốn kiểm tra xem ListView của mình có được cuộn xuống hết hay không mà không cần tạo trình xử lý sự kiện, bạn có thể sử dụng câu lệnh if này:
if (yourListView.getLastVisiblePosition() == yourListView.getAdapter().getCount() -1 &&
yourListView.getChildAt(yourListView.getChildCount() - 1).getBottom() <= yourListView.getHeight())
{
//It is scrolled all the way down here
}
Đầu tiên nó kiểm tra xem vị trí cuối cùng có thể có trong tầm nhìn hay không. Sau đó, nó sẽ kiểm tra xem phần dưới cùng của nút cuối cùng có khớp với phần dưới cùng của ListView hay không. Bạn có thể làm điều gì đó tương tự để biết nếu nó ở trên cùng:
if (yourListView.getFirstVisiblePosition() == 0 &&
yourListView.getChildAt(0).getTop() >= 0)
{
//It is scrolled all the way up here
}
getChildCount()
trả về các chế độ xem trong nhóm xem, với chế độ xem tái chế không giống với số lượng mục trong bộ điều hợp. Tuy nhiên, vì ListView chuyển xuống từ AdapterView nên bạn có thể sử dụng getCount()
trực tiếp trên ListView.
Cách tôi đã làm điều đó:
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) {
}
});
Điều gì đó ảnh hưởng đến:
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)
}
}
Điều này đã làm việc cho tôi.
Điều này có thể là
@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");
}
}
Khá khó khăn khi phải xử lý việc cuộn, phát hiện khi nào nó hoàn thành và nó thực sự nằm ở cuối danh sách (không phải cuối màn hình hiển thị) và chỉ kích hoạt dịch vụ của tôi một lần, để tìm nạp dữ liệu từ web. Tuy nhiên nó đang hoạt động tốt bây giờ. Mã như sau vì lợi ích của bất kỳ ai gặp phải tình huống tương tự.
LƯU Ý: Tôi đã phải di chuyển mã liên quan đến bộ điều hợp của mình vào onViewCreate thay vì onCreate và phát hiện cuộn chủ yếu như thế này:
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);
}
}
Đây RideListSimpleCursorAdapter.REACHED_THE_END là một biến bổ sung trong SimpleCustomAdapter của tôi được đặt như thế này:
if (position == getCount() - 1) {
REACHED_THE_END = true;
} else {
REACHED_THE_END = false;
}
Chỉ khi cả hai điều kiện này đáp ứng, điều đó có nghĩa là tôi thực sự ở cuối danh sách và dịch vụ của tôi sẽ chỉ chạy một lần. Nếu tôi không bắt được REACHED_THE_END, ngay cả việc cuộn ngược cũng sẽ kích hoạt lại dịch vụ, miễn là mục cuối cùng ở chế độ xem.
canScrollVertically(int direction)
hoạt động cho tất cả các Chế độ xem và dường như thực hiện những gì bạn yêu cầu, với ít mã hơn hầu hết các câu trả lời khác. Hãy cắm một số dương và nếu kết quả là sai, bạn đang ở dưới cùng.
I E:
if (!yourView.canScrollVertically(1)) {
//you've reached bottom
}
Để mở rộng một chút về một trong các câu trả lời trên, đây là những gì tôi phải làm để nó hoạt động hoàn toàn. Dường như có khoảng 6dp đệm tích hợp bên trong ListViews và onScroll () đã được gọi khi danh sách trống. Điều này xử lý cả hai điều đó. Nó có thể được tối ưu hóa một chút, nhưng được viết nhiều hơn để rõ ràng hơn.
Lưu ý: Tôi đã thử một số kỹ thuật chuyển đổi dp sang pixel khác nhau và kỹ thuật dp2px () này là tốt nhất.
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());
}
Tôi chưa thể bình luận vì tôi chưa có đủ danh tiếng, nhưng trong câu trả lời của @Ali Imran và @Wroclai, tôi nghĩ rằng điều gì đó còn thiếu sót. Với đoạn mã đó, khi bạn cập nhật preLast, nó sẽ không bao giờ thực thi Nhật ký nữa. Trong vấn đề cụ thể của tôi, tôi muốn thực hiện một số hoạt động mỗi khi tôi cuộn xuống dưới cùng, nhưng khi preLast được cập nhật thành LastItem, hoạt động đó sẽ không bao giờ được thực thi nữa.
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;
}
}
Với "else" đó bây giờ bạn có thể thực thi mã của mình (Nhật ký, trong trường hợp này là) mỗi khi bạn cuộn xuống dưới cùng một lần nữa.
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
}
}
}
Đối với danh sách của bạn để gọi khi danh sách đến cuối cùng và nếu xảy ra lỗi, thì điều này sẽ không gọi lại endoflistview . Mã này cũng sẽ giúp ích cho tình huống này.
@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;
}
trong đó LIST_UP_THRESHOLD_VALUE có thể là bất kỳ giá trị số nguyên nào (tôi đã sử dụng 5) nơi danh sách của bạn được cuộn lên và trong khi quay lại cuối, điều này sẽ gọi lại chế độ xem cuối danh sách.
Tôi đã tìm thấy một cách rất hay để tự động tải trang tiếp theo được đặt theo cách không yêu cầu của riêng bạn ScrollView
(như câu trả lời được chấp nhận yêu cầu).
Trên ParseQueryAdapter có một phương thức được gọi getNextPageView
là ở đó để cho phép bạn cung cấp chế độ xem tùy chỉnh của riêng bạn xuất hiện ở cuối danh sách khi có nhiều dữ liệu hơn để tải, vì vậy nó sẽ chỉ kích hoạt khi bạn đã đến cuối tập trang hiện tại. (theo mặc định đó là chế độ xem "tải thêm .."). Phương thức này chỉ được gọi khi có nhiều dữ liệu hơn để tải vì vậy đây là một nơi tuyệt vời để gọi. loadNextPage();
Cách này bộ điều hợp thực hiện tất cả công việc khó khăn cho bạn trong việc xác định khi nào dữ liệu mới nên được tải và nó sẽ không được gọi nếu bạn có đến cuối tập dữ liệu.
public class YourAdapter extends ParseQueryAdapter<ParseObject> {
..
@Override
public View getNextPageView(View v, ViewGroup parent) {
loadNextPage();
return super.getNextPageView(v, parent);
}
}
Sau đó, bên trong hoạt động / phân đoạn của bạn, bạn chỉ cần đặt bộ điều hợp và dữ liệu mới sẽ tự động được cập nhật cho bạn như một phép thuật.
adapter = new YourAdapter(getActivity().getApplicationContext());
adapter.setObjectsPerPage(15);
adapter.setPaginationEnabled(true);
yourList.setAdapter(adapter);
Để phát hiện xem mục cuối cùng có hiển thị đầy đủ hay không , bạn có thể thêm phép tính đơn giản vào mục hiển thị cuối cùng của chế độ xem bằng cách 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;
}
}
Tôi đã đi với:
@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
}
}
Trong phương thức getView()
(của một BaseAdapter
lớp được xác định), người ta có thể kiểm tra xem vị trí của dạng xem hiện tại có bằng với danh sách các mục trong Adapter
. Nếu đúng như vậy, thì có nghĩa là chúng ta đã đến cuối / cuối danh sách:
@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
}
}
Tôi tìm thấy một cách tốt hơn để phát hiện cuối cuộn listview ở dưới cùng, trước tiên hãy phát hiện đầu cuối của cuộn bằng cách
Triển khai onScrollListener này để phát hiện phần cuối của cuộn trong 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! ***/
}
}
cuối cùng kết hợp câu trả lời của 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");
}
}
}
};
Cảm ơn lớn với các áp phích trong stackoverflow! Tôi đã kết hợp một số ý tưởng và tạo trình lắng nghe lớp cho các hoạt động và phân đoạn (vì vậy mã này có thể tái sử dụng nhiều hơn khiến mã viết nhanh hơn và sạch hơn nhiều).
Tất cả những gì bạn phải làm khi có lớp của tôi là triển khai giao diện (và tất nhiên là tạo phương thức cho nó) được khai báo trong lớp của tôi và tạo đối tượng của lớp này truyền các đối số.
/**
* 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;
}
}
}
Bạn cần thêm tài nguyên chân trang xml trống vào listView của mình và phát hiện xem chân trang này có hiển thị hay không.
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;
}
Sau đó, trong listView của bạn, người nghe cuộn, bạn làm điều này
@
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);
}
Nếu bạn đặt thẻ trên chế độ xem của mục cuối cùng của chế độ xem danh sách, thì sau này, bạn có thể truy xuất chế độ xem có thẻ, nếu chế độ xem là rỗng thì đó là do chế độ xem không được tải nữa. Như thế này:
private class YourAdapter extends CursorAdapter {
public void bindView(View view, Context context, Cursor cursor) {
if (cursor.isLast()) {
viewInYourList.setTag("last");
}
else{
viewInYourList.setTag("notLast");
}
}
}
sau đó nếu bạn cần biết liệu mục cuối cùng đã được tải chưa
View last = yourListView.findViewWithTag("last");
if (last != null) {
// do what you want to do
}
Janwilx72 nói đúng, nhưng sdk tối thiểu là 21, vì vậy tôi tạo phương pháp này :
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;
}
}
cho 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{}
Có lẽ muộn, chỉ dành cho những người đến sau。
Nếu bạn đang sử dụng bộ điều hợp tùy chỉnh với chế độ xem danh sách của mình (hầu hết mọi người đều vậy!), Một giải pháp tuyệt vời được đưa ra ở đây!
https://stackoverflow.com/a/55350409/1845404
Phương thức getView của bộ điều hợp phát hiện khi nào danh sách đã được cuộn đến mục cuối cùng. Nó cũng bổ sung hiệu chỉnh cho những lần hiếm hoi khi một số vị trí trước đó được gọi ngay cả sau khi bộ điều hợp đã hiển thị chế độ xem cuối cùng.
Tôi đã làm điều đó và làm việc cho tôi:
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");
}
}
Thao tác này sẽ cuộn xuống danh sách của bạn đến mục cuối cùng.
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);