گرافیک در Compose

بسیاری از برنامه‌ها باید بتوانند دقیقاً آنچه را که روی صفحه نمایش داده می‌شود کنترل کنند. این کنترل می‌تواند به کوچکی قرار دادن یک کادر یا دایره روی صفحه در جای مناسب باشد، یا می‌تواند چیدمان دقیقی از عناصر گرافیکی در سبک‌های مختلف باشد.

طراحی اولیه با اصلاح کننده ها و DrawScope

روش اصلی برای رسم چیزی سفارشی در Compose استفاده از اصلاح‌کننده‌هایی مانند Modifier.drawWithContent ، Modifier.drawBehind و Modifier.drawWithCache است.

برای مثال، برای رسم چیزی پشت کامپوزیبل خود، می‌توانید از اصلاح‌کننده‌ی drawBehind برای شروع اجرای دستورات ترسیم استفاده کنید:

Spacer(
    modifier = Modifier
        .fillMaxSize()
        .drawBehind {
            // this = DrawScope
        }
)

اگر تنها چیزی که نیاز دارید یک کامپوننت قابل ترکیب است که بتواند رسم کند، می‌توانید از کامپوننت Canvas استفاده کنید. کامپوننت Canvas یک پوشش مناسب در اطراف Modifier.drawBehind است. شما Canvas در طرح‌بندی خود به همان روشی که با هر عنصر رابط کاربری Compose دیگر انجام می‌دهید، قرار می‌دهید. درون Canvas ، می‌توانید عناصر را با کنترل دقیق بر سبک و مکان آنها رسم کنید.

همه اصلاح‌کننده‌های ترسیم، یک DrawScope ، یک محیط ترسیم محدود که حالت خاص خود را حفظ می‌کند، را در معرض نمایش قرار می‌دهند. این به شما امکان می‌دهد پارامترهایی را برای گروهی از عناصر گرافیکی تنظیم کنید. DrawScope چندین فیلد مفید مانند size ، یک شیء Size که ابعاد فعلی DrawScope را مشخص می‌کند، ارائه می‌دهد.

برای رسم چیزی، می‌توانید از یکی از توابع رسم متعدد در DrawScope استفاده کنید. برای مثال، کد زیر یک مستطیل در گوشه سمت چپ بالای صفحه رسم می‌کند:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    drawRect(
        color = Color.Magenta,
        size = canvasQuadrantSize
    )
}

مستطیل صورتی رنگی که روی زمینه سفید کشیده شده و یک چهارم صفحه را اشغال می‌کند
شکل ۱. مستطیل رسم شده با استفاده از Canvas در Compose.

برای کسب اطلاعات بیشتر در مورد اصلاح‌کننده‌های مختلف ترسیم، به مستندات اصلاح‌کننده‌های گرافیکی مراجعه کنید.

سیستم مختصات

برای رسم چیزی روی صفحه، باید آفست ( x و y ) و اندازه آیتم خود را بدانید. در بسیاری از متدهای رسم در DrawScope ، موقعیت و اندازه توسط مقادیر پارامتر پیش‌فرض ارائه می‌شوند. پارامترهای پیش‌فرض معمولاً آیتم را در نقطه [0, 0] روی بوم قرار می‌دهند و size پیش‌فرضی را ارائه می‌دهند که کل ناحیه رسم را پر می‌کند، همانطور که در مثال بالا می‌بینید - می‌توانید ببینید که مستطیل در بالا سمت چپ قرار گرفته است. برای تنظیم اندازه و موقعیت آیتم خود، باید سیستم مختصات را در Compose درک کنید.

مبدا سیستم مختصات ( [0,0] ) در بالاترین و سمت چپ‌ترین پیکسل در ناحیه ترسیم قرار دارد. x با حرکت به سمت راست و y با حرکت به سمت پایین افزایش می‌یابد.

