前言
这两天新写一个Demo时发现了几个UI问题:
- fragment内顶部AppBarLayout的高度缺失(缺失高度即系统状态栏高度), 切换Fragment时才会恢复;
- RecyclerView顶部缩入了AppBarLayout中, 缩入高度即系统状态栏高度, 而且也在切换Fragment时才会恢复;
现象如下
- AppBarLayout高度缺失:
- RecyclerView顶部缩入了AppBar:
布局文件如下
- activity布局
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.MainActivity">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorButtonNormal"
app:tabIndicator="@null"
app:tabIndicatorHeight="0dp"
app:tabMode="fixed"/>
</LinearLayout>
- fragment布局
<androidx.constraintlayout.widget.ConstraintLayout
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">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true" /** 注意 **/
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:navigationIcon="@drawable/icon_search"
app:title=" ">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvBarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="多媒体稿"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="20sp"/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rcvDraft"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
解决路程
考虑几个UI错位的高度均是系统状态栏高度, 而且在切换fragment之后就恢复, 猜测是Activity创建Fragment时AppBarLayout的WindowInsetsCompat还没有来得及更新, 导致AppBarLayout的paddingTop计算错误导致, 所以测试了一下延迟设置的方式.
-
AppBarLayout中的WindowInsetsCompat更新操作public AppBarLayout(Context context, AttributeSet attrs, int defStyleAttr) { ViewCompat.setOnApplyWindowInsetsListener( this, new androidx.core.view.OnApplyWindowInsetsListener() { @Override public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) { return onWindowInsetChanged(insets); // 更新WindowInsetsCompat } }); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); final int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (ViewCompat.getFitsSystemWindows(this)) { // 简化大量代码 int newHeight = getMeasuredHeight(); newHeight += getTopInset(); setMeasuredDimension(getMeasuredWidth(), newHeight); } } final int getTopInset() { return lastInsets != null ? lastInsets.getSystemWindowInsetTop() : 0; } -
修改
Activity中设置Fragment方式为post, 解决TabBarLayout高度显示错误override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 解决TabBarLayout不能fitSystem的问题 tabLayout.post { initFragments() viewPager.adapter = MainPagerAdapter(fragmentList, supportFragmentManager) tabLayout.setupWithViewPager(viewPager) initTabs() } } -
修改
Fragment中更新RecyclerView方式为post, 解决RecyclerView顶部缩入了AppBarLayout问题override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 解决RCV缩入了AppbarLayout的问题 rcvDraft.post { rcvDraft.adapter = draftAdapter } }
修改之后问题确实消失了, 但是感觉不应该, fitsSystemWindows和AppBarLayout都出现了这么长时间了, 不应该还会有这样的问题, 又搜了一下, 果然发现端倪了.
根本原因
说出来都生气, 妈个鸡, 原来是ConstraintLayout的bug, 出问题时使用的是androidx.constraintlayout:constraintlayout:2.0.0-beta2, 在Maven仓库中找到最新的正式版(1.1.3)更换后就没有问题了, 根本不用什么post.
可能这里有小伙伴要问了, 为什么ConstraintLayout内部控件的fitsSystemWindows要受其父布局影响呢?
简单说就是WindowInsets是自父布局到子布局传递的, 就像TouchEvent一样, 涉及到消耗和传递, 这里就不重复讲了, 详细点的可以看下这篇文章:
带你彻底弄懂状态栏透明的细节 —— 深入分析 fitsSystemWindows
附
另外, 还有一个RecyclerView.Adapter#notifyDataSetChanged()无效/必须滑动一下才会更新的问题, 也是因为这个ConstraintLayout版本的bug.