فيديو تعليمي

البرنامج التعليمي في Jetpack Compose

Jetpack Compose هي مجموعة أدوات حديثة مخصّصة لتصميم واجهة مستخدم Android الأصلية. يعمل Jetpack Compose على تبسيط عملية تطوير واجهة المستخدم على Android وتسريعها من خلال استخدام رموز برمجية أقل وأدوات فعّالة وواجهات برمجة تطبيقات سهلة الاستخدام بلغة Kotlin.

في هذا البرنامج التعليمي، ستنشئ مكونًا بسيطًا لواجهة المستخدم بدوال تعريفية. لن يتم تعديل أي من تنسيقات XML أو استخدام أداة تعديل التنسيق. بدلاً من ذلك، ستستدعي دوال قابلة للتعديل لتحديد العناصر التي تريدها، وستتولى أداة التحويل البرمجي Compose الباقي.

معاينة كاملة
معاينة كاملة

الدرس الأول: الدوال القابلة للتعديل

تم تصميم Jetpack Compose استنادًا إلى وظائف يمكن إنشاؤها. تتيح لك هذه الدوال تحديد واجهة المستخدم الخاصة بتطبيقك بطريقة آلية عن طريق وصف الشكل الذي يجب أن تبدو عليه وتوفير تبعيات البيانات، بدلاً من التركيز على عملية إنشاء واجهة المستخدم (إعداد عنصر أو إرفاقه بعنصر رئيسي، وما إلى ذلك). لإنشاء دالة قابلة للتعديل، ما عليك سوى إضافة التعليق التوضيحي @Composable إلى اسم الدالة.

إضافة عنصر نصي

للبدء، يجب تنزيل أحدث إصدار من استوديو Android وإنشاء تطبيق من خلال النقر على مشروع جديد، ثم النقر على نشاط فارغ ضمن فئة الهاتف والجهاز اللوحي. أدخِل اسمًا لتطبيقك Composetutorial أو انقر على إنهاء. يحتوي النموذج التلقائي حاليًا على بعض عناصر Compose، ولكن يمكنك في هذا الدليل التوجيهي إعداده خطوة بخطوة.

أولاً، اعرض النص "Hello world!" من خلال إضافة عنصر نصي داخل طريقة onCreate. ويمكنك إجراء ذلك من خلال تحديد مجموعة محتوى وطلب وظيفة Text القابلة للإنشاء. تحدّد كتلة setContent تنسيق النشاط حيث يتم استدعاء الدوال القابلة للتعديل. لا يمكن استدعاء الدوال القابلة للتعديل إلا من خلال دوال أخرى مركّبة.

يستخدم Jetpack Compose مكوّنًا إضافيًا لبرنامج التجميع في Kotlin لتحويل هذه الوظائف القابلة للإنشاء إلى عناصر في واجهة المستخدم الخاصة بالتطبيق. على سبيل المثال، تعرض الدالة Text القابلة للإنشاء التي يتم تحديدها من خلال "مكتبة واجهة المستخدم لإنشاء المحتوى" تصنيفًا نصيًا على الشاشة.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
عرض المعاينة
إخفاء المعاينة

تعريف الدالة المركّبة

لجعل الدالة قابلة للإنشاء، أضِف التعليق التوضيحي @Composable. لتجربة ذلك، حدِّد الدالة MessageCard التي تمرّر اسمًا وتستخدمها لإعداد العنصر النصي.

// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
عرض المعاينة
إخفاء المعاينة

معاينة الدالة في "استوديو Android"

يتيح لك التعليق التوضيحي @Preview معاينة الدوال القابلة للإنشاء ضمن "استوديو Android" بدون الحاجة إلى إنشاء التطبيق وتثبيته على جهاز Android أو محاكي. يجب استخدام التعليق التوضيحي في دالة قابلة للتعديل لا تقبل المعلَمات. ولهذا السبب، لا يمكنك معاينة الدالة MessageCard مباشرةً. بدلاً من ذلك، أنشِئ دالة ثانية باسم PreviewMessageCard تستدعي MessageCard باستخدام مَعلمة مناسبة. أضِف التعليق التوضيحي @Preview قبل @Composable.

// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
عرض المعاينة
إخفاء المعاينة

أعد بناء مشروعك. لا يتغيّر التطبيق بحدّ ذاته، لأنّ وظيفة PreviewMessageCard الجديدة لا تُدعى في أي مكان، إلا أنّ "استوديو Android" يضيف نافذة معاينة يمكنك توسيعها من خلال النقر على طريقة العرض المقسمة (التصميم/الرمز). تعرض هذه النافذة معاينة لعناصر واجهة المستخدم التي تم إنشاؤها من خلال دوال قابلة للتعديل تم وضع علامة عليها بالتعليق التوضيحي @Preview. لتعديل المعاينات في أي وقت، انقر على زر إعادة التحميل في أعلى نافذة المعاينة.

معاينة دالة قابلة للتعديل في "استوديو Android"
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Text

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.runtime.Composable

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
عرض المعاينة
إخفاء المعاينة
معاينة دالة قابلة للتعديل في "استوديو Android"

الدرس الثاني: التخطيطات

عناصر واجهة المستخدم هرمية، مع عناصر مضمنة في عناصر أخرى. في ميزة "إنشاء"، يمكنك إنشاء تسلسل هرمي لواجهة المستخدم من خلال استدعاء دوال قابلة للإنشاء من دوال أخرى قابلة للإنشاء.

إضافة نصوص متعددة

لقد قمت حتى الآن بإنشاء أول دالة قابلة للإنشاء والمعاينة! لاكتشاف المزيد من إمكانات Jetpack Compose، سيتم إنشاء شاشة مراسلة بسيطة تحتوي على قائمة بالرسائل التي يمكن توسيعها باستخدام بعض الصور المتحركة.

يمكنك البدء من خلال جعل الرسالة قابلة أكثر إفادة من خلال عرض اسم المؤلف ومحتوى الرسالة. عليك أولاً تغيير المَعلمة القابلة للتعديل لقبول كائن Message بدلاً من String، وإضافة Text عنصر آخر في العنصر MessageCard القابل للإنشاء. احرص على تعديل المعاينة أيضًا.

// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
عرض المعاينة
إخفاء المعاينة

تُنشئ هذه التعليمة البرمجية عنصرَين نصيَين داخل عرض المحتوى. وبما أنّك لم تقدّم أي معلومات عن طريقة ترتيبها، يتم رسم عناصر النص فوق بعضها، ما يجعل النص غير قابل للقراءة.

استخدام عمود

تتيح لك دالة Column ترتيب العناصر عموديًا. أضِف Column إلى الدالة MessageCard.
يمكنك استخدام Row لترتيب العناصر أفقيًا و Box لتكديس العناصر.

// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

عرض المعاينة
إخفاء المعاينة

إضافة عنصر صورة

يمكنك تحسين بطاقة رسائلك من خلال إضافة صورة ملف شخصي للمُرسِل. استخدِم مدير الموارد لاستيراد صورة من مكتبة الصور أو استخدِم هذه الصورة. أضِف عنصر Row للحصول على تصميم منظّم بشكل جيد ومساحة Image بداخله.

// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
عرض المعاينة
إخفاء المعاينة

ضبط التنسيق

يحتوي تنسيق الرسالة على البنية الصحيحة، لكن عناصره غير متباعدة بشكل جيد والصورة كبيرة جدًا. تستخدم ميزة "الكتابة" معدِّلات لتزيين عنصر مركّب أو إعداده. فهي تتيح لك تغيير حجم العنصر أو تنسيقه أو مظهره أو إضافة تفاعلات عالية المستوى، مثل إتاحة إمكانية النقر على عنصر ما. يمكنك ربطهما ببعضها لإنشاء مواد مركّبة أكثر ثراءً. عليك استخدام بعضها لتحسين التصميم.

// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
عرض المعاينة
إخفاء المعاينة
// ...

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
عرض المعاينة
إخفاء المعاينة
معاينة عنصرين نصيين متداخلين
// ...
import androidx.compose.foundation.layout.Column

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.ui.res.painterResource

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
عرض المعاينة
إخفاء المعاينة

الدرس الثالث: التصميم المتعدد الأبعاد

تم تصميم Compose لدعم مبادئ التصميم المتعدد الأبعاد. والعديد من عناصر واجهة المستخدم الخاصة بها تنفّذ تصميمًا متعدد الأبعاد على نحو غير تقليدي، في هذا الدرس، ستتعلّم طريقة استخدام تطبيقات "التصميم المتعدد الأبعاد" المصغَّرة.

استخدام التصميم متعدد الأبعاد

يحتوي تصميم رسالتك الآن على تخطيط، ولكنه لا يبدو رائعًا حتى الآن.

يوفر Jetpack Compose تنفيذًا للتصميم المتعدد الأبعاد 3 وعناصر واجهة المستخدم الخاصة به بشكل جديد. يمكنك تحسين مظهر MessageCard الذي يتم إنشاؤه باستخدام تصميم متعدد الأبعاد.

للبدء، عليك لفّ الدالة MessageCard بمظهر Material الذي تم إنشاؤه في مشروعك، ComposeTutorialTheme، بالإضافة إلى Surface. نفِّذ ذلك في كلٍّ من @Preview والدالة setContent. وسيسمح ذلك للمحتوى الذي تم إنشاؤه باكتساب أنماط كما هو محدّد في مظهر تطبيقك، ما يضمن اتّساق المحتوى فيها.

تم تصميم "التصميم المتعدد الأبعاد" استنادًا إلى ثلاث ركائز: Color وTypography وShape. ستضيفها واحدة تلو الأخرى.

ملاحظة: يُنشئ نموذج نشاط "الكتابة الفارغة" مظهرًا تلقائيًا لمشروعك يتيح لك تخصيص MaterialTheme. إذا اخترت اسمًا مختلفًا لمشروع Composetutorial، يمكنك العثور على المظهر المخصّص في ملف Theme.kt في حزمة ui.theme الفرعية.

// ...

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("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
عرض المعاينة
إخفاء المعاينة

اللون

يمكنك استخدام MaterialTheme.colorScheme للنمط باستخدام ألوان من المظهر الملتف. يمكنك استخدام هذه القيم من المظهر في أي مكان مطلوب فيه اللون. يستخدم هذا المثال ألوانًا ديناميكية للمظاهر (تحدّدها الإعدادات المفضّلة للجهاز). يمكنك ضبط dynamicColor على false في ملف MaterialTheme.kt لتغيير هذه الحالة.

اختَر نمط العنوان وأضِف حدًا للصورة.

// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@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.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
عرض المعاينة
إخفاء المعاينة

أسلوب الخط

تتوفّر أنماط "أسلوب الخط المتعدد" في MaterialTheme، ما عليك سوى إضافتها إلى العناصر Text.

// ...

@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.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
عرض المعاينة
إخفاء المعاينة

شكل

يمكنك استخدام Shapeإضافة اللمسات النهائية. أولاً، عليك لف النص الأساسي للرسالة حول Surface يمكن إنشاؤه. يسمح ذلك بتخصيص شكل نص الرسالة ومسقط رأسه. تتم أيضًا إضافة المساحة المتروكة إلى الرسالة للحصول على تنسيق أفضل.

// ...
import androidx.compose.material3.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.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
عرض المعاينة
إخفاء المعاينة

تفعيل المظهر الداكن

يمكن تفعيل المظهر الداكن (أو الوضع الليلي) لتجنّب الشاشة الساطعة خاصةً في الليل، أو بهدف الحدّ من استهلاك بطارية الجهاز. بفضل ميزة "التصميم المتعدد الأبعاد"، يمكن لتطبيق Jetpack Compose التعامل مع المظهر الداكن تلقائيًا. وعند استخدام الألوان والنصوص والخلفيات في Material Design، ستتكيف تلقائيًا مع الخلفية الداكنة.

يمكنك إنشاء عدة معاينات في ملفك كدوال منفصلة أو إضافة تعليقات توضيحية متعددة للدالة نفسها.

أضِف تعليقًا توضيحيًا جديدًا للمعاينة وفعِّل الوضع الليلي.

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
عرض المعاينة
إخفاء المعاينة

يتم تحديد خيارات الألوان للمظهر الفاتح والداكن في ملف Theme.kt الذي أنشأه IDE.

حتى الآن، أنشأت عنصرًا في واجهة المستخدم للرسالة يعرض صورة ونصَّين بأنماط مختلفة، ولا يظهر هذا العنصر بشكل جيد في المظهرَين الفاتح والداكن.

// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
عرض المعاينة
إخفاء المعاينة
// ...

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("Lexi", "Take a look at Jetpack Compose, it's great!")
            )
        }
    }
}


  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.foundation.border
