自定义CollapsibleToolbar结束场景莫名出现内边距问题咨询
自定义CollapsibleToolbar折叠后出现莫名内边距的问题
我正在实现基于MotionLayout和AppBarLayout的自定义CollapsibleToolbar,但遇到了一个奇怪的问题:在折叠后的结束场景(end_scene)中,明明没有给Toolbar设置内边距(padding),但布局却出现了明显的内边距偏移,导致内容无法对齐到父容器边缘。
相关代码
content_main.xml
<com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@null" android:theme="@style/AppTheme.AppBarOverlay"> <foo.bar.CollapsibleToolbar xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/constraintToolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" app:layoutDescription="@xml/main_motion" android:minHeight="160dp" app:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"> <TextView android:id="@+id/month" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="40sp" android:textColor="#000000" android:text="April" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> <TextView android:id="@+id/month_goal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/month" android:textSize="18sp" android:text="1/30" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/month"/> <ImageView android:id="@+id/app_icon" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/ic_launcher" android:contentDescription="@string/this_app_name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/> <com.dinuscxj.progressbar.CircleProgressBar android:id="@+id/days_graph" android:layout_width="128dp" android:layout_height="128dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toTopOf="@+id/name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.24000001" app:layout_constraintVertical_chainStyle="packed" app:line_width="20dp" app:progress_end_color="@color/colorAccent" app:progress_shader="sweep" app:progress_start_color="@color/colorPrimary" app:progress_start_degree="-90" app:progress_stroke_cap="round" app:progress_stroke_width="20dp" app:progress_text_color="@android:color/black" app:style="solid_line" /> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/cv_profile" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" android:background="@color/colorWhite" android:elevation="@dimen/cardview_elevation" android:clickable="true" android:focusable="true" android:foreground="@drawable/selector_ripple" android:contentDescription="Profile" android:src="@drawable/img_default_profile" app:layout_constraintStart_toStartOf="@+id/days_graph" app:layout_constraintEnd_toEndOf="@+id/days_graph" app:layout_constraintTop_toTopOf="@+id/days_graph" app:layout_constraintBottom_toBottomOf="@id/days_graph"/> <ImageView android:id="@+id/circle_profile_overlay" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" android:alpha="1" android:contentDescription="Profile" android:scaleType="fitCenter" android:src="@drawable/ic_check_circle" android:elevation="2dp" app:layout_constraintStart_toStartOf="@+id/days_graph" app:layout_constraintEnd_toEndOf="@+id/days_graph" app:layout_constraintTop_toTopOf="@+id/days_graph" app:layout_constraintBottom_toBottomOf="@+id/days_graph" /> <TextView android:id="@+id/graph_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:bufferType="spannable" android:text="30/365" android:textColor="@android:color/black" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/days_graph" /> <TextView android:id="@+id/change_info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:textColor="@android:color/black" android:layout_marginStart="@dimen/usual_margin" android:layout_marginEnd="@dimen/usual_margin" android:bufferType="spannable" android:text="@string/click_to_change_goal" android:textAlignment="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/name"/> </foo.bar.CollapsibleToolbar> </com.google.android.material.appbar.AppBarLayout>
main_motion_end.xml
<?xml version="1.0" encoding="utf-8"?> <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" android:orientation="vertical"> <TextView android:id="@+id/month" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="40sp" android:textColor="#000000" android:text="April" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> <TextView android:id="@+id/month_goal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/month" android:textSize="18sp" android:text="1/30" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/month"/> <ImageView android:id="@+id/app_icon" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/ic_launcher" android:contentDescription="@string/this_app_name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"/> <com.dinuscxj.progressbar.CircleProgressBar android:id="@+id/days_graph" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp" app:line_width="5dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/cv_profile" android:layout_width="36dp" android:layout_height="36dp" android:layout_gravity="center" android:src="@drawable/img_default_profile" app:layout_constraintStart_toStartOf="@+id/days_graph" app:layout_constraintEnd_toEndOf="@+id/days_graph" app:layout_constraintTop_toTopOf="@+id/days_graph" app:layout_constraintBottom_toBottomOf="@+id/days_graph"/> <ImageView android:id="@+id/circle_profile_overlay" android:layout_width="36dp" android:layout_height="36dp" android:layout_gravity="center" android:alpha="1" android:contentDescription="Profile" android:scaleType="fitCenter" android:src="@drawable/ic_check_circle" android:elevation="2dp" app:layout_constraintStart_toStartOf="@+id/days_graph" app:layout_constraintEnd_toEndOf="@+id/days_graph" app:layout_constraintTop_toTopOf="@+id/days_graph" app:layout_constraintBottom_toBottomOf="@+id/days_graph" /> <TextView android:id="@+id/graph_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:bufferType="spannable" android:text="30/365" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> <TextView android:id="@+id/change_info" android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/> </androidx.constraintlayout.widget.ConstraintLayout>
xml/main_motion.xml
<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2018 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto"> <Transition motion:constraintSetStart="@layout/main_motion_start" motion:constraintSetEnd="@layout/main_motion_end" motion:duration="1000" motion:onTouchUp="stop" motion:motionInterpolator="linear"> </Transition> </MotionScene>
CollapsibleToolbar.kt
/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package foo.bar import android.content.Context import android.util.AttributeSet import androidx.constraintlayout.motion.widget.MotionLayout import com.google.android.material.appbar.AppBarLayout class CollapsibleToolbar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : MotionLayout(context, attrs, defStyleAttr), AppBarLayout.OnOffsetChangedListener { override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { progress = -verticalOffset / appBarLayout?.totalScrollRange?.toFloat()!! } override fun onAttachedToWindow() { super.onAttachedToWindow() (parent as? AppBarLayout)?.addOnOffsetChangedListener(this) } }
问题原因分析
这个问题的核心原因是你给CollapsibleToolbar设置了android:fitsSystemWindows="true"属性:
- 当该属性为true时,系统会自动为视图添加与状态栏高度一致的顶部内边距,避免内容被状态栏遮挡。
- 在展开状态下,Toolbar高度足够,这个内边距不会被明显感知;但折叠到
minHeight(160dp)时,系统添加的状态栏内边距就会凸显出来,造成内容偏移的视觉效果。 - 另外,结束场景的根布局未设置
android:fitsSystemWindows="false",导致MotionLayout切换场景时继承了父容器的适配行为,内边距被保留。
解决方案
方案一:直接移除fitsSystemWindows属性
如果你的页面不需要Toolbar适配状态栏(比如Activity是全屏模式、状态栏透明且内容需要延伸到状态栏下方),可以直接删除CollapsibleToolbar的android:fitsSystemWindows="true"属性:
<foo.bar.CollapsibleToolbar ... <!-- 移除该行 --> <!-- android:fitsSystemWindows="true" --> ...>
方案二:在结束场景中显式清除内边距
如果需要保留展开状态的状态栏适配,但希望折叠后内容对齐边缘,可以修改main_motion_end.xml的根布局:
<androidx.constraintlayout.widget.ConstraintLayout ... android:fitsSystemWindows="false" android:padding="0dp" ...>
方案三:代码中动态调整内边距
也可以在CollapsibleToolbar的代码中,根据折叠进度动态控制padding:
override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { progress = -verticalOffset / appBarLayout?.totalScrollRange?.toFloat()!! // 折叠到结束状态时清除padding,展开时恢复状态栏适配内边距 if (progress >= 1f) { setPadding(0, 0, 0, 0) } else { val statusBarHeight = resources.getDimensionPixelSize(android.R.dimen.status_bar_height) setPadding(0, statusBarHeight, 0, 0) } }
以上三种方案都能解决折叠后Toolbar出现莫名内边距的问题,你可以根据自己的页面需求选择最合适的方式。
内容的提问来源于stack exchange,提问作者Ericano Rhee
相关产品推荐
相关产品推荐