یک شبکه که سیستم مختصات بالا سمت چپ [0، 0] و پایین سمت راست [عرض، ارتفاع] را نشان می‌دهد
شکل ۲. سیستم مختصات ترسیمی / شبکه ترسیمی.

برای مثال، اگر می‌خواهید یک خط مورب از گوشه بالا سمت راست ناحیه بوم به گوشه پایین سمت چپ رسم کنید، می‌توانید از تابع DrawScope.drawLine() استفاده کنید و یک نقطه شروع و پایان را با موقعیت‌های x و y مربوطه مشخص کنید:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasWidth = size.width
    val canvasHeight = size.height
    drawLine(
        start = Offset(x = canvasWidth, y = 0f),
        end = Offset(x = 0f, y = canvasHeight),
        color = Color.Blue
    )
}

تبدیل‌های اساسی

DrawScope تبدیل‌هایی را برای تغییر مکان یا نحوه اجرای دستورات ترسیم ارائه می‌دهد.

مقیاس

از DrawScope.scale() برای افزایش اندازه عملیات ترسیم خود به میزان یک عامل استفاده کنید. عملیاتی مانند scale() برای همه عملیات ترسیم درون لامبدا مربوطه اعمال می‌شود. برای مثال، کد زیر scaleX 10 برابر و scaleY 15 برابر افزایش می‌دهد:

Canvas(modifier = Modifier.fillMaxSize()) {
    scale(scaleX = 10f, scaleY = 15f) {
        drawCircle(Color.Blue, radius = 20.dp.toPx())
    }
}

دایره‌ای که به صورت غیر یکنواخت مقیاس‌بندی شده است
شکل ۳. اعمال عملیات مقیاس‌بندی بر روی دایره‌ای روی بوم.

ترجمه

از DrawScope.translate() برای جابجایی عملیات ترسیم خود به بالا، پایین، چپ یا راست استفاده کنید. برای مثال، کد زیر ترسیم را ۱۰۰ پیکسل به سمت راست و ۳۰۰ پیکسل به بالا منتقل می‌کند:

Canvas(modifier = Modifier.fillMaxSize()) {
    translate(left = 100f, top = -300f) {
        drawCircle(Color.Blue, radius = 200.dp.toPx())
    }
}

دایره‌ای که از مرکز خود خارج شده است
شکل ۴. اعمال عملیات ترجمه به یک دایره روی بوم.

چرخش

از DrawScope.rotate() برای چرخاندن عملیات ترسیم خود حول یک نقطه محوری استفاده کنید. برای مثال، کد زیر یک مستطیل را ۴۵ درجه می‌چرخاند:

Canvas(modifier = Modifier.fillMaxSize()) {
    rotate(degrees = 45F) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

تلفنی با مستطیلی که در مرکز صفحه نمایش ۴۵ درجه چرخیده است
شکل ۵. ما از rotate() برای اعمال چرخش به محدوده ترسیم فعلی استفاده می‌کنیم که مستطیل را ۴۵ درجه می‌چرخاند.

درج

از DrawScope.inset() برای تنظیم پارامترهای پیش‌فرض DrawScope فعلی، تغییر مرزهای ترسیم و انتقال ترسیمات بر اساس آن استفاده کنید:

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    inset(horizontal = 50f, vertical = 30f) {
        drawRect(color = Color.Green, size = canvasQuadrantSize)
    }
}

این کد به طور موثری به دستورات ترسیم، فاصله‌گذاری (padding) اضافه می‌کند:

مستطیلی که دور تا دور آن با پد پوشانده شده است
شکل ۶. اعمال inset به دستورات ترسیم.

تبدیل‌های چندگانه

برای اعمال چندین تبدیل به ترسیمات خود، از تابع DrawScope.withTransform() استفاده کنید، که یک تبدیل واحد ایجاد و اعمال می‌کند که تمام تغییرات مورد نظر شما را ترکیب می‌کند. استفاده از withTransform() کارآمدتر از فراخوانی‌های تو در تو برای تبدیل‌های تکی است، زیرا همه تبدیل‌ها با هم در یک عملیات واحد انجام می‌شوند، به جای اینکه Compose نیاز به محاسبه و ذخیره هر یک از تبدیل‌های تو در تو داشته باشد.

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

Canvas(modifier = Modifier.fillMaxSize()) {
    withTransform({
        translate(left = size.width / 5F)
        rotate(degrees = 45F)
    }) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

تلفنی با مستطیل چرخان که به کنار صفحه نمایش منتقل شده است
شکل ۷. withTransform برای اعمال چرخش و انتقال استفاده کنید، مستطیل را بچرخانید و آن را به سمت چپ منتقل کنید.

عملیات ترسیم رایج

متن را رسم کنید

برای رسم متن در Compose، معمولاً می‌توانید از Text composable استفاده کنید. با این حال، اگر در DrawScope هستید یا می‌خواهید متن خود را به صورت دستی با قابلیت شخصی‌سازی رسم کنید، می‌توانید از متد DrawScope.drawText() استفاده کنید.

برای رسم متن، با استفاده از rememberTextMeasurer یک TextMeasurer ایجاد کنید و drawText با measurer فراخوانی کنید:

val textMeasurer = rememberTextMeasurer()

Canvas(modifier = Modifier.fillMaxSize()) {
    drawText(textMeasurer, "Hello")
}

نمایش یک سلام کشیده شده روی بوم
شکل ۸. ترسیم متن روی بوم.

متن را اندازه گیری کنید

رسم متن کمی متفاوت از سایر دستورات رسم کار می‌کند. معمولاً، شما به دستور رسم، اندازه (عرض و ارتفاع) را می‌دهید تا شکل/تصویر را رسم کند. در مورد متن، چند پارامتر وجود دارد که اندازه متن رندر شده را کنترل می‌کنند، مانند اندازه فونت، فونت، لیگاتورها و فاصله حروف.

با استفاده از Compose، می‌توانید بسته به عوامل فوق، از یک TextMeasurer برای دسترسی به اندازه اندازه‌گیری شده متن استفاده کنید. اگر می‌خواهید یک پس‌زمینه پشت متن رسم کنید، می‌توانید از اطلاعات اندازه‌گیری شده برای بدست آوردن اندازه ناحیه‌ای که متن اشغال می‌کند استفاده کنید:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixedWidth((size.width * 2f / 3f).toInt()),
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

این قطعه کد یک پس‌زمینه صورتی روی متن ایجاد می‌کند:

متن چند خطی که ⅔ از کل صفحه را اشغال می‌کند، با یک مستطیل پس‌زمینه
شکل ۹. متن چندخطی که ⅔ اندازه کل صفحه را اشغال می‌کند، با یک مستطیل پس‌زمینه.

تنظیم محدودیت‌ها، اندازه فونت یا هر ویژگی که بر اندازه اندازه‌گیری شده تأثیر می‌گذارد، منجر به اندازه جدیدی می‌شود که گزارش می‌شود. می‌توانید یک اندازه ثابت برای width و height تعیین کنید و سپس متن از TextOverflow تعیین شده پیروی می‌کند. برای مثال، کد زیر متن را در ⅓ ارتفاع و ⅓ عرض ناحیه قابل ترکیب رندر می‌کند و TextOverflow روی TextOverflow.Ellipsis تنظیم می‌کند:

val textMeasurer = rememberTextMeasurer()

Spacer(
    modifier = Modifier
        .drawWithCache {
            val measuredText =
                textMeasurer.measure(
                    AnnotatedString(longTextSample),
                    constraints = Constraints.fixed(
                        width = (size.width / 3f).toInt(),
                        height = (size.height / 3f).toInt()
                    ),
                    overflow = TextOverflow.Ellipsis,
                    style = TextStyle(fontSize = 18.sp)
                )

            onDrawBehind {
                drawRect(pinkColor, size = measuredText.size.toSize())
                drawText(measuredText)
            }
        }
        .fillMaxSize()
)

متن اکنون در محدوده‌ها با یک حذف در انتها ترسیم شده است:

متن روی پس‌زمینه صورتی کشیده شده، با حذفیه‌ای که متن را قطع می‌کند.
شکل 10. TextOverflow.Ellipsis . حذف با محدودیت‌های ثابت در اندازه‌گیری متن.

تصویر را رسم کنید

برای رسم یک ImageBitmap با DrawScope ، تصویر را با استفاده از ImageBitmap.imageResource() بارگذاری کنید و سپس drawImage فراخوانی کنید:

val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)

Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    drawImage(dogImage)
})

تصویر سگی که روی بوم نقاشی کشیده شده است
شکل ۱۱. رسم یک ImageBitmap روی Canvas.

رسم اشکال اولیه

توابع رسم شکل زیادی در DrawScope وجود دارد. برای رسم یک شکل، از یکی از توابع رسم از پیش تعریف شده، مانند drawCircle ، استفاده کنید:

val purpleColor = Color(0xFFBA68C8)
Canvas(
    modifier = Modifier
        .fillMaxSize()
        .padding(16.dp),
    onDraw = {
        drawCircle(purpleColor)
    }
)

رابط برنامه‌نویسی کاربردی

خروجی

drawCircle()

دایره رسم کنید

drawRect()

رسم مستقیم

drawRoundedRect()

رسم مستطیل گرد

drawLine()

خط کشیدن

drawOval()

بیضی بکشید

drawArc()

رسم قوس

drawPoints()

نقاط رسم

رسم مسیر

یک مسیر مجموعه‌ای از دستورالعمل‌های ریاضی است که پس از اجرا، منجر به ترسیم می‌شود. DrawScope می‌تواند با استفاده از متد DrawScope.drawPath() یک مسیر ترسیم کند.

برای مثال، فرض کنید می‌خواهید یک مثلث رسم کنید. می‌توانید با استفاده از اندازه ناحیه رسم، مسیری را با توابعی مانند lineTo() و moveTo() ایجاد کنید. سپس، تابع drawPath() با این مسیر تازه ایجاد شده فراخوانی کنید تا یک مثلث به دست آورید.

Spacer(
    modifier = Modifier
        .drawWithCache {
            val path = Path()
            path.moveTo(0f, 0f)
            path.lineTo(size.width / 2f, size.height / 2f)
            path.lineTo(size.width, 0f)
            path.close()
            onDrawBehind {
                drawPath(path, Color.Magenta, style = Stroke(width = 10f))
            }
        }
        .fillMaxSize()
)

یک مثلث مسیر بنفش وارونه که در Compose کشیده شده است
شکل ۱۲. ایجاد و ترسیم Path در Compose.

دسترسی به شیء Canvas

با DrawScope ، شما دسترسی مستقیم به شیء Canvas ندارید. می‌توانید از DrawScope.drawIntoCanvas() برای دسترسی به خود شیء Canvas که می‌توانید توابع را روی آن فراخوانی کنید، استفاده کنید.

برای مثال، اگر یک Drawable سفارشی دارید که می‌خواهید روی بوم رسم کنید، می‌توانید به بوم دسترسی پیدا کنید و Drawable#draw() فراخوانی کنید و شیء Canvas را به آن ارسال کنید:

val drawable = ShapeDrawable(OvalShape())
Spacer(
    modifier = Modifier
        .drawWithContent {
            drawIntoCanvas { canvas ->
                drawable.setBounds(0, 0, size.width.toInt(), size.height.toInt())
                drawable.draw(canvas.nativeCanvas)
            }
        }
        .fillMaxSize()
)

یک ShapeDrawable بیضی شکل مشکی که تمام اندازه را اشغال می‌کند
شکل ۱۳. دسترسی به canvas برای رسم یک Drawable .

بیشتر بدانید

برای اطلاعات بیشتر در مورد طراحی در Compose، به منابع زیر نگاهی بیندازید:

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