گرافیک در Compose

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

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

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

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

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

اگر تنها چیزی که نیاز دارید یک composable است که ترسیم می‌کند، می‌توانید از composable Canvas استفاده کنید. Canvas composable is a convenient wrapper around 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] و پایین سمت راست [width, height] را نشان می‌دهد
شکل ۲. سیستم مختصات طراحی / شبکه طراحی.

برای مثال، اگر می‌خواهید خطی مورب از گوشه بالا سمت راست ناحیه بوم به گوشه پایین سمت چپ بکشید، می‌توانید از تابع 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 را ۱۰ برابر و scaleY را ۱۵ برابر افزایش می‌دهد:

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

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

ترجمه Google

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

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

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

چرخاندن

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

این کد به‌طور مؤثری به فرمان‌های طراحی حاشیه اضافه می‌کند:

مستطیلی که دورتادور آن لایه‌گذاری شده است
شکل ۶. درحال اعمال کردن درج به فرمان‌های طراحی.

تبدیل‌های متعدد

برای اعمال چند تبدیل به طراحی‌هایتان، از تابع 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 استفاده کنید. بااین‌حال، اگر در DrawScope هستید یا می‌خواهید نوشتارتان را به‌صورت دستی با سفارشی‌سازی بکشید، می‌توانید از روش DrawScope.drawText() استفاده کنید.

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

val textMeasurer = rememberTextMeasurer()

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

نمایش کلمه Hello که روی «بوم نقاشی» کشیده شده است
شکل ۸. درحال کشیدن نوشتار روی Canvas.

اندازه‌گیری نوشتار

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

با 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()
)

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

نوشتار روی پس‌زمینه صورتی کشیده شده است و سه‌نقطه نوشتار را قطع می‌کند.
شکل ۱۰. 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)
    }
)

API

خروجی

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 بیضی‌شکل سیاه که اندازه کامل را اشغال می‌کند
شکل ۱۳. درحال دسترسی به بوم برای کشیدن Drawable.

بیشتر بدانید

برای اطلاعات بیشتر درباره «ترسیم» در Compose، منابع زیر را ببینید: