مقدمه
Jetpack Compose انقلابی در توسعه UI اندروید است. علی سلمانیان در این مقاله، راهنمای کاملی از Compose ارائه میدهد که به شما کمک میکند تا اپلیکیشنهای مدرن و زیبا بسازید. Compose با استفاده از Kotlin، توسعه UI را سادهتر و قدرتمندتر کرده است.
Jetpack Compose چیست؟
Jetpack Compose یک toolkit مدرن برای ساخت UI های اندروید است که:
- از declarative programming استفاده میکند
- کد کمتر و خوانایی بیشتری دارد
- State management سادهتری ارائه میدهد
- با Material Design 3 سازگار است
- Performance بهتری دارد
🚀 مزایای اصلی Compose:
- Declarative UI: UI را بر اساس state توصیف میکنید
- Reusable Components: Component های قابل استفاده مجدد
- Powerful State Management: مدیریت state پیشرفته
- Material Design 3: پشتیبانی کامل از Material Design
- Animation Support: انیمیشنهای قدرتمند
راهاندازی پروژه Compose
1. تنظیمات اولیه
برای شروع با Compose، ابتدا باید پروژه خود را پیکربندی کنید:
// build.gradle (Module: app)
android {
compileSdk 34
defaultConfig {
applicationId "com.example.mycomposeapp"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.4'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
implementation 'androidx.activity:activity-compose:1.8.2'
// Compose BOM
implementation platform('androidx.compose:compose-bom:2023.10.01')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
// Navigation
implementation 'androidx.navigation:navigation-compose:2.7.5'
// ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
// Testing
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2023.10.01')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}
2. MainActivity با Compose
// MainActivity.kt
package com.example.mycomposeapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import com.example.mycomposeapp.ui.theme.MyComposeAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyComposeAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MyApp()
}
}
}
}
}
@Composable
fun MyApp() {
// Your app content here
Text(
text = "سلام به Compose!",
style = MaterialTheme.typography.headlineLarge
)
}
مفاهیم اصلی Compose
1. Composable Functions
Composable functions قلب Compose هستند. در اینجا مثالهای کاربردی میبینید:
// Basic Composable
@Composable
fun Greeting(name: String) {
Text(
text = "سلام $name!",
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.primary
)
}
// Composable with State
@Composable
fun Counter() {
var count by remember { mutableIntStateOf(0) }
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "شمارش: $count",
style = MaterialTheme.typography.headlineLarge
)
Spacer(modifier = Modifier.height(16.dp))
Row {
Button(
onClick = { count-- },
modifier = Modifier.padding(8.dp)
) {
Text("کاهش")
}
Button(
onClick = { count++ },
modifier = Modifier.padding(8.dp)
) {
Text("افزایش")
}
}
}
}
// Complex Composable with Parameters
@Composable
fun UserCard(
user: User,
onEditClick: (User) -> Unit,
onDeleteClick: (User) -> Unit,
modifier: Modifier = Modifier
) {
Card(
modifier = modifier
.fillMaxWidth()
.padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = user.name,
style = MaterialTheme.typography.headlineSmall
)
Text(
text = user.email,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Row {
IconButton(onClick = { onEditClick(user) }) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "ویرایش"
)
}
IconButton(onClick = { onDeleteClick(user) }) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "حذف"
)
}
}
}
}
}
}
2. State Management پیشرفته
مدیریت state در Compose نیاز به درک عمیق دارد:
// State Hoisting
@Composable
fun TodoScreen() {
var todoText by remember { mutableStateOf("") }
var todos by remember { mutableStateOf(listOf()) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
// Input field
OutlinedTextField(
value = todoText,
onValueChange = { todoText = it },
label = { Text("کار جدید") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
// Add button
Button(
onClick = {
if (todoText.isNotBlank()) {
todos = todos + Todo(
id = System.currentTimeMillis(),
text = todoText,
isCompleted = false
)
todoText = ""
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("اضافه کردن")
}
Spacer(modifier = Modifier.height(16.dp))
// Todo list
LazyColumn {
items(todos) { todo ->
TodoItem(
todo = todo,
onToggleComplete = { updatedTodo ->
todos = todos.map {
if (it.id == updatedTodo.id) updatedTodo else it
}
},
onDelete = { todoToDelete ->
todos = todos.filter { it.id != todoToDelete.id }
}
)
}
}
}
}
@Composable
fun TodoItem(
todo: Todo,
onToggleComplete: (Todo) -> Unit,
onDelete: (Todo) -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = todo.isCompleted,
onCheckedChange = {
onToggleComplete(todo.copy(isCompleted = it))
}
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = todo.text,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
textDecoration = if (todo.isCompleted)
TextDecoration.LineThrough else null
)
IconButton(onClick = { onDelete(todo) }) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "حذف"
)
}
}
}
}
3. Navigation در Compose
Navigation Component برای Compose:
// Navigation Setup
@Composable
fun MyApp() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile/$it")
}
)
}
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId") ?: ""
ProfileScreen(
userId = userId,
onNavigateBack = { navController.popBackStack() }
)
}
composable("settings") {
SettingsScreen(
onNavigateBack = { navController.popBackStack() }
)
}
}
}
// Home Screen
@Composable
fun HomeScreen(
onNavigateToProfile: (String) -> Unit
) {
val users = remember {
listOf(
User("1", "علی سلمانیان", "ali@example.com"),
User("2", "مریم احمدی", "maryam@example.com"),
User("3", "حسن رضایی", "hasan@example.com")
)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "لیست کاربران",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
LazyColumn {
items(users) { user ->
UserCard(
user = user,
onClick = { onNavigateToProfile(user.id) }
)
}
}
}
}
// Profile Screen
@Composable
fun ProfileScreen(
userId: String,
onNavigateBack: () -> Unit
) {
var user by remember { mutableStateOf(null) }
var isLoading by remember { mutableStateOf(true) }
LaunchedEffect(userId) {
// Simulate API call
delay(1000)
user = User(userId, "علی سلمانیان", "ali@example.com")
isLoading = false
}
if (isLoading) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
} else {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
TopAppBar(
title = { Text("پروفایل") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "بازگشت"
)
}
}
)
user?.let { userData ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = userData.name,
style = MaterialTheme.typography.headlineMedium
)
Text(
text = userData.email,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
}
Material Design 3 در Compose
1. Theme و Color System
// Theme.kt
@Composable
fun MyComposeAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFEADDFF),
onPrimaryContainer = Color(0xFF21005D),
secondary = Color(0xFF625B71),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFE8DEF8),
onSecondaryContainer = Color(0xFF1D192B),
tertiary = Color(0xFF7D5260),
onTertiary = Color(0xFFFFFFFF),
tertiaryContainer = Color(0xFFFFD8E4),
onTertiaryContainer = Color(0xFF31111D),
error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFFFFBFE),
onBackground = Color(0xFF1C1B1F),
surface = Color(0xFFFFFBFE),
onSurface = Color(0xFF1C1B1F),
surfaceVariant = Color(0xFFE7E0EC),
onSurfaceVariant = Color(0xFF49454F),
outline = Color(0xFF79747E)
)
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFFD0BCFF),
onPrimary = Color(0xFF381E72),
primaryContainer = Color(0xFF4F378B),
onPrimaryContainer = Color(0xFFEADDFF),
secondary = Color(0xFFCCC2DC),
onSecondary = Color(0xFF332D41),
secondaryContainer = Color(0xFF4A4458),
onSecondaryContainer = Color(0xFFE8DEF8),
tertiary = Color(0xFFEFB8C8),
onTertiary = Color(0xFF492532),
tertiaryContainer = Color(0xFF633B48),
onTertiaryContainer = Color(0xFFFFD8E4),
error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF1C1B1F),
onBackground = Color(0xFFE6E1E5),
surface = Color(0xFF1C1B1F),
onSurface = Color(0xFFE6E1E5),
surfaceVariant = Color(0xFF49454F),
onSurfaceVariant = Color(0xFFCAC4D0),
outline = Color(0xFF938F99)
)
2. Typography System
// Typography.kt
val Typography = Typography(
displayLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp
),
displayMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 45.sp,
lineHeight = 52.sp,
letterSpacing = 0.sp
),
displaySmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 36.sp,
lineHeight = 44.sp,
letterSpacing = 0.sp
),
headlineLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 32.sp,
lineHeight = 40.sp,
letterSpacing = 0.sp
),
headlineMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
lineHeight = 36.sp,
letterSpacing = 0.sp
),
headlineSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 24.sp,
lineHeight = 32.sp,
letterSpacing = 0.sp
),
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
titleMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.15.sp
),
titleSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
),
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.15.sp
),
bodyMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.25.sp
),
bodySmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.4.sp
),
labelLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
lineHeight = 20.sp,
letterSpacing = 0.1.sp
),
labelMedium = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 12.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)
انیمیشنها در Compose
1. Basic Animations
// Simple Animation
@Composable
fun AnimatedCounter() {
var count by remember { mutableIntStateOf(0) }
val animatedCount by animateIntAsState(
targetValue = count,
animationSpec = tween(300)
)
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "شمارش: $animatedCount",
style = MaterialTheme.typography.headlineLarge
)
Button(onClick = { count++ }) {
Text("افزایش")
}
}
}
// Visibility Animation
@Composable
fun AnimatedVisibilityExample() {
var visible by remember { mutableStateOf(false) }
Column {
Button(onClick = { visible = !visible }) {
Text(if (visible) "مخفی کردن" else "نمایش")
}
AnimatedVisibility(
visible = visible,
enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut()
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = "این متن با انیمیشن نمایش داده میشود",
modifier = Modifier.padding(16.dp)
)
}
}
}
}
// Complex Animation
@Composable
fun AnimatedCard() {
var expanded by remember { mutableStateOf(false) }
val rotation by animateFloatAsState(
targetValue = if (expanded) 180f else 0f,
animationSpec = tween(500)
)
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.clickable { expanded = !expanded }
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "کارت قابل انبساط",
style = MaterialTheme.typography.titleMedium
)
Icon(
imageVector = Icons.Default.ExpandMore,
contentDescription = null,
modifier = Modifier.rotate(rotation)
)
}
AnimatedVisibility(
visible = expanded,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut()
) {
Column {
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "این محتوای اضافی است که با انیمیشن نمایش داده میشود.",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
}
بهترین روشهای توسعه
1. Performance Optimization
// LazyColumn for Large Lists
@Composable
fun ProductList(products: List) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(
items = products,
key = { product -> product.id }
) { product ->
ProductCard(product = product)
}
}
}
// Memoization with remember
@Composable
fun ExpensiveComputation(data: List) {
val processedData = remember(data) {
data.map { item ->
// Expensive computation
processData(item)
}
}
LazyColumn {
items(processedData) { item ->
DataItem(item = item)
}
}
}
// State Hoisting
@Composable
fun SearchableList(
items: List- ,
onItemClick: (Item) -> Unit
) {
var searchQuery by remember { mutableStateOf("") }
var filteredItems by remember(searchQuery, items) {
derivedStateOf {
items.filter { item ->
item.name.contains(searchQuery, ignoreCase = true)
}
}
}
Column {
SearchBar(
query = searchQuery,
onQueryChange = { searchQuery = it }
)
LazyColumn {
items(filteredItems) { item ->
ItemCard(
item = item,
onClick = { onItemClick(item) }
)
}
}
}
}
2. Testing در Compose
// Unit Test
@Test
fun `Counter should increment when button clicked`() {
composeTestRule.setContent {
Counter()
}
composeTestRule
.onNodeWithText("شمارش: 0")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("افزایش")
.performClick()
composeTestRule
.onNodeWithText("شمارش: 1")
.assertIsDisplayed()
}
// UI Test
@Test
fun `UserCard should display user information`() {
val user = User("1", "علی سلمانیان", "ali@example.com")
composeTestRule.setContent {
UserCard(
user = user,
onEditClick = {},
onDeleteClick = {}
)
}
composeTestRule
.onNodeWithText("علی سلمانیان")
.assertIsDisplayed()
composeTestRule
.onNodeWithText("ali@example.com")
.assertIsDisplayed()
}
// Integration Test
@Test
fun `Navigation should work correctly`() {
composeTestRule.setContent {
MyApp()
}
// Navigate to profile
composeTestRule
.onNodeWithText("علی سلمانیان")
.performClick()
composeTestRule
.onNodeWithText("پروفایل")
.assertIsDisplayed()
}
💡 نکات مهم Compose:
- همیشه از
rememberبرای state استفاده کنید - State را به بالاترین سطح ممکن منتقل کنید
- از
LazyColumnبرای لیستهای بزرگ استفاده کنید - Component ها را کوچک و قابل استفاده مجدد نگه دارید
- از Material Design 3 استفاده کنید
مقالات مرتبط
برای یادگیری بیشتر، مقالات زیر را مطالعه کنید:
- توسعه اپلیکیشن اندروید با Java و Kotlin
- طراحی UI/UX برای اپلیکیشنهای موبایل
- مدیریت پروژه با Git و GitHub
نتیجهگیری
Jetpack Compose آینده توسعه UI اندروید است. با استفاده از declarative programming و Kotlin، میتوانید اپلیکیشنهای زیبا و کارآمد بسازید. علی سلمانیان آماده کمک به شما در پروژههای Compose است.