You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Java中NestedScrollView内分页功能失效问题求助

问题:NestedScrollView内Firestore分页功能失效

我正在开发一款Android应用,从Firebase Firestore获取数据并实现分页功能,但NestedScrollView内的分页功能无法正常工作。


XML布局代码

<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HomeFragment">

<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appBarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/Theme.AppCompat.Light">

            <TextView
                android:id="@+id/location"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:drawableStart="@drawable/ic_baseline_location_on_24red"
                android:drawableEnd="@drawable/ic_baseline_keyboard_arrow_down_24"
                android:fontFamily="@font/baloo_bhai"
                android:text="Your Location"
                android:textColor="#050505"
                android:textSize="20dp" />

        </androidx.appcompat.widget.Toolbar>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/toolbar"
            android:padding="5dp"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <androidx.appcompat.widget.SearchView
                android:id="@+id/searchbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                android:background="@drawable/searchbardesign"
                android:focusable="false"
                app:iconifiedByDefault="false"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:queryHint="search here eg. rooms in delhi... " />

        </RelativeLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipeFreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="?actionBarSize"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <androidx.core.widget.NestedScrollView
            android:id="@+id/nestedScrollView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="?actionBarSize"
            android:clipToPadding="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

                <org.imaginativeworld.whynotimagecarousel.ImageCarousel
                    android:id="@+id/carousel"
                    android:layout_width="match_parent"
                    android:layout_height="150dp"
                    android:layout_marginBottom="10dp"
                    app:autoPlay="false"
                    app:autoPlayDelay="3000"
                    app:autoWidthFixing="true"
                    app:carouselType="SHOWCASE"
                    app:imageScaleType="fitXY"
                    app:infiniteCarousel="true"
                    app:scaleOnScroll="false"
                    app:scalingFactor="0.15"
                    app:showBottomShadow="false"
                    app:showCaption="false"
                    app:showNavigationButtons="false"
                    app:showTopShadow="false"
                    app:touchToPause="true" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/roomsRecyclerView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layoutAnimation="@anim/layout_animation_slide_from_bottom"
                    android:orientation="vertical"
                    android:scrollbars="vertical" />

                <ProgressBar
                    android:id="@+id/recyclerView_progress"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:visibility="gone" />

            </LinearLayout>

        </androidx.core.widget.NestedScrollView>
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

</androidx.drawerlayout.widget.DrawerLayout>

Java代码

// onCreate内的代码
recyclerViewRooms.setNestedScrollingEnabled(false);

nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
    @Override
    public void onScrollChanged() {
        View view = (View) nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1);
        int diff = (view.getBottom() - (nestedScrollView.getHeight() + nestedScrollView.getScrollY()));

        if (diff == 0) {
            paginationCode();
        }
    }
});

// 分页方法
public void paginationCode() {
    FirebaseFirestore firebaseFirestore = FirebaseFirestore.getInstance();
    Query productsRef =  firebaseFirestore.collectionGroup("room_details");
    Query query = productsRef.orderBy("location", Query.Direction.ASCENDING).limit(limit);

    progressBar.setVisibility(View.VISIBLE);

    query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
        @Override
        public void onComplete(@NonNull Task<QuerySnapshot> task) {
            if (task.isSuccessful()) {
                for (DocumentSnapshot d : task.getResult()) {
                    RoomsDetails obj = d.toObject(RoomsDetails.class);
                    roomsDetails.add(obj);
                }
                roomsAdapter.notifyDataSetChanged();
            }
            lastVisible = task.getResult().getDocuments().get(task.getResult().size() - 1);

            RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                        isScrolling = true;
                        progressBar.setVisibility(View.GONE);
                    }
                }

                @Override
                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    LinearLayoutManager linearLayoutManager = ((LinearLayoutManager) recyclerView.getLayoutManager());
                    int firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
                    int visibleItemCount = linearLayoutManager.getChildCount();
                    int totalItemCount = linearLayoutManager.getItemCount();

                    if (isScrolling && (firstVisibleItemPosition + visibleItemCount == totalItemCount) && !isLastItemReached) {
                        isScrolling = false;
                        progressBar.setVisibility(View.VISIBLE);

                        Query nextQuery = productsRef.orderBy("location", Query.Direction.ASCENDING).startAfter(lastVisible).limit(limit);
                        nextQuery.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                            @Override
                            public void onComplete(@NonNull Task<QuerySnapshot> t) {
                                if (t.isSuccessful()) {
                                    for (DocumentSnapshot d : t.getResult()) {
                                        RoomsDetails productModel = d.toObject(RoomsDetails.class);
                                        roomsDetails.add(productModel);
                                        Toast.makeText(getContext(), "hey this is", Toast.LENGTH_SHORT).show();
                                    }
                                    progressBar.setVisibility(View.GONE);
                                    roomsAdapter.notifyDataSetChanged();
                                    lastVisible = t.getResult().getDocuments().get(t.getResult().size() - 1);

                                    if (t.getResult().size() < limit) {
                                        isLastItemReached = true;
                                    }
                                }
                            }
                        });
                    }
                }
            };
            recyclerViewRooms.addOnScrollListener(onScrollListener);
        }
    });
}

