اصلاح کننده های گرافیکی

علاوه بر Canvas composable، Compose چندین Modifiers گرافیکی مفید دارد که به ترسیم محتوای سفارشی کمک می‌کنند. این اصلاح‌کننده‌ها مفید هستند زیرا می‌توانند روی هر ترکیب‌کننده‌ای اعمال شوند.

اصلاح‌کننده‌های ترسیم

تمام دستورات ترسیم با یک اصلاح‌کننده ترسیم در Compose انجام می‌شوند. سه اصلاح‌کننده ترسیم اصلی در Compose وجود دارد:

اصلاح‌کننده پایه برای ترسیم، drawWithContent است که در آن می‌توانید ترتیب ترسیم Composable خود و دستورات ترسیم صادر شده درون اصلاح‌کننده را تعیین کنید. drawBehind یک پوشش مناسب در اطراف drawWithContent است که ترتیب ترسیم را در پشت محتوای composable تنظیم می‌کند. drawWithCache یا onDrawBehind یا onDrawWithContent درون آن فراخوانی می‌کند - و مکانیزمی برای ذخیره‌سازی اشیاء ایجاد شده در آنها فراهم می‌کند.

Modifier.drawWithContent : انتخاب ترتیب ترسیم

Modifier.drawWithContent به شما امکان می‌دهد عملیات DrawScope قبل یا بعد از محتوای composable اجرا کنید. حتماً drawContent فراخوانی کنید تا محتوای واقعی composable رندر شود. با این modifier، می‌توانید ترتیب عملیات را تعیین کنید، اگر می‌خواهید محتوای شما قبل یا بعد از عملیات طراحی سفارشی شما ترسیم شود.

برای مثال، اگر می‌خواهید یک گرادیان شعاعی روی محتوای خود ایجاد کنید تا جلوه‌ای شبیه به چراغ قوه در رابط کاربری ایجاد شود، می‌توانید موارد زیر را انجام دهید:

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
}

شکل ۱ : از Modifier.drawWithContent روی یک Composable برای ایجاد یک رابط کاربری از نوع چراغ قوه استفاده شده است.

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 ترسیم شده است
شکل ۲ : متن و پس‌زمینه ترسیم شده با استفاده از Modifier.drawBehind

Modifier.drawWithCache : رسم و ذخیره سازی اشیاء رسم شده

Modifier.drawWithCache اشیاء ایجاد شده در داخل خود را در حافظه پنهان (cache) نگه می‌دارد. اشیاء تا زمانی که اندازه ناحیه ترسیم یکسان باشد، یا هر شیء خوانده شده در حالت خوانده شده تغییر نکرده باشد، در حافظه پنهان (cache) باقی می‌مانند. این اصلاح‌کننده برای بهبود عملکرد فراخوانی‌های ترسیم مفید است زیرا از نیاز به تخصیص مجدد اشیاء (مانند: 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
شکل ۳ : ذخیره سازی شیء Brush با drawWithCache

اصلاح‌کننده‌های گرافیکی

Modifier.graphicsLayer : اعمال تبدیلات به composables

Modifier.graphicsLayer یک اصلاح‌کننده است که محتوای ترسیم قابل ترکیب را به یک لایه ترسیم تبدیل می‌کند. یک لایه چندین عملکرد مختلف ارائه می‌دهد، مانند:

  • جداسازی دستورالعمل‌های ترسیم آن (مشابه RenderNode ). دستورالعمل‌های ترسیمی که به عنوان بخشی از یک لایه ثبت می‌شوند، می‌توانند بدون اجرای مجدد کد برنامه، توسط خط لوله رندر به طور کارآمدی دوباره صادر شوند.
  • تبدیل‌هایی که بر تمام دستورالعمل‌های ترسیم موجود در یک لایه اعمال می‌شوند.
  • قابلیت رسترسازی برای ترکیب‌بندی. وقتی یک لایه رستر می‌شود، دستورالعمل‌های ترسیم آن اجرا می‌شوند و خروجی در یک بافر خارج از صفحه ثبت می‌شود. ترکیب چنین بافری برای فریم‌های بعدی سریع‌تر از اجرای دستورالعمل‌های تکی است، اما وقتی تبدیل‌هایی مانند مقیاس‌بندی یا چرخش اعمال می‌شوند، مانند یک بیت‌مپ رفتار خواهد کرد.

دگرگونی‌ها

Modifier.graphicsLayer دستورالعمل‌های ترسیم خود را ایزوله می‌کند؛ برای مثال، می‌توان با استفاده از Modifier.graphicsLayer تبدیل‌های مختلفی را اعمال کرد. این تبدیل‌ها را می‌توان بدون نیاز به اجرای مجدد لامبدا ترسیم، متحرک‌سازی یا اصلاح کرد.

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
        }
)

