이 포스트는 Android 공식 홈페이지의 Jetpack Compose 튜토리얼 을 기반으로 작성되었습니다.
Compose
는 Material Design 원칙을 지원하도록 빌드되었다. 따라서 Compose UI 요소가 Material Design을 구성할 수 있도록 구현한다.
이전 포스트에서 구현한 MessageCard
컴포저블의 디자인을 개선해보자.
먼저 ComposeTutorialTheme
과 Surface
로 MessageCard
함수를 래핑한다. @Preview
함수와 @setContent
함수에서도 같은 작업을 실행한다.
이렇게 하면 컴포저블이 앱 테마에 정의된 스타일을 상속하여 일관성이 보장된다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeTutorialTheme {
Surface(modifier = Modifier.fillMaxSize()) {
MessageCard(Message("Android", "Jetpack Compose"))
}
}
}
}
}
@Preview
@Composable
fun PreviewMessageCard() {
ComposeTutorialTheme {
Surface {
MessageCard(
msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
)
}
}
}
Material
의 색상, 서체, 도형 스타일은 각각 MaterialTheme.colors
, MaterialTheme.typography
, MaterialTheme.shapes
에서 가져와 적용할 수 있다.
// ...
import androidx.compose.material.Surface
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = msg.author,
color = MaterialTheme.colors.secondaryVariant,
style = MaterialTheme.typography.subtitle2
)
Spacer(modifier = Modifier.height(4.dp))
Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
Text(
text = msg.body,
modifier = Modifier.padding(all = 4.dp),
style = MaterialTheme.typography.body2
)
}
}
}
}
목록을 표현하기 위해 메시지를 2개 이상 포함하도록 레이아웃을 수정하자.
먼저 여러 메시지를 표현하는 Conversation
함수를 만든다. 여기서 LazyColumn
과 LazyRow
를 사용하는데 이 컴포저블은 화면에 표시되는 요소만 렌더링하므로 긴 목록 등을 표시할 때 매우 효율적이다.
LazyColumn
하위 요소로 items
라는 요소가 있다. 이는 List
를 매개변수로 가져오고 리스트의 각 객체 요소 Message
의 인스턴스인 message
를 가져와 처리할 수 있게 해준다.
@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageCard(message)
}
}
}
@Preview
@Composable
fun PreviewConversation() {
ComposeTutorialTheme {
Conversation(SampleData.conversationSample)
}
}
긴 메시지의 전체 내용을 보여줄 때 메시지를 확장하는 애니메이션을 추가한다.
UI가 접혔는지 확장되었는지 상태를 저장하려면 상태 값을 추적해야한다. 이를 위해서는 remember
와 mutableStateOf
함수를 사용해야한다.
remeber
함수를 사용하면 메모리에 로컬 UI 상태를 저장하고 mutableStateOf
에 전달된 값의 변경사항을 추적할 수 있다.
이 상태를 추적하고 해당 상태값이 업데이트되면 자동으로 UI가 다시 그려지는데 이를 재구성(Recomposition) 이라고 한다.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeTutorialTheme {
Conversation(SampleData.conversationSample)
}
}
}
}
@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = null,
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
// We keep track if the message is expanded or not in this
// variable
var isExpanded by remember { mutableStateOf(false) }
// We toggle the isExpanded variable when we click on this Column
Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
Text(
text = msg.author,
color = MaterialTheme.colors.secondaryVariant,
style = MaterialTheme.typography.subtitle2
)
Spacer(modifier = Modifier.height(4.dp))
Surface(
shape = MaterialTheme.shapes.medium,
elevation = 1.dp,
) {
Text(
text = msg.body,
modifier = Modifier.padding(all = 4.dp),
// If the message is expanded, we display all its content
// otherwise we only display the first line
maxLines = if (isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.body2
)
}
}
}
}
이제 메시지를 클릭하면 isExpanded
에 따라 메시지 콘텐츠의 배경을 변경할 수 있다.
clickable
modifier(수정자)를 사용하여 컴포저블의 클릭 이벤트를 처리한다. 단순히 Surface
의 배경색을 전환하는 대신 MaterialTheme.colors.surface
에서 MaterialTheme.colors.primary
로 또는 그 반대로 값을 점진적으로 수정하여 배경색에 애니메이션을 적용할 수 있다. 이를 위해 animateColorAsState
함수를 사용한다. 마지막으로 animateContentSize
수정자를 사용하여 메시지 컨테이너 크기에 부드럽게 애니메이션을 적용한다.