أدوات تعديل الرسومات

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

أدوات تعديل الرسم

يتم تنفيذ جميع أوامر الرسم باستخدام أداة تعديل الرسم في Compose. هناك ثلاثة معدِّلات رئيسية للرسم في Compose:

المعدِّل الأساسي للرسم هو drawWithContent، حيث يمكنك تحديد ترتيب الرسم للعنصر القابل للإنشاء وأوامر الرسم الصادرة داخل المعدِّل. ‫drawBehind هو برنامج تضمين مناسب حول drawWithContent تم ضبط ترتيب الرسم فيه على أن يكون خلف محتوى العنصر المركّب. drawWithCache يستدعي إما onDrawBehind أو onDrawWithContent بداخله، ويوفر آلية لتخزين العناصر التي تم إنشاؤها فيهما مؤقتًا.

Modifier.drawWithContent: اختيار ترتيب الرسم

تتيح لك Modifier.drawWithContent تنفيذ عمليات DrawScope قبل أو بعد محتوى الدالة المركّبة. احرص على استدعاء drawContent لعرض المحتوى الفعلي للدالة المركّبة. باستخدام معدِّل العرض هذا، يمكنك تحديد ترتيب العمليات، سواء أردت عرض المحتوى قبل عمليات الرسم المخصّصة أو بعدها.

على سبيل المثال، إذا أردت عرض تدرّج شعاعي فوق المحتوى لإنشاء تأثير ثقب المفتاح في ضوء الفلاش على واجهة المستخدم، يمكنك إجراء ما يلي:

var pointerOffset by remember {
    mutableStateOf(Offset(0f, 0f))
}
Column(
    modifier = Modifier
        .fillMaxSize()
        .pointerInput("dragging") {
            detectDragGestures { change, dragAmount ->
                pointerOffset += dragAmount
            }
        }
        .onSizeChanged {
            pointerOffset = Offset(it.width / 2f, it.height / 2f)
        }
        .drawWithContent {
            drawContent()
            // draws a fully black area with a small keyhole at pointerOffset that’ll show part of the UI.
            drawRect(
                Brush.radialGradient(
                    listOf(Color.Transparent, Color.Black),
                    center = pointerOffset,
                    radius = 100.dp.toPx(),
                )
            )
        }
) {
    // Your composables here
}

الشكل 1: تم استخدام Modifier.drawWithContent فوق دالة مركّبة لإنشاء تجربة واجهة مستخدم على شكل ضوء الفلاش.

Modifier.drawBehind: الرسم خلف دالة مركّبة

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

لرسم مستطيل مستدير الزوايا خلف Text:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawBehind {
            drawRoundRect(
                Color(0xFFBBAAEE),
                cornerRadius = CornerRadius(10.dp.toPx())
            )
        }
        .padding(4.dp)
)

التي تنتج النتيجة التالية:

نص وخلفية مرسومة باستخدام Modifier.drawBehind
الشكل 2: نص وخلفية مرسومة باستخدام Modifier.drawBehind

Modifier.drawWithCache: رسم عناصر الرسم وتخزينها مؤقتًا

يحتفظ Modifier.drawWithCache بالكائنات التي يتم إنشاؤها بداخله في ذاكرة التخزين المؤقت. يتم تخزين العناصر مؤقتًا طالما أنّ حجم مساحة الرسم هو نفسه، أو لم تتغير أي عناصر حالة تمت قراءتها. يُعدّ معدّل التعديل هذا مفيدًا لتحسين أداء طلبات الرسم، إذ أنّه يتجنّب الحاجة إلى إعادة تخصيص العناصر (مثل: Brush, Shader, Path وما إلى ذلك) التي يتم إنشاؤها عند الرسم.

بدلاً من ذلك، يمكنك أيضًا تخزين العناصر مؤقتًا باستخدام remember خارج المعدِّل. ومع ذلك، لا يمكننا دائمًا إجراء ذلك لأنّه ليس بإمكانك الوصول إلى المقطوعة الموسيقية في بعض الأحيان. قد يكون استخدام drawWithCache أكثر فعالية إذا كانت الكائنات تُستخدم للرسم فقط.

على سبيل المثال، إذا أنشأت Brush لرسم تدرّج خلف Text، سيؤدي استخدام drawWithCache إلى تخزين الكائن Brush مؤقتًا إلى أن يتغيّر حجم مساحة الرسم:

Text(
    "Hello Compose!",
    modifier = Modifier
        .drawWithCache {
            val brush = Brush.linearGradient(
                listOf(
                    Color(0xFF9E82F0),
                    Color(0xFF42A5F5)
                )
            )
            onDrawBehind {
                drawRoundRect(
                    brush,
                    cornerRadius = CornerRadius(10.dp.toPx())
                )
            }
        }
)

تخزين عنصر Brush مؤقتًا باستخدام drawWithCache
الشكل 3: تخزين عنصر Brush مؤقتًا باستخدام drawWithCache

معدِّلات الرسومات

Modifier.graphicsLayer: تطبيق عمليات التحويل على العناصر القابلة للإنشاء

Modifier.graphicsLayer هو عنصر تعديل يجعل محتوى الدالة المركّبة يظهر في طبقة رسم. توفّر الطبقة بعض الوظائف المختلفة، مثل:

  • عزل تعليمات الرسم (على غرار RenderNode)، إذ يمكن إعادة إصدار تعليمات الرسم التي تم التقاطها كجزء من طبقة بكفاءة من خلال مسار العرض بدون إعادة تنفيذ الرمز البرمجي للتطبيق.
  • التحويلات التي تنطبق على جميع تعليمات الرسم الواردة ضمن طبقة.
  • تحويل الصور النقطية إلى متجهات لإمكانات التركيب عند تحويل طبقة إلى صورة نقطية، يتم تنفيذ تعليمات الرسم الخاصة بها ويتم تسجيل الناتج في مخزن مؤقت خارج الشاشة. تجميع المخزن المؤقت هذا للّقطات اللاحقة أسرع من تنفيذ التعليمات الفردية، ولكن سيتصرف على أنّه صورة نقطية عند تطبيق عمليات التحويل، مثل تغيير الحجم أو التدوير.

عمليات التحويل

توفّر Modifier.graphicsLayer عزلًا لتعليمات الرسم، على سبيل المثال، يمكن تطبيق تحويلات مختلفة باستخدام Modifier.graphicsLayer. ويمكن تحريك هذه العناصر أو تعديلها بدون الحاجة إلى إعادة تنفيذ دالة lambda الخاصة بالرسم.

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

يمكن تطبيق عمليات التحويل التالية باستخدام أداة التعديل هذه:

المقياس - زيادة الحجم

تعمل المفتاحان scaleX وscaleY على تكبير المحتوى أو تصغيره في الاتجاه الأفقي أو العمودي، على التوالي. تشير القيمة 1.0f إلى عدم حدوث تغيير في المقياس، وتشير القيمة 0.5f إلى نصف البُعد.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.scaleX = 1.2f
            this.scaleY = 0.8f
        }
)

الشكل 4: تطبيق scaleX وscaleY على عنصر Image قابل للإنشاء
الترجمة

يمكن تغيير translationX وtranslationY باستخدام graphicsLayer، translationX ينقل العنصر القابل للإنشاء إلى اليسار أو اليمين. translationY ينقل العنصر القابل للإنشاء للأعلى أو للأسفل.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.translationX = 100.dp.toPx()
            this.translationY = 10.dp.toPx()
        }
)

الشكل 5: تطبيق translationX وtranslationY على الصورة باستخدام Modifier.graphicsLayer
تدوير

اضبط rotationX للتدوير أفقيًا، وrotationY للتدوير عموديًا، وrotationZ للتدوير على المحور Z (التدوير العادي). يتم تحديد هذه القيمة بالدرجات (0-360).

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

الشكل 6: تم ضبط rotationX وrotationY وrotationZ على "صورة" من خلال Modifier.graphicsLayer
الأصل

يمكن تحديد transformOrigin. ويتم استخدامها بعد ذلك كنقطة يتم منها إجراء عمليات التحويل. استخدمت جميع الأمثلة حتى الآن TransformOrigin.Center، الذي يقع في (0.5f, 0.5f). إذا حدّدت نقطة الأصل على أنّها (0f, 0f)، ستبدأ عمليات التحويل من أعلى يمين الدالة المركّبة.

إذا غيّرت نقطة الأصل باستخدام عملية تحويل rotationZ، يمكنك ملاحظة أنّ العنصر يدور حول أعلى يمين العنصر القابل للإنشاء:

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "Sunset",
    modifier = Modifier
        .graphicsLayer {
            this.transformOrigin = TransformOrigin(0f, 0f)
            this.rotationX = 90f
            this.rotationY = 275f
            this.rotationZ = 180f
        }
)

الشكل 7: التدوير الذي تم تطبيقه مع ضبط TransformOrigin على 0f و0f

القص والتشكيل

تحدّد "الشكل" المخطط التفصيلي الذي يتم قص المحتوى عنده عند استخدام clip = true. في هذا المثال، نضبط مربّعَين لعرض مقطعَين مختلفَين، أحدهما باستخدام متغيّر المقطع graphicsLayer، والآخر باستخدام أداة التغليف الملائمة Modifier.clip.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .size(200.dp)
            .graphicsLayer {
                clip = true
                shape = CircleShape
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }
    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(CircleShape)
            .background(Color(0xFF4DB6AC))
    )
}

يتم قص محتوى المربّع الأول (النص الذي يقول "مرحبًا يا Compose") ليناسب شكل الدائرة:

تم تطبيق Clip على الدالة البرمجية القابلة للإنشاء Box
الشكل 8: تطبيق المقطع على عنصر Box القابل للإنشاء

إذا طبّقت translationY على الدائرة الوردية العلوية، ستلاحظ أنّ حدود الدالة المركّبة لا تزال كما هي، ولكن يتم رسم الدائرة أسفل الدائرة السفلية (وخارج حدودها).

تم تطبيق المقطع مع translationY، وحدود حمراء للمخطط التفصيلي
الشكل 9: مقطع تم تطبيق الترجمة عليه، وحدود حمراء للمخطط التفصيلي

لاقتصاص الدالة المركّبة إلى المنطقة التي يتم رسمها فيها، يمكنك إضافة Modifier.clip(RectangleShape) أخرى في بداية سلسلة عناصر التعديل. وسيظل المحتوى داخل الحدود الأصلية.

Column(modifier = Modifier.padding(16.dp)) {
    Box(
        modifier = Modifier
            .clip(RectangleShape)
            .size(200.dp)
            .border(2.dp, Color.Black)
            .graphicsLayer {
                clip = true
                shape = CircleShape
                translationY = 50.dp.toPx()
            }
            .background(Color(0xFFF06292))
    ) {
        Text(
            "Hello Compose",
            style = TextStyle(color = Color.Black, fontSize = 46.sp),
            modifier = Modifier.align(Alignment.Center)
        )
    }

    Box(
        modifier = Modifier
            .size(200.dp)
            .clip(RoundedCornerShape(500.dp))
            .background(Color(0xFF4DB6AC))
    )
}

المقطع المطبّق أعلى تحويل graphicsLayer
الشكل 10: تطبيق المقطع في أعلى عملية تحويل graphicsLayer

إصدار أولي

يمكن استخدام Modifier.graphicsLayer لضبط alpha (مستوى الشفافية) للطبقة بأكملها. 1.0f معتم بالكامل و0.0f غير مرئي.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "clock",
    modifier = Modifier
        .graphicsLayer {
            this.alpha = 0.5f
        }
)

صورة تم تطبيق قناة ألفا عليها
الشكل 11: صورة تم تطبيق قناة ألفا عليها

استراتيجية التركيب

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

الاستراتيجيات المختلفة هي:

تلقائي (الإعداد التلقائي)

يتم تحديد استراتيجية التركيب من خلال بقية معلَمات graphicsLayer. تعرض هذه السمة الطبقة في مخزن مؤقت خارج الشاشة إذا كانت قيمة ألفا أقل من 1.0f أو تم ضبط RenderEffect. عندما تكون قيمة قناة ألفا أقل من 1f، يتم إنشاء طبقة تركيب تلقائيًا لعرض المحتوى ثم رسم المخزن المؤقت خارج الشاشة هذا في الوجهة باستخدام قناة ألفا المقابلة. يؤدي ضبط RenderEffect أو تجاوز حد التمرير إلى عرض المحتوى دائمًا في مخزن مؤقت خارج الشاشة بغض النظر عن قيمة CompositingStrategy.

خارج الشاشة

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

أحد الأمثلة على استخدام CompositingStrategy.Offscreen هو مع BlendModes. في المثال التالي، لنفترض أنّك تريد إزالة أجزاء من عنصر Image مركّب من خلال إصدار أمر رسم يستخدم BlendMode.Clear. إذا لم تضبط compositingStrategy على CompositingStrategy.Offscreen، سيتفاعل BlendMode مع كل المحتوى في الخلفية.

Image(
    painter = painterResource(id = R.drawable.dog),
    contentDescription = "Dog",
    contentScale = ContentScale.Crop,
    modifier = Modifier
        .size(120.dp)
        .aspectRatio(1f)
        .background(
            Brush.linearGradient(
                listOf(
                    Color(0xFFC5E1A5),
                    Color(0xFF80DEEA)
                )
            )
        )
        .padding(8.dp)
        .graphicsLayer {
            compositingStrategy = CompositingStrategy.Offscreen
        }
        .drawWithCache {
            val path = Path()
            path.addOval(
                Rect(
                    topLeft = Offset.Zero,
                    bottomRight = Offset(size.width, size.height)
                )
            )
            onDrawWithContent {
                clipPath(path) {
                    // this draws the actual image - if you don't call drawContent, it wont
                    // render anything
                    this@onDrawWithContent.drawContent()
                }
                val dotSize = size.width / 8f
                // Clip a white border for the content
                drawCircle(
                    Color.Black,
                    radius = dotSize,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    ),
                    blendMode = BlendMode.Clear
                )
                // draw the red circle indication
                drawCircle(
                    Color(0xFFEF5350), radius = dotSize * 0.8f,
                    center = Offset(
                        x = size.width - dotSize,
                        y = size.height - dotSize
                    )
                )
            }
        }
)

من خلال ضبط قيمة CompositingStrategy على Offscreen، يتم إنشاء زخرفة خارج الشاشة لتنفيذ الأوامر (مع تطبيق BlendMode على محتوى هذه الدالة المركّبة فقط). ثم يتم عرضها فوق المحتوى المعروض على الشاشة، بدون التأثير في المحتوى الذي تم رسمه.

‫Modifier.drawWithContent على صورة تعرض إشارة دائرية، مع BlendMode.Clear داخل التطبيق
الشكل 12: Modifier.drawWithContent على صورة تعرض مؤشرًا دائريًا، مع BlendMode.Clear وCompositingStrategy.Offscreen داخل التطبيق

في حال عدم استخدام CompositingStrategy.Offscreen، ستؤدي نتائج تطبيق BlendMode.Clear إلى محو جميع وحدات البكسل في الوجهة، بغض النظر عمّا تم ضبطه من قبل، ما يؤدي إلى إظهار مخزن العرض المؤقت (باللون الأسود) للنافذة. لن تعمل العديد من BlendModes التي تتضمّن قناة ألفا على النحو المتوقّع بدون مخزن مؤقت خارج الشاشة. لاحظ الحلقة السوداء حول مؤشر الدائرة الحمراء:

Modifier.drawWithContent على صورة تعرض مؤشرًا دائريًا، مع ضبط BlendMode.Clear وعدم ضبط CompositingStrategy
الشكل 13: Modifier.drawWithContent على صورة تعرض مؤشرًا دائريًا، مع ضبط BlendMode.Clear وعدم ضبط CompositingStrategy

لتوضيح ذلك أكثر، إذا كان التطبيق يتضمّن خلفية نافذة شفافة ولم تستخدِم CompositingStrategy.Offscreen، سيتفاعل BlendMode مع التطبيق بأكمله، وسيؤدي إلى محو جميع وحدات البكسل لعرض التطبيق أو خلفية الشاشة أدناه، كما في المثال التالي:

لم يتم ضبط CompositingStrategy ويتم استخدام BlendMode.Clear مع تطبيق يحتوي على خلفية نافذة شفافة. تظهر خلفية الشاشة الوردية من خلال المنطقة المحيطة بدائرة الحالة الحمراء.
الشكل 14: لم يتم ضبط CompositingStrategy واستخدام BlendMode.Clear مع تطبيق يحتوي على خلفية نافذة شفافة. لاحظ كيف تظهر خلفية الشاشة الوردية من خلال المنطقة المحيطة بدائرة الحالة الحمراء.

يُرجى العِلم أنّه عند استخدام CompositingStrategy.Offscreen، يتم إنشاء مادة عرض خارج الشاشة بحجم مساحة الرسم، ثم يتم عرضها على الشاشة. سيتم تلقائيًا اقتصاص أي أوامر رسم يتم تنفيذها باستخدام هذه الاستراتيجية إلى هذه المنطقة. يوضّح مقتطف الرمز التالي الاختلافات عند التبديل إلى استخدام مواد عرض خارج الشاشة:

@Composable
fun CompositingStrategyExamples() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
    ) {
        // Does not clip content even with a graphics layer usage here. By default, graphicsLayer
        // does not allocate + rasterize content into a separate layer but instead is used
        // for isolation. That is draw invalidations made outside of this graphicsLayer will not
        // re-record the drawing instructions in this composable as they have not changed
        Canvas(
            modifier = Modifier
                .graphicsLayer()
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            // ... and drawing a size of 200 dp here outside the bounds
            drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }

        Spacer(modifier = Modifier.size(300.dp))

        /* Clips content as alpha usage here creates an offscreen buffer to rasterize content
        into first then draws to the original destination */
        Canvas(
            modifier = Modifier
                // force to an offscreen buffer
                .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
                .size(100.dp) // Note size of 100 dp here
                .border(2.dp, color = Color.Blue)
        ) {
            /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the
            content gets clipped */
            drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx()))
        }
    }
}

‫CompositingStrategy.Auto مقابل CompositingStrategy.Offscreen - يتم قص المقاطع خارج الشاشة إلى المنطقة، بينما لا يتم ذلك تلقائيًا
الشكل 15: CompositingStrategy.Auto مقابل CompositingStrategy.Offscreen - مقاطع خارج الشاشة إلى المنطقة، حيث لا يتم عرضها تلقائيًا
ModulateAlpha

تعدّل استراتيجية التركيب هذه قيمة ألفا لكل تعليمات الرسم المسجّلة ضمن graphicsLayer. لن يتم إنشاء مخزن مؤقت خارج الشاشة لقيمة ألفا أقل من 1.0f ما لم يتم ضبط RenderEffect، لذا يمكن أن يكون أكثر كفاءة في عرض ألفا. ومع ذلك، يمكن أن يقدّم نتائج مختلفة للمحتوى المتداخل. في حالات الاستخدام التي يُعرف فيها مسبقًا أنّ المحتوى لا يتداخل، يمكن أن يوفّر ذلك أداءً أفضل من CompositingStrategy.Auto مع قيم ألفا أقل من 1.

يوضّح المثال التالي استراتيجيات تركيب مختلفة، مثل تطبيق قيم ألفا مختلفة على أجزاء مختلفة من العناصر القابلة للإنشاء، وتطبيق استراتيجية Modulate:

@Preview
@Composable
fun CompositingStrategy_ModulateAlpha() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(32.dp)
    ) {
        // Base drawing, no alpha applied
        Canvas(
            modifier = Modifier.size(200.dp)
        ) {
            drawSquares()
        }

        Spacer(modifier = Modifier.size(36.dp))

        // Alpha 0.5f applied to whole composable
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    alpha = 0.5f
                }
        ) {
            drawSquares()
        }
        Spacer(modifier = Modifier.size(36.dp))

        // 0.75f alpha applied to each draw call when using ModulateAlpha
        Canvas(
            modifier = Modifier
                .size(200.dp)
                .graphicsLayer {
                    compositingStrategy = CompositingStrategy.ModulateAlpha
                    alpha = 0.75f
                }
        ) {
            drawSquares()
        }
    }
}

private fun DrawScope.drawSquares() {

    val size = Size(100.dp.toPx(), 100.dp.toPx())
    drawRect(color = Red, size = size)
    drawRect(
        color = Purple, size = size,
        topLeft = Offset(size.width / 4f, size.height / 4f)
    )
    drawRect(
        color = Yellow, size = size,
        topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f)
    )
}

val Purple = Color(0xFF7E57C2)
val Yellow = Color(0xFFFFCA28)
val Red = Color(0xFFEF5350)

تطبّق ModulateAlpha قيمة ألفا المضبوطة على كل أمر رسم على حدة
الشكل 16: تطبِّق ModulateAlpha قيمة ألفا المضبوطة على كل أمر رسم فردي

كتابة محتوى دالة مركّبة إلى صورة نقطية

تتمثّل إحدى حالات الاستخدام الشائعة في إنشاء Bitmap من دالة مركّبة. لنسخ محتوى الدالة المركّبة إلى Bitmap، أنشئ GraphicsLayer باستخدام rememberGraphicsLayer().

أعِد توجيه أوامر الرسم إلى الطبقة الجديدة باستخدام drawWithContent() وgraphicsLayer.record{}. بعد ذلك، ارسم الطبقة في اللوحة المرئية باستخدام drawLayer:

val coroutineScope = rememberCoroutineScope()
val graphicsLayer = rememberGraphicsLayer()
Box(
    modifier = Modifier
        .drawWithContent {
            // call record to capture the content in the graphics layer
            graphicsLayer.record {
                // draw the contents of the composable into the graphics layer
                this@drawWithContent.drawContent()
            }
            // draw the graphics layer on the visible canvas
            drawLayer(graphicsLayer)
        }
        .clickable {
            coroutineScope.launch {
                val bitmap = graphicsLayer.toImageBitmap()
                // do something with the newly acquired bitmap
            }
        }
        .background(Color.White)
) {
    Text("Hello Android", fontSize = 26.sp)
}

يمكنك حفظ الصورة النقطية على القرص ومشاركتها. لمزيد من التفاصيل، يمكنك الاطّلاع على مقتطف المثال الكامل. احرص على التحقّق من أذونات الجهاز قبل محاولة الحفظ على القرص.

أداة تعديل الرسومات المخصّصة

لإنشاء معدِّل مخصّص، عليك تنفيذ واجهة DrawModifier. يمنحك ذلك إذن الوصول إلى ContentDrawScope، وهو الإذن نفسه الذي يتم عرضه عند استخدام Modifier.drawWithContent(). يمكنك بعد ذلك استخراج عمليات الرسم الشائعة إلى معدِّلات رسم مخصّصة لتنظيف الرمز البرمجي وتوفير أغلفة ملائمة، على سبيل المثال، Modifier.background() هو DrawModifier ملائم.

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

class FlippedModifier : DrawModifier {
    override fun ContentDrawScope.draw() {
        scale(1f, -1f) {
            this@draw.drawContent()
        }
    }
}

fun Modifier.flipped() = this.then(FlippedModifier())

بعد ذلك، استخدِم مفتاح التعديل المعكوس هذا على Text:

Text(
    "Hello Compose!",
    modifier = Modifier
        .flipped()
)

أداة تعديل مخصّصة معكوسة على النص
الشكل 17: أداة تعديل مخصّصة معكوسة على النص

مراجع إضافية

للاطّلاع على المزيد من الأمثلة حول استخدام graphicsLayer والرسم المخصّص، يُرجى الرجوع إلى المراجع التالية: