الرسومات في Compose

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

رسم أساسي باستخدام عوامل التعديل وDrawScope

إنّ الطريقة الأساسية لرسم شكل مخصّص في ميزة "الإنشاء" هي باستخدام عوامل التعديل، مثل Modifier.drawWithContent، Modifier.drawBehind، و Modifier.drawWithCache.

على سبيل المثال، لرسم شيء خلف العنصر القابل للتجميع، يمكنك استخدام المُعدِّل drawBehind لبدء تنفيذ أوامر الرسم:

Spacer(
    modifier = Modifier
        .fillMaxSize()
        .drawBehind {
            // this = DrawScope
        }
)

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

تعرض جميع عوامل تعديل الرسم DrawScope، وهي بيئة رسم محددة النطاق تحافظ على حالتها الخاصة. يتيح لك ذلك ضبط المَعلمات لمجموعة من العناصر الرسومية. يوفّر DrawScope عدة حقول مفيدة، مثل size، وهو Size يحدّد الأبعاد الحالية للعنصر DrawScope.

لرسم شكل، يمكنك استخدام إحدى دوال الرسم العديدة في DrawScope. على سبيل المثال، ترسم التعليمة البرمجية التالية مستطيلاً في أعلى يمين الشاشة:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    drawRect(
        color = Color.Magenta,
        size = canvasQuadrantSize
    )
}

مستطيل وردي مرسوم على خلفية بيضاء يشغل ربع الشاشة
الشكل 1. مستطيل مرسوم باستخدام "لوحة الرسم" في ميزة "الإنشاء"

لمزيد من المعلومات عن عوامل تعديل الرسم المختلفة، اطّلِع على مستندات عوامل تعديل الرسومات.

نظام الإحداثيات

لرسم عنصر على الشاشة، عليك معرفة الإزاحة (x وy) وحجم العنصر. في العديد من طرق الرسم في DrawScope، يتم تحديد الموضع والحجم من خلال قيم المَعلمات التلقائية. بشكل عام، تؤدي المَعلمات التلقائية إلى وضع العنصر في نقطة [0, 0] على اللوحة، وتوفير قيمة default size تملأ مساحة الرسم بالكامل، كما هو موضّح في المثال أعلاه. يمكنك رؤية وضع المستطيل في أعلى يمين اللوحة. لتعديل حجم العنصر وموقعه، عليك فهم نظام الإحداثيات في ميزة "الإنشاء".

تقع نقطة الأصل لنظام الإحداثيات ([0,0]) في أعلى يمين البكسل في منطقة الرسم. تزداد قيمة x عند التحرك لليسار وتزداد قيمة y عند التحرك للأسفل.

شبكة تعرض نظام الإحداثيات الذي يعرض أعلى يمين [0, 0] وأسفل يمين [width, height]
الشكل 2: نظام إحداثيات الرسم / شبكة الرسم

على سبيل المثال، إذا كنت تريد رسم خط قطري من أعلى يسار مساحة اللوحة إلى أسفل يمينها، يمكنك استخدام الدالة DrawScope.drawLine() وتحديد إزاحة البداية والنهاية باستخدام المواضع x وy المقابلة:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )
}

عمليات التحويل الأساسية

DrawScope يوفّر عمليات تحويل لتغيير مكان تنفيذ أو كيفية تنفيذ أوامر الرسم.

تغيير الحجم

استخدِم DrawScope.scale() لزيادة حجم عمليات الرسم بمقدار معيّن. تنطبق العمليات مثل scale() على جميع عمليات الرسم ضمن دالة LAMBDA المقابلة. على سبيل المثال، تزيد التعليمة البرمجية التالية scaleX 10 مرات وscaleY 15 مرة:

Canvas(modifier = Modifier.fillMaxSize()) {
    scale(scaleX = 10f, scaleY = 15f) {
        drawCircle(Color.Blue, radius = 20.dp.toPx())
    }
}

دائرة تم تكبيرها بشكل غير موحّد
الشكل 3. تطبيق عملية تغيير حجم على دائرة في "لوحة الرسم"

ترجمة

