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

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

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

يتم تنفيذ جميع أوامر الرسم باستخدام معدِّل الرسم في Compose. هناك ثلاثة تعديلات رئيسية للرسم في 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. يمكن تحريكها أو تعديلها دون الحاجة إلى إعادة تنفيذ رسم 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: تم تطبيق translateX و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: تم ضبط الدورانX، والتدوير Y، والتدوير على الصورة بواسطة 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") إلى شكل الدائرة:

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

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

تم تطبيق المقطع على TranslateY، وحد أحمر للمخطط
الشكل 9: تم تطبيق المقطع مع translateY، وحدّ أحمر للمخطط

لاقتصاص العنصر القابل للإنشاء حسب المنطقة التي تم رسمه فيها، يمكنك إضافة عنصر 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.خارج الشاشة
الشكل 15: CompositingStrategy.Auto مقابل CompositingStrategy.Offscreen
ModulateAlpha

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

في ما يلي مثال آخر على استراتيجيات التركيبة المختلفة، مثلاً تطبيق لغات ألفا مختلفة على أجزاء مختلفة من العنصر القابل للإنشاء وتطبيق استراتيجية Modulate.

@Preview
@Composable
fun CompositingStratgey_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 والرسم المخصّص، يمكنك الاطّلاع على المراجع التالية: