نمایشهای سفارشی اساس رابط کاربری منحصربهفرد هر برنامه موبایل هستند. نمایشهای سفارشی در برنامههای موبایل به شما امکان میدهند گرافیک، انیمیشن و عناصر خاص خود را که در کامپوننتهای استاندارد در دسترس نیستند ایجاد کنید. به گزارش Apple Developer، 2025، drawRect روش اصلی ترسیم سفارشی در توسعه موبایل iOS باقی مانده است. در Android، onDraw با Canvas همان نقش را ایفا میکند، و در Flutter — CustomPainter.
نکات اصلی
نمایشهای سفارشی کامپوننتهای رابط کاربری تعریفشده توسط کاربر هستند که متدهای استاندارد ترسیم و اندازهگیری را برای ایجاد گرافیک منحصربهفرد بازنویسی میکنند. نمودارها، انیمیشنها، دیاگرامها، عناصر چت سفارشی و ماسکهای AR — اینها سناریوهای معمول استفاده از نمایشهای سفارشی در برنامههای موبایل هستند. کامپوننتهای استاندارد UI (Button، TextView، Label) 80٪ وظایف را پوشش میدهند، اما 20٪ باقیمانده نیاز به کنترل کامل بر ترسیم در توسعه موبایل دارند. ترسیم در توسعه موبایل مهارتی است که یک توسعهدهنده junior را از senior متمایز میکند.
نمایشهای سفارشی در برنامههای موبایل برای گرافیک 2D منحصربهفرد، انیمیشنهای پیچیده، نمودارها و تجسم دادهها مورد نیاز هستند. اگر UIKit استاندارد عنصر مورد نیاز را ارائه نمیدهد — یک نمایش سفارشی ایجاد میشود. در برنامههای موبایل، نمایشهای سفارشی برای نوارهای پیشرفت، دکمههای سفارشی، پرکردنهای گرادیان، آیکونهای متحرک و بومهای نقاشی با انگشت استفاده میشوند. مهم است که زیادهروی نکنید — برای سبکهای ساده، پیکربندی کامپوننتهای استاندارد از طریق API ظاهر کافی است.
در iOS، نمایشهای سفارشی با ارثبری از UIView و بازنویسی متد drawRect ایجاد میشوند. drawRect در اولین رندر و پس از هر فراخوانی setNeedsDisplay() فراخوانی میشود. درون drawRect، توسعهدهنده یک CGContext از طریق UIGraphicsGetCurrentContext() دریافت میکند — بومنقاشی برای خطوط، اشکال، گرادیانها و متن. Core Graphics (Quartz 2D) یک موتور رندر سطح پایین مبتنی بر CPU است که کنترل کامل بر هر پیکسل را میدهد. UIBezierPath یک لفافهبند مناسب برای ایجاد مسیرها با نحو مختصر است.
class CircleView: UIView {
private var fillColor: UIColor = .systemBlue
override func drawRect(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.setFillColor(fillColor.cgColor)
ctx.addEllipse(in: bounds.insetBy(dx: 4, dy: 4))
ctx.drawPath(using: .fill)
}
func updateColor(_ newColor: UIColor) {
fillColor = newColor
setNeedsDisplay()
}
}
Core Graphics CGContext را فراهم میکند — بومنقاشی برای تمام عملیات ترسیم. CGGradient گرادیانهای خطی و شعاعی ایجاد میکند با پشتیبانی از CGColorSpace (sRGB، Display P3). تبدیلها از طریق CGAffineTransform انجام میشوند — ماتریس 3x3 برای چرخش، مقیاسگذاری و جابجایی. CGContextTranslateCTM، CGContextRotateCTM و CGContextScaleCTM سیستم مختصات را تغییر میدهند. Core Graphics روی CPU کار میکند، بنابراین گرافیک پیچیده میتواند پردازنده را بارگذاری کند — برای شتاب GPU از Metal استفاده کنید.
UIBezierPath یک لفافهبند شیگرا روی CGPath است. متد addArc(withCenter:radius:startAngle:endAngle:clockwise:) کمانها و دایرهها ایجاد میکند، addCurve منحنیهای Bézier ایجاد میکند. مسیرها از عملیات اتحاد، اشتراک و تفریق از طریق addClip پشتیبانی میکنند. UIBezierPath برای ماسکهای سفارشی (maskView)، مسیرهای برش و اشکال پیچیده — ستارهها، قلبها، آیکونهای سفارشی مناسب است.
در Android، یک نمایش سفارشی View را با بازنویسی onDraw(Canvas) گسترش میدهد. Canvas شامل تمام متدهای ترسیم در توسعه موبایل است: drawCircle، drawRect، drawLine، drawText، drawBitmap، drawPath و drawRoundRect. Paint پارامترها را پیکربندی میکند — رنگ، ضخامت خط، سبک (FILL، STROKE، FILL_AND_STROKE)، صافسازی (isAntiAlias) و اندازه متن. onDraw هر بار در invalidate() فراخوانی میشود و نباید حاوی عملیات سنگین باشد — تمام محاسبات کش میشوند.
class CustomCircleView(context: Context, attrs: AttributeSet?)
: View(context, attrs) {
private val paint = Paint().apply {
style = Paint.Style.FILL
color = Color.BLUE
isAntiAlias = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val cx = width / 2f
val cy = height / 2f
val radius = minOf(cx, cy) - 4f
canvas.drawCircle(cx, cy, radius, paint)
}
}
Path در Android معادل UIBezierPath است: addCircle، addRect، addOval، lineTo، cubicTo و quadTo. PathMeasure طول مسیر و مختصات را در هر نقطه محاسبه میکند — برای متحرکسازی حرکت یک جسم در طول مسیر مفید است. برای انیمیشنها، از ValueAnimator با invalidate() در فراخوان استفاده میشود. انیماتور میتواند رنگ، شعاع، موقعیت را تغییر دهد — هر ویژگی که بر ترسیم تأثیر میگذارد. نمایشهای سفارشی در Android همچنین از StateListDrawable و animated-vector برای انیمیشن از طریق XML پشتیبانی میکنند.
گرادیانها در Android از طریق Shader تنظیم میشوند — LinearGradient، RadialGradient و SweepGradient. Shader از طریق paint.setShader(shader) به Paint اختصاص داده میشود. LinearGradient یک گرادیان خطی بین دو نقطه با موقعیتهای رنگی اختیاری ایجاد میکند. RadialGradient — شعاعی از مرکز به لبهها. SweepGradient — مخروطی حول یک نقطه مرکزی. Shader.TileMode (CLAMP، REPEAT، MIRROR) رفتار فراتر از مرزهای منطقه را تعریف میکند.
Flutter از CustomPainter استفاده میکند — کلاسی با متدهای paint(Canvas canvas, Size size) و shouldRepaint. Canvas در Flutter همان متدها را ارائه میدهد که در Android وجود دارد: drawCircle، drawRect، drawLine، drawPath، drawParagraph و drawImage. Paint در Flutter رنگ، ضخامت، سبک و صافسازی را پیکربندی میکند. Flutter از drawRect یا onDraw استفاده نمیکند — تمام ترسیم از طریق CustomPainter انجام میشود که در ویجت CustomPaint جاسازی شده است.
class CirclePainter extends CustomPainter {
final Color color;
CirclePainter({required this.color});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill
..isAntiAlias = true;
final radius = size.shortestSide / 2 - 4;
canvas.drawCircle(size.center(Offset.zero), radius, paint);
}
@override
bool shouldRepaint(CirclePainter oldDelegate) =>
oldDelegate.color != color;
}
shouldRepaint تعیین میکند که آیا CustomPainter نیاز به ترسیم مجدد دارد یا خیر. فقط زمانی true برگردانید که دادههای مؤثر بر رندر تغییر کنند — رنگ، اندازه، دادههای نمودار. RepaintBoundary ترسیم مجدد را در یک لایه جداگانه ایزوله میکند — برای ویجتهایی که به ندرت تغییر میکنند استفاده کنید. Flutter setNeedsDisplay یا invalidate ندارد — ترسیم مجدد از طریق setState یا ValueNotifier با گوش دادن به تغییرات فعال میشود.
Flutter از LinearGradient، RadialGradient و SweepGradient پشتیبانی میکند — همان انواعی که در Android وجود دارد. Gradient از طریق createShader(Rect bounds) یک Shader برای Paint ایجاد میکند. تبدیلها از طریق canvas.rotate، canvas.scale، canvas.translate و canvas.skew انجام میشوند. Flutter از ماتریس تبدیل 4x4 (Matrix4) برای ویجت Transform استفاده میکند. انیمیشنهای ترسیم در Flutter با AnimationController همراه با setState برای ترسیم مجدد CustomPainter ساخته میشوند.
اندازهگیری صحیح نمایشهای سفارشی اساس رفتار قابل پیشبینی رابط کاربری است. در iOS، اندازه نمایش از طریق sizeThatFits و intrinsicContentSize تعیین میشود. sizeThatFits اندازه بهینه را برای محدودیتهای داده شده برمیگرداند. intrinsicContentSize — اندازه طبیعی محتوا بدون محدودیتهای خارجی. در Android، اندازهگیری در onMeasure با پارامترهای MeasureSpec (UNSPECIFIED، EXACTLY، AT_MOST) پیادهسازی میشود. در Flutter، اندازه به paint(Canvas, Size) منتقل میشود و میتواند از طریق computeDryLayout یا LayoutBuilder محاسبه شود.
intrinsicContentSize یک CGSize برمیگرداند — اندازه طبیعی نمایش بر اساس محتوای آن. setContentHuggingPriority و setContentCompressionResistancePriority رفتار را در هنگام کشش و فشردگی کنترل میکنند. sizeThatFits توسط سیستم برای محاسبه اندازه در Auto Layout فراخوانی میشود. برای نمایشهای سفارشی، اگر کامپوننت شما اندازه طبیعی دارد intrinsicContentSize را بازنویسی کنید — به عنوان مثال، یک دایره همیشه باید 100x100 باشد.
onMeasure widthMeasureSpec و heightMeasureSpec را میپذیرد. MeasureSpec شامل حالت و اندازه است: UNSPECIFIED (بدون محدودیت، نمایش اندازه خود را انتخاب میکند)، EXACTLY (اندازه دقیق از والد)، AT_MOST (حداکثر اندازه). setMeasuredDimension(width, height) نتیجه اندازهگیری را ذخیره میکند. هنگام تغییر دادهها، برای اندازهگیری مجدد requestLayout() را فراخوانی کنید. ViewGroupهای تو در تو نیاز به بازنویسی onLayout برای چیدمان عناصر فرزند دارند.
| پلتفرم | روش اندازهگیری | فراخوانی هنگام تغییر | بازگشت |
|---|---|---|---|
| iOS | sizeThatFits / intrinsicContentSize | setNeedsLayout / invalidateIntrinsicContentSize | CGSize |
| Android | onMeasure / setMeasuredDimension | requestLayout / invalidate | void (اندازه را ذخیره میکند) |
| Flutter | computeDryLayout | setState / LayoutBuilder | Size |
computeDryLayout یک متد RenderObject برای محاسبه اندازه بدون چرخه چیدمان کامل است. LayoutBuilder.getSize — جایگزینی برای دریافت فضای موجود در متد build. در Flutter، اندازه بوم به عنوان Size به paint منتقل میشود و نیاز به اندازهگیری جداگانه ندارد — CustomPainter درون مرزهای منتقل شده ترسیم میکند. با این حال، برای RenderObjectهای سفارشی، باید performLayout و computeDryLayout را بازنویسی کنید.
سوالات متداول
drawRect یک متد UIView برای ترسیم سفارشی از طریق Core Graphics است. در اولین رندر نمایش و پس از هر فراخوانی setNeedsDisplay() فراخوانی میشود. برای خطوط، اشکال و متن از CGContext درون drawRect استفاده کنید.
کلاسی ایجاد کنید که View را گسترش میدهد، onDraw(Canvas canvas) را بازنویسی کنید و از Canvas API استفاده کنید: drawCircle، drawRect، drawPath. Paint رنگ، ضخامت و سبک را پیکربندی میکند. برای بهروزرسانی، invalidate() را فراخوانی کنید.
CustomPainter یک کلاس Flutter برای ترسیم سفارشی روی Canvas است. متدهای paint(Canvas, Size) و shouldRepaint را پیادهسازی کنید. از طریق ویجت CustomPaint قرار میگیرد. Flutter drawRect ندارد — تمام ترسیم از طریق CustomPainter انجام میشود.
در iOS از sizeThatFits و intrinsicContentSize استفاده کنید. در Android — onMeasure با MeasureSpec. در Flutter، اندازه به paint(Canvas, Size) منتقل میشود یا از طریق computeDryLayout محاسبه میشود. هر پلتفرم از روش اندازهگیری خود استفاده میکند.
نمایشهای سفارشی برای گرافیک منحصربهفرد، انیمیشنها، نمودارها و عناصری که با UIKit یا View استاندارد قابل پیادهسازی نیستند مورد نیاز هستند. برای سبکهای ساده، پیکربندی کامپوننتهای استاندارد کافی است — معماری را بیدلیل پیچیده نکنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.