گرافیک در Compose

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

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

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

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

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

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

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

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

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

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

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

دستگاه مختصات

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

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

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

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

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

ترجمه کردن

از DrawScope.translate() برای حرکت دادن عملیات طراحی خود به بالا، پایین، چپ یا راست استفاده کنید. به عنوان مثال، کد زیر نقاشی را 100 پیکسل به سمت راست و 300 پیکسل به بالا حرکت می دهد:

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

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

چرخش

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

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

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

درج شده

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

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

این کد به طور موثر padding را به دستورات ترسیم اضافه می کند:

مستطیلی که دور تا دور آن را پر کرده است
شکل 6 . اعمال یک 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
        )
    }
}

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

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

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

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

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

val textMeasurer = rememberTextMeasurer()

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

نمایش سلامی که روی بوم کشیده شده است
شکل 8 . طراحی متن روی بوم.

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

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

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

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

نوشتار چند خطی با یک مستطیل پس‌زمینه، ⅔ اندازه کل منطقه را اشغال می‌کند
شکل 9 . نوشتار چند خطی با یک مستطیل پس‌زمینه، ⅔ اندازه کل منطقه را اشغال می‌کند.

تنظیم محدودیت‌ها، اندازه فونت یا هر ویژگی که بر اندازه اندازه‌گیری شده تأثیر می‌گذارد، اندازه جدیدی گزارش می‌شود. شما می توانید اندازه ثابتی را هم برای 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)
})

تصویری از یک سگ که روی بوم کشیده شده است
شکل 11 . ترسیم 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()
)

مثلث مسیر بنفش وارونه که روی Compose کشیده شده است
شکل 12 . ایجاد و ترسیم یک 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 مشکی بیضی شکل که اندازه کامل را می گیرد
شکل 13 . دسترسی به بوم برای ترسیم یک Drawable .

بیشتر بدانید

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

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}