앞서 keyPositionType
에서 좌표계에 대해 알아보았으니 적용해보자.
framePosition
별로 앞서 배운 keyPositionType
을 이용해 위치를 명시한다.
그러면 달은 keyPosition
에서 지정한 시간에 지정한 위치를 지나게 될 것이다.
앞서 살펴본 달 예제에서 아래 두 keyPosition
을 추가한다면 curve를 좀더 둥글게 만들 수 있을 것이다.
<KeyPosition
motion:framePosition="25"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6" />
<KeyPosition
motion:framePosition="75"
motion:motionTarget="@id/moon"
motion:keyPositionType="parentRelative"
motion:percentY="0.6" />
이번에는 keyPosition
이 아닌 keyAttribute
로 motion 중의 속성을 바꿔보자.
keyPosition
때와 같이 framePosition
과 motionTarget
을 잡아준다.
KeyAttribute
는 View에 적용될 수 있는 attribute들을 지원한다.
아래의 attribute들을 고려해서 좀더 풍부한 애니메이션을 만들 수 있을 것이다.
visibility
, alpha
, elevation
, rotation
, scale
, tranlation
<KeyAttribute
android:rotation="-360"
android:scaleX="2.0"
android:scaleY="2.0"
motion:framePosition="50"
motion:motionTarget="@id/moon" />
위 keyAttribute
를 줘서 중간지점에서 360도 회전 및 4배 커지게 할 수 있다.
<KeyAttribute
android:alpha="0.0"
motion:framePosition="85"
motion:motionTarget="@id/credits" />
위 keyAttrivute
를 줘서 원래 0 ~ 100으로 가며 서서히 보이던 credit을 85부터 서서히 보이게 만들 수 있다.
KeyAttribute
에는 앞서 설명한 attribute 말고도 다른 attribute를 적용할 수 있다.
예를 들면 backgroundColor
도 적용할 수 있다.
내부적으로 reflection을 이용해 적당한 setter를 찾아 적용하는 방식이다.
우선 CustomAttribute
안에서 사용할 attributeName
을 정한다.
예를 들면 setColorFilter()
라는 setter를 쓰려고 했으면 colorFilter
로 lowerCamelCase로 표현한다.
그리고 customXXXValue
에 type에 맞는 값을 넣어주면 된다. XXX에는 다음과 같은 type이 쓰일 수 있다.
Color
, Integer
, Float
, String
, Dimension
, Boolean
<KeyAttribute
motion:framePosition="0"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF" />
</KeyAttribute>
<KeyAttribute
motion:framePosition="50"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFB612" />
</KeyAttribute>
<KeyAttribute
motion:framePosition="100"
motion:motionTarget="@id/moon">
<CustomAttribute
motion:attributeName="colorFilter"
motion:customColorValue="#FFFFFF" />
</KeyAttribute>
위 코드에서는 colorFilter
를 사용해서 start(0)와 end(100)에 #FFFFFF흰색을, 50일 때 #FFB612노란색을 적용했다.
onSwipe를 써서 drag event를 처리하는 방법을 아래 예제를 통해 익혀보자.
기본적으로 drag event를 생각했다면 다음과 같이 onSwipe를 Transition 아래에 추가한다.
<OnSwipe motion:touchAnchorId="@id/moon" />
하지만 이대로는 달을 오른쪽 끝까지 쉽게 drag할 수 없을 것이다.
링크를 떠올려보면 touchAnchorSide
나 dragDirection
을 사용해야할 것 같다.
하지만 이 경우 touchAnchorSide
는 달이 회전하기때문에 쉽게 원하는 모양이 나오지 않을 것이다.
대신 dragDirection
을 dragRight
으로 지정하면 오른쪽으로 잘 이동할 것이다.
만약 path가 너무 복잡하면 간단한 path를 따르는 invisible한 뷰를 도입할 수 있다.(?)
touchAnchorSide
는 진행방향과 일관되는 방향(또는 반대방향)으로 정한다. (?)
(여기서는 left / right다. top / bottom은 올라갔다 내려오므로 일관되지 않음)
<OnSwipe
motion:dragDirection="dragRight"
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="right" />
CoordinatorLayout과 같이 사용해서 collapsible header를 만들어보자.
레이아웃 구조는 아래와 같이 해서 AppBarLayout과 NestedScrollView가 스크롤 정보를 공유하게 한다.
<CoordinatorLayout>
<AppBarLayout>
<MotionLayout>
</MotionLayout>
</AppBarLayout>
<NestedScrollView>
</NestedScrollView>
</CoordinatorLayout>
우선 MotionLayout에 scroll을 넣고, 최소한의 높이를 보장하기 위해 아래를 추가한다.
<androidx.constraintlayout.motion.widget.MotionLayout
...
android:minHeight="88dp"
app:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed">
그다음 AppBarLayout에 걸리는 스크롤 효과를 MotionLayout에서의 progress로 적용하기 위해 listener를 등록한다.
즉 코드에서 직접 MotionLayout의 progress를 지정해준다.
private fun coordinateMotion() {
val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
val motionLayout: MotionLayout = findViewById(R.id.motion_layout)
val listener = AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
motionLayout.progress = seekPosition
}
appBarLayout.addOnOffsetChangedListener(listener)
}
+ AppBarLayout이 안에 있는 MotionLayout을 resize 시켜주는게 아니므로, constraintTop이라도 있으면 화면을 심하게 벗어나는 등 이상하게 보여질 것이다.