1

I am having a CollapsingToolbar and a bottom navbar as shown:

<androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:fitsSystemWindows="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintTop_toTopOf="parent">

        <!-- Scrollable view here -->

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appBar"
            android:layout_width="match_parent"
            android:layout_height="@dimen/height335"
            android:background="@drawable/ic_appbar_bg"
            android:fitsSystemWindows="true"
            app:elevation="0dp">

            <com.google.android.material.appbar.CollapsingToolbarLayout
                android:id="@+id/collapsingToolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:contentScrim="@color/colorPrimaryDark"
                app:toolbarId="@+id/toolbar"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">

                <androidx.appcompat.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    android:background="@android:color/transparent"
                    android:contentInsetStart="0dp"
                    android:contentInsetLeft="0dp"
                    android:gravity="center"
                    app:contentInsetEnd="0dp"
                    app:contentInsetEndWithActions="0dp"
                    app:contentInsetLeft="0dp"
                    app:contentInsetRight="0dp"
                    app:contentInsetStart="0dp"
                    app:contentInsetStartWithNavigation="2dp"
                    app:layout_collapseMode="pin"
                    app:theme="@style/ToolbarTheme">

                    <include
                        android:id="@+id/toolbar_header_view"
                        layout="@layout/widget_header_view_top"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        bind:cardDetails="@{viewModel}" />
                </androidx.appcompat.widget.Toolbar>

                <include
                    layout="@layout/widget_header"
                    bind:cardDetails="@{viewModel}" />

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

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:id="@+id/swipe"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <!-- just a nestedscrollview
            override fun onTouchEvent(ev: MotionEvent): Boolean {
                return scrollable && super.onTouchEvent(ev)
            }

            override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
                return scrollable && super.onInterceptTouchEvent(ev)
            }

            fun setScrollingEnabled(enabled: Boolean) {
                scrollable = enabled
            } -->
            <com.android.utils.LockableNestedScrollView
                android:id="@+id/nestedScrollView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"
                android:clipChildren="false"
                android:clipToPadding="false"
                android:fillViewport="true">

               <LinearLayout>
                    <Some views...
                    <androidx.fragment.app.FragmentContainerView
                        android:id="@+id/nav_host_fragment"
                        android:layout_width="match_parent"
                        app:layout_behavior="@string/appbar_scrolling_view_behavior"
                        android:layout_height="match_parent" />

                </LinearLayout>

            </com.android.utils.LockableNestedScrollView>

        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="@{()->viewModel.fabClicked()}"
        android:src="@drawable/ic_plus"
        app:fabCustomSize="@dimen/fab70"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="1" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        android:elevation="@dimen/elevation3"
        android:paddingTop="@dimen/padding10"
        app:itemTextAppearanceActive="@style/BottomNavigationViewTextStyle"
        app:itemTextAppearanceInactive="@style/BottomNavigationViewTextStyle"
        app:labelVisibilityMode="labeled"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/nav_view" />

Fragment container will be replaced with fragments when nav bar items are clicked. The issue is that in one of the fragment I have a recyclerview. The onBindView of all the items in the recyclerview is getting invoked all at once. I need to avoid this mainly because I am trying to implement Pagination like this:

override fun onBindViewHolder(holder: TopUpViewHolder, position: Int) {
    val item = myDataset[position]
    holder.bind(item)
    if (position == this.itemCount - 1){
        // do your load more task here
        viewModel.fetchTopUpData()
    }
}

(I tried attaching scroll listener and implement pagination like this. But the canScrollVertically(1) is getting invoked even when the user has not reached the end, probably because of nestedscrollview itself.)

Here is my fragment with recyclerview:

<?xml version="1.0" encoding="utf-8"?>

