일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Android
- MediaPlayer
- Observable
- Android 13
- Kotlin
- rx
- mvvm
- PagingLib
- liveData
- mysql
- 테스트 자동화
- node.js
- node
- android13
- Koin
- SWIFTUI
- 인앱결제
- google play
- MotionLayout
- RxKotlin
- Reactive
- SwiftUI Tutorial
- databinding
- paging
- MediaSession
- Animation
- list
- 동영상
- junit
- GCP
- Today
- Total
봄날은 갔다. 이제 그 정신으로 공부하자
MotionLayout - entrance scene sample 분석 본문
이 글은 android 개발자 사이트에서 제공하는 MotionLayout 샘플 소스 중 entrance scene의 동작을 분석한 글로 MotionLayout 관련 마지막 글입니다. 해당 샘플을 분석한 이유는 entrance scene 샘플의 동작이 다른 샘플에 비해 복잡하기 때문이며 복잡한 만큼 샘플에
적용된 MotionLayout 요소가 많아 분석 및 향후 개발에 도움이 될 것으로 판단되기 때문 입니다.
이전 글이 궁금하신 분은 여기로...
샘플 소스 사이트: https://github.com/android/views-widgets-samples/tree/master/ConstraintLayoutExamples
분석을 시작하기 전에
분석을 시작하기 전에 분석하려는 MotionLayout의 동작은 아래와 같습니다.
샘플소스의 motionlayoutintegrations 폴더에 해당하는 부분으로 motionLayout이 적용된 layout xml 파일은 activity_entrance.xml 파일입니다.
Activity 소스 코드인 Entrance.kt 파일은 아래와 같이 애니메이션 처리는 1도 없는 것으로 보아 위 적용된 모든 애니메이션 처리는 motionLayout을 사용한 것임을 알 수 있습니다.
class Entrance : AppCompatActivity() {
private lateinit var binding: ActivityEntranceBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEntranceBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
activity_entrance.xml 파일을 시작으로 위 적용된 애니메이션을 분석해보도록 하겠습니다.
activity_entrance.xml에 MotionLayout을 어떻게 적용했을까?
xml 파일은 아래와 같습니다.
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/activity_entrance_scene"
android:background="?attr/colorPrimary"
tools:context=".Entrance">
<ImageView
android:id="@+id/scrolling_background"
android:layout_width="0dp"
android:layout_height="2000dp"
android:scaleType="centerCrop"
android:src="@drawable/star_parallax"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:contentDescription="@string/star_background" />
<TextView
android:id="@+id/spacemoji_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="24dp"
android:alpha="0"
android:text="@string/spacemoji"
android:textColor="?attr/colorOnPrimary"
style="?attr/textAppearanceHeadline2" />
<TextView
android:id="@+id/spacemoji_subtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/spacemoji_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:alpha="0"
android:layout_marginTop="16dp"
android:text="@string/quick_access_to_space_themed_emoji"
android:textColor="?attr/colorOnPrimary"
style="?attr/textAppearanceSubtitle1" />
<androidx.cardview.widget.CardView
android:id="@+id/card1"
android:alpha="0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/spacemoji_subtext"
app:layout_constraintBottom_toTopOf="@id/card2"
app:layout_constraintVertical_chainStyle="packed"
android:layout_margin="16dp"
android:layout_width="0dp"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/image"
android:layout_width="54dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="8dp"
android:layout_marginVertical="16dp"
android:text="@string/ringed_planet_emoji"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
/>
<!-- Title, secondary and supporting text -->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/image"
app:layout_constraintTop_toTopOf="@id/image"
android:layout_marginStart="16dp"
android:text="@string/ringed_planet"
android:textAppearance="?attr/textAppearanceHeadline6"
/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="@id/title"
android:layout_marginTop="8dp"
android:text="@string/added_in_2019_in_unicode_12_0"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card2"
android:alpha="0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card1"
app:layout_constraintBottom_toTopOf="@id/card3"
android:layout_margin="16dp"
android:layout_width="0dp"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/image2"
android:layout_width="54dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="8dp"
android:layout_marginVertical="16dp"
android:text="@string/rocket_emoji"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
/>
<!-- Title, secondary and supporting text -->
<TextView
android:id="@+id/title2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/image2"
app:layout_constraintTop_toTopOf="@id/image2"
android:layout_marginStart="16dp"
android:text="@string/rocket"
android:textAppearance="?attr/textAppearanceHeadline6"
/>
<TextView
android:id="@+id/subtitle2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/title2"
app:layout_constraintStart_toStartOf="@id/title2"
android:layout_marginTop="8dp"
android:text="@string/added_in_2010_in_unicode_6_0"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card3"
android:alpha="0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card2"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_margin="16dp"
android:layout_width="0dp"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/image3"
android:layout_width="54dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="8dp"
android:layout_marginVertical="16dp"
android:text="@string/space_moster_emoji"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
/>
<!-- Title, secondary and supporting text -->
<TextView
android:id="@+id/title3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/image3"
app:layout_constraintTop_toTopOf="@id/image3"
android:layout_marginStart="16dp"
android:text="@string/space_monster"
android:textAppearance="?attr/textAppearanceHeadline6"
/>
<TextView
android:id="@+id/subtitle3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/title3"
app:layout_constraintStart_toStartOf="@id/title3"
android:layout_marginTop="8dp"
android:text="@string/added_in_2010_in_unicode_6_0"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?android:attr/textColorSecondary"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.motion.widget.MotionLayout>
위 layout xml을 표현하면 아래 그림과 같이 보라색 화면으로 보여집니다.
눈여겨 봐야할 부분은 "androidx.constraintlayout.motion.widget.MotionLayout" 부분 입니다.
앞선 글에서 언급하였듯이 MotionLayout은 ConstraintLayout의 서브 클래스이므로 MotionLayout 하위 요소들은
ConstraintLayout에서 사용했던 app:... 들을 그대로 사용할 수 있습니다.
해당 화면에 MotionLayout을 적용하기 위해 "app:layoutDescription="@xml/activity_entrance_scene""을
선언한 것을 확인할 수 있는데 해당 부분을 통해 MotionLayout 애니메이션이 적용됩니다.
이제 xml 폴더에 있는 activity_entrance_scene.xml을 분석해보겠습니다.
MotionLayout 실행 파일인 activity_entrance_scene.xml 분석하기
MotionLayout 실행 파일인 activity_entrance_scene.xml 파일은 아래와 같이 파일 전체를 감사는 한개의 <MotionScene>과 여러개의 <ConstraintSet> 그리고 다수의 <Transition>으로 구성되어 있음을 알 수 있습니다.
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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">
<!-- This animation uses a series of autoprogress between different ConstraintSet to build
keyframes of the animation.
As the animation plays, MotionLayout will display each ConstraintSet in turn.
Each ConstraintSet uses deriveConstraintsFrom the previous constraint set to only specify the
changes needed for the next KeyFrame.
The structure of this animation is best viewed and edited in the Motion
Editor in Android Studio
-->
<ConstraintSet android:id="@+id/initial" >
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
/>
</ConstraintSet>
<ConstraintSet android:id="@+id/empty" >
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="2000dp"
android:translationY="100px"
/>
</ConstraintSet>
<ConstraintSet
android:id="@+id/first_half"
motion:deriveConstraintsFrom="@+id/empty">
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_bias="0.80"
android:layout_width="0dp"
android:layout_height="2000dp"
/>
</ConstraintSet>
<ConstraintSet
android:id="@+id/middle_animations"
motion:deriveConstraintsFrom="@+id/first_half" >
<Constraint
android:id="@+id/spacemoji_text"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha=".99"
android:layout_marginTop="24dp"
/>
<Constraint
android:id="@+id/spacemoji_subtext"
motion:layout_constraintTop_toBottomOf="@id/spacemoji_text"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha=".99"
android:layout_marginTop="16dp"
/>
<Constraint
android:id="@+id/card1"
motion:layout_constraintTop_toBottomOf="parent"
motion:layout_constraintBottom_toTopOf="@id/card2"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
motion:layout_constraintVertical_chainStyle="packed"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card2"
motion:layout_constraintTop_toBottomOf="@id/card1"
motion:layout_constraintBottom_toTopOf="@id/card3"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card3"
motion:layout_constraintTop_toBottomOf="@id/card2"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_bias="0.70"
android:layout_width="0dp"
android:layout_height="2000dp"
/>
</ConstraintSet>
<ConstraintSet
android:id="@+id/second_half"
motion:deriveConstraintsFrom="@+id/middle_animations" >
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="2000dp"
/>
<Constraint
android:id="@+id/card1"
motion:layout_constraintTop_toBottomOf="@id/spacemoji_subtext"
motion:layout_constraintBottom_toTopOf="@id/card2"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_chainStyle="packed"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:alpha="1.0"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card3"
motion:layout_constraintTop_toBottomOf="@id/card2"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:alpha="1.0"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card2"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="@id/card1"
motion:layout_constraintBottom_toTopOf="@id/card3"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:alpha="1.0"
motion:layout_constraintStart_toStartOf="parent"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
/>
</ConstraintSet>
<!-- Each transition uses animateToEnd to combine a sequence of smaller animations into one
big entrance animation. -->
<Transition
motion:constraintSetStart="@+id/initial"
motion:constraintSetEnd="@+id/empty"
motion:motionInterpolator="linear"
motion:autoTransition="animateToEnd"
motion:duration="200" >
</Transition>
<Transition
motion:constraintSetStart="@+id/empty"
motion:constraintSetEnd="@+id/first_half"
motion:autoTransition="animateToEnd"
motion:motionInterpolator="linear"
motion:duration="500" >
</Transition>
<Transition
motion:constraintSetStart="@+id/first_half"
motion:constraintSetEnd="@+id/middle_animations"
motion:autoTransition="animateToEnd"
motion:motionInterpolator="linear"
motion:duration="300">
<KeyFrameSet >
<KeyAttribute
motion:motionTarget="@+id/spacemoji_text"
motion:framePosition="20"
android:alpha="1"
/>
<KeyAttribute
motion:motionTarget="@+id/spacemoji_subtext"
motion:framePosition="80"
android:alpha="0"
/>
</KeyFrameSet>
</Transition>
<Transition
motion:constraintSetStart="@+id/middle_animations"
motion:constraintSetEnd="@+id/second_half"
motion:autoTransition="animateToEnd"
motion:motionInterpolator="easeOut"
motion:duration="7000" >
<KeyFrameSet >
<KeyPosition
motion:motionTarget="@+id/card1"
motion:framePosition="20"
motion:percentY="1.0"
/>
<KeyAttribute
motion:motionTarget="@+id/card1"
motion:framePosition="20"
android:alpha="1"
/>
<KeyPosition
motion:motionTarget="@+id/card2"
motion:framePosition="25"
motion:percentY="1.0"
/>
<KeyAttribute
motion:motionTarget="@+id/card2"
motion:framePosition="25"
android:alpha="1"
/>
<KeyPosition
motion:motionTarget="@+id/card3"
motion:framePosition="30"
motion:percentY="1" />
<KeyAttribute
motion:motionTarget="@+id/card3"
motion:framePosition="30"
android:alpha="1"
/>
</KeyFrameSet>
</Transition>
</MotionScene>
분석하려면 애니메이션 모션 시퀀스를 파악하는 것이 중요합니다. 모션 시퀀스는 아래와 같습니다.
<MotionScene>내에 여러개의 <Transition>이 존재하므로 android는 적절한 순서로 모션 시퀀스를 실행하는데
여기에서는 선언된 순서대로 <Transition>이 실행됩니다.
선언된 순서대로 실행되는데에는 <ConstraintSet>의 "motion:deriveConstraintsFrom" 요소가 영향을 끼칩니다.
선언된 <ConstraintSet> 중 "initial"과 "empty"를 제외한 모든 <ConstraintSet>에 "motion:deriveConstraintsFrom" 요소를 추가해 <ConstraintSet>을 모션 시퀀스에 영향을 끼치게 됩니다.
각각의 <Transition>은 시작을 나타내는 "motion:constraintSetStart"와 종료를 나타내는 "motion:constraintSetEnd" 요소를 포함하고 있으며 이 요소들의 id는 <ConstraintSet>의 id입니다.
<Transition> initial ~ empty
처음 실행되는 <Transition>은 아래 Transition이며 initial로 시작해서 empty로 종료되고 적용되는 motion Interpolator는 linear이고 아주 짧은 시간(200ms)만 실행되고 종료됩니다.
<Transition
motion:constraintSetStart="@+id/initial"
motion:constraintSetEnd="@+id/empty"
motion:motionInterpolator="linear"
motion:autoTransition="animateToEnd"
motion:duration="200" >
</Transition>
실행되는 <ConstraintSet> 요소는 아래와 같으며 layout xml의 백그라운드 이미지인 "scrolling_background" 이미지를 투명하게 처리했다가 높이를 2000px로 변경하면서 이미지를 위로 올리고 점차 불투명하게 처리함을 확인할 수 있습니다.
<ConstraintSet android:id="@+id/initial" >
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
/>
</ConstraintSet>
<ConstraintSet android:id="@+id/empty" >
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="2000dp"
android:translationY="100px"
/>
</ConstraintSet>
해당 애니메이션은 200ms동안만 실행되므로 사용자가 인식하기는 거의 불가능합니다.
<Transition> empty ~ first_half
두번째 실행되는 <Transition>은 empty ConstraintSet로 시작해서 first_half ConstraintSet으로 종료됩니다.
해당 애니메이션도 아주 짧은 시간(500ms)동안만 실행되므로 사용자가 인식하기 어렵습니다.
<Transition
motion:constraintSetStart="@+id/empty"
motion:constraintSetEnd="@+id/first_half"
motion:autoTransition="animateToEnd"
motion:motionInterpolator="linear"
motion:duration="500" >
</Transition>
실행되는 <ConstraintSet> 요소는 아래와 같으며 이 것 역시 백그라운드 이미지인 "scrolling_background" View만 제어함을 알 수 있습니다.
상단으로 올라간 View를 "motion:layout_constraintVertical_bias="0.80" 만큼 내려 줍니다.
<ConstraintSet android:id="@+id/empty" >
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="2000dp"
android:translationY="100px"
/>
</ConstraintSet>
<ConstraintSet
android:id="@+id/first_half"
motion:deriveConstraintsFrom="@+id/empty">
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_bias="0.80"
android:layout_width="0dp"
android:layout_height="2000dp"
/>
</ConstraintSet>
<Transition> first_half ~ middle_animation
세번째 실행되는 <Transition>은 first_half ConstraintSet으로 시작해 middle_animation ConstraintSet으로 종료됩니다.
세번째 실행되는 <Transition>은 이전 실행된 것과 다르게 하위에 <KeyFrameSet>을 한개 가지고 있는 것을 알 수 있습니다. 해당 부분을 분석해보면 아래와 같습니다. <keyFrameSet>은 하위에 속성을 나타내는 <keyAttribute> 두개를 가지고 있습니다. 첫번째 <keyAttribute>는 "spacemoji_text" View를 전체 애니메이션 실행의 20% 실행 시점에 보여주도록 처리하고. 두번째 <keyAttribute>는 "spacemoji_subtext" View를 전체 애니메이션 실행의 80% 실행 시점에 보여주도록 처리합니다. 이러한 처리로 인해 상단의 TextView 두개가 순차적으로 보여지게 됩니다.
<Transition
motion:constraintSetStart="@+id/first_half"
motion:constraintSetEnd="@+id/middle_animations"
motion:autoTransition="animateToEnd"
motion:motionInterpolator="linear"
motion:duration="300">
<KeyFrameSet >
<KeyAttribute
motion:motionTarget="@+id/spacemoji_text"
motion:framePosition="20"
android:alpha="1"
/>
<KeyAttribute
motion:motionTarget="@+id/spacemoji_subtext"
motion:framePosition="80"
android:alpha="0"
/>
</KeyFrameSet>
</Transition>
실행되는 <ConstraintSet> 요소는 아래와 같습니다.
first_half ConstraintSet은 scrolling_background View에 대해서만 정의하고 있지만 middle_animations ConstraintSet은 scrolling_background View 외에도 spacemoji_text, spacemoji_subtext View 그리고 추가로 card1, card2, card3에 대해 Constraint 요소가 정의되어 있는 것을 알수 있는데, 세번째 Transition에서 card1, card2, card3 Constraint 요소는 사용되지 않습니다.
<ConstraintSet
android:id="@+id/first_half"
motion:deriveConstraintsFrom="@+id/empty">
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_bias="0.80"
android:layout_width="0dp"
android:layout_height="2000dp"
/>
</ConstraintSet>
<ConstraintSet
android:id="@+id/middle_animations"
motion:deriveConstraintsFrom="@+id/first_half" >
<Constraint
android:id="@+id/spacemoji_text"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha=".99"
android:layout_marginTop="24dp"
/>
<Constraint
android:id="@+id/spacemoji_subtext"
motion:layout_constraintTop_toBottomOf="@id/spacemoji_text"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha=".99"
android:layout_marginTop="16dp"
/>
<Constraint
android:id="@+id/card1"
motion:layout_constraintTop_toBottomOf="parent"
motion:layout_constraintBottom_toTopOf="@id/card2"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
motion:layout_constraintVertical_chainStyle="packed"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card2"
motion:layout_constraintTop_toBottomOf="@id/card1"
motion:layout_constraintBottom_toTopOf="@id/card3"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card3"
motion:layout_constraintTop_toBottomOf="@id/card2"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_bias="0.70"
android:layout_width="0dp"
android:layout_height="2000dp"
/>
</ConstraintSet>
<Transition> middle_animation ~ second_half
마지막으로 실행되는 <Transition>은 middle_animation ConstraintSet으로 시작해 second_half ConstraintSet으로 종료됩니다. 마지막으로 실행되는 <Transition>은 하위에 여러개의 <KeyFrameSet>을 가지고 있습니다.
이 KeyFrameSet은 총 6개 keyPosition & keyAttribute 3개 set인 것을 확인할 수 있습니다.
각 세트 애니메이션은 20%, 25, 30%에서 시작되는 것을 알 수 있습니다.
이 애니메이션으로 인해 아이템 3개가 순차적으로 아래에서 위로 올라오게 됩니다.
<Transition
motion:constraintSetStart="@+id/middle_animations"
motion:constraintSetEnd="@+id/second_half"
motion:autoTransition="animateToEnd"
motion:motionInterpolator="easeOut"
motion:duration="7000" >
<KeyFrameSet >
<KeyPosition
motion:motionTarget="@+id/card1"
motion:framePosition="20"
motion:percentY="1.0"
/>
<KeyAttribute
motion:motionTarget="@+id/card1"
motion:framePosition="20"
android:alpha="1"
/>
<KeyPosition
motion:motionTarget="@+id/card2"
motion:framePosition="25"
motion:percentY="1.0"
/>
<KeyAttribute
motion:motionTarget="@+id/card2"
motion:framePosition="25"
android:alpha="1"
/>
<KeyPosition
motion:motionTarget="@+id/card3"
motion:framePosition="30"
motion:percentY="1" />
<KeyAttribute
motion:motionTarget="@+id/card3"
motion:framePosition="30"
android:alpha="1"
/>
</KeyFrameSet>
</Transition>
구체적인 실행 <ConstraintSet> 요소는 아래와 같습니다.
<!-- middle_animation ConstraintSet은 위 코드 참조 -->
<ConstraintSet
android:id="@+id/second_half"
motion:deriveConstraintsFrom="@+id/middle_animations" >
<Constraint
android:id="@+id/scrolling_background"
motion:layout_constraintTop_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="2000dp"
/>
<Constraint
android:id="@+id/card1"
motion:layout_constraintTop_toBottomOf="@id/spacemoji_subtext"
motion:layout_constraintBottom_toTopOf="@id/card2"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_chainStyle="packed"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:alpha="1.0"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card3"
motion:layout_constraintTop_toBottomOf="@id/card2"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:alpha="1.0"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
/>
<Constraint
android:id="@+id/card2"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="@id/card1"
motion:layout_constraintBottom_toTopOf="@id/card3"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:alpha="1.0"
motion:layout_constraintStart_toStartOf="parent"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
/>
</ConstraintSet>
하나만 예를 들어 설명하면 card1은 middle_animation ConstraintSet에 아래와 같이 선언되어 있고
<Constraint
android:id="@+id/card1"
motion:layout_constraintTop_toBottomOf="parent"
motion:layout_constraintBottom_toTopOf="@id/card2"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
motion:layout_constraintVertical_chainStyle="packed"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
/>
second_half ConstraintSet에 아래와 같이 선언되어 있습니다.
<Constraint
android:id="@+id/card1"
motion:layout_constraintTop_toBottomOf="@id/spacemoji_subtext"
motion:layout_constraintBottom_toTopOf="@id/card2"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintVertical_chainStyle="packed"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:alpha="1.0"
android:layout_marginBottom="16dp"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
/>
card1 View는 애니메이션 시작 시 Display 하단에 위치해 있다가 종료 시 spacemoji_subtext View의 16dp 하단에 위치하게 됩니다. 또한 애니메이션 시작시 보여지지 않다가 점점 보여지게 처리가되어 종료 시에는 완전히 보여지게 됩니다.
마무리
해당 샘플 소스는 android에서 개발 편의성을 위해 직접 제공하는 것이니 만큼 다양한 MotionLayout 요소들이 포함되어 있음을 확인할 수 있습니다. 또한 MotionLayout을 사용하는 것만으로도 기존 코드에서 직접 Animation을 구현하던 것과 동일한 효과를 얻을 수 있다는 것을 확인할 수 있습니다.
'학습' 카테고리의 다른 글
Google Play 결제 시스템 - 준비하기 (0) | 2020.12.14 |
---|---|
Google Play 결제 시스템 - 개요 (0) | 2020.12.14 |
MotionLayout - xml 구성요소 (0) | 2020.11.25 |
MotionLayout - overview (0) | 2020.11.25 |
UI Test 적용하기 (0) | 2020.11.25 |