작업 일지

[Android] 폴더블 기기 UI 대응하기

O_Gyong 2022. 11. 22.

일반적인 스마트 폰에 맞춰서 개발을 하다가 갤럭시 폴드를 접하고 나서 문제가 생겼다.

대부분의 위젯에  width와 height을 고정 dp 값으로 사용했는데 갤럭시 폴드가 접혀있을 때는 위젯이 잘리고,

펼쳐져 있을 때는 위젯의 크기가 작아 보이거나 위젯들 사이의 여백이 너무 크다는 것이었다.

예시 이미지 / (왼쪽)일반폰, (가운데)폴드: 접힘, (오른쪽)폴드: 펼침


처음 이 문제를 직면하고 나서는 ConstraintLayout의 bias 속성을 사용해서 반응형으로 처리하면 될 것이라고 생각했다.

bias를 사용하면 일반폰과 폴더블 폰이 접혔을 때는 UI가 괜찮게 나온다. 하지만 폴더블 폰이 펼쳐졌을 때 해상도가 높다 보니 버튼 크기가 이상하게 보일 정도로 커지는 문제가 있었다.

예시 이미지 / (왼쪽)일반폰, (가운데)폴드: 접힘, (오른쪽)폴드: 펼침


기기 타입 찾기

이 문제를 해결하려면 기기 타입별로 다른 dp 값을 적용시켜야 한다고 생각이 들었다.

val screenSizeType = resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK

if (screenSizeType == Configuration.SCREENLAYOUT_SIZE_XLARGE) {
    println("SCREENLAYOUT_SIZE_XLARGE")
}else if(screenSizeType == Configuration.SCREENLAYOUT_SIZE_LARGE) {
    println("SCREENLAYOUT_SIZE_LARGE")
}else if(screenSizeType == Configuration.SCREENLAYOUT_SIZE_NORMAL) {
    println("SCREENLAYOUT_SIZE_NORMAL")
}else if(screenSizeType == Configuration.SCREENLAYOUT_SIZE_SMALL) {
    println("SCREENLAYOUT_SIZE_SMALL")
}else if(screenSizeType == Configuration.SCREENLAYOUT_SIZE_UNDEFINED) {
    println("SCREENLAYOUT_SIZE_UNDEFINED")
}else {
    println("else")
}

안드로이드는 스크린 사이즈를 정수로 반환하여 알려주는 함수가 있다.

하지만 폴드를 접었을 때 일반폰과 똑같은 SCREENLAYOUT_SIZE_NORMAL이 찍혔다.

폴드를 접었을 때의 처리를 별도로 하기 위해서 실제 해상도 값이 필요했다.

val display = applicationContext?.resources?.displayMetrics
println("widthPixels : ${display?.widthPixels}")

 

widthPixels는 기기의 가로 화소 수를 나타낸다.

위쪽부터 일반, 폴드:접힘, 폴드:펼침에 해당하는 가로 화소 수이다.

이 크기를 이용해서 기기 타입을 나눴다. 

 

 

/**
 * 기기 타입 찾기
 */
private fun getDeviceType() {
    val display = applicationContext?.resources?.displayMetrics

    deviceType =
        if(display == null) {
            "NORMAL"
        }else {
            // 태블릿, 폴드 펼침
            if(display.widthPixels > 1600) {
                "TABLET"
            }
            // 미니, 폴드 닫힘
            else if(display.widthPixels < 980) {
                "MINI"
            }
            // 일반
            else{
                "NORMAL"
            }
        }
}

 

넉넉하게 widthPixels 값에 따라 "TABLET", "MINI", "NORMAL"로 값을 초기화했다.

실제로는 deviceType을 싱글톤 객체로 선언하고 스플래시 화면에서 초기화하여 다양한 화면에서 사용했다.


기기 타입에 맞춰 버튼 크기 적용

<LinearLayout
    android:id="@+id/ll_1"
    android:layout_width="75dp"
    android:layout_height="75dp"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent">

    <Button
        android:id="@+id/btn_1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:backgroundTint="#676666" />
</LinearLayout>

코드 상으로 버튼의 width나 height에 값을 적용시키지 못한다.

하지만 LinearLayout과 같은 레이아웃은 LayoutParams를 통해서 크기 조절이 가능하다.

xml에서 버튼의 width와 height을 "match_parent"로 변경하고 LinearLayout을 부모 뷰로 둔다.

이때 LinearLayout의 크기를 기존에 버튼이 갖고 있던 크기로 세팅을 해준다.

private lateinit var llLayoutParams1: ViewGroup.LayoutParams
private lateinit var llLayoutParams2: ViewGroup.LayoutParams
private lateinit var llLayoutParams3: ViewGroup.LayoutParams
private lateinit var llLayoutParams4: ViewGroup.LayoutParams

/**
 * 버튼 크기 정하기
 */
private fun setBtnSize() {

    llLayoutParams1 = mBinding.ll1.layoutParams
    llLayoutParams2 = mBinding.ll2.layoutParams
    llLayoutParams3 = mBinding.ll3.layoutParams
    llLayoutParams4 = mBinding.ll4.layoutParams

    when(deviceType) {
        "MINI" -> {
            setLayoutParams(llLayoutParams1, 60F)
            setLayoutParams(llLayoutParams2, 60F)
            setLayoutParams(llLayoutParams3, 60F)
            setLayoutParams(llLayoutParams4, 60F)
        }
        "NORMAL" -> {
            setLayoutParams(llLayoutParams1, 75F)
            setLayoutParams(llLayoutParams2, 75F)
            setLayoutParams(llLayoutParams3, 75F)
            setLayoutParams(llLayoutParams4, 75F)
        }
        "TABLET" -> {
            setLayoutParams(llLayoutParams1, 90F)
            setLayoutParams(llLayoutParams2, 90F)
            setLayoutParams(llLayoutParams3, 90F)
            setLayoutParams(llLayoutParams4, 90F)
        }
        else -> {
            setLayoutParams(llLayoutParams1, 75F)
            setLayoutParams(llLayoutParams2, 75F)
            setLayoutParams(llLayoutParams3, 75F)
            setLayoutParams(llLayoutParams4, 75F)
        }
    }

    mBinding.ll1.layoutParams = llLayoutParams1
    mBinding.ll2.layoutParams = llLayoutParams2
    mBinding.ll3.layoutParams = llLayoutParams3
    mBinding.ll4.layoutParams = llLayoutParams4
}

 

각 버튼을 감싼 layoutParams 변수들은 필드 변수로 선언하여 다른 메서드에서 사용할 수 있도록 한다.

일반폰일 때는 dp가 75, 폴드:접힘 일 땐 60, 폴드:펼힘 일 때는 90으로 설정하였다.

 

/**
 * LayoutParams의 크기 변환
 */
private fun setLayoutParams(llLayoutParams: ViewGroup.LayoutParams, value: Float) {
    llLayoutParams.width = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        value,
        resources?.displayMetrics
    ).toInt()

    llLayoutParams.height = TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        value,
        resources?.displayMetrics
    ).toInt()
}

TypedValue.applyDimension은 dp와 pixel 간의 단위를 변환시켜주는 함수이다.


예시 이미지 / (왼쪽)일반폰, (가운데)폴드: 접힘, (오른쪽)폴드: 펼침

 

모든 기기에서 잘 표시될 것이라는 보장은 없지만..

기기 타입을 세 가지로 분류하여 UI를 각기 다르게 했기 때문에 괜찮지 않을까 싶다.

 

전체 코드

댓글