استخدِم رمز DrawScope.translate() لنقل عمليات الرسم للأعلى أو للأسفل أو لليسار أو لليمين. على سبيل المثال، يؤدي الرمز التالي إلى تحريك الرسم 100 بكسل إلى اليمين و300 بكسل للأعلى:

Canvas(modifier = Modifier.fillMaxSize()) {
    translate(left = 100f, top = -300f) {
        drawCircle(Color.Blue, radius = 200.dp.toPx())
    }
}

دائرة تم نقلها خارج المركز
الشكل 4. تطبيق عملية ترجمة على دائرة على "لوحة الرسم"

تدوير

استخدِم رمز DrawScope.rotate() لتدوير عمليات الرسم حول نقطة محورية. على سبيل المثال، يلي الرمز البرمجي الذي يدير مستطيلاً بزاوية 45 درجة:

Canvas(modifier = Modifier.fillMaxSize()) {
    rotate(degrees = 45F) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

هاتف يتضمّن مستطيلاً تم تدويره بزاوية 45 درجة في منتصف الشاشة
الشكل 5. نستخدم rotate() لتطبيق دوران على نطاق الرسم الحالي، ما يؤدي إلى تدوير المستطيل بمقدار 45 درجة.

مساحة داخلية

استخدِم DrawScope.inset() لضبط المَعلمات التلقائية لملف DrawScope الحالي، ما يؤدي إلى تغيير حدود الرسم وترجمة الرسومات وفقًا لذلك:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    inset(horizontal = 50f, vertical = 30f) {
        drawRect(color = Color.Green, size = canvasQuadrantSize)
    }
}

تضيف هذه التعليمة البرمجية مساحة بادئة إلى أوامر الرسم بفعالية:

مستطيل تمّت إضافة حشو حوله
الشكل 6. تطبيق جزء مُدمَج على أوامر الرسم

عمليات تحويل متعدّدة

لتطبيق عمليات تحويل متعددة على رسوماتك، استخدِم دالة DrawScope.withTransform() التي تُنشئ عملية تحويل واحدة وتدمج جميع التغييرات المطلوبة. إنّ استخدام withTransform() أكثر فعالية من إجراء طلبات بحث متداخلة لعمليات تحول individual ، لأنّه يتم تنفيذ جميع عمليات التحويل معًا في عملية واحدة، بدلاً من أن تحتاج Compose إلى احتساب كل عملية من عمليات التحويل المتداخلة وحفظها.

على سبيل المثال، يطبّق الرمز البرمجي التالي عملية ترجمة ودوران على الصندوق المُربّع:

Canvas(modifier = Modifier.fillMaxSize()) {
    withTransform({
        translate(left = size.width / 5F)
        rotate(degrees = 45F)
    }) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

هاتف به مستطيل مُدرَج على جانب الشاشة
الشكل 7. استخدِم withTransform لتطبيق كلّ من التدوير والترجمة، أي تدوير المستطيل ونقله إلى اليمين.

عمليات الرسم الشائعة

رسم نص

لرسم نص في ميزة "الإنشاء"، يمكنك عادةً استخدام العنصر القابل للتجميع Text. ومع ذلك، إذا كنت في DrawScope أو أردت رسم النص يدويًا مع تخصيص، يمكنك استخدام الأسلوب DrawScope.drawText().

لرسم نص، أنشئ TextMeasurer باستخدام rememberTextMeasurer واستخدِم drawText مع أداة القياس:

val textMeasurer = rememberTextMeasurer()

Canvas(modifier = Modifier.fillMaxSize()) {
    drawText(textMeasurer, "Hello")
}

عرض كلمة Hello مرسومة على "لوحة الرسم"
الشكل 8. رسم نص على "لوحة الرسم"

قياس النص

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

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

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()),
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

يُنشئ مقتطف الرمز البرمجي هذا خلفية وردية للنص:

نص مكوّن من عدة أسطر يشغل ثلثي المساحة الكاملة، مع مستطيل في الخلفية
الشكل 9. نص مكوّن من عدة أسطر يشغل ثلثي حجم المنطقة بالكامل، مع مستطيل في الخلفية

