ViewPager 在 Coordinator 布局中的高度超过可用
ViewPager's height in Coordinator layout is more than available
我有一个 CoordinatorLayout
,Toolbar
和 AppBarLayout
里面有一个 TabLayout
。此外,我在 CoordinatorLayout
内但在 ViewPager
外有一个 ViewPager
。
问题是 ViewPager's
的高度比实际可用的要大,导致我的 Fragment
的一些视图被剪掉了。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/lightGray"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar2"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways"/>
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:scrollbars="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="false"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
这就是我说 ViewPager
高度错误的意思。
一个可能的黑客可以添加与工具栏相同的底部边距,即
?attr/actionBarSize。您甚至可以 fiddle 与其他可能的 ui 边距技巧一起使用,以获得最佳结果。
我花了很多时间在谷歌上搜索和应用建议的答案,但我没有成功,所以决定更深入地研究这个问题并找到一个完整的答案,我想在这里分享。也许它会对某人有所帮助。
所以问题是,当 ViewPager 与 Collapsing Toolbar 一起使用时,第一个无法正确计算其高度。如果你想让 ViewPager 将所有 space 填充到屏幕底部,但不要更多 - 那就有问题了。仅添加 layout_marginBottom
不会解决问题,因为 Collapsing Toolbar 会在用户滚动时更改其高度。
因此,如果您希望 ViewPager 相应地调整其高度以适应 Collapsing Toolbar 高度变化,您需要两件事:
- 自定义滚动行为。
- GlobalLayoutListener,它会在第一次绘制时固定 ViewPager 高度。
它们在这里(用 Kotlin 编写,但它只是一个单独的文件,可以直接放入 Java 项目中):
import android.app.Activity
import android.content.Context
import android.graphics.Point
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
/**
* Custom extension of AppBarLayout.ScrollingViewBehavior to deal with ViewPager height
* in a bunch with Collapsing Toolbar Layout. Works dynamically when AppBar Layout height is changing.
*/
class ViewPagerScrollingBehavior(context: Context, attributeSet: AttributeSet? = null) :
AppBarLayout.ScrollingViewBehavior(context, attributeSet) {
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
val layoutParams = child.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.height = child.height - (dependency.bottom - child.top)
child.layoutParams = layoutParams
child.requestLayout()
return super.onDependentViewChanged(parent, child, dependency)
}
}
/**
* Custom implementation of ViewTreeObserver.OnGlobalLayoutListener to fix the View height
* in a bunch with Collapsing Toolbar Layout. Works when View is drawn on the screen for first time.
* To be used with ViewPagerScrollingBehavior.
*/
class FixHeightGlobalLayoutListener(val activity: Activity, val view: View) : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val display = activity.windowManager.defaultDisplay
val size = Point()
display.getSize(size)
val height = size.y
val location = IntArray(2)
view.getLocationOnScreen(location)
view.post {
val layoutParams = view.layoutParams as ViewGroup.LayoutParams
layoutParams.height = height - location[1]
view.layoutParams = layoutParams
view.requestLayout()
}
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
并在您的代码中使用它们:
将自定义行为添加到您的 ViewPager:app:layout_behavior="your.package.ViewPagerScrollingBehavior"
将自定义 OnGlobalLayoutListener 添加到您的 ViewPager:viewPager.getViewTreeObserver().addOnGlobalLayoutListener(new FixHeightGlobalLayoutListener(this, viewPager));
UDPATE:我不再推荐这个了。我发现它导致了对 onDependentViewChanged
.
的无限循环调用
根据上面 Yevhenii 的回答,我想我已经用更简单的方法解决了这个问题:
class KeepWithinParentBoundsScrollingBehavior : AppBarLayout.ScrollingViewBehavior {
constructor() : super()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
if (dependency !is AppBarLayout) {
return super.onDependentViewChanged(parent, child, dependency)
}
val layoutParams = child.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.height = parent.height - dependency.bottom
child.layoutParams = layoutParams
return super.onDependentViewChanged(parent, child, dependency)
}
}
然后在 ViewPager
或 AppBar
下面的任何视图上设置 app:layout_behavior="your.package.KeepWithinParentBoundsScrollingBehavior"
。
请注意,这不是适用于所有 CoordinatorLayout
的通用解决方案,但当您在应用栏下方有一个您不想让其超出底部的视图时,它似乎可以工作父 CoordinatorLayout。
更新:
对于键盘消失的情况,您还应该在 ViewPager 上设置 app:layout_anchor="@id/app_bar"
。如果不这样做,ViewPager
布局将不会在键盘消失时刷新,并且 ViewPager
将显示为被截断。
您不应将行为类型“appbar_scrolling_view_behavior”设置为您的 ViewPager。但是将其设置为外部布局。类似下面
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/tabbed_fragments_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
我有一个 CoordinatorLayout
,Toolbar
和 AppBarLayout
里面有一个 TabLayout
。此外,我在 CoordinatorLayout
内但在 ViewPager
外有一个 ViewPager
。
问题是 ViewPager's
的高度比实际可用的要大,导致我的 Fragment
的一些视图被剪掉了。
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/lightGray"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar2"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways"/>
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:scrollbars="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="false"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
这就是我说 ViewPager
高度错误的意思。
一个可能的黑客可以添加与工具栏相同的底部边距,即
?attr/actionBarSize。您甚至可以 fiddle 与其他可能的 ui 边距技巧一起使用,以获得最佳结果。
我花了很多时间在谷歌上搜索和应用建议的答案,但我没有成功,所以决定更深入地研究这个问题并找到一个完整的答案,我想在这里分享。也许它会对某人有所帮助。
所以问题是,当 ViewPager 与 Collapsing Toolbar 一起使用时,第一个无法正确计算其高度。如果你想让 ViewPager 将所有 space 填充到屏幕底部,但不要更多 - 那就有问题了。仅添加 layout_marginBottom
不会解决问题,因为 Collapsing Toolbar 会在用户滚动时更改其高度。
因此,如果您希望 ViewPager 相应地调整其高度以适应 Collapsing Toolbar 高度变化,您需要两件事:
- 自定义滚动行为。
- GlobalLayoutListener,它会在第一次绘制时固定 ViewPager 高度。
它们在这里(用 Kotlin 编写,但它只是一个单独的文件,可以直接放入 Java 项目中):
import android.app.Activity
import android.content.Context
import android.graphics.Point
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
/**
* Custom extension of AppBarLayout.ScrollingViewBehavior to deal with ViewPager height
* in a bunch with Collapsing Toolbar Layout. Works dynamically when AppBar Layout height is changing.
*/
class ViewPagerScrollingBehavior(context: Context, attributeSet: AttributeSet? = null) :
AppBarLayout.ScrollingViewBehavior(context, attributeSet) {
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
val layoutParams = child.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.height = child.height - (dependency.bottom - child.top)
child.layoutParams = layoutParams
child.requestLayout()
return super.onDependentViewChanged(parent, child, dependency)
}
}
/**
* Custom implementation of ViewTreeObserver.OnGlobalLayoutListener to fix the View height
* in a bunch with Collapsing Toolbar Layout. Works when View is drawn on the screen for first time.
* To be used with ViewPagerScrollingBehavior.
*/
class FixHeightGlobalLayoutListener(val activity: Activity, val view: View) : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
val display = activity.windowManager.defaultDisplay
val size = Point()
display.getSize(size)
val height = size.y
val location = IntArray(2)
view.getLocationOnScreen(location)
view.post {
val layoutParams = view.layoutParams as ViewGroup.LayoutParams
layoutParams.height = height - location[1]
view.layoutParams = layoutParams
view.requestLayout()
}
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
并在您的代码中使用它们:
将自定义行为添加到您的 ViewPager:
app:layout_behavior="your.package.ViewPagerScrollingBehavior"
将自定义 OnGlobalLayoutListener 添加到您的 ViewPager:
viewPager.getViewTreeObserver().addOnGlobalLayoutListener(new FixHeightGlobalLayoutListener(this, viewPager));
UDPATE:我不再推荐这个了。我发现它导致了对 onDependentViewChanged
.
根据上面 Yevhenii 的回答,我想我已经用更简单的方法解决了这个问题:
class KeepWithinParentBoundsScrollingBehavior : AppBarLayout.ScrollingViewBehavior {
constructor() : super()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
if (dependency !is AppBarLayout) {
return super.onDependentViewChanged(parent, child, dependency)
}
val layoutParams = child.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.height = parent.height - dependency.bottom
child.layoutParams = layoutParams
return super.onDependentViewChanged(parent, child, dependency)
}
}
然后在 ViewPager
或 AppBar
下面的任何视图上设置 app:layout_behavior="your.package.KeepWithinParentBoundsScrollingBehavior"
。
请注意,这不是适用于所有 CoordinatorLayout
的通用解决方案,但当您在应用栏下方有一个您不想让其超出底部的视图时,它似乎可以工作父 CoordinatorLayout。
更新:
对于键盘消失的情况,您还应该在 ViewPager 上设置 app:layout_anchor="@id/app_bar"
。如果不这样做,ViewPager
布局将不会在键盘消失时刷新,并且 ViewPager
将显示为被截断。
您不应将行为类型“appbar_scrolling_view_behavior”设置为您的 ViewPager。但是将其设置为外部布局。类似下面
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/tabbed_fragments_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>