<data>

    <import type="android.view.View" />

    <variable
        name="viewmodel"
        type="com.android.ui.topup.viewmodel.TopUpViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:padding="@dimen/padding15">

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

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/status_header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="@dimen/padding20"
            android:visibility="@{viewmodel.headerVisibility}">

            <TextView
                android:id="@+id/recentLabel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margin20"
                android:text="@string/recent_top_ups"
                android:textAllCaps="true"
                android:textColor="@color/topup_label_text_color"
                android:textSize="@dimen/text12"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/viewAll"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/margin20"
                android:onClick="@{()->viewmodel.viewAllClicked()}"
                android:text="@string/view_all"
                android:textColor="@color/topup_label_color_orenge"
                android:textSize="@dimen/text14"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </androidx.constraintlayout.widget.ConstraintLayout>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/topUpRCView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:nestedScrollingEnabled="false"
            android:clipChildren="false"
            android:clipToPadding="false"
            app:scrollListener="@{viewmodel.scrollListener}" />

        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:visibility="@{viewmodel.fetchingFlag?View.VISIBLE:View.GONE}" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/empty_list"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="@{viewmodel.listEmpty}"
        android:orientation="vertical">
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:src="@drawable/ic_illustration_receipt"/>
        <TextView
            android:id="@+id/empty_list_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/you_have_no_topup_requests"
            android:fontFamily="@font/colfaxregular"
            android:textColor="#333547"
            android:layout_marginTop="36dp"
            android:textSize="@dimen/text16"
            android:alpha=".3"/>

    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Things that I have tried:

  1. Added android:nestedScrollingEnabled="false" in the recyclerview
  2. Added app:layout_behavior="@string/appbar_scrolling_view_behavior"
  3. Changed the height of recyclerview to match_parent wrap_content also 0 and set layout weight as 1(RCV is inside a linearlayout)
  4. https://stackoverflow.com/a/44470106/6341943 => adding a header viewholder for some reason
  5. https://stackoverflow.com/a/37558761/6341943 => cant do this because, I have a bottomnavbar

Now for the Weird part If I keep my fragment inside a viewpager, for some reason the issue is not happening. (Even this hack is not usable is because it's messing up my collpsingToolbarLayout)

hushed_voice
  • 1,836
  • 20
  • 41

1 Answers1

1

I have wasted at least a week behind this and the only solution that works is to remove the nestedscrollview. To make the recyclerview to actually recycle the view, you HAVE TO REMOVE the nestedscrollview. If you happen to have multiple views inside instead of just the recyclerview, You have to add them as items of the recyclerview by setting different viewholders, and then adding

app:layout_behavior="@string/appbar_scrolling_view_behavior"

to the recyclerview or the parent of recyclerview(the parent cannot have any other view other than Recyclerview for this to work).

In my case, the requirement was as follows:

  <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:fitsSystemWindows="true"
        app:layout_constraintTop_toTopOf="parent">

        <!-- Scrollable view here -->

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

            <com.google.android.material.appbar.CollapsingToolbarLayout
                ....>

                <androidx.appcompat.widget.Toolbar
                    ....>

                    <include
                        android:id="@+id/toolbar_header_view"
                        .... />
                </androidx.appcompat.widget.Toolbar>

                <include layout="@layout/widget_header" />
            </com.google.android.material.appbar.CollapsingToolbarLayout>
        </com.google.android.material.appbar.AppBarLayout>

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            ....>
            <LinearLayout orientation="vertical" ....>
                <View .../>  <!-- I needed to have these two views along with recyclerview and i wanted them to do nestedscroll along with recyclerview -->
                <View .../>
                <androidx.recyclerview.widget.RecyclerView
                .... />
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

I needed to have two views before recyclerview and I wanted them to do nestedscroll along with recyclerview. The only thing that worked was by removing these to views and adding them as the first two items of recyclerview, and then adding

app:layout_behavior="@string/appbar_scrolling_view_behavior"

to the swipetorefresh layout

Hope this helps someone. I will add more info If required.

hushed_voice
  • 1,836
  • 20
  • 41