setContent{}
블럭이 Activity의 레이아웃을 만드는 코드에 해당한다.setContent{}
에서 Composable Function을 호출한다.class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
var isButtonClicked by remember { mutableStateOf(false) }
ComposeStudyTheme {
Conversation(
messages = SampleData.conversationSample,
isClicked = isButtonClicked,
onMessageClick = { if (!isButtonClicked) isButtonClicked = true }
)
}
}
}
}
@Composable
어노테이션이 붙은 함수
fun Conversation(messages: List<Message>, isClicked: Boolean, onMessageClick: () -> Unit) {
val spaceSize: Int by animateIntAsState(if (isClicked) 20 else -50)
LazyColumn(verticalArrangement = Arrangement.spacedBy(spaceSize.dp)) {
items(messages) { message ->
MessageCard(message = message, onMessageClick = onMessageClick)
}
}
}
Text()
를 연달아서 호출하면 각 텍스트 뷰에 대한 위치 지정을 하지 않았으므로 겹쳐서 나타나게 된다.
Surface
를 활용하여 Text
의 모양을 변경할 수 있다.Column
, Row
, Modifier
, Spacing
, Material Design
, Surface
@Composable
fun MessageCard(message: Message, onMessageClick: () -> Unit) {
Row(modifier = Modifier
.padding(all = 8.dp)
.clickable {
Log.d("MainActivity", "Is Clicked ")
onMessageClick()
})
{
Image(
painter = painterResource(id = R.drawable.flowers),
contentDescription = "this is flower",
modifier = Modifier
.size(40.dp)
.border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
.clip(CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = message.author,
color = MaterialTheme.colors.secondaryVariant,
style = MaterialTheme.typography.subtitle2,
)
Spacer(modifier = Modifier.height(4.dp))
Surface(
shape = MaterialTheme.shapes.medium,
elevation = 1.dp,
modifier = Modifier
.animateContentSize()
.padding(1.dp)
) {
Text(
text = message.content,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(all = 4.dp)
)
}
}
}
}
LazyColumn
& LazyRow
LazyColumn
LazyColumn
을 호출하는 Composable Function List
을 파라미터로 받아야 한다.items
라는 이름의 자식 Composable Function을 호출한다.items
은 List
의 각 요소에 대해서 반복문을 돌면서 람다식을 호출한다.@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageCard(message = message)
}
}
}
Compose에서는 remember
함수와 mutableStateOf
함수로 변수의 상태 변화를 추적할 수 있도록 해준다.
remember
를 사용하여 변수의 상태를 메모리에 저장하며 mutableStateOf
함수에 전달된 값을 추적할 수 있다.mutableStateOf
로 지정된 값은 변화될 때, Composable Function이 뷰를 업데이트 한다.remember
만 사용하고 mutableStateOf
는 사용하지 않을 때 어떻게 되는가❓mutableStateOf
값이 초기(initial) 값으로 초기화된다.mutableStateOf
는 자신의 값이 바뀔 때 자신을 참조하고 있는 모든 Composable Function에 대한 re-compose를 하게 된다. remember
가 recompose될 때에도 해당 변수의 값은 유지되도록 만들어준다.@Composable
fun HelloContent() {
var name by remember { mutableStateOf("") }
Text(
text = "Hello, $name",
modifer = Modifer.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChaned = { name = it },
label = { Text("Name") }
)
}
OutlinedTextField
가 EditTextView에 해당한다. 이것을 통해 사용자의 입력이 들어오면,onValueChanged
함수가 호출되어 name
변수의 값이 바뀐다.name
변수는 mutableStateOf
로 생성되었기 때문에, 자신의 값이 바뀌면 자신을 참조하고 있는 모든remember
로 만들어진 변수는 다른 Composable Function의 파라미터로 사용할 수 있다.주의사항‼️ configuration change(ex) 화면 회전) 가 발생했을 때는 remeber 변수는 유지되지 않는다.
이를 위해서는 rememberSavable
을 사용해야 한다.
rememberSavable
은 Bundle
에 담길 수 있는 값이면 무엇이든 저장해놓는다.
만약 Bundle에 담길 수 없는 값이라면 저장할 수 있는 객체로 직접 만들어야 한다.
animateColorAsState()
val surfaceColor: Color by animateColorAsState(
if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
)
animateContentSize()
Surface(
shape = MaterialTheme.shapes.medium,
elevation = 1.dp,
color = surfaceColor,
modifier = Modifier
.animateContentSize()
.padding(1.dp)
) {
Text(
text = message.content,
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(all = 4.dp),
maxLines = if (isExpanded) Int.MAX_VALUE else 1,
)
}
Composable이 자기 자신의 상태를 갖고 있는 것은 좋지 않다. 자신의 상태를 직접 관리하는 Composable은 재활용하기 어렵고, 테스트하기 어려운 Composable이다.
가장 간단한 방법은, Composable은 자신의 상태를 parameter로 전달받고,
이벤트가 발생했음을 뷰로 표현하기 위해서 함수를 사용하는 것이다.
@Composable
fun HelloContent() {
var name by remember { mutableStateOf("") }
Text(
text = "Hello, $name",
modifer = Modifer.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChaned = { name = it },
label = { Text("Name") }
)
}
@Composable
fun HelloScreen() {
var name: String by rememberSavable { mutableStateOf("") }
HelloContent(name = name, onNameChange = { name = it })
}
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Text(
text = "Hello, $name",
modifer = Modifer.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChaned = onNameChange(name),
label = { Text("Name") }
)
}
HelloContent
의 파라미터 중 name
은 현재 OutlinedTextField가 가리키는 값이 된다.onNameChange
는 Composable의 상태(여기서는 name
)을 업데이트할 때 호출되는 람다식이다.class HelloViewModel : ViewModel() {
private val _name = MutableLiveData("")
val name: LiveData<String> = _name
}
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
var name: String by helloViewModel.name.observeAsState("")
HelloContent(name = name, onNameChange = { name = it })
}
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Text(
text = "Hello, $name",
modifer = Modifer.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChaned = onNameChange(name),
label = { Text("Name") }
)
}
이렇게 Composable의 상태를 ViewModel에 보관하면, ViewModel은 View보다 생명주기가 길기 때문에,
configuration change가 발생하더라도 view의 상태를 유지할 수 있다.
Composable의 상태를 바꾸는 함수도 viewModel로 hoisting하자.
class HelloViewModel : ViewModel() {
private val _name = MutableLiveData("")
val name: LiveData<String> = _name
fun onNameChange(newName: String) {
_name.value = newName
}
}
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
var name: String by helloViewModel.name.observeAsState("")
HelloContent(name = name, onNameChange = { helloViewModel.onNameChange(it) })
}
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Text(
text = "Hello, $name",
modifer = Modifer.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChaned = onNameChange(name),
label = { Text("Name") }
)
}
name
값을 바꾸는 개체가 된다.name
값을 바꿀 수 있도록 하는 것이 좋다.