안드로이드/compose

[Compose] 라디오 버튼 형식으로 View 출력하기

딩보 2023. 12. 7. 00:17

 

개발 요구사항

최근 3일 내(오늘, 내일, 모레) 식단을 식사 시간대별로 분류하여 출력

  • 오늘, 내일, 모레 버튼을 라디오 버튼 형식으로 구현
  • 각각의 버튼을 클릭하면 상응하는 날짜의 식단을 Text로 출력

 

개발 내용

  1. BtnDateView : 라디오 버튼을 구현한 파일
  2. MainActivity : 라디오 버튼의 현재값을 받아 ContentView에 전달하는 역할
  3. ContentView : 라디오 버튼의 현재값에 따른 출력 파일

selectedDay라는 변수 위주!

요런 느낌

 

 

 

DateEnum

enum class DateEnum(val date:String) {
    TODAY("오늘"),
    TOMORROW("내일"),
    AFTER_TOMORROW("모레")
}

 

BtnDateView

라디오 버튼을 구현한 파일

기본 라디오 버튼은 custom하기가 어렵기 때문에 Column에 라디오 버튼 역할을 부여하였다.

object BtnDateView {
    const val FONT_SIZE = 15
    @Composable
    fun main(selectedDay: MutableState<DateEnum>) {
        RadioGroup(selectedDay)
    }
}

@Composable
private fun RadioGroup(selectedDay: MutableState<DateEnum>) {
    val dateList = listOf(
        DateEnum.TODAY,
        DateEnum.TOMORROW,
        DateEnum.AFTER_TOMORROW
    )

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 60.dp),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        dateList.forEach { text ->
            RadioBtn(
                modifier = Modifier.weight(1f),
                text = text.date, selectedOption = selectedDay.value,
                // 현재 선택된 값으로 selectedDay의 값 변경
                onOptionSelected = { selectedDay.value = it }
            )
        }
    }
}

@Composable
private fun RadioBtn(
    modifier: Modifier,
    text: String,
    selectedOption: DateEnum,
    onOptionSelected: (DateEnum) -> Unit
) {
    Column(
        modifier = modifier
            .selectable(
            	// 현재 선택되어 있는 뷰가 어떤 것인지
                selected = (text == selectedOption.date),
                // 뷰 클릭시, DateEnum.date 중에서 text와 일치하는 첫번째 값을 현재 라디오 선택값으로 함
                onClick = {
                    onOptionSelected(DateEnum.values().first { it.date == text })
                },
                role = Role.RadioButton
            ),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            modifier = Modifier
                .padding(bottom = 10.dp),
            text = text,
            color = Color.Black,
            fontSize = FONT_SIZE.sp,
        )

		// 선택된 뷰의 텍스트 아래에 노란색 밑줄 긋기
        if (selectedOption.date == text) {
            Box(
                modifier = Modifier
                    .width(40.dp)
                    .height(4.dp)
                    .background(color = colorResource(id = R.color.dark_yellow))
            )
        }
    }
}

 

 

 

 

MainActivity

remember은 View 값이 바뀔 때 사용하는 api이다. remember을 사용하지 않고 그냥 임의로 값을 바꾸면 View의 값은 바뀌지 않으니 주의!

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
        ``` 메뉴를 서버에서 가져오는 과정 (본문과는 무관)
            val menu = remember { mutableStateOf<MealPlan?>(null) }

            lifecycleScope.launch {
                menu.value = mealPlanFetcher.fetchMealPlanData()
            }
         ```
        
            MainView(menu.value)
        }
    }

    @Composable
    fun MainView(menu: MealPlan?) {
    
    	// selectedDay의 초기값을 오늘로 remember 변수 선언
        val selectedDay = remember { mutableStateOf(DateEnum.TODAY) }
        
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
        ) {
            Column(
                modifier = Modifier
                    .fillMaxWidth()
                    .verticalScroll(scrollState)
                    .weight(1f)
            ) {
                BtnDateView.main(selectedDay)
                ContentView.main(menu, selectedDay)
            }
        }
    }
}

 

 

 

ContentView

object ContentView {
    private val mealTimes = listOf("아침", "점심", "저녁")

    @Composable
    fun main(menu: MealPlan?, selectedDay: MutableState<DateEnum>) {
    	// selectedDay의 값에 따라 dayPlans에 해당하는 날짜의 메뉴 할당
        val dayPlans = when (selectedDay.value) {
            DateEnum.TODAY -> menu?.today
            DateEnum.TOMORROW -> menu?.tomorrow
            DateEnum.AFTER_TOMORROW -> menu?.theDayAfterTomorrow
        }
        
        // dayPlans의 값이 null이 아니면 해당하는 날짜의 아침, 점심, 저녁을 리스트로 형성
        // null이면 null 반환
        val dayPlansList = dayPlans?.let { listOf(it.morning, dayPlans.lunch, dayPlans.dinner) }
        
        // 메뉴 출력뷰에 메뉴 띄움
        if (dayPlansList != null) {
            mealPlanView(dayPlansList)
        }
        // dayPlansList가 null이라면 메뉴 출력뷰에 "업데이트 예정"을 띄움
        else {
            val nullCourse = Course("업데이트 예정", null)
            val mealPlansList = listOf(nullCourse)
            val dayPlansList = listOf(mealPlansList, mealPlansList, mealPlansList)
            mealPlanView(dayPlansList = dayPlansList)
        }
    }

	// 메뉴 출력뷰
    @Composable
    private fun mealPlanView(dayPlansList: List<List<Course>>) {
        BoxWithConstraints {
            val screenWidth = this.maxWidth
            val itemWidth = screenWidth / 2

			// LazyRow를 통해 메뉴리스트 출력(recyclerview 같은 역할)
            LazyRow {
                itemsIndexed(dayPlansList) { index, dayPlan ->
                	// 아람별은 데이터 구성이 많이 중첩적이어서 한 번 더 풀어주는 과정을 거쳐야 함
                    // 보통은 여기서 List로 끝날테니 바로 출력하면 끝날 듯!!
                    DayPlanView.main(
                        mealTime = mealTimes[index],
                        courses = dayPlan,
                        modifier = Modifier.width(itemWidth)
                    )
                }
            }
        }
    }
}

 

 

결과물

 

 

 

전체 코드는 깃허브 참고

https://github.com/wpslxm20/Arambyeol_AOS