First nonsense: Before coming to the company, the project is done by the outsourcing company, interview at the beginning, no contact with too much time figure K line this piece, feel very difficult, I can handle not! But after a period of time, found that the previous do is a piece of crushed, but this goods is the main function ah, sooner or later their own fencing, pain determined, open, this would like to use open source control, but want to implement it yourself: then the article
Start with Surfaceview, but this goods in the upper and lower sliding will appear black edge, this problem I was also tangled for a long time, think of products will definitely play back, hit back also disgraced, forget not much things to use the view bar, nonsense really TM many, start it.
1, create the project (Android Studio) 2, yes, first, save your time: 3, the activity is set to horizontal screen, do not set does not matter, I think the horizontal screen of a good view
android: screenorientation="Landscape"
4, the two base class time-sharing graph point data and the data of each point of the candlestick, the notes are very clear
/** * Ticks required data field */public class Cminute {//Time public long time;//latest price public double price;//volume public long count;//average public Double average;//change public double rate;//Price public double money;p ublic long GetTime () {return time;} Public String Gettimestr () {SimpleDateFormat SDF = new SimpleDateFormat ("hh:mm"); try {return Sdf.format (new Date (TIME * 10 00));} catch (Exception e) {return "--:--";}}}
public class Stickdata implements parcelable { //time private long; Open private double open; Close private double close; Highest private double high; Minimum private double low; Volume private Long Count; Closed yesterday private double last; Percentage change private double rate; Price private double money; Calculates the zero value of the moving average to save the private double mavalue; 5 segment EMA private double sma5; 10 segment EMA private double sma10; 20 segment EMA private double sma20; Volume 5-segment moving average private double countSma5; Volume 10-segment moving average private double countSma10; Three parameters of the MACD private double dif;//line private double dea;//line private double macd;//columnar //KDJ three lines private double K; private double D; Private Double J; A private double RSV is required to calculate K ; Candlestick funds //Super Large single equity private double sp; Large single equity private double BG; Medium single Equity private double MD; Small single equity private double sm;
5. Steps for drawing
@Override protected void OnDraw (canvas canvas) { super.ondraw (canvas); 1, initialize the required data initwidthandheight (); 2, Draw grid DrawGrid (canvas); 3, Draw the line (the price line of the time line, the average line or the average of the candlestick) drawlines (canvas); if (linetype! = Type_fenshi) { //4, if it is a candle, draw another candlestick chart drawcandles (canvas); } 5, write the XY axis text (written early will be overwritten) drawText (canvas); 6, draw the indicator to be displayed switch (indextype) {case Index_vol: drawvol (canvas); break; Case INDEX_ZJ: drawzj (canvas); break; Case INDEX_MACD: drawmacd (canvas); break; Case INDEX_KDJ: drawkdj (canvas); break; } }
6, drawing implementation
In fact, the time-sharing line is the drawing line, candlestick Chart is also a line, but more than draw a rectangle, if analyzed into such words, simply learn more, then I will teach you to draw a line to draw a rectangle ....
Here omit 10000 words, OK said finished (actually is needless to say, just then two methods Drawline,drawrect), next we focus on the calculation of position:
We actually get the data, it is not possible to directly display the coordinate system, because it may be very large and small, first of all, say the y-axis bar
Y-Axis
y = height-input * Height/(max-min);
Y: Calculation results
Height:view height
Max: A set of data maximum values displayed
Min: Minimum value in a set of data displayed
When displaying ticks, the maximum and minimum values need to be fetched at the average price and
Maximum and minimum values can be removed from the highest and lowest when the candlestick is displayed
X-Axis
x = Width/drawcount * i;
X: Calculation results
Width:view width
Drawcount: Total number of impressions
such as Shanghai index, the morning of the opening of the afternoon 2 hours, because the time-sharing chart is not units per minute, then Drawcount is the 60*4,k line is required to calculate the width of drawcount, in my Code, candlesticks and candlesticks after the white space ratio is 10:2
7, indicators
Time-sharing chart of the funds due to the use of other interfaces, the demo will not show, you can refer to the indicator of the money trend of the candlestick (on a few lines, simple bar)
MACD, KDJ, VOL5, VOL10, VOL20 These indicators can Baidu, I do not much, the calculation method are the same, I directly paste code, K line of four indicators, in addition to funds, other indicators can directly through the bar of the high and low open the calculation of yesterday,
public class Indexparseutil {//EMA span (SMA5,SMA10,SMA20), note that when modifying this value, you need to increase the SMA field inside the Stickdata, modify the Initsma method of this class, otherwise it will not take effect public s tatic Final int start_sma5 = 5; public static final int start_sma10 = 10; public static final int start_sma20 = 20; 26: When calculating MACD, 26 segment CLOSE average dif= (EMA (close,12)-EMA (close,26)) public static final int start_dif = 26; 35: When calculating MACD, 35 section begins to take the previous 9th DIF value Dea:=ema (dif,9) public static final int start_dea = 35; 12: Calculate K-value public static final int start_k = 12; 15: Calculate DJ public static final int start_dj = 15; 9: Calculate RSV public static final int start_rev = 9; public static final int[] SMA = {start_sma5,start_sma10, start_sma20}; /** * Calculate MACD * @param list */public static void INITMACD (List<stickdata> list) {if (list = = N ULL) return; 1 Calculates all DIF for (int i = 0; i < list.size (); i++) {if (i + start_dif <= list.size ()) { List.get (i + start_dif-1). Setdif (Getclosesma (List.sUblist (i + start_dif-12, i + start_dif))-Getclosesma (list.sublist (i + start_dif-26, i + start_dif))); }}//2 calculates all the DEA for (int i = 0; i < list.size (); i++) {if (i + Start_dea <= list.siz E ()) {List.get (i + start_dea-1). Setdea (Getdifsma (list.sublist (i + start_dea-9, i + Start_dea)); 3 Calculate MACD list.get (i + start_dea-1). SETMACD (2d * (List.get (i + start_dea-1). Getdif ()-list. Get (i + start_dea-1). Getdea ())); }}}/** * Calculate KDJ * @param list */public static void Initkdj (List<stickdata> list) { if (list = = null) return; 1 Calculates all REV for (int i = 0; i < list.size (); i++) {if (i + Start_rev <= list.size ()) { The 9th day begins with the calculation of RSV stickdata data = list.get (i + start_rev-1); double[] Maxandmin = getmaxandmin (List.sublist (i, i + Start_rev)); List.get (i + STart_rev-1). SETRSV ((Data.getclose ()-maxandmin[1])/(Maxandmin[0]-maxandmin[1]) * 100); }}//2 calculates all K for (int i = 0; i < list.size (); i++) {if (i + Start_k <= list.size ()) {List.get (i + start_k-1). SETK (Getrsvsma (list.sublist (i + start_k-3, i + start_k)); }}//3 calculates all DJ for (int i = 0; i < list.size (); i++) {if (i + START_DJ <= list.size ( ) {Stickdata data = List.get (i + start_dj-1); List.get (i + start_dj-1). SETD (Getksma (list.sublist (i + start_dj-3, i + START_DJ))); List.get (i + start_dj-1). SETJ (3 * DATA.GETK ()-2 * DATA.GETD ()); }}}/** * Calculates the moving averages for all data in the list and assigns them to the inside * * @param list K line data */public static void Initsma (list<stickdata> List) {if (list = = null) return; for (int i = 0; i < list.size (), i++) {for (int j:sma) { if (i + J <= list.size ()) {//5th day start Calculation 5th ema if (j = = Start_sma5) { The amount of SMA5 list.get (i + j-1). SETCOUNTSMA5 (Getcountsma (List.sublist (i, i + j))); K-SMA5 List.get (i + j-1). SETSMA5 (Getclosesma (List.sublist (i, i + j))); } else//10th day start Calculation 10th EMA if (j = = start_sma10) { The amount of SMA10 list.get (i + j-1). SETCOUNTSMA10 (Getcountsma (List.sublist (i, i + j))) ; K-SMA10 List.get (i + j-1). SETSMA10 (Getclosesma (List.sublist (i, i + j))); }else//20th Day start Calculation 20th EMA if (j = = start_sma20) { K-SMA20 List.get (i + j-1). SETSMA20 (Getclosesma (List.sublist (i, i + j))); }}}}}/** * When calculating KDJ, take 9th the highest lowest value * @param datas * @return */priv Ate static double[] Getmaxandmin (list<stickdata> datas) {if (datas = null | | datas.size () = = 0) r Eturn new double[]{0, 0}; Double max = datas.get (0). Gethigh (); Double min = datas.get (0). Getlow (); for (Stickdata data:datas) {max = max > Data.gethigh ()? Max:data.getHigh (); min = min < Data.getlow ()? Min:data.getLow (); } return new Double[]{max, Min}; /** * Candlestick Amount Calculated Moving average * @param datas * @return * * * private static double Getcountsma (LIST<STICKDATA&G T Datas) {if (datas = = null) return-1; Double sum = 0; for (Stickdata data:datas) {sum + = Data.getcount (); } return Numberutil.doubledecimal (Sum/datas.size ()); /** * Candlestick Price calculates the moving average price * @param datas * @retURN */private static double Getclosesma (list<stickdata> datas) {if (datas = = null) return-1; Double sum = 0; for (Stickdata data:datas) {sum + = Data.getclose (); } return Numberutil.doubledecimal (Sum/datas.size ()); The moving average of/** * Candlestick dif * @param datas * @return * */private static double Getdifsma (List<stickdata> ; Datas) {if (datas = = null) return-1; Double sum = 0; for (Stickdata data:datas) {sum + = Data.getdif (); } return Numberutil.doubledecimal (Sum/datas.size ()); /** * 3rd RSV moving average, k value * @param datas * @return * */private static double Getrsvsma (list<stickdata > Datas) {if (datas = = null) return-1; Double sum = 0; for (Stickdata data:datas) {sum + = DATA.GETRSV (); } return Numberutil.doubledecimal (Sum/datas.size ()); }/** * 3rd K moving average, i.e. D value * @paramDatas * @return * * * private static double Getksma (list<stickdata> datas) {if (datas = = null) retur n-1; Double sum = 0; for (Stickdata data:datas) {sum + = DATA.GETK (); } return Numberutil.doubledecimal (Sum/datas.size ()); }}
8, swipe and zoom
This is simple, the time-sharing line does not support sliding and scaling, only the K-line needs: Because the K-line data is more, the default one screen display is not complete, so need to directly swipe, zoom, may be want to see the trend of it (I guess)!
The method is to swipe and zoom directly through gestures,
Then: I got 600 data, showing 500-600, sliding, as long as the 100 move forward on it, such as sliding to 450-550; Zoom, it is even easier, if a screen display 100, then you set a screen display 80 or 120 is scaled, is not so easy!
9, Cross Line
Well, the picture is over, need to cross the line out to walk two steps!
Let's take a look at my layout.
<relativelayout android:layout_width= "0DP" android:layout_height= "match_parent" android:layout_ weight= "686" > <eat.arvin.com.mychart.view.fenshiview android:id= "@+id/cff_fenshiview" Android:layout_width= "Match_parent" android:layout_height= "match_parent"/> < Eat.arvin.com.mychart.view.CrossView android:id= "@+id/cff_cross" android:layout_width= "Match_parent" android:layout_height= "match_parent" android:visibility= "Gone"/> </RelativeLayout>
Understand, the two goods are separate, I just fenshiview inside capture Click event, and then determine whether the point has data, and some words in the Crossview draw line, to draw two lines, Europe
@Override Public Boolean onsingletapup (Final motionevent e) { //delay 300 milliseconds display, double-click to make time for new Handler (). Postdelayed (New Runnable () { @Override public void Run () { //Click Show Cross-line if (crossview! = null) { if ( Crossview.getvisibility () = = View.gone) { oncrossmove (E.getx (), e.gety ());}}} , Double_ Tap_delay); Return Super.onsingletapup (e); }
Crossview
public class Crossview extends View {/** * cross-line movement monitoring */public interface Onmovelistener {/** * Cross-line movement (callback to the location of data storage, to determine whether to draw a line, then call this interface to draw a line method) * * @param x x coordinate * @param y y-axis coordinates */void Oncrossmove (float x, float y); /** * cross-line vanishing callback */void Ondismiss (); } private Crossbean Bean; Gesture control private Gesturedetector gesturedetector; Private Onmovelistener Onmovelistener; Public Crossview (context context, AttributeSet Attrs) {Super (context, attrs); Gesturedetector = new Gesturedetector (GetContext (), new Gesturedetector.simpleongesturelistener () {@Override public boolean onsingletapup (Motionevent e) {//Click Hide Crosshairs setvisibility (GONE); if (Onmovelistener! = null) Onmovelistener.ondismiss (); Return Super.onsingletapup (e); } @Override Public Boolean Onscroll (motionevent E1, motionevent E2, float Distancex, float distancey) {//sliding, notification to interface if (Onmovelistener! = null) {Onmovelistener.oncrossmove (E2.getx (), e2.gety ()); } return Super.onscroll (E1, E2, Distancex, Distancey); } }); } @Override public boolean ontouchevent (Motionevent event) {if (gesturedetector! = null) gestured Etector.ontouchevent (event); return true; } @Override protected void OnDraw (canvas canvas) {super.ondraw (canvas); Drawcrossline (canvas); }/** *//Cross line according to X, y * * @param canvas */private void Drawcrossline (canvas canvas) {//When this point is not there Without drawing if (bean.x < 0 | | BEAN.Y < 0) return; Boolean Isjunxian = bean.y2 >= 0; Paint p = new paint (); P.setantialias (TRUE); P.setcolor (Colorutil.color_cross_line); P.setstrokewidth (2f); P.setstylE (Paint.Style.FILL); Horizontal canvas.drawline (0, BEAN.Y, getwidth (), BEAN.Y, p); Vertical line Canvas.drawline (bean.x, 0, bean.x, GetHeight (), p); if (Isjunxian) {///EMA Draw dot//Draw cross line and EMA Price line intersection of round canvas.drawcircle (bean.x, BEAN.Y, p); P.setcolor (Colorutil.color_sma_line); Canvas.drawcircle (bean.x, Bean.y2, p); } p.setcolor (Color.Black); P.settextsize (32f); 1, write the price (vertical bar on the left, the price needs to write to the right) Drawpricetextwithrect (canvas, bean.x, BEAN.Y, Bean.price, p); 2, Write Time Drawtimetextwithrect (canvas, bean.x, Bean.gettime (), p); 3, write the text of the indicator drawindextexts (canvas); P.reset (); } private void drawindextexts (canvas canvas) {if (Bean.indextext = = NULL | | bean.indexcolor = = NULL) return; Paint p = new paint (); P.setantialias (TRUE); P.settextsize (26f); float x = 0; Float y = getheight () * (Chartconstant.main_scale + ChaRtconstant.time_scale) + 25; for (int i = 0;i < Bean.indexText.length; i++) {P.setcolor (bean.indexcolor[i]); Canvas.drawtext (Bean.indextext[i], x, Y, p); x + = Lineutil.gettextwidth (P, bean.indextext[i]) + 30; }}/** * Write time, and box */private void Drawtimetextwithrect (canvas canvas, float x, String, Paint p) { P.settextalign (Paint.Align.LEFT); float textWidth = Lineutil.gettextwidth (p, time) + 20; Float y = getheight () * Chartconstant.main_scale; Paint RP = new paint (); Rp.setcolor (Color.White); Rp.setstyle (Paint.Style.FILL); Rp.setstrokewidth (2f); 1, first draw white bottom float StartX = X-TEXTWIDTH/2; float EndX = x + textwidth/2; if (StartX < 0) {StartX = 2f; EndX = StartX + textWidth; } if (EndX > GetWidth ()) {EndX = GetWidth ()-2; StartX = Endx-textwidth; } Canvas.drawrECT (StartX, y + 2, EndX, Y + +, RP); Rp.setcolor (Color.Black); Rp.setstyle (Paint.Style.STROKE); 2, then draw black box Canvas.drawrect (StartX, y + 2, EndX, Y + +, RP); 3, Write text Canvas.drawtext (time, StartX + ten, y + 27.5f, p); }/** * Write text and bring a background to the text, which is equivalent to drawing a rect behind the text */private void Drawpricetextwithrect (canvas canvas, float x, float y, String text, Paint p) {Float textWidth = lineutil.gettextwidth (p, text) + 10; Paint RP = new paint (); Rp.setcolor (Color.White); Rp.setstyle (Paint.Style.FILL); Rp.setstrokewidth (2f); float starty = y-15f; float EndY = y + 15f; if (Starty < 0) {starty = 0f; EndY = Starty + 30f; } else if (EndY > GetHeight ()) {EndY = GetHeight (); Starty = endy-30f; if (x <) {//x axis is on the left, the box is drawn on the right//1, draw the white bottom first canvas.drawrect (GetWidth ()-TextWidth , Starty, GetWidth (), ENdY, RP); Rp.setcolor (Color.Black); Rp.setstyle (Paint.Style.STROKE); 2, then draw the black box Canvas.drawrect (getwidth ()-TextWidth, Starty, GetWidth (), EndY, RP); P.settextalign (Paint.Align.RIGHT); Canvas.drawtext (text, getwidth ()-5f, endY-3, p); } else {//x axis on the right, change frame to left canvas.drawrect (0, Starty, TextWidth, EndY, RP); Rp.setcolor (Color.Black); Rp.setstyle (Paint.Style.STROKE); Canvas.drawrect (0, Starty, TextWidth, EndY, RP); P.settextalign (Paint.Align.LEFT); Canvas.drawtext (text, 5f, endY-3, p); }}/** * Draw the cross line of the tick line */public void DrawLine (Crossbean bean) {This.bean = bean; Postinvalidate (); }/** * Set mobile listening * * @param onmovelistener */public void Setonmovelistener (Onmovelistener onmoveliste NER) {this.onmovelistener = Onmovelistener; }}
10, some optimization
Time-sharing line: The server only needs to return the changed points, do not need to return all, these missing points directly using the previous minute completion
Candlestick: Because the K-line data is huge, so if the server calculates a good indicator back to the client, it will make the data volume *1.5 almost, so these indicators are still in the local calculation, only need to be shown, and do not need to repeat the calculation
11,github
Https://github.com/xuzhou4520/AChart1
Teach you how to draw androidk line time-sharing chart and index