0

I have RecyclerView inside NestedScrollView. What I want is to scroll entire content of NestedScrollView if NestedScrollViews content (including list passed to RecyclerViews adapter) is above certain height.

I have custom NestedScrollView class to handle this which is measuring height of all children inside onMeasure and if total children height will pass certain value, it should enable scrolling instead of expanding NestedScrollView height. (I need this feature because NestedScrollView is allowed to be expanded to maximum of 40% of display height).

This feature is working well if I'm filling NestedScrollView with generic components containing RelativeLayouts, TextViews, Buttons etc. But RecyclerView is completely blocking scrolling of entire NestedScrollView for some reason.

Here is logging from NestedScrollView:

ScrollViewWithMaxHeight: onMeasure curr_height: 11678, max: 966

NestedScrollView:

class ScrollViewWithMaxHeight: NestedScrollView{
    private var maxHeight = WITHOUT_MAX_HEIGHT_VALUE

    constructor(context: Context) : super(context) {}
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}
    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {}

    var onHeightChangeListener: ((Int)->Unit)? = null
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var measuredHeight = heightMeasureSpec
        val currHeight = getCurrContentHeight()
        try {
            val heightSize: Int
            App.log("ScrollViewWithMaxHeight: onMeasure curr_height: $currHeight, max: $maxHeight")
            if (maxHeight != WITHOUT_MAX_HEIGHT_VALUE && currHeight > maxHeight) {
                heightSize = maxHeight
            } else {
                heightSize = currHeight
            }
            measuredHeight = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST)
            layoutParams.height = heightSize
            onHeightChangeListener?.invoke(heightSize)
        } catch (e: Exception) {

        } finally {
            App.log("ScrollViewWithMaxHeight: onMeasure final: $measuredHeight")
            super.onMeasure(widthMeasureSpec, measuredHeight)
        }
    }

    private fun getCurrContentHeight(): Int{
        var height = 0
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            val h = child.measuredHeight
            App.log("ScrollViewWithMaxHeight: getCurrChildHeight: $h")
            if (h > height) height = h
        }

        return height
    }

    fun setOnDynamicHeightChangeListener(l: (Int)->Unit){
        onHeightChangeListener = l
    }

    fun setMaxHeight(maxHeight: Int) {
        this.maxHeight = maxHeight
    }

    companion object {
        var WITHOUT_MAX_HEIGHT_VALUE = -1
    }
}

RecyclerView init + layout:

list = findViewById<RecyclerView>(R.id.list).also { rv ->
                rv.isNestedScrollingEnabled = false
                rv.setOnTouchListener { _, _ -> false }
                rv.adapter = adapter
                rv.layoutManager = LinearLayoutManager(a)
            }

Layout:

 <RelativeLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:clipChildren="false">
    
            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/list"
                android:paddingTop="8dp"
                android:paddingBottom="8dp"
                android:clipToPadding="false"
                android:clipChildren="false"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
    
            <components.loading_indicator.LoadingIndicator
                android:id="@+id/progress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true" />
    
        </RelativeLayout>

NestedScrollView init:

it.findViewById<ScrollViewWithMaxHeight>(R.id.scrollParent).also { sv->
                sv.setMaxHeight(getScrollViewHeight())
                sv.setOnDynamicHeightChangeListener { h-> }
                sv.isFillViewport = true
                sv.viewTreeObserver.addOnScrollChangedListener {
                    isScrollOnTop = sv.scrollY == 0
                }
            }

Here is issue what is causing Scroll lags:

2023-03-28 09:48:18.430 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:18.435 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:18.440 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:18.445 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.955 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.962 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.967 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.973 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.979 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.985 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.990 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:27.996 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:28.002 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:28.008 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:28.013 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:28.019 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:28.025 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:28.031 I  RecyclerViewAdapter: onCreateViewHolder: creating item
2023-03-28 09:48:28.037 I  RecyclerViewAdapter: onCreateViewHolder: creating item
...

This is log inside onCreateViewHolder. Its called on every single item in list. Even as I try to scroll, its called again. Not sure what is causing this. I have RecyclerViews all over my app and only this one is not recycling.

Ok I found it what is causing this. Its really weird. I have another horizontal RecyclerView above NestedScrollView (which is obviously not part of NestedScrollView because its static and its not suppose to scroll vertically. Its just horizontal picker with items in it.

As soon as I removed it from layout, vertical scrolling worked as usual with proper recycling.

My layout design and component design is modular and I inject layout with inflation and addView function to parents so I can make standalone components within code which can be injected into any empty layout. I removed NestedScrollView from it because its not necessary to reconstruct the bug. It is happening even if there is only RecyclerView as component (so I dont need nested scrolling at all).

As soon as horizontal_picker_content is injected there and used, its blocking recycling of vertical_list_container which is place where my vertical RecyclerView is injected.

So I reconstructed how my final layout would look like in my DialogBottomSheet so you can see how it is structured:

<RelativeLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:orientation="vertical"
        tools:ignore="UselessParent">

        <LinearLayout
            android:id="@+id/topInfo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

        </LinearLayout>

        <androidx.coordinatorlayout.widget.CoordinatorLayout
            android:id="@+id/bottomContentContainer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:clipChildren="false">

            <FrameLayout
                android:id="@+id/bottomSheetContainer"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_insetEdge="bottom"
                app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

                <RelativeLayout
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:clipToPadding="false"
                    android:clipChildren="false"
                    android:background="@color/background_paper"
                    app:layout_insetEdge="bottom"
                    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

                    <RelativeLayout
                        android:id="@+id/peekSwiper"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content">

                        <LinearLayout
                            android:id="@+id/dialog_title_block"
                            android:layout_width="match_parent"
                            android:layout_height="20dp"
                            android:orientation="horizontal"
                            android:elevation="0dp"
                            android:translationZ="4dp"
                            android:background="@drawable/bg_bottomsheet_top"/>

                    </RelativeLayout>

                    <LinearLayout
                        android:id="@+id/dialog_bottom_block"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:orientation="vertical"
                        android:layout_below="@id/peekSwiper"
                        android:layout_gravity="center_horizontal"
                        android:gravity="center_horizontal">

                        <LinearLayout
                            android:id="@+id/dialog_title_content"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:background="@color/background_paper"
                            android:orientation="vertical">

                            <RelativeLayout
                                android:id="@+id/horizontal_picker_content"
                                android:orientation="vertical"
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:clipToPadding="false"
                                android:clipChildren="false"
                                android:gravity="center">

                                <androidx.recyclerview.widget.RecyclerView
                                    android:id="@+id/horizontalPickList"
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:orientation="horizontal"
                                    android:scrollbars="horizontal"
                                    android:clipToPadding="false"
                                    android:clipChildren="false"
                                    android:paddingStart="16dp"
                                    android:paddingEnd="16dp"
                                    android:paddingTop="8dp"
                                    android:paddingBottom="8dp"/>

                                <include
                                    layout="@layout/menu_item_separator"
                                    android:id="@+id/separator"
                                    android:layout_width="wrap_content"
                                    android:layout_height="1dp"
                                    android:layout_below="@+id/horizontalPickList"/>

                            </RelativeLayout>

                        </LinearLayout>

                        <LinearLayout
                            android:id="@+id/dialog_content"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:orientation="vertical">
                            
                            <RelativeLayout
                                android:layout_width="match_parent"
                                android:layout_height="wrap_content"
                                android:clipChildren="false"
                                android:clipToPadding="false"
                                android:background="@color/background_paper">

                                <LinearLayout
                                    android:id="@+id/vertical_content_parent"
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:clipToPadding="false"
                                    android:clipChildren="false"
                                    android:orientation="vertical">

                                    <LinearLayout
                                        android:id="@+id/vertical_content_container"
                                        android:layout_width="match_parent"
                                        android:layout_height="wrap_content"
                                        android:clipToPadding="false"
                                        android:clipChildren="false"
                                        android:orientation="vertical">

                                    </LinearLayout>

                                </LinearLayout>

                                <LinearLayout
                                    android:id="@+id/vertical_list_container"
                                    android:layout_width="match_parent"
                                    android:layout_height="wrap_content"
                                    android:layout_below="@id/vertical_content_parent"
                                    android:clipToPadding="false"
                                    android:clipChildren="false"
                                    android:orientation="vertical">

                                </LinearLayout>

                            </RelativeLayout>

                        </LinearLayout>

                    </LinearLayout>

                    <LinearLayout
                        android:id="@+id/buttonLayout"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_alignBottom="@id/dialog_bottom_block"
                        android:layout_gravity="center_horizontal"
                        android:gravity="center_horizontal"
                        android:layout_centerHorizontal="true"
                        android:orientation="vertical">

                    </LinearLayout>

                </RelativeLayout>

            </FrameLayout>

        </androidx.coordinatorlayout.widget.CoordinatorLayout>

    </RelativeLayout>
4
  • Have you tried setNestedScrollingEnabled(false) method on the RecyclerView instance? Commented Mar 24, 2023 at 20:41
  • yes. Its not working. I even tried to remove NestedScrollView parent and its still not recycling Commented Mar 27, 2023 at 10:47
  • rv.isNestedScrollingEnabled = false try removing this line and letting the RecyclerView handle the nested scrolling. You can also remove the rv.setOnTouchListener { _, _ -> false } line, as it doesn't seem to be doing anything. Commented Mar 27, 2023 at 18:04
  • rv.setOnTouchListener { _, _ -> false } supposed to enable handling of scrolling by NestedScrollView. And same with nested scrolling. You need to handle scrolling by NestedScrollView in case of multiple views inside NestedScrollView. Commented Mar 28, 2023 at 10:25

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.