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

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

تعديل الرسم

يتم تنفيذ جميع أوامر الرسم باستخدام أداة تعديل الرسم في 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))
    )
}

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

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

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

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

لقص العنصر القابل للإنشاء إلى المنطقة التي يتم رسمه فيها، يمكنك إضافة 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 أو overscroll دائمًا إلى عرض المحتوى في مخزن مؤقت خارج الشاشة بغض النظر عن قيمة 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()
)

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

مراجع إضافية

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