يؤدّي تعديل القيود أو حجم الخط أو أيّ خاصيّة تؤثّر في الحجم المقاس إلى إدراج حجم جديد في التقرير. يمكنك ضبط حجم ثابت لكل من width وheight، ثم يتبع النص مجموعة TextOverflow. على سبيل المثال، يعرض الرمز البرمجي التالي النص في ثلث الارتفاع وثلث العرض للمساحة القابلة للتجميع، ويضبط TextOverflow على TextOverflow.Ellipsis:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixed(
                        width = (size.width / 3f).toInt(),
                        height = (size.height / 3f).toInt()
                    ),
                    overflow = TextOverflow.Ellipsis,
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

يتم الآن رسم النص ضمن القيود مع إضافة علامة حذف في النهاية:

نص مرسوم على خلفية زهرية، مع علامة اقتباس تقطع النص
الشكل 10. TextOverflow.Ellipsis مع قيود ثابتة على قياس النص

رسم صورة

لرسم ImageBitmap باستخدام DrawScope، حمِّل الصورة باستخدام ImageBitmap.imageResource() ثم اتصل بالرقم drawImage:

val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)

Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    drawImage(dogImage)
})

صورة كلب مرسومة على "لوحة الرسم"
الشكل 11. رسم ImageBitmap على "لوحة الرسم"

رسم أشكال أساسية

هناك العديد من دوال رسم الأشكال على DrawScope. لرسم شكل، استخدِم إحدى دوالّ الرسم المحدّدة مسبقًا، مثل drawCircle:

val purpleColor = Color(0xFFBA68C8)
Canvas(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp),
    onDraw = {
        drawCircle(purpleColor)
    }
)

واجهة برمجة التطبيقات

الإخراج

drawCircle()

رسم دائرة

drawRect()

draw rect

drawRoundedRect()

رسم مستطيل مستدير

drawLine()

رسم خط

drawOval()

رسم شكل بيضاوي

drawArc()

رسم قوس

drawPoints()

نقاط الرسم

رسم مسار

المسار هو سلسلة من التعليمات الحسابية التي تؤدي إلى ظهور رسم بعد تنفيذها. يمكن أن يرسم DrawScope مسارًا باستخدام الطريقة DrawScope.drawPath().

على سبيل المثال، لنفترض أنّك أردت رسم مثلث. يمكنك إنشاء مسار باستخدام وظائف مثل lineTo() وmoveTo() باستخدام حجم مساحة الرسم. بعد ذلك، اتصل بـ drawPath() باستخدام هذا المسار الذي تم إنشاؤه حديثًا للحصول على مثلث.

Spacer(
    modifier = Modifier
        .drawWithCache {
            val path = Path()
            path.moveTo(0f, 0f)
            path.lineTo(size.width / 2f, size.height / 2f)
            path.lineTo(size.width, 0f)
            path.close()
            onDrawBehind {
                drawPath(path, Color.Magenta, style = Stroke(width = 10f))
            }
        }
        .fillMaxSize()
)

مثلث مسار بنفسجي مقلوب مرسوم على ميزة "إنشاء"
الشكل 12. إنشاء رمز Path ورسمه في ميزة "الإنشاء"

الوصول إلى عنصر Canvas

باستخدام DrawScope، لا يمكنك الوصول مباشرةً إلى عنصر Canvas. يمكنك استخدام DrawScope.drawIntoCanvas() للوصول إلى عنصر Canvas نفسه الذي يمكنك استدعاء الدوال عليه.

على سبيل المثال، إذا كان لديك Drawable مخصّص تريد رسمه على اللوحة، يمكنك الوصول إلى اللوحة واستدعاء Drawable#draw() مع تمرير Canvas:

val drawable = ShapeDrawable(OvalShape())
Spacer(
    modifier = Modifier
        .drawWithContent {
            drawIntoCanvas { canvas ->
                drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt())
                drawable.draw(canvas.nativeCanvas)
            }
        }
        .fillMaxSize()
)

شكل بيضاوي أسود من ShapeDrawable يشغل الحجم الكامل
الشكل 13. الوصول إلى اللوحة لرسم Drawable

مزيد من المعلومات

لمزيد من المعلومات حول ميزة "الرسم" في ميزة "الإنشاء"، يمكنك الاطّلاع على المراجع التالية: