이 시리즈에서는 MotionLayout을 제외한 ConstraintLayout 2.0의 기능을 소개합니다.
ConstraintLayout 1.1에 대한 내용은 이전 포스팅을 참고해주세요.
Google I/O 2019와 함께 ConstraintLayout 2.0.0의 베타 채널이 배포되었습니다.
베타 릴리즈가 되면 기존의 함수 이름이 더 이상 바뀌지 않습니다. 즉 실제 프로덕션 환경에서 충분히 사용할만한 수준이 되었다는 뜻이지요.
2.0의 새로운 기능들을 살펴보기 전에, 먼저 핵심적인 개념 몇 가지를 정리해보았습니다.
그 동안 ConstraintLayout에서 추구했던 것 중 하나가 "뷰의 구조를 플랫하게 만드는 것"이었는데, 실제로 구현할 때는 매번 Constraint를 설정하는게 귀찮아서 오히려 계층적으로 구현하는게 편한 경우도 있었습니다.
VirtualLayout은 이런 문제를 해결함과 동시에 평면적인 UI 구조를 유지할 수 있게 하고, 더불어서 기존의 UI 시스템으로는 불가능한 다양한 기능을 제공합니다. VirtualLayout을 알아보기 전에, ConstraintHelper에 대해서 짧게 이야기해볼게요.
ConstraintHelper는 UI 계층에 포함되지 않으면서, 다른 뷰들에 특정한 동작을 적용할 수 있습니다. ConstraintLayout 1.1에서는 Guideline과 Barrier, Group이 바로 Helper였습니다.
ConstraintHelper를 사용하면 다음과 같은 장점이 있다고 합니다. (구글의 말에 따르면 말이죠..)
예시를 들기 위해서, 다음과 같은 간단한 동작을 하는 Helper를 직접 만들어보았습니다.
그리기 전에 뷰들을 보이지 않도록 설정을 하고, 1초 뒤에 Scale 효과와 함께 보이게 하는 코드입니다.
class TestHelper : ConstraintHelper {
/* secondary constructors omitted */
private var container: ConstraintLayout? = null
override fun updatePreLayout(container: ConstraintLayout?) {
super.updatePreLayout(container)
getViews(container)?.forEach { view ->
view.visibility = View.INVISIBLE
}
}
override fun updatePostLayout(container: ConstraintLayout?) {
super.updatePostLayout(container)
this.container
?.let { this.container = container }
?: run {
getViews(container)?.forEach { view ->
view.revealAfter(1000L)
}
}
}
}
revealAfter()는 직접 만든 확장 함수입니다. 중요한 부분은 아니라서 생략했습니다.
위와 같이 ConstraintHelper는 몇 가지 함수를 제공해서 뷰의 그리는 과정을 간섭할 수 있도록 합니다.
updatePostMeasure(ConstraintLayout)
updatePreLayout(ConstraintLayout)
updatePostLayout(ConstraintLayout)
updatePostConstraints(ConstraintLayout)
이렇게 나뉘어있긴 하지만, 일반적으로 updatePostLayout()
만으로도 충분한 것 같습니다.
2.0에서는 이러한 Helper의 개념을 확장해서 VirtualLayout을 내놓았습니다. ConstraintHelper와의 차이점이 무엇인지 궁금해서 문서를 조금 찾아봤는데, 명확하게 설명되어 있는 자료가 없어서 소스 코드와 Google I/O 등을 보고 추측을 해보았습니다.
먼저 VirtualLayout은 개념적으로 ConstraintHelper와 동일합니다. 즉, UI 계층에 포함되지 않으면서 다른 뷰들에 영향을 줍니다. 여기에 더불어 다음과 같은 함수를 통해 뷰의 위치를 정하는 과정을 간섭할 수 있습니다.
// VirtualLayout.java
public void onMeasure(androidx.constraintlayout.solver.widgets.VirtualLayout layout, int widthMeasureSpec, int heightMeasureSpec)
VirtualLayout 중 하나인 Flow 역시 이 함수를 오버라이딩하고 있습니다.
// Flow.java
@Override
public void onMeasure(androidx.constraintlayout.solver.widgets.VirtualLayout layout, int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (layout != null) {
layout.measure(widthMode, widthSize, heightMode, heightSize);
this.setMeasuredDimension(layout.getMeasuredWidth(), layout.getMeasuredHeight());
} else {
this.setMeasuredDimension(0, 0);
}
}
결론적으로 ConstraintHelper가 뷰의 동작을 캡슐화한다면, VirtualLayout은 뷰의 위치에 대한 다양한 기능을 캡슐화한다고 볼 수 있겠습니다.
간단하게 정리를 해보면 다음과 같습니다.