شکل ۴ : اعمال scaleX و 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()
        }
)

شکل ۵ : 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
        }
)

شکل ۶ : تنظیم 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
        }
)

شکل ۷ : چرخش اعمال شده با TransformOrigin تنظیم شده روی ۰f، ۰f

گیره و شکل

شکل، طرح کلی را مشخص می‌کند که محتوا هنگام clip = true به آن می‌چسبد. در این مثال، ما دو کادر را طوری تنظیم می‌کنیم که دو کلیپ مختلف داشته باشند - یکی با استفاده از متغیر clip graphicsLayer و دیگری با استفاده از wrapper مناسب 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))
    )
}

محتویات کادر اول (متن «سلام نوشتن») به شکل دایره برش داده شده است:

کلیپ به Box composable اعمال شد
شکل 8 : کلیپ اعمال شده به Box composable

اگر سپس یک translationY روی دایره صورتی بالا اعمال کنید، می‌بینید که مرزهای Composable هنوز یکسان هستند، اما دایره زیر دایره پایینی (و خارج از مرزهای آن) رسم می‌شود.

کلیپ با translationY و حاشیه قرمز برای طرح کلی اعمال شد
شکل ۹ : کلیپ اعمال شده با translationY و حاشیه قرمز برای طرح کلی

برای اینکه عنصر ترکیب‌پذیر (composable) به ناحیه‌ای که در آن ترسیم شده است، متصل شود، می‌توانید یک 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))
    )
}

کلیپ اعمال شده روی گرافیکتبدیل لایه
شکل 10 : کلیپ اعمال شده روی تبدیل لایه گرافیکی

آلفا

Modifier.graphicsLayer می‌توان برای تنظیم alpha (میزان شفافیت) برای کل لایه استفاده کرد. 1.0f کاملاً مات و 0.0f نامرئی است.

Image(
    painter = painterResource(id = R.drawable.sunset),
    contentDescription = "clock",
    modifier = Modifier
        .graphicsLayer {
            this.alpha = 0.5f
        }
)

تصویر با آلفای اعمال شده
شکل ۱۱ : تصویر با آلفای اعمال شده

استراتژی ترکیب‌بندی

کار با آلفا و شفافیت ممکن است به سادگی تغییر یک مقدار آلفا نباشد. علاوه بر تغییر آلفا، گزینه‌ای برای تنظیم CompositingStrategy روی graphicsLayer نیز وجود دارد. CompositingStrategy نحوه ترکیب (کنار هم قرار گرفتن) محتوای قابل ترکیب با سایر محتوای ترسیم شده روی صفحه را تعیین می‌کند.

استراتژی‌های مختلف عبارتند از:

خودکار (پیش‌فرض)

استراتژی ترکیب توسط بقیه پارامترهای graphicsLayer تعیین می‌شود. اگر آلفا کمتر از ۱.۰f باشد یا RenderEffect تنظیم شده باشد، لایه را در یک بافر خارج از صفحه رندر می‌کند. هر زمان که آلفا کمتر از ۱f باشد، یک لایه ترکیب به طور خودکار ایجاد می‌شود تا محتویات را رندر کند و سپس این بافر خارج از صفحه را با آلفای مربوطه به مقصد ترسیم کند. تنظیم RenderEffect یا overscroll همیشه محتوا را صرف نظر از تنظیم CompositingStrategy در یک بافر خارج از صفحه رندر می‌کند.

خارج از صفحه

محتویات فایل composable همیشه قبل از رندر شدن در مقصد، به یک بافت خارج از صفحه یا بیت‌مپ تبدیل (rasterize) می‌شوند. این قابلیت برای اعمال عملیات BlendMode برای ماسک کردن محتوا و برای بهبود عملکرد هنگام رندر کردن مجموعه‌های پیچیده از دستورالعمل‌های ترسیم مفید است.

