MotionLayout是用于创建复杂的布局动画的绝佳工具。但是,它确实有一些容易解决的缺点,但最初可能令人困惑。在这篇文章中,我们将讨论布局中视图的可见性方面的一些怪异之处。

对于此特定问题,安装实际上需要花费比修复更长的时间,但是请耐心等待,因为重要的是要了解初始MotionLayout
XML最初看起来是正确的,但没有给我们预期的行为。
我们将首先设置一个非常简单的ConstraintLayout
包含两个视图的视图-a TextView
和a MaterialSwitch
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<?xml version=“1.0” encoding=“utf-8”?>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:padding=“8dp”>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id=“@+id/switch1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_margin=“8dp”
android:text=“@string/toggle_text”
app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent” />
<TextView
android:id=“@+id/textView”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:padding=“8dp”
android:text=“@string/sample_text”
android:textAppearance=“?textAppearanceHeadline3”
android:visibility=“gone”
app:layout_constraintBottom_toTopOf=“@id/switch1”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent” />
</androidx.constraintlayout.widget.ConstraintLayout>
|
的TextView
默认可见性为GONE
,我们通过ip开关切换可见性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.switch1.setOnCheckedChangeListener { _, isChecked –>
binding.textView.visibility = if (isChecked) View.VISIBLE else View.GONE
}
}
}
|
这给了我们切换开关的行为TextView
:

到目前为止,一切都很好。现在,我们将转换ConstraintLayout
为MotionLayout
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
<?xml version=“1.0” encoding=“utf-8”?>
<androidx.constraintlayout.motion.widget.MotionLayout
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:padding=“8dp”
app:layoutDescription=“@xml/activity_visibility_scene”>
<com.google.android.material.switchmaterial.SwitchMaterial
android:id=“@+id/switch1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_margin=“8dp”
android:text=“@string/toggle_text”
app:layout_constraintBottom_toBottomOf=“parent”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent” />
<TextView
android:id=“@+id/textView”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:padding=“8dp”
android:text=“@string/sample_text”
android:textAppearance=“?textAppearanceHeadline3”
android:visibility=“gone”
app:layout_constraintBottom_toTopOf=“@id/switch1”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent” />
</androidx.constraintlayout.motion.widget.MotionLayout>
|
然后我们创建相应的MotionScene
XML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?xml version=“1.0” encoding=“utf-8”?>
<Transition
app:constraintSetEnd=“@+id/end”
app:constraintSetStart=“@id/start”
app:duration=“1000” />
<ConstraintSet android:id=“@+id/start”>
<Constraint android:id=“@id/textView”>
<Layout
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
app:layout_constraintBottom_toTopOf=“@+id/switch1”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent” />
<PropertySet android:visibility=“gone” />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id=“@+id/end” />
</MotionScene>
|
在MotionScene
实际上什么也不做,因为我们从来没有触发转换。我已经提供Constraint
了,TextView
用于指定初始状态。如果我们忽略它,则会得到完全相同的行为–但我只是想清楚一点,包括它并不能避免我们将要看到的问题。
假设这MotionLayout
是ConstraintLayout
它的子类,这似乎是一个合理的假设,即我们将获得与以前完全相同的行为,但事实并非如此:

我们的可见性控件TextView
不再起作用。原因是MotionLayout
实际上要控制布局的各个方面,以便在进行MotionLayout
过渡时正确地操纵事物。那就是破坏了我们对TextView
s可见性的程序控制。为了克服这个问题,我们需要告诉MotionLayout
我们要控制此特定控件的可见性,并且可以设置一个属性来精确地做到这一点:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<?xml version=“1.0” encoding=“utf-8”?>
<Transition
app:constraintSetEnd=“@+id/end”
app:constraintSetStart=“@id/start”
app:duration=“1000” />
<ConstraintSet android:id=“@+id/start”>
<Constraint android:id=“@id/textView”>
<Layout
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
app:layout_constraintBottom_toTopOf=“@+id/switch1”
app:layout_constraintEnd_toEndOf=“parent”
app:layout_constraintStart_toStartOf=“parent”
app:layout_constraintTop_toTopOf=“parent” />
<PropertySet
android:visibility=“gone”
app:visibilityMode=“ignore” />
</Constraint>
</ConstraintSet>
<ConstraintSet android:id=“@+id/end” />
</MotionScene>
|
visibilityMode
控制是否MotionLayout
将控制此特定对象的可见性View
– normal
表示MotionLayout
具有控制权的工具的默认值,但是如果将ignore
其设置为它意味着我们具有控制权。进行这一小更改将使我们拥有以前的行为:

尽管对于有效的单行修复来说,这是一个相当漫长的设置,但重要的是要知道MotionLayout
会改变静态布局的一些基本行为,包括ConstraintLayout
。尽管解决起来很容易,但是我第一次遇到这个问题时,我已经摸了一下头,才了解正在发生的事情以及如何克服它。