Android/ConstraintSet

[Android] ConstraintSet으로 View의 제약조건 수정하기

O_Gyong 2022. 12. 1. 17:26

ConstraintSet은 xml에서 ConstraintLayout이 하는 동작을 코드 상으로 할 수 있게 해주는 클래스이다.

앱에서 어떤 조건에 따라 한 레이아웃을 화면 상단에 배치하거나 하단에 배치하는 등의 연출이 가능하다.

 

예제로 버튼을 클릭했을 때 검정색 뷰의 위치를 옮겨보려고 한다.

상단, 중단, 하단 텍스트가 존재하고 각 텍스트마다 이동 버튼이 존재한다.

이동 버튼을 클릭하면 검정색의 뷰가 해당 텍스트 아래로 이동된다.

참고 자료


ConstraintSet 예제

class MainActivity : AppCompatActivity() {
    private lateinit var mBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mBinding.root)

        mBinding.btnTop.setOnClickListener {
            setConstraint("TOP")
        }

        mBinding.btnMiddle.setOnClickListener {
            setConstraint("MIDDLE")
        }

        mBinding.btnBottom.setOnClickListener {
            setConstraint("BOTTOM")
        }
    }

    /**
     * layout 이동 메서드
     */
    private fun setConstraint(location: String) {
        val constraints = ConstraintSet()
        constraints.clone(mBinding.clMain)
        constraints.removeFromVerticalChain(mBinding.clMove.id)
        constraints.removeFromHorizontalChain(mBinding.clMove.id)

        when(location) {
            "TOP" -> setConstraintConnect(constraints, mBinding.tvTop.id)
            "MIDDLE" -> setConstraintConnect(constraints, mBinding.tvMiddle.id)
            "BOTTOM" -> setConstraintConnect(constraints, mBinding.tvBottom.id)
        }

        constraints.applyTo(mBinding.clMain)
    }

    /**
     * 전달받은 targetId를 기준으로 clMove에 제약조건 적용 메서드
     */
    private fun setConstraintConnect(constraints: ConstraintSet, targetId: Int) {
        // clMove의 app:layout_constraintStart_toStartOf="targetId"와 같음
        constraints.connect(
            mBinding.clMove.id,
            ConstraintSet.START,
            targetId,
            ConstraintSet.START
        )

        // clMove의 app:layout_constraintTop_toBottomOf="targetId"와 같음
        constraints.connect(
            mBinding.clMove.id,
            ConstraintSet.TOP,
            targetId,
            ConstraintSet.BOTTOM
        )

        // marginTop 5 부여
        constraints.setMargin(
            mBinding.clMove.id,
            ConstraintSet.TOP,
            // dp 값 적용
            TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                5f,
                resources?.displayMetrics
            ).toInt()
        )
    }
}

ConstraintSet의 clone에 ConstraintLayout을 넘겨주면 해당 ConstraintLayout의 정보(제약 조건)를 복사하게 된다.

정보를 얻게 된 ConstraintSet 객체는 넘겨받은 ConstraintLayout의 자식 뷰들을 제어할 수 있게 된다.

 

이 정보로 알 수 있는 것은 내가 옮기고자 하는 뷰가 있다면, 그 뷰의 부모 Layout이 ConstraintLayout이어야 한다는 것이다.

(+ ConstraintLayout의 자식 뷰만 제어가 가능하다.)

/**
 * layout 이동 메서드
 */
private fun setConstraint(location: String) {
    val constraints = ConstraintSet()
    constraints.clone(mBinding.clMain)
    constraints.removeFromVerticalChain(mBinding.clMove.id)
    constraints.removeFromHorizontalChain(mBinding.clMove.id)

    when(location) {
        "TOP" -> setConstraintConnect(constraints, mBinding.tvTop.id)
        "MIDDLE" -> setConstraintConnect(constraints, mBinding.tvMiddle.id)
        "BOTTOM" -> setConstraintConnect(constraints, mBinding.tvBottom.id)
    }

    constraints.applyTo(mBinding.clMain)
}

객체를 생성하고 옮기려는 뷰(clMove)의 부모 Layout인 clMain을 clone에 전달했다.

 

이후 removeFromVertical(Horizontal)Chain을 사용했는데, 전달된 뷰의 수직(수평) 제약을 제거한 것이다.

해당 코드를 넣지 않으면 처음 xml에서 초기화된 제약 조건이 남아있기 때문에 원하는 결과가 안 나올 수 있다.

 

마지막에 applyTo로 전달받은 ConstraintLayout에 제약 조건을 적용한다.


/**
 * 전달받은 targetId를 기준으로 clMove에 제약조건 적용 메서드
 */
private fun setConstraintConnect(constraints: ConstraintSet, targetId: Int) {
    // clMove의 app:layout_constraintStart_toStartOf="targetId"와 같음
    constraints.connect(
        mBinding.clMove.id,
        ConstraintSet.START,
        targetId,
        ConstraintSet.START
    )

    // clMove의 app:layout_constraintTop_toBottomOf="targetId"와 같음
    constraints.connect(
        mBinding.clMove.id,
        ConstraintSet.TOP,
        targetId,
        ConstraintSet.BOTTOM
    )

    // marginTop 5 부여
    constraints.setMargin(
        mBinding.clMove.id,
        ConstraintSet.TOP,
        // dp 값 적용
        TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP,
            5f,
            resources?.displayMetrics
        ).toInt()
    )
}

connect를 이용해서 두 뷰 사이의 제약 조건을 설정할 수 있다.

해당 동작을 통해 검정색의 뷰가 TextView의 수평으로 시작점, 수직으로는 아래에 위치할 수 있게 된다.

 

setMargin을 이용하면 뷰 사이에 마진도 적용할 수 있다.


전체 코드