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

بالإضافة إلى 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: تخزين كائن الفرشاة مؤقتًا باستخدام 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: تم تطبيق الترجمة X و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: التوجيهات س، وتدوير ص، وتدوير Z على الصورة بواسطة 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: تم تطبيق المقطع على الترجمة 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))
    )
}

تم تطبيق المقطع على عملية تحويلGraphicLayer
الشكل 10: تم تطبيق المقطع على عملية تحويلGraphicLayer

إصدار أولي

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

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