我有自定义
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
布局中,但结果相同 - 无法滚动它。
更新 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
.
它终于工作了,但我对其中的滚动行为有疑问:
我需要解决这些问题,它应该可以正常工作。
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>
我一直坚持类似的问题,但
CollapsingToolbar
虽然也许同样的事情也适用于它。尝试在您的main_content_layout.xml
中进行以下更改
NestedScrollBar
具有此属性
app:layout_behavior="@string/appbar_scrolling_view_behavior"
具有您选择的内部父级布局。android:nestedScrollingEnabled="false"
中添加此属性RecyclerView
它位于 Scrollable 容器内,用于正确滚动。