یک مثال از استفاده از CompositingStrategy.Offscreen با BlendModes است. با نگاهی به مثال زیر، فرض کنید می‌خواهید بخش‌هایی از یک Image قابل ترکیب را با صدور دستور draw که از 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 فقط روی محتویات این composable اعمال می‌کند). سپس آن را روی آنچه از قبل روی صفحه نمایش داده شده است، رندر می‌کند و بر محتوای ترسیم شده تأثیری نمی‌گذارد.

Modifier.drawWithContent روی تصویری که نشانگر دایره است، با BlendMode.Clear درون برنامه
شکل ۱۲ : Modifier.drawWithContent روی تصویری که نشانگر دایره است، با BlendMode.Clear و CompositingStrategy.Offscreen درون برنامه

اگر از CompositingStrategy.Offscreen استفاده نکرده باشید، نتایج اعمال BlendMode.Clear تمام پیکسل‌های مقصد را پاک می‌کند، صرف نظر از اینکه قبلاً چه چیزی تنظیم شده است - و بافر رندر پنجره (سیاه) را قابل مشاهده باقی می‌گذارد. بسیاری از BlendModes که شامل alpha می‌شوند، بدون یک بافر offscreen آنطور که انتظار می‌رود کار نمی‌کنند. به حلقه سیاه دور نشانگر دایره قرمز توجه کنید:

Modifier.drawWithContent روی تصویری که نشانگر دایره است، با BlendMode.Clear و بدون تنظیم CompositingStrategy
شکل ۱۳ : Modifier.drawWithContent روی تصویری که نشانگر دایره است، با BlendMode.Clear و بدون تنظیم CompositingStrategy

برای درک بیشتر این موضوع: اگر برنامه دارای پس‌زمینه پنجره شفاف باشد و شما از CompositingStrategy.Offscreen استفاده نکرده باشید، BlendMode با کل برنامه تعامل خواهد داشت. تمام پیکسل‌ها را پاک می‌کند تا برنامه یا تصویر زمینه زیر آن نمایش داده شود، مانند این مثال:

بدون تنظیم CompositingStrategy و استفاده از BlendMode.Clear با برنامه‌ای که پس‌زمینه پنجره شفاف دارد. تصویر زمینه صورتی از طریق ناحیه اطراف دایره وضعیت قرمز نشان داده می‌شود.
شکل ۱۴ : بدون تنظیم 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 - offscreen به ناحیه‌ای کلیپ می‌کند که حالت خودکار این کار را نمی‌کند
شکل ۱۵ : CompositingStrategy.Auto در مقابل CompositingStrategy.Offscreen - حالت offscreen به ناحیه‌ای که حالت auto به آن نمی‌رسد، کلیپس می‌شود.
ModulateAlpha

این استراتژی ترکیب، آلفا را برای هر یک از دستورالعمل‌های ترسیم ثبت‌شده در graphicsLayer تعدیل می‌کند. این استراتژی برای آلفای زیر ۱.۰f بافر خارج از صفحه ایجاد نمی‌کند، مگر اینکه یک RenderEffect تنظیم شده باشد، بنابراین می‌تواند برای رندر آلفا کارآمدتر باشد. با این حال، می‌تواند نتایج متفاوتی را برای محتوای همپوشانی ارائه دهد. برای مواردی که از قبل مشخص است که محتوا همپوشانی ندارد، این استراتژی می‌تواند عملکرد بهتری نسبت به CompositingStrategy.Auto با مقادیر آلفای کمتر از ۱ ارائه دهد.

مثال دیگری از استراتژی‌های مختلف ترکیب‌بندی در زیر آمده است - اعمال آلفاهای مختلف به بخش‌های مختلف ترکیب‌پذیرها، و اعمال استراتژی 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 مجموعه آلفا را برای هر دستور ترسیم جداگانه اعمال می‌کند.
شکل ۱۶ : ModulateAlpha مجموعه آلفا را برای هر دستور ترسیم جداگانه اعمال می‌کند

محتویات یک composable را در یک bitmap بنویسید

یک مورد استفاده رایج، ایجاد یک Bitmap از یک composable است. برای کپی کردن محتویات composable خود به یک Bitmap ، با استفاده از rememberGraphicsLayer() یک GraphicsLayer ایجاد کنید.

با استفاده از 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()
)

اصلاح‌کننده‌ی وارونه‌ی سفارشی روی متن
شکل ۱۷ : اصلاحگر سفارشی وارونه روی متن

منابع اضافی

برای مثال‌های بیشتر با استفاده از graphicsLayer و طراحی سفارشی، منابع زیر را بررسی کنید:

{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}