الرسومات في 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] على لوحة الرسم وتوفر علامة 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()

رسم مستطيل

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.

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

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