الرسومات في Compose

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

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

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

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

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

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

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

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

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

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

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

نظام الإدارة

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

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

شبكة توضح نظام الإحداثيات أعلى اليسار به [0، 0] وأسفل اليمين [عرض، ارتفاع]
الشكل 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() أكثر فعالية من إجراء استدعاءات مدمجة لعمليات التحويل الفردية، لأنّه يتم تنفيذ جميع عمليات التحويل معًا في عملية واحدة، بدلاً من الحاجة إلى حساب كل من عمليات التحويل المتداخلة وحفظها.

على سبيل المثال، تطبق التعليمة البرمجية التالية كلاً من الترجمة والتدوير على المستطيل:

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")
}

عرض رسالة "مرحبًا" مرسومة على لوحة رسم
الشكل 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()
)

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

نص متعدد الأسطر يشغل مساحة تبلغ 2⁄3 المساحة الكاملة، ويحتوي على مستطيل في الخلفية
الشكل 9. نص متعدد الأسطر يشغل 2⁄3 المساحة الكاملة، ويحتوي على مستطيل في الخلفية.

يؤدي ضبط القيود أو حجم الخط أو أي خاصية تؤثر على الحجم المقاس إلى الحصول على حجم جديد يتم الإبلاغ عنه. يمكنك ضبط حجم ثابت لكل من width وheight، ويتبع النص مجموعة TextOverflow. على سبيل المثال، يعرض الرمز التالي النص بـ 1⁄3 ارتفاع و1⁄3 عرض المساحة القابلة للإنشاء، ويضبط 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()
)

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

جارٍ الوصول إلى العنصر "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()
)

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

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

لمزيد من المعلومات حول الرسم في Compose، ألق نظرة على الموارد التالية: