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

بالإضافة إلى الدالة المركّبة 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 على 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))
    )
}

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

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

إذا طبّقت بعد ذلك 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: تم تطبيق القص فوق عملية تحويل 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: صورة تم تطبيق alpha عليها

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

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

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

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

يتم تحديد استراتيجية التركيب من خلال بقية graphicsLayer المعلمات. تعرض هذه الاستراتيجية الطبقة في مخزن مؤقت خارج الشاشة إذا كانت قيمة alpha أقل من 1.0f أو تم ضبط RenderEffect. عندما تكون قيمة alpha أقل من 1f، يتم إنشاء طبقة تركيب تلقائيًا لعرض المحتويات ثم رسم هذا المخزن المؤقت خارج الشاشة في الوجهة باستخدام قيمة alpha المقابلة. يؤدي ضبط a 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 التي تتضمّن alpha على النحو المتوقّع بدون مخزن مؤقت خارج الشاشة. لاحظ الحلقة السوداء حول المؤشر الدائري الأحمر:

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

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

يوضّح المثال التالي استراتيجيات تركيب مختلفة، حيث يتم تطبيق قيم alpha مختلفة على أجزاء مختلفة من الدوال المركّبة، وتطبيق استراتيجية 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 قيمة alpha التي تم ضبطها على كل أمر رسم فردي

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

rememberGraphicsLayer()

إحدى حالات الاستخدام الشائعة هي إنشاء 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()
)

Custom Flipped Modifier on Text
الشكل 17: معدِّل معكوس مخصّص على النص

مراجع إضافية

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