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

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

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

يتم تنفيذ جميع أوامر الرسم باستخدام مُعدِّل للرسم في ميزة "الإنشاء". هناك ثلاثة عناصر رئيسية لتعديل الرسم في ميزة "الإنشاء":

المُعدِّل الأساسي للرسم هو drawWithContent، حيث يمكنك تحديد ترتيب الرسم لعنصر Composable وأوامر الرسم الصادرة داخل المُعدِّل. 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 فوق عنصر Composable لإنشاء تجربة واجهة مستخدم من النوع "مصباح يدوي".

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

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

عناصر تعديل الرسومات

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

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

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

التحويلات

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

لا يغيّر 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 composable
الترجمة

يمكن تغيير 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 على Image باستخدام 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 على Image بواسطة 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") لتتلاءم مع شكل الدائرة:

مقطع تم تطبيقه على عنصر قابل للتجميع في المربّع
الشكل 8: مقطع تم تطبيقه على عنصر Box composable

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

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

لقص العنصر القابل للتجميع على المنطقة التي تم رسمه فيها، يمكنك إضافة رمز 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: تطبيق المقطع على سطح الرسومات. تحويل الطبقات

إصدار أولي

يمكن استخدام 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.

خارج الشاشة

يتم دائمًا تحويل محتوى العنصر القابل للتجميع إلى ملف bitmap أو ملف ملف bmp مخصّص للعرض خارج الشاشة قبل عرضه في الوجهة. يكون ذلك مفيدًا عند تطبيق عمليات 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، يتم إنشاء ملف ‎shader خارج الشاشة لتنفيذ الأوامر (مع تطبيق BlendMode على ملف ‎shader المرتبط بمحتوى هذا المكوّن فقط). وبعد ذلك، يتم عرضها فوق ما تم عرضَه سابقًا على الشاشة، بدون التأثير في المحتوى الذي سبق أن تم رسمه.

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: لقطات خارج الشاشة إلى المنطقة التي لا تفعل فيها auto
الشكل 15: CompositingStrategy.Auto مقابل CompositingStrategy.Offscreen: مقاطع خارج الشاشة إلى المنطقة التي لا تعمل فيها ميزة Auto
ModulateAlpha

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

في ما يلي مثال آخر على استراتيجيات التركيب المختلفة، وهو تطبيق ترانزِيبات dithered مختلفة على أجزاء مختلفة من العناصر القابلة للتجميع، وتطبيق استراتيجية 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 يقلب المحتوى عموديًا، يمكنك إنشاء 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 والرسم المخصّص، اطّلِع على الموارد التالية: