Android:RecyclerView 不在 CoordinatorLayout 内滚动

问题描述 投票:0回答:1

我有自定义

LinearLayout
,可以根据它所包含的内容将高度设置为最大或最小。但是如果这个
LinearLayout
里面有RecyclerView,当我调用
adapter.notifyDataSetChange()
以刷新
RecyclerView
内容时,onMeasure仍然返回0。

RecyclerView
中有大约 300 个项目,但是
onMeasure
的父母的
RecyclerView
仍然测量 0 高度。我需要将
RecyclerView
的高度限制为在视图初始化时设置的特定值。

自定义线性布局

class LinearLayoutWithVariableHeight: LinearLayout {
    companion object {
        var WITHOUT_HEIGHT_VALUE = -1
    }

    private var maxHeight = WITHOUT_HEIGHT_VALUE
    private var minHeight = WITHOUT_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) {}

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        var measuredHeight = heightMeasureSpec
        val currHeight = getCurrContentHeight()
        try {
            val heightSize: Int
            App.log("LinearLayoutWithMaxHeight: onMeasure curr_height: $currHeight, max: $maxHeight")
            if (maxHeight != WITHOUT_HEIGHT_VALUE && currHeight > maxHeight) {
                App.log("LinearLayoutWithMaxHeight: onMeasure set xy max height")
                heightSize = maxHeight
            } else if (minHeight != WITHOUT_HEIGHT_VALUE && currHeight < minHeight){
                App.log("LinearLayoutWithMaxHeight: onMeasure set xy min height")
                heightSize = min(minHeight, maxHeight)
            } else {
                heightSize = currHeight
            }

            App.log("LinearLayoutWithMaxHeight: onMeasure heightSize: $heightSize")
            measuredHeight = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.AT_MOST)
            layoutParams.height = heightSize
        } catch (e: Exception) {

        } finally {
            App.log("LinearLayoutWithMaxHeight: 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("LinearLayoutWithMaxHeight: getCurrChildHeight: $h")
            if (h > height) height = h
        }

        return height
    }

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

    fun setMinHeight(minHeight: Int) {
        this.minHeight = minHeight
    }
}

更新: 我尝试制作类似于 RecyclerView 而不是其父级的东西,它运行良好但 RecyclerView 现在不可滚动:

class RecyclerViewWithMaxHeight: RecyclerView {
    companion object {
        var WITHOUT_HEIGHT_VALUE = -1
    }

    private var maxHeight = WITHOUT_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) {}

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        App.log("RecyclerViewWithMaxHeight: onMeasure")
        super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST))
    }

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

main_content_layout.xml:

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

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

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

            </LinearLayout>

        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/dialog_main_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/dialog_top">

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

            </LinearLayout>

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

                <com.app.components.RecyclerViewWithMaxHeight
                    android:id="@+id/list"
                    android:clipToPadding="false"
                    android:clipChildren="false"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    tools:listitem="@layout/test_item"/>

            </LinearLayout>

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

            </LinearLayout>

        </RelativeLayout>

    </RelativeLayout>

更新 2:

所以如果我只使用上面的这个布局,它会完美地滚动。但是由于此布局是 bottomsheet 的一部分,而 bottomsheet 是另一个布局的一部分,因此我必须用

CoordinatorLayout
和一些父项(在本例中为
FrameLayout
)将其包裹起来。如果我这样做,则
RecyclerView
的滚动不起作用。但我不确定为什么会这样。我需要在 BottomSheet 中滚动,这在逻辑上是
CoordinatorLayout
的一部分,因为在其他情况下我不能使用
BottomSheetBehavior
.

<androidx.coordinatorlayout.widget.CoordinatorLayout
        android:id="@+id/bottomContentContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:clipChildren="false">
        ... some other content anchored to top of content below
        <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">

            ... main_content_layout.xml

        </FrameLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

我尝试将

app:layout_behavior="@string/appbar_scrolling_view_behavior"
添加到我的
RecyclerView
布局中,但结果相同 - 无法滚动它。

Layou期望:

更新 3 当我现在使用

non-NestedScrollView
版本在
RecyclerView
内使用单个
CoordinatorLayout
时,我发现了触摸事件的一些问题。

如果我开始将它拖动到

RecyclerView
和不可滚动的
RecyclerView
之间的边界上,我可以滚动
View
。我必须专门将手指放在那里才能开始滚动。例如,如果我在 RecyclerView 中间执行此操作,它就不会滚动。

添加了小的

onTouchListener
,如果
RecyclerView
低于
CoordinatorLayout
,它实际上根本不会开火。

list.setOnTouchListener { view, motionEvent ->
                App.log("ListOnTouch -> motionEvent: $motionEvent")
                true
            }

我不确定为什么

CoordinatorLayout
阻止了我的
RecyclerView
的触摸事件,以及为什么它没有在列表和上面的其他视图之间的边界上被阻止。

我试图将

android:descendantFocusability="afterDescendants"
添加到
CoordinatorLayout
最顶层的视图(在我的例子中是
FrameLayout
)但它根本没有帮助。