import androidx.compose.material3.MaterialTheme

@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.colorScheme.primary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
عرض المعاينة
إخفاء المعاينة
// ...

@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.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.bodyMedium
           )
       }
   }
}

  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.material3.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.colorScheme.primary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colorScheme.secondary,
               style = MaterialTheme.typography.titleSmall
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.bodyMedium
               )
           }
       }
   }
}

  
عرض المعاينة
إخفاء المعاينة
// ...
import android.content.res.Configuration

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
    Surface {
      MessageCard(
        msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
      )
    }
   }
}
  
عرض المعاينة
إخفاء المعاينة
عرض معاينة تعرض كل من المواد المركبة ذات المظهر الفاتح والداكن.

الدرس 4: القوائم والرسوم المتحركة

توجد القوائم والصور المتحركة في كل مكان في التطبيقات. في هذا الدرس، ستتعلّم طريقة استخدام ميزة "الكتابة" لتسهيل إنشاء القوائم وإضافة صور متحركة.

إنشاء قائمة بالرسائل

عندما تكون المحادثة مصحوبة برسالة واحدة، قد تشعر بالوحدة، لذلك سنغيّر المحادثة إلى أكثر من رسالة واحدة. ستحتاج إلى إنشاء دالة Conversation تعرض رسائل متعددة. بالنسبة إلى حالة الاستخدام هذه، استخدِم LazyColumn و LazyRow في Compose. لا تعرض هذه المواد المركبة سوى العناصر المرئية على الشاشة، ولذلك فهي مصممة لتكون فعّالة جدًا مع القوائم الطويلة.

في مقتطف الرمز هذا، يتبيّن لك أنّ LazyColumn لديه طفل items. وهو يستخدم List كمَعلمة، وتتلقّى دالة lambda معلَمة نسمّيها message (يمكننا تسميتها كما نرغب في ذلك) وهي مثال على Message. باختصار، يتم استدعاء دالة lambda هذه لكل عنصر من سمات List المقدَّمة. انسخ نموذج مجموعة بيانات إلى مشروعك للمساعدة في بدء المحادثة بسرعة.

// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
عرض المعاينة
إخفاء المعاينة

تحريك الرسائل أثناء توسيعها

أصبحت المحادثة أكثر إثارة للاهتمام. حان وقت اللعب بالرسوم المتحركة! كما يمكنك إضافة إمكانية توسيع الرسالة لعرض رسالة أطول، مع تحريك كل من حجم المحتوى ولون الخلفية. لتخزين حالة واجهة المستخدم المحلية هذه، عليك تتبُّع ما إذا كان قد تم توسيع الرسالة أم لا. لتتبُّع التغيّر الذي حدث في الحالة، عليك استخدام الدالتَين remember و mutableStateOf.

يمكن للدوالّ القابلة للإنشاء تخزين الحالة المحلّية في الذاكرة باستخدام remember، وتتبُّع التغييرات في القيمة التي تم تمريرها إلى mutableStateOf. وعند استخدام هذه الحالة، ستتم إعادة رسم المحتوى (والعناصر الثانوية) تلقائيًا عند تعديل القيمة. وتُعرف هذه العملية باسم إعادة الإنشاء.

باستخدام واجهات برمجة التطبيقات لحالة Compose مثل remember وmutableStateOf، تؤدي أي تغييرات على الحالة إلى تعديل واجهة المستخدم تلقائيًا.

ملاحظة: عليك إضافة عمليات الاستيراد التالية لاستخدام بنية الموقع المفوَّضة في Kotlin بشكل صحيح (الكلمة الرئيسية by). يؤدّي استخدام Alt+Enter أو Option+Enter إلى إضافة هذه القيم بالنيابة عنك.
import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue

// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

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.colorScheme.primary, 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.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 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.bodyMedium
                )
            }
        }
    }
}

  
عرض المعاينة
إخفاء المعاينة

يمكنك الآن تغيير خلفية محتوى الرسالة استنادًا إلى isExpanded عند النقر على رسالة. ستستخدم أداة التعديل clickable لمعالجة أحداث النقر في العنصر القابل للإنشاء. وبدلاً من تبديل لون الخلفية في Surface فقط، يمكنك تحريك لون الخلفية من خلال تعديل قيمته تدريجيًا من MaterialTheme.colorScheme.surface إلى MaterialTheme.colorScheme.primary والعكس صحيح. لإجراء ذلك، استخدِم الدالة animateColorAsState. أخيرًا، ستستخدم معدِّل البيانات animateContentSize لتحريك حجم حاوية الرسالة بسلاسة:

// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@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.colorScheme.secondary, 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) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(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.bodyMedium
                )
            }
        }
    }
}

  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.foundation.clickable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

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.colorScheme.primary, 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.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 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.bodyMedium
                )
            }
        }
    }
}

  
عرض المعاينة
إخفاء المعاينة
// ...
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize

@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.colorScheme.secondary, 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) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor by animateColorAsState(
            if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                shadowElevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(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.bodyMedium
                )
            }
        }
    }
}

  
عرض المعاينة
إخفاء المعاينة

الخطوات التالية

تهانينا، لقد أنهيت البرنامج التعليمي حول الإنشاء! لقد أنشأت شاشة محادثة بسيطة تعرض بكفاءة قائمة تضم الرسائل القابلة للتوسيع والمتحرّكة التي تحتوي على صورة ونصوص، وقد صُمّمت بالاستناد إلى مبادئ "التصميم المتعدد الأبعاد" مع مظهر داكن مع إمكانية المعاينات، وكل ذلك أقل من 100 سطر من الرموز.

إليك ما تعلمته حتى الآن:

  • تعريف الدوال المكوِّنة
  • عند إضافة عناصر مختلفة إلى العنصر
  • هيكلة مكون واجهة المستخدم باستخدام مواد التخطيط القابلة للتعديل
  • توسيع المواد الكيميائية باستخدام مفاتيح التعديل
  • إنشاء قائمة فعالة
  • تتبع الحالة وتعديلها
  • إضافة تفاعل المستخدم على عنصر قابل للتعديل
  • الرسوم المتحركة للرسائل أثناء توسيعها

إذا كنت تريد التعمّق في بعض هذه الخطوات، اطّلِع على المراجع أدناه.

الخطوات التالية

الإعداد
الآن وبعد الانتهاء من البرنامج التعليمي حول "إنشاء"، أصبحت جاهزًا لبدء الإنشاء باستخدام "الإنشاء".
مسار
اطّلِع على مجموعة من الفيديوهات التعليمية والدروس التطبيقية حول الترميز التي ستساعدك في تعلّم وإتقان استخدام Jetpack Compose.