问题分析与修复方案

核心问题点

  1. 重复添加滚动监听器:每次调用paginationCode()都会给RecyclerView添加新的OnScrollListener,导致同一滚动动作触发多次分页请求。
  2. 滚动逻辑冲突:同时监听NestedScrollView和RecyclerView的滚动,且RecyclerView已禁用嵌套滚动,导致滚动判断混乱。
  3. 初始加载与分页逻辑耦合:paginationCode()同时处理初始加载和分页加载,逻辑冗余且容易出错。
  4. 缺少加载状态控制:没有标记当前是否正在加载数据,导致滚动到底部时重复触发请求。
  5. RecyclerView高度设置错误:在NestedScrollView内使用match_parent高度会导致RecyclerView无法正确计算滚动位置。

修复后的代码

1. 修改XML中的RecyclerView高度

将RecyclerView的高度改为wrap_content,让NestedScrollView统一处理滚动:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/roomsRecyclerView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layoutAnimation="@anim/layout_animation_slide_from_bottom"
    android:orientation="vertical"
    android:scrollbars="vertical" />
2. 重构Java代码,分离逻辑
// 全局变量
private boolean isLoading = false;
private boolean isLastItemReached = false;
private DocumentSnapshot lastVisible = null;
private int limit = 10; // 每页加载数量

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 初始化RecyclerView
    recyclerViewRooms.setNestedScrollingEnabled(false);
    recyclerViewRooms.setLayoutManager(new LinearLayoutManager(getContext()));
    roomsAdapter = new RoomsAdapter(roomsDetails);
    recyclerViewRooms.setAdapter(roomsAdapter);
    
    // 只添加一次滚动监听器
    setupRecyclerViewScrollListener();
    // 初始加载数据
    loadInitialData();
}

// 设置RecyclerView滚动监听器
private void setupRecyclerViewScrollListener() {
    recyclerViewRooms.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            // 标记用户正在滚动
            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
                isLoading = false;
            }
        }

        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            // 避免重复加载、已加载完所有数据时不触发
            if (!isLoading && !isLastItemReached) {
                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                int visibleItemCount = layoutManager.getChildCount();
                int totalItemCount = layoutManager.getItemCount();
                int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();

                // 判断是否滚动到列表底部
                if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount) {
                    isLoading = true;
                    progressBar.setVisibility(View.VISIBLE);
                    loadNextPage();
                }
            }
        }
    });
}

// 初始加载第一页数据
private void loadInitialData() {
    isLoading = true;
    progressBar.setVisibility(View.VISIBLE);
    FirebaseFirestore db = FirebaseFirestore.getInstance();
    
    Query query = db.collectionGroup("room_details")
            .orderBy("location", Query.Direction.ASCENDING)
            .limit(limit);

    query.get().addOnCompleteListener(task -> {
        if (task.isSuccessful() && task.getResult() != null) {
            roomsDetails.clear();
            for (DocumentSnapshot doc : task.getResult()) {
                RoomsDetails obj = doc.toObject(RoomsDetails.class);
                roomsDetails.add(obj);
            }
            roomsAdapter.notifyDataSetChanged();
            
            // 更新最后一条可见数据和是否加载完成
            if (!task.getResult().isEmpty()) {
                lastVisible = task.getResult().getDocuments().get(task.getResult().size() - 1);
                isLastItemReached = task.getResult().size() < limit;
            } else {
                isLastItemReached = true;
            }
        }
        isLoading = false;
        progressBar.setVisibility(View.GONE);
    });
}

// 加载下一页数据
private void loadNextPage() {
    if (lastVisible == null) return;
    
    FirebaseFirestore db = FirebaseFirestore.getInstance();
    Query nextQuery = db.collectionGroup("room_details")
            .orderBy("location", Query.Direction.ASCENDING)
            .startAfter(lastVisible)
            .limit(limit);

    nextQuery.get().addOnCompleteListener(task -> {
        if (task.isSuccessful() && task.getResult() != null) {
            for (DocumentSnapshot doc : task.getResult()) {
                RoomsDetails obj = doc.toObject(RoomsDetails.class);
                roomsDetails.add(obj);
            }
            roomsAdapter.notifyDataSetChanged();
            
            // 更新最后一条可见数据和是否加载完成
            if (!task.getResult().isEmpty()) {
                lastVisible = task.getResult().getDocuments().get(task.getResult().size() - 1);
                isLastItemReached = task.getResult().size() < limit;
            } else {
                isLastItemReached = true;
            }
        }
        isLoading = false;
        progressBar.setVisibility(View.GONE);
    });
}

内容的提问来源于stack exchange,提问作者Puru Chaudhary

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 01:25:12