更新 4

因为我可能发现了一个导致这种奇怪行为的问题,所以这里有一些见解。

正如我提到的,我正在使用这个

CoordinatorLayout
BottomsheetBehavior
附加到它最上面的孩子。

我发现

CoordinatorLayout
不支持其最顶层子项中的多个可滚动子项,因为
BottomsheetBehavior
仅与
View
层次结构中的第一个滚动子项一起工作。

我在上面有一个水平的

RecyclerView
,然后在它下面有一个垂直的。

我做了这个定制

BottomsheetBehavior
,它应该处理每个
RecyclerView
里面
CoordinatorLayout
.

它终于工作了,但我对其中的滚动行为有疑问:

  • 如果我触摸它,它总是将我的 RecyclerView 滚动到顶部并从那里开始
  • 如果我滚动并从触摸屏上抬起手指,它将停止滚动(没有那种通用的快速滚动效果)。

我需要解决这些问题,它应该可以正常工作。

class MultiScrollRecyclerViewBottomsheetBehavior<V : View>(
    context: Context,
    attrs: AttributeSet
) : BottomSheetBehavior<V>(context, attrs) {

    private var activeRecyclerView: RecyclerView? = null
    private var coordinatorHeight = 0
    override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
        val lp = child.layoutParams as CoordinatorLayout.LayoutParams
        if (lp.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) {
            child.layout(0, parent.height - peekHeight, parent.width, parent.height)
        } else {
            super.onLayoutChild(parent, child, layoutDirection)
        }

        // get the height of the CoordinatorLayout
        App.log("MultiScrollBottomsheetBehavior: onLayoutChild: parent: ${parent.height}")
        coordinatorHeight = parent.height

        return true
    }

    override fun onInterceptTouchEvent(
        parent: CoordinatorLayout,
        child: V,
        event: MotionEvent
    ): Boolean {
        if (event.action == MotionEvent.ACTION_DOWN) {
            // Find the topmost RecyclerView that is below the touch point
            activeRecyclerView = findChildRecyclerView(parent, event.x.toInt(), event.y.toInt())
        }
        return super.onInterceptTouchEvent(parent, child, event)
    }

    override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
        // Forward touch events to the topmost RecyclerView
        return activeRecyclerView?.onTouchEvent(event) ?: super.onTouchEvent(parent, child, event)
    }

    override fun onNestedPreScroll(
        coordinatorLayout: CoordinatorLayout, child: V,
        target: View, dx: Int, dy: Int, consumed: IntArray,
        type: Int
    ) {
        if (type == ViewCompat.TYPE_TOUCH && state == STATE_EXPANDED) {
            // Forward scroll events to the topmost RecyclerView
            activeRecyclerView?.let {
                if (dy > 0 && !it.canScrollVertically(1)) {
                    // If the RecyclerView is scrolled to the bottom and the user tries to scroll further down,
                    // let the default behavior handle the event
                    super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
                } else {
                    // Otherwise, forward the event to the RecyclerView
                    it.scrollBy(0, dy)
                    consumed[1] = dy
                }
            }
        } else {
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
        }
    }

    /**
     * Recursively searches the [parent] view hierarchy for a [RecyclerView] that contains the given [x] and [y] coordinates.
     */
    private fun findChildRecyclerView(parent: ViewGroup, x: Int, y: Int): RecyclerView? {
        for (i in 0 until parent.childCount) {
            val child = parent.getChildAt(i)
            if (child is RecyclerView && isPointInsideView(x, y, child)) {
                return child
            } else if (child is ViewGroup) {
                val recyclerView = findChildRecyclerView(child, x, y)
                if (recyclerView != null) {
                    return recyclerView
                }
            }
        }
        return null
    }

    /**
     * Returns true if the given [x] and [y] coordinates are inside the bounds of the [view].
     */
    private fun isPointInsideView(x: Int, y: Int, view: View): Boolean {
        val location = IntArray(2)
        view.getLocationOnScreen(location)
        val viewX = location[0]
        val viewY = location[1]
        return (x >= viewX && x <= viewX + view.width && y >= viewY && y <= viewY + view.height)
    }
}

我只是像这样将它添加到我最顶层的父级:

<FrameLayout
                android:id="@+id/bottomSheetContainer"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_insetEdge="bottom"
                app:layout_behavior="com.project.utils.MultiScrollRecyclerViewBottomsheetBehavior">

            </FrameLayout>
android android-recyclerview android-linearlayout android-coordinatorlayout
1个回答
0
投票

我一直坚持类似的问题,但

CollapsingToolbar
虽然也许同样的事情也适用于它。尝试在您的
main_content_layout.xml

中进行以下更改
  1. 添加
    NestedScrollBar
    具有此属性
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    具有您选择的内部父级布局。
  2. 在您的
    android:nestedScrollingEnabled="false"
    中添加此属性
    RecyclerView
    它位于 Scrollable 容器内,用于正确滚动。
© www.soinside.com 2019 - 2024. All rights reserved.