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

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

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

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

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

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. ويمكن تحريكها أو تعديلها بدون الحاجة إلى إعادة تنفيذ دالة drawing 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: تم تطبيق المقياسX وScaleY على صورة قابلة للإنشاء
الترجمة

يمكن تغيير 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 على الدائرة الوردية العلوية، سترى أن حدود "Composable" لا تزال كما هي، ولكن الدائرة يتم رسمها أسفل الدائرة السفلية (وخارجها).

تم تطبيق المقطع على TranslateY، وحد أحمر للمخطط
الشكل 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.

خارج الشاشة

يتم دائمًا تحويل محتوى العنصر القابل للإنشاء إلى زخرفة خارج الشاشة أو صورة نقطية قبل عرضه إلى الوجهة. ويفيد ذلك في تطبيق عمليات 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، يتم إنشاء ملف ملف 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 يقلب المحتوى عموديًا، يمكنك إنشاء واحدًا على النحو التالي:

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 والرسم المخصّص، اطّلِع على الموارد التالية: