יישום תרשים נרות באפליקציית מסחר ניידת
לעתים קרובות אנו נתקלים במצבים שבהם לקוח זקוק לתרשים מסחר עם ביצועים של 60fps וגלילה חלקה על פני יותר מ-10,000 נרות. ספריות מוכנות מראש או שאינן תומכות בתרש�י נרות או דורשות רישיונות יקרים. הצוות שלנו פיתח פתרון מקורי על Flutter באמצעות CustomPainter שעוקף את המגבלות הללו וחוסך בעלויות רישוי. עם ניסיון של למעלה מ-7 שנים בפיתוח מובייל ויותר מ-50 פרויקטים שיושמו הכוללים תרשימים מותאמים אישית, אנו מבטיחים איכות גבוהה.
תרשים נרות הוא טכנית סוג התרשים המורכב ביותר עבור אפליקציית מובייל. לא בגלל המתמטיקה של OHLC, אלא בשל מערך הדרישות ההכרחיות לחוויית מסחר: גלילה חלקה/זום על פני יותר מ-10,000 נרות, קו חיתוך עם קואורדינטות במגע, עדכונים בזמן אמת ללא ציור מחדש מלא, מעבר בין מסגרות זמן ללא הבהובים וטיפול נכון בכיוון המסך. כל הדרישות הללו יחד גורמות לרוב הספריות המוכנות להיכשל. יישום Canvas מקורי מספק גלילה חלקה פי 3 בהשוואה ל-WebView במכשירים ישנים יותר.
מדוע ספריות סטנדרטיות לא מתאימות לתרשים נרות
fl_chart — אין תמיכה בתרש�י נרות מובנית. אפשר לפרוץ את זה עם BarChartRod מותאם אישית, אבל זו פריצה ללא זום וביצועים מקובלים.
syncfusion_flutter_charts — כולל SfCartesianChart עם CandleSeries, תומך בזום/גלילה ועדכוני נתונים. זו אפשרות עובדת עבור רוב המשימות. אבל הרישיון המסחרי ($995 לשנה למפתח) הופך אותה ללא מעשית עבור סטארטאפים. עבור צוות של שני מפתחים, הרישיון יעלה $1,990 לשנה.
TradingView Lightweight Charts ב-WebView — הגישה הנפוצה ביותר באפליקציות production של בורסות גדולות. הספרייה בנויה לממשק מסחר production, מותאמת לנפחי נתונים גדולים ותומכת בכל התכונות הנדרשות. תקורה מ-WebView קיימת אך זניחה במכשירים מודרניים. עוד: TradingView Lightweight Charts.
| גישה | לוח זמנים | ביצועים | רישיון | מתאים עבור |
|---|---|---|---|---|
| WebView + TradingView | 1-2 שבועות | 30fps במכשירים ישנים | חינם | השקה מהירה |
| Native CustomPainter | 3-5 שבועות | 60fps בכל המכשירים | ללא רישיון | תכונה מרכזית |
| Syncfusion Flutter Charts | 1-2 שבועות | 45fps | $995 לשנה | אבות טיפוס |
באיזו גישה לבחור: WebView או Native?
אם התרשים הוא אלמנט משני וזמן ההשקה קריטי, בחרו ב-WebView עם TradingView. אם התרשים הוא המסך המרכזי של האפליקציה, יישום מקורי מספק גלילה חלקה פי 3 במכשירים ישנים יותר. אנו נעזור לכם לבחור במהלך ייעוץ — ננתח את הדרישות שלכם ונציע את הפתרון האופטימלי.
| קריטריון | WebView + TradingView | Native CustomPainter |
|---|---|---|
| זמן ל-MVP | 1-2 שבועות | 3-5 שבועות |
| ביצועים | 30fps (מכשירים ישנים) | 60fps (כל המכשירים) |
| גמישות התאמה אישית | מוגבלת | מלאה |
| תלות ברשת | אין | אין |
כיצד ליישם עדכונים בזמן אמת ללא פיגור?
חיבור WebSocket לבורסה מספק נתוני tick. בקבלת tick חדש, אנו מעדכנים רק את הנר האחרון ללא בנייה מחדש של כל הרשימה:
void onTickReceived(Tick tick) {
if (_candles.isEmpty) return;
final last = _candles.last;
_candles[_candles.length - 1] = last.copyWith(
high: max(last.high, tick.price),
low: min(last.low, tick.price),
close: tick.price,
volume: last.volume + tick.volume,
);
if (tick.timestamp >= last.timestamp + timeframe.milliseconds) {
_candles.add(Candle.fromTick(tick));
if (_candles.length > maxCandlesInMemory) _candles.removeAt(0);
}
_chartController.notifyListeners();
}void onTickReceived(Tick tick) { if (_candles.isEmpty) return; final last = _candles.last; _candles[_candles.length - 1] = last.copyWith( high: max(last.high, tick.price), low: min(last.low, tick.price), close: tick.price, volume: last.volume + tick.volume, ); if (tick.timestamp >= last.timestamp + timeframe.milliseconds) { _candles.add(Candle.fromTick(tick)); if (_candles.length > maxCandlesInMemory) _candles.removeAt(0); } _chartController.notifyListeners(); } + ValueNotifier רק סביב ValueListenableBuilder — רק ה-Canvas מצויר מחדש, לא ה-AppBar או הפאנלים הצדדיים.
כיצד להבטיח זום חלק על 10,000 נרות?
האתגר המרכזי הוא לא לצייר מחדש את כל התרשים בכל זום. אנו משתמשים בווירטואליזציה: רק הנרות הנראים מוצגים, ובזום אנו מחשבים מחדש את הטווח הנראה. בנוסף, אנו מאחסנים מחירים במטמון ומתאימים נרות לפיקסל בהתאם לקנה המידה.
getVisibleRange(int totalCandles, double canvasWidth) {
final visibleCandlesCount = (canvasWidth / (candleWidth * scale)).floor();
final start = ((totalCandles - visibleCandlesCount) * offset / maxOffset).clamp(0, totalCandles - visibleCandlesCount);
return Range(start, start + visibleCandlesCount);
}גישה זו מבטיחה 60fps אפילו במכשירים עם Android 10 ו-iOS 12.
יישום מקורי על Flutter באמצעות CustomPainter
עבור אפליקציות שבהן התרשים הוא המסך הראשי, יישום מקורי מספק 60fps בכל מכשיר. רכיבים מרכזיים:
מבנה נתונים
class Candle {
final int timestamp;
final double open;
final double high;
final double low;
final double close;
final double volume;
bool get isBullish => close >= open;
} צינור רינדור
CustomPaint עם getVisibleRange(int totalCandles, double canvasWidth) { final visibleCandlesCount = (canvasWidth / (candleWidth * scale)).floor(); final start = ((totalCandles - visibleCandlesCount) * offset / maxOffset).clamp(0, totalCandles - visibleCandlesCount); return Range(start, start + visibleCandlesCount); } — נקרא בכל שינוי נתונים. כדי להימנע מציור מחדש של כל התרשים בנר חדש:
class CandlestickPainter extends CustomPainter {
final List<Candle> candles;
final CandleChartController controller;
@override
void paint(Canvas canvas, Size size) {
final visibleRange = controller.getVisibleRange(candles.length, size.width);
final visibleCandles = candles.sublist(visibleRange.start, visibleRange.end);
final priceRange = _calculatePriceRange(visibleCandles);
final candleWidth = size.width / visibleCandles.length * controller.scale;
for (var i = 0; i < visibleCandles.length; i++) {
_drawCandle(canvas, visibleCandles[i], i, candleWidth, size.height, priceRange);
}
if (controller.crosshairVisible) {
_drawCrosshair(canvas, controller.crosshairPosition, size);
}
}
// ... _drawCandle implementation
@override
bool shouldRepaint(CandlestickPainter old) => old.candles != candles || old.controller != controller;
} טיפול במחוות: גלילה וצביטה לזום
class Candle { final int timestamp; final double open; final double high; final double low; final double close; final double volume; bool get isBullish => close >= open; } עם CustomPainter לצביטה לזום, shouldRepaint לגלילה לאורך ציר הזמן. הגבלת קנה מידה — חשוב: בלעדיה, משתמשים יוכלו לזום לנר בודד שממלא את כל המסך.
GestureDetector(
onScaleUpdate: (details) {
setState(() {
controller.scale = (controller.scale * details.scale).clamp(0.5, 10.0);
controller.offset += details.focalPointDelta.dx;
});
},
child: CustomPaint(
painter: CandlestickPainter(candles, controller),
),
) קו חיתוך בלחיצה ארוכה
GestureDetector(
onLongPressStart: (details) {
controller.crosshairVisible = true;
controller.crosshairPosition = details.localPosition;
final candleIndex = controller.positionToIndex(details.localPosition.dx, candles.length);
if (candleIndex < candles.length) _showCandleInfo(candles[candleIndex]);
},
onLongPressMoveUpdate: (details) {
controller.crosshairPosition = details.localPosition;
},
onLongPressEnd: (_) => controller.crosshairVisible = false,
) מעבר בין מסגרות זמן
כפתורים 1m / 5m / 15m / 1h / 4h / 1D / 1W. במעבר, אנו מבקשים סט חדש של נרות ומפעילים אנימציית crossfade כדי למנוע הבהובים. השתמשו ב-class CandlestickPainter extends CustomPainter { final List<Candle> candles; final CandleChartController controller; @override void paint(Canvas canvas, Size size) { final visibleRange = controller.getVisibleRange(candles.length, size.width); final visibleCandles = candles.sublist(visibleRange.start, visibleRange.end); final priceRange = _calculatePriceRange(visibleCandles); final candleWidth = size.width / visibleCandles.length * controller.scale; for (var i = 0; i < visibleCandles.length; i++) { _drawCandle(canvas, visibleCandles[i], i, candleWidth, size.height, priceRange); } if (controller.crosshairVisible) { _drawCrosshair(canvas, controller.crosshairPosition, size); } } // ... _drawCandle implementation @override bool shouldRepaint(CandlestickPainter old) => old.candles != candles || old.controller != controller; } עם מסגרת הזמן כמפתח.
אינדיקטורים טכניים (אופציונלי)
MA, EMA, Bollinger Bands, RSI — כל אחד מצויר כשכבה נוספת ב-GestureDetector. RSI ו-MACD ממוקמים על onScaleStart/Update תחתון נפרד עם גובה קבוע וציר זמן משותף עם התרשים הראשי.
תהליך עבודה לתרשים נרות
- אנליזה — הגדרת דרישות ביצועים, רשימת אינדיקטורים, מסגרות זמן.
- עיצוב — בחירת גישה (WebView או native), עיצוב ארכיטקטורת רינדור.
- יישום — כתיבת קוד, חיבור WebSocket, יישום מחוות.
- בדיקות — בדיקה על יותר מ-10 מכשירים אמיתיים, כולל דגמים ישנים.
- פריסה — פרסום ל-App Store / Google Play, מסירת קוד מקור.
מה כלול
- יישום CustomPainter לתרשים נרות או אינטגרציה של TradingView WebView
- מחוות גלילה/זום עם מגבלות מתאימות
- קו חיתוך עם פאנל מידע
- עדכונים בזמן אמת באמצעות WebSocket
- מעבר בין מסגרות זמן
- אינדיקטורים טכניים (MA, EMA, Bollinger Bands — בתיאום)
- פסי נפח בפאנל התחתון
לוח זמנים
WebView + TradingView Lightweight Charts: 1–2 שבועות (כולל אינטגרציית WebSocket). Native CustomPainter עם פונקציונליות מלאה של תרשים מסחר: 3–5 שבועות. העלות מחושבת באופן אישי. קבלו ייעוץ — צרו קשר כדי להעריך את הפרויקט שלכם.







