گرافیک در 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
    )
}

مستطیل صورتی که روی پس‌زمینه سفید کشیده شده است و یک‌چهارم صفحه را اشغال می‌کند
شکل ۱. مستطیل بااستفاده از «بوم نقاشی» در «نوشتن» رسم شد.

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

سیستم مختصات

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

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

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

برای مثال، اگر می‌خواهید خط موربی از گوشه بالا سمت راست ناحیه بوم نقاشی به گوشه پایین سمت چپ بکشید، می‌توانید از تابع 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())
    }
}

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

ترجمه Google

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

این کد عملاً به دستورات طراحی حاشیه اضافه می‌کند:

مستطیلی که از همه طرف حاشیه دارد
شکل ۶. درحال اعمال کردن حاشیه داخلی روی فرمان‌های طراحی.

چندین تبدیل

برای اعمال چندین تبدیل روی طراحی‌هایتان، از تابع 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 برای اعمال هم چرخش و هم انتقال استفاده کنید، مستطیل را بچرخانید و آن را به چپ منتقل کنید.

عملیات رایج طراحی

کشیدن نوشتار

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

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

val textMeasurer = rememberTextMeasurer()

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

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

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

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

با «نوشتن»، می‌توانید از 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)
})

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

کشیدن شکل‌های پایه

در 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()
)

مثلث مسیر بنفش وارونه‌ای که در «نوشتن» کشیده شده است
شکل ۱۲. درحال ایجاد و طراحی Path در «نوشتن».

دسترسی به 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.

بیشتر بدانید

برای اطلاعات بیشتر درباره «کشیدن در نوشتن»، به منابع زیر نگاهی بیندازید: