├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── ibm.json │ ├── java │ └── com │ │ └── github │ │ └── tifezh │ │ └── kchart │ │ ├── DataHelper.java │ │ ├── DataRequest.java │ │ ├── ExampleActivity.java │ │ ├── LoadMoreActivity.java │ │ ├── MainActivity.java │ │ ├── MinuteChartActivity.java │ │ └── chart │ │ ├── KChartAdapter.java │ │ ├── KLineEntity.java │ │ └── MinuteLineEntity.java │ └── res │ ├── color │ └── tab_light_text_color_selector.xml │ ├── layout │ ├── activity_example.xml │ ├── activity_example_light.xml │ ├── activity_main.xml │ └── activity_minute_chart.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── demo.gif ├── menu.png ├── style1.png └── style2.png ├── kchartlib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── tifezh │ │ └── kchartlib │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── tifezh │ │ │ └── kchartlib │ │ │ ├── chart │ │ │ ├── BaseKChartAdapter.java │ │ │ ├── BaseKChartView.java │ │ │ ├── KChartTabView.java │ │ │ ├── KChartView.java │ │ │ ├── MinuteChartView.java │ │ │ ├── ScrollAndScaleView.java │ │ │ ├── TabView.java │ │ │ ├── base │ │ │ │ ├── IAdapter.java │ │ │ │ ├── IChartDraw.java │ │ │ │ ├── IDateTimeFormatter.java │ │ │ │ └── IValueFormatter.java │ │ │ ├── draw │ │ │ │ ├── BOLLDraw.java │ │ │ │ ├── KDJDraw.java │ │ │ │ ├── MACDDraw.java │ │ │ │ ├── MainDraw.java │ │ │ │ ├── RSIDraw.java │ │ │ │ └── VolumeDraw.java │ │ │ ├── entity │ │ │ │ ├── IBOLL.java │ │ │ │ ├── ICandle.java │ │ │ │ ├── IKDJ.java │ │ │ │ ├── IKLine.java │ │ │ │ ├── IMACD.java │ │ │ │ ├── IMinuteLine.java │ │ │ │ ├── IRSI.java │ │ │ │ └── IVolume.java │ │ │ └── formatter │ │ │ │ ├── BigValueFormatter.java │ │ │ │ ├── DateFormatter.java │ │ │ │ ├── TimeFormatter.java │ │ │ │ └── ValueFormatter.java │ │ │ └── utils │ │ │ ├── DateUtil.java │ │ │ └── ViewUtil.java │ └── res │ │ ├── color │ │ └── tab_text_color_selector.xml │ │ ├── layout │ │ ├── item_tab.xml │ │ └── layout_tab.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── github │ └── tifezh │ └── kchartlib │ └── ExampleUnitTest.java └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | 39 | # Keystore files 40 | *.jks 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KChartView 2 | KChart for Android ;股票k线图 3 | 4 | 效果预览 5 | ------- 6 | * 支持macd,kdj,rsi,boll多种指标切换,支持横竖屏切换,支持长按,缩放,滑动,fling事件等。 7 |
8 | 9 |
10 | 11 | * 支持自定义样式 12 |
13 |         14 | 15 |
16 | 17 | #### 配置及使用 18 | 19 | ##### xml简单配置 20 | ```xml 21 | 25 | 26 | ``` 27 | 28 | 29 | ##### xml中配置示例 30 | 31 | ```xml 32 | 69 | 70 | ``` 71 | 72 | ##### 数据实体继承IKLine类 获取各个指标的值 73 | 74 | ```java 75 | public class KLineEntity implements KLine { 76 | 77 | public String getDatetime() { 78 | return Date; 79 | } 80 | 81 | public float getOpenPrice() { 82 | return Open; 83 | } 84 | 85 | public float getHighPrice() { 86 | return High; 87 | } 88 | 89 | public float getLowPrice() { 90 | return Low; 91 | } 92 | 93 | public float getClosePrice() { 94 | return Close; 95 | } 96 | 97 | public float getMA5Price() { 98 | return MA5Price; 99 | } 100 | 101 | public float getMA10Price() { 102 | return MA10Price; 103 | } 104 | 105 | public float getMA20Price() { 106 | return MA20Price; 107 | } 108 | 109 | public float getDea() { 110 | return dea; 111 | } 112 | 113 | public float getDif() { 114 | return dif; 115 | } 116 | 117 | public float getMacd() { 118 | return macd; 119 | } 120 | 121 | public float getK() { 122 | return k; 123 | } 124 | 125 | public float getD() { 126 | return d; 127 | } 128 | 129 | public float getJ() { 130 | return j; 131 | } 132 | 133 | public float getRsi1() { 134 | return rsi1; 135 | } 136 | 137 | public float getRsi2() { 138 | return rsi2; 139 | } 140 | 141 | public float getRsi3() { 142 | return rsi3; 143 | } 144 | 145 | public float getUp() { 146 | return up; 147 | } 148 | 149 | public float getMb() { 150 | return mb; 151 | } 152 | 153 | public float getDn() { 154 | return dn; 155 | } 156 | 157 | public String Date; 158 | public float Open; 159 | public float High; 160 | public float Low; 161 | public float Close; 162 | public float Volume; 163 | public float MA5Price; 164 | public float MA10Price; 165 | public float MA20Price; 166 | public float dea; 167 | public float dif; 168 | public float macd; 169 | public float k; 170 | public float d; 171 | public float j; 172 | public float rsi1; 173 | public float rsi2; 174 | public float rsi3; 175 | public float up; 176 | public float mb; 177 | public float dn; 178 | } 179 | ``` 180 | 181 | ##### 定义数据适配器 182 | 183 | ```java 184 | public class KChartAdapter extends BaseKChartAdapter { 185 | private List datas = new ArrayList<>(); 186 | public KChartAdapter() { 187 | } 188 | 189 | @Override 190 | public int getCount() { 191 | return datas.size(); 192 | } 193 | 194 | @Override 195 | public Object getItem(int position) { 196 | return datas.get(position); 197 | } 198 | 199 | @Override 200 | public Date getDate(int position) { 201 | try { 202 | String s = datas.get(position).Date; 203 | String[] split = s.split("/"); 204 | Date date = new Date(); 205 | date.setYear(Integer.parseInt(split[0]) - 1900); 206 | date.setMonth(Integer.parseInt(split[1]) - 1); 207 | date.setDate(Integer.parseInt(split[2])); 208 | return date; 209 | } catch (Exception e) { 210 | e.printStackTrace(); 211 | } 212 | return null; 213 | } 214 | 215 | /** 216 | * 向头部添加数据 217 | */ 218 | public void addHeaderData(List data) { 219 | if (data != null && !data.isEmpty()) { 220 | datas.addAll(data); 221 | notifyDataSetChanged(); 222 | } 223 | } 224 | 225 | /** 226 | * 向尾部添加数据 227 | */ 228 | public void addFooterData(List data) { 229 | if (data != null && !data.isEmpty()) { 230 | datas.addAll(0, data); 231 | notifyDataSetChanged(); 232 | } 233 | } 234 | 235 | /** 236 | * 改变某个点的值 237 | * @param position 索引值 238 | */ 239 | public void changeItem(int position,KLineEntity data) 240 | { 241 | datas.set(position,data); 242 | notifyDataSetChanged(); 243 | } 244 | } 245 | ``` 246 | ##### 加载数据 247 | 248 | ```java 249 | mKChartView.showLoading(); 250 | new Thread(new Runnable() { 251 | @Override 252 | public void run() { 253 | final List data = DataRequest.getALL(ExampleActivity.this); 254 | runOnUiThread(new Runnable() { 255 | @Override 256 | public void run() { 257 |                //追加数据 258 |                mAdapter.addFooterData(data); 259 |                //开始动画 260 |                mKChartView.startAnimation(); 261 |                //刷新完成 262 |                mKChartView.refreshEnd(); 263 | } 264 | }); 265 | } 266 | }).start(); 267 | ``` 268 | ##### 更多使用方法请下载demo查看 269 | 270 | #### 扩展性 271 | 272 | ##### 添加其他指数 以添加kdj指标为例 273 | * 定义kdj中的值 274 | ```java 275 | public interface IKDJ { 276 | 277 | /** 278 | * K值 279 | */ 280 | float getK(); 281 | 282 | /** 283 | * D值 284 | */ 285 | float getD(); 286 | 287 | /** 288 | * J值 289 | */ 290 | float getJ(); 291 | 292 | } 293 | ``` 294 | * 实现IChartDraw接口 295 | ```java 296 | public class KDJDraw implements IChartDraw { 297 | 298 | private Paint mKPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 299 | private Paint mDPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 300 | private Paint mJPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 301 | 302 | public KDJDraw(BaseKChartView view) { 303 | } 304 | 305 | @Override 306 | public void drawTranslated(@Nullable IKDJ lastPoint, @NonNull IKDJ curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKChartView view, int position) { 307 | view.drawChildLine(canvas, mKPaint, lastX, lastPoint.getK(), curX, curPoint.getK()); 308 | view.drawChildLine(canvas, mDPaint, lastX, lastPoint.getD(), curX, curPoint.getD()); 309 | view.drawChildLine(canvas, mJPaint, lastX, lastPoint.getJ(), curX, curPoint.getJ()); 310 | } 311 | 312 | @Override 313 | public void drawText(@NonNull Canvas canvas, @NonNull BaseKChartView view, int position, float x, float y) { 314 | String text = ""; 315 | IKDJ point = (IKDJ) view.getItem(position); 316 | text = "K:" + view.formatValue(point.getK()) + " "; 317 | canvas.drawText(text, x, y, mKPaint); 318 | x += mKPaint.measureText(text); 319 | text = "D:" + view.formatValue(point.getD()) + " "; 320 | canvas.drawText(text, x, y, mDPaint); 321 | x += mDPaint.measureText(text); 322 | text = "J:" + view.formatValue(point.getJ()) + " "; 323 | canvas.drawText(text, x, y, mJPaint); 324 | } 325 | 326 | @Override 327 | public float getMaxValue(IKDJ point) { 328 | return Math.max(point.getK(), Math.max(point.getD(), point.getJ())); 329 | } 330 | 331 | @Override 332 | public float getMinValue(IKDJ point) { 333 | return Math.min(point.getK(), Math.min(point.getD(), point.getJ())); 334 | } 335 | 336 | /** 337 | * 设置K颜色 338 | */ 339 | public void setKColor(int color) { 340 | mKPaint.setColor(color); 341 | } 342 | 343 | /** 344 | * 设置D颜色 345 | */ 346 | public void setDColor(int color) { 347 | mDPaint.setColor(color); 348 | } 349 | 350 | /** 351 | * 设置J颜色 352 | */ 353 | public void setJColor(int color) { 354 | mJPaint.setColor(color); 355 | } 356 | 357 | /** 358 | * 设置曲线宽度 359 | */ 360 | public void setLineWidth(float width) 361 | { 362 | mKPaint.setStrokeWidth(width); 363 | mDPaint.setStrokeWidth(width); 364 | mJPaint.setStrokeWidth(width); 365 | } 366 | 367 | /** 368 | * 设置文字大小 369 | */ 370 | public void setTextSize(float textSize) 371 | { 372 | mKPaint.setTextSize(textSize); 373 | mDPaint.setTextSize(textSize); 374 | mJPaint.setTextSize(textSize); 375 | } 376 | } 377 | ``` 378 | * 添加kdj指标 379 | ```java 380 | mKChartView.addChildDraw("KDJ", mKDJDraw); 381 | ``` 382 | ##### 自定义画线,画柱体 以macd为例 383 | * 重写 drawTranslated(在此方法中绘画的会滑动和缩放) 方法 ,如下所示: 384 | ```java 385 | @Override 386 | public void drawTranslated(@Nullable IMACD lastPoint, @NonNull IMACD curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKChartView view, int position) { 387 | drawMACD(canvas, view, curX, curPoint.getMacd()); 388 | view.drawChildLine(canvas, mDIFPaint, lastX, lastPoint.getDea(), curX, curPoint.getDea()); 389 | view.drawChildLine(canvas, mDEAPaint, lastX, lastPoint.getDif(), curX, curPoint.getDif()); 390 | } 391 | /** 392 | * 画macd 393 | * @param canvas 394 | * @param x 395 | * @param macd 396 | */ 397 | private void drawMACD(Canvas canvas, BaseKChartView view, float x, float macd) { 398 | float macdy = view.getChildY(macd); 399 | float r = mMACDWidth / 2; 400 | float zeroy=view.getChildY(0); 401 | if (macd > 0) { 402 | canvas.drawRect(x - r, macdy, x + r, zeroy, mRedPaint); 403 | } else { 404 | canvas.drawRect(x - r, zeroy, x + r, macdy, mGreenPaint); 405 | } 406 | } 407 | ``` 408 | 409 | License 410 | ------- 411 | 412 | Copyright 2018 tifezh 413 | 414 | Licensed under the Apache License, Version 2.0 (the "License"); 415 | you may not use this file except in compliance with the License. 416 | You may obtain a copy of the License at 417 | 418 | http://www.apache.org/licenses/LICENSE-2.0 419 | 420 | Unless required by applicable law or agreed to in writing, software 421 | distributed under the License is distributed on an "AS IS" BASIS, 422 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 423 | See the License for the specific language governing permissions and 424 | limitations under the License. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion "27.0.1" 6 | defaultConfig { 7 | applicationId "com.github.tifezh.kchart" 8 | minSdkVersion 15 9 | targetSdkVersion 27 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | sourceSets { main { assets.srcDirs = ['src/main/assets', 'src/main/assets/'] } } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'com.android.support:appcompat-v7:27.0.1' 25 | implementation project(path: ':kchartlib') 26 | implementation 'com.jakewharton:butterknife:8.8.1' 27 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 28 | implementation 'com.google.code.gson:gson:2.8.0' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/DataHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart; 2 | 3 | import com.github.tifezh.kchart.chart.KLineEntity; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 数据辅助类 计算macd rsi等 9 | * Created by tifezh on 2016/11/26. 10 | */ 11 | 12 | public class DataHelper { 13 | 14 | /** 15 | * 计算RSI 16 | * 17 | * @param datas 18 | */ 19 | static void calculateRSI(List datas) { 20 | float rsi1 = 0; 21 | float rsi2 = 0; 22 | float rsi3 = 0; 23 | float rsi1ABSEma = 0; 24 | float rsi2ABSEma = 0; 25 | float rsi3ABSEma = 0; 26 | float rsi1MaxEma = 0; 27 | float rsi2MaxEma = 0; 28 | float rsi3MaxEma = 0; 29 | for (int i = 0; i < datas.size(); i++) { 30 | KLineEntity point = datas.get(i); 31 | final float closePrice = point.getClosePrice(); 32 | if (i == 0) { 33 | rsi1 = 0; 34 | rsi2 = 0; 35 | rsi3 = 0; 36 | rsi1ABSEma = 0; 37 | rsi2ABSEma = 0; 38 | rsi3ABSEma = 0; 39 | rsi1MaxEma = 0; 40 | rsi2MaxEma = 0; 41 | rsi3MaxEma = 0; 42 | } else { 43 | float Rmax = Math.max(0, closePrice - datas.get(i - 1).getClosePrice()); 44 | float RAbs = Math.abs(closePrice - datas.get(i - 1).getClosePrice()); 45 | rsi1MaxEma = (Rmax + (6f - 1) * rsi1MaxEma) / 6f; 46 | rsi1ABSEma = (RAbs + (6f - 1) * rsi1ABSEma) / 6f; 47 | 48 | rsi2MaxEma = (Rmax + (12f - 1) * rsi2MaxEma) / 12f; 49 | rsi2ABSEma = (RAbs + (12f - 1) * rsi2ABSEma) / 12f; 50 | 51 | rsi3MaxEma = (Rmax + (24f - 1) * rsi3MaxEma) / 24f; 52 | rsi3ABSEma = (RAbs + (24f - 1) * rsi3ABSEma) / 24f; 53 | 54 | rsi1 = (rsi1MaxEma / rsi1ABSEma) * 100; 55 | rsi2 = (rsi2MaxEma / rsi2ABSEma) * 100; 56 | rsi3 = (rsi3MaxEma / rsi3ABSEma) * 100; 57 | } 58 | point.rsi1 = rsi1; 59 | point.rsi2 = rsi2; 60 | point.rsi3 = rsi3; 61 | } 62 | } 63 | 64 | /** 65 | * 计算kdj 66 | * 67 | * @param datas 68 | */ 69 | static void calculateKDJ(List datas) { 70 | float k = 0; 71 | float d = 0; 72 | 73 | for (int i = 0; i < datas.size(); i++) { 74 | KLineEntity point = datas.get(i); 75 | final float closePrice = point.getClosePrice(); 76 | int startIndex = i - 8; 77 | if (startIndex < 0) { 78 | startIndex = 0; 79 | } 80 | float max9 = Float.MIN_VALUE; 81 | float min9 = Float.MAX_VALUE; 82 | for (int index = startIndex; index <= i; index++) { 83 | max9 = Math.max(max9, datas.get(index).getHighPrice()); 84 | min9 = Math.min(min9, datas.get(index).getLowPrice()); 85 | 86 | } 87 | float rsv = 100f * (closePrice - min9) / (max9 - min9); 88 | if (i == 0) { 89 | k = rsv; 90 | d = rsv; 91 | } else { 92 | k = (rsv + 2f * k) / 3f; 93 | d = (k + 2f * d) / 3f; 94 | } 95 | point.k = k; 96 | point.d = d; 97 | point.j = 3f * k - 2 * d; 98 | } 99 | 100 | } 101 | 102 | /** 103 | * 计算macd 104 | * 105 | * @param datas 106 | */ 107 | static void calculateMACD(List datas) { 108 | float ema12 = 0; 109 | float ema26 = 0; 110 | float dif = 0; 111 | float dea = 0; 112 | float macd = 0; 113 | 114 | for (int i = 0; i < datas.size(); i++) { 115 | KLineEntity point = datas.get(i); 116 | final float closePrice = point.getClosePrice(); 117 | if (i == 0) { 118 | ema12 = closePrice; 119 | ema26 = closePrice; 120 | } else { 121 | // EMA(12) = 前一日EMA(12) X 11/13 + 今日收盘价 X 2/13 122 | // EMA(26) = 前一日EMA(26) X 25/27 + 今日收盘价 X 2/27 123 | ema12 = ema12 * 11f / 13f + closePrice * 2f / 13f; 124 | ema26 = ema26 * 25f / 27f + closePrice * 2f / 27f; 125 | } 126 | // DIF = EMA(12) - EMA(26) 。 127 | // 今日DEA = (前一日DEA X 8/10 + 今日DIF X 2/10) 128 | // 用(DIF-DEA)*2即为MACD柱状图。 129 | dif = ema12 - ema26; 130 | dea = dea * 8f / 10f + dif * 2f / 10f; 131 | macd = (dif - dea) * 2f; 132 | point.dif = dif; 133 | point.dea = dea; 134 | point.macd = macd; 135 | } 136 | 137 | } 138 | 139 | /** 140 | * 计算 BOLL 需要在计算ma之后进行 141 | * 142 | * @param datas 143 | */ 144 | static void calculateBOLL(List datas) { 145 | for (int i = 0; i < datas.size(); i++) { 146 | KLineEntity point = datas.get(i); 147 | final float closePrice = point.getClosePrice(); 148 | if (i == 0) { 149 | point.mb = closePrice; 150 | point.up = Float.NaN; 151 | point.dn = Float.NaN; 152 | } else { 153 | int n = 20; 154 | if (i < 20) { 155 | n = i + 1; 156 | } 157 | float md = 0; 158 | for (int j = i - n + 1; j <= i; j++) { 159 | float c = datas.get(j).getClosePrice(); 160 | float m = point.getMA20Price(); 161 | float value = c - m; 162 | md += value * value; 163 | } 164 | md = md / (n - 1); 165 | md = (float) Math.sqrt(md); 166 | point.mb = point.getMA20Price(); 167 | point.up = point.mb + 2f * md; 168 | point.dn = point.mb - 2f * md; 169 | } 170 | } 171 | 172 | } 173 | 174 | /** 175 | * 计算ma 176 | * 177 | * @param datas 178 | */ 179 | static void calculateMA(List datas) { 180 | float ma5 = 0; 181 | float ma10 = 0; 182 | float ma20 = 0; 183 | 184 | for (int i = 0; i < datas.size(); i++) { 185 | KLineEntity point = datas.get(i); 186 | final float closePrice = point.getClosePrice(); 187 | 188 | ma5 += closePrice; 189 | ma10 += closePrice; 190 | ma20 += closePrice; 191 | if (i >= 5) { 192 | ma5 -= datas.get(i - 5).getClosePrice(); 193 | point.MA5Price = ma5 / 5f; 194 | } else { 195 | point.MA5Price = ma5 / (i + 1f); 196 | } 197 | if (i >= 10) { 198 | ma10 -= datas.get(i - 10).getClosePrice(); 199 | point.MA10Price = ma10 / 10f; 200 | } else { 201 | point.MA10Price = ma10 / (i + 1f); 202 | } 203 | if (i >= 20) { 204 | ma20 -= datas.get(i - 20).getClosePrice(); 205 | point.MA20Price = ma20 / 20f; 206 | } else { 207 | point.MA20Price = ma20 / (i + 1f); 208 | } 209 | } 210 | } 211 | 212 | /** 213 | * 计算MA BOLL RSI KDJ MACD 214 | * 215 | * @param datas 216 | */ 217 | static void calculate(List datas) { 218 | calculateMA(datas); 219 | calculateMACD(datas); 220 | calculateBOLL(datas); 221 | calculateRSI(datas); 222 | calculateKDJ(datas); 223 | calculateVolumeMA(datas); 224 | } 225 | 226 | private static void calculateVolumeMA(List entries) { 227 | float volumeMa5 = 0; 228 | float volumeMa10 = 0; 229 | 230 | for (int i = 0; i < entries.size(); i++) { 231 | KLineEntity entry = entries.get(i); 232 | 233 | volumeMa5 += entry.getVolume(); 234 | volumeMa10 += entry.getVolume(); 235 | 236 | if (i >= 5) { 237 | 238 | volumeMa5 -= entries.get(i - 5).getVolume(); 239 | entry.MA5Volume = (volumeMa5 / 5f); 240 | } else { 241 | 242 | entry.MA5Volume = (volumeMa5 / (i + 1f)); 243 | } 244 | 245 | if (i >= 10) { 246 | volumeMa10 -= entries.get(i - 10).getVolume(); 247 | entry.MA10Volume = (volumeMa10 / 5f); 248 | } else { 249 | entry.MA10Volume = (volumeMa10 / (i + 1f)); 250 | } 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/DataRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.github.tifezh.kchart.chart.KLineEntity; 8 | import com.github.tifezh.kchart.chart.MinuteLineEntity; 9 | import com.google.gson.Gson; 10 | import com.google.gson.reflect.TypeToken; 11 | 12 | import java.io.InputStream; 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.List; 16 | import java.util.Random; 17 | 18 | /** 19 | * 模拟网络请求 20 | * Created by tifezh on 2017/7/3. 21 | */ 22 | 23 | public class DataRequest { 24 | private static List datas = null; 25 | private static Random random = new Random(); 26 | 27 | public static String getStringFromAssert(Context context, String fileName) { 28 | try { 29 | InputStream in = context.getResources().getAssets().open(fileName); 30 | int length = in.available(); 31 | byte[] buffer = new byte[length]; 32 | in.read(buffer); 33 | return new String(buffer, 0, buffer.length, "UTF-8"); 34 | } catch (Exception e) { 35 | e.printStackTrace(); 36 | } 37 | return ""; 38 | } 39 | 40 | public static List getALL(Context context) { 41 | if (datas == null) { 42 | final List data = new Gson().fromJson(getStringFromAssert(context, "ibm.json"), new TypeToken>() {}.getType()); 43 | DataHelper.calculate(data); 44 | datas = data; 45 | } 46 | return datas; 47 | } 48 | 49 | /** 50 | * 分页查询 51 | * 52 | * @param context 53 | * @param offset 开始的索引 54 | * @param size 每次查询的条数 55 | */ 56 | public static List getData(Context context, int offset, int size) { 57 | List all = getALL(context); 58 | List data = new ArrayList<>(); 59 | int start = Math.max(0, all.size() - 1 - offset - size); 60 | int stop = Math.min(all.size(), all.size() - offset); 61 | for (int i = start; i < stop; i++) { 62 | data.add(all.get(i)); 63 | } 64 | return data; 65 | } 66 | 67 | /** 68 | * 随机生成分时数据 69 | */ 70 | public static List 71 | getMinuteData(@NonNull Date startTime, 72 | @NonNull Date endTime, 73 | @Nullable Date firstEndTime, 74 | @Nullable Date secondStartTime) { 75 | List list = new ArrayList<>(); 76 | long startDate=startTime.getTime(); 77 | if(firstEndTime==null&&secondStartTime==null) { 78 | while (startDate <= endTime.getTime()) { 79 | MinuteLineEntity data = new MinuteLineEntity(); 80 | data.time = new Date(startDate); 81 | startDate += 60000; 82 | list.add(data); 83 | } 84 | } 85 | else { 86 | while (startDate <= firstEndTime.getTime()) { 87 | MinuteLineEntity data = new MinuteLineEntity(); 88 | data.time = new Date(startDate); 89 | startDate += 60000; 90 | list.add(data); 91 | } 92 | startDate=secondStartTime.getTime(); 93 | while (startDate <= endTime.getTime()) { 94 | MinuteLineEntity data = new MinuteLineEntity(); 95 | data.time = new Date(startDate); 96 | startDate += 60000; 97 | list.add(data); 98 | } 99 | } 100 | randomLine(list); 101 | randomVolume(list); 102 | float sum = 0; 103 | for (int i = 0; i < list.size(); i++) { 104 | MinuteLineEntity data = list.get(i); 105 | sum += data.price; 106 | data.avg = 1f * sum / (i + 1); 107 | } 108 | return list; 109 | } 110 | 111 | private static void randomVolume(List list){ 112 | for(MinuteLineEntity data:list){ 113 | data.volume= (int) (Math.random()*Math.random()*Math.random()*Math.random()*10000000); 114 | } 115 | } 116 | 117 | /** 118 | * 生成随机曲线 119 | */ 120 | private static void randomLine(List list) { 121 | float STEP_MAX = 0.9f; 122 | float STEP_CHANGE = 1f; 123 | float HEIGHT_MAX = 200; 124 | 125 | float height = (float) (Math.random() * HEIGHT_MAX); 126 | float slope = (float) ((Math.random() * STEP_MAX) * 2 - STEP_MAX); 127 | 128 | for (int x = 0; x < list.size(); x++) { 129 | height += slope; 130 | slope += (Math.random() * STEP_CHANGE) * 2 - STEP_CHANGE; 131 | 132 | if (slope > STEP_MAX) { 133 | slope = STEP_MAX; 134 | } 135 | if (slope < -STEP_MAX) { 136 | slope = -STEP_MAX; 137 | } 138 | 139 | if (height > HEIGHT_MAX) { 140 | height = HEIGHT_MAX; 141 | slope *= -1; 142 | } 143 | if (height < 0) { 144 | height = 0; 145 | slope *= -1; 146 | } 147 | 148 | list.get(x).price = height + 1000; 149 | } 150 | } 151 | } 152 | 153 | 154 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/ExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart; 2 | 3 | import android.content.res.Configuration; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.view.Window; 10 | import android.view.WindowManager; 11 | import android.widget.LinearLayout; 12 | import android.widget.RelativeLayout; 13 | import android.widget.TextView; 14 | 15 | import com.github.tifezh.kchart.chart.KChartAdapter; 16 | import com.github.tifezh.kchart.chart.KLineEntity; 17 | import com.github.tifezh.kchartlib.chart.BaseKChartView; 18 | import com.github.tifezh.kchartlib.chart.KChartView; 19 | import com.github.tifezh.kchartlib.chart.formatter.DateFormatter; 20 | 21 | import java.util.List; 22 | 23 | import butterknife.BindView; 24 | import butterknife.ButterKnife; 25 | 26 | public class ExampleActivity extends AppCompatActivity { 27 | 28 | 29 | @BindView(R.id.title_view) 30 | RelativeLayout mTitleView; 31 | @BindView(R.id.tv_price) 32 | TextView mTvPrice; 33 | @BindView(R.id.tv_percent) 34 | TextView mTvPercent; 35 | @BindView(R.id.ll_status) 36 | LinearLayout mLlStatus; 37 | @BindView(R.id.kchart_view) 38 | KChartView mKChartView; 39 | private KChartAdapter mAdapter; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | int type = getIntent().getIntExtra("type", 0); 45 | if (type == 0) { 46 | setContentView(R.layout.activity_example); 47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 48 | Window window = getWindow(); 49 | window.setFlags( 50 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, 51 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 52 | } 53 | } else { 54 | setContentView(R.layout.activity_example_light); 55 | } 56 | ButterKnife.bind(this); 57 | initView(); 58 | initData(); 59 | } 60 | 61 | private void initView() { 62 | mAdapter = new KChartAdapter(); 63 | mKChartView.setAdapter(mAdapter); 64 | mKChartView.setDateTimeFormatter(new DateFormatter()); 65 | mKChartView.setGridRows(4); 66 | mKChartView.setGridColumns(4); 67 | mKChartView.setOnSelectedChangedListener(new BaseKChartView.OnSelectedChangedListener() { 68 | @Override 69 | public void onSelectedChanged(BaseKChartView view, Object point, int index) { 70 | KLineEntity data = (KLineEntity) point; 71 | Log.i("onSelectedChanged", "index:" + index + " closePrice:" + data.getClosePrice()); 72 | } 73 | }); 74 | } 75 | 76 | private void initData() { 77 | mKChartView.showLoading(); 78 | new Thread(new Runnable() { 79 | @Override 80 | public void run() { 81 | final List data = DataRequest.getALL(ExampleActivity.this); 82 | runOnUiThread(new Runnable() { 83 | @Override 84 | public void run() { 85 | mAdapter.addFooterData(data); 86 | mKChartView.startAnimation(); 87 | mKChartView.refreshEnd(); 88 | } 89 | }); 90 | } 91 | }).start(); 92 | } 93 | 94 | @Override 95 | public void onConfigurationChanged(Configuration newConfig) { 96 | super.onConfigurationChanged(newConfig); 97 | if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 98 | mLlStatus.setVisibility(View.GONE); 99 | mKChartView.setGridRows(3); 100 | mKChartView.setGridColumns(8); 101 | } else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 102 | mLlStatus.setVisibility(View.VISIBLE); 103 | mKChartView.setGridRows(4); 104 | mKChartView.setGridColumns(4); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/LoadMoreActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart; 2 | 3 | import android.content.res.Configuration; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.LinearLayout; 9 | import android.widget.RelativeLayout; 10 | import android.widget.TextView; 11 | 12 | import com.github.tifezh.kchart.chart.KChartAdapter; 13 | import com.github.tifezh.kchart.chart.KLineEntity; 14 | import com.github.tifezh.kchartlib.chart.BaseKChartView; 15 | import com.github.tifezh.kchartlib.chart.KChartView; 16 | import com.github.tifezh.kchartlib.chart.formatter.DateFormatter; 17 | 18 | import java.util.List; 19 | 20 | import butterknife.BindView; 21 | import butterknife.ButterKnife; 22 | 23 | /** 24 | * Created by tifezh on 2017/7/3. 25 | */ 26 | 27 | public class LoadMoreActivity extends AppCompatActivity implements KChartView.KChartRefreshListener { 28 | 29 | @BindView(R.id.title_view) 30 | RelativeLayout mTitleView; 31 | @BindView(R.id.tv_price) 32 | TextView mTvPrice; 33 | @BindView(R.id.tv_percent) 34 | TextView mTvPercent; 35 | @BindView(R.id.ll_status) 36 | LinearLayout mLlStatus; 37 | @BindView(R.id.kchart_view) 38 | KChartView mKChartView; 39 | private KChartAdapter mAdapter; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_example_light); 45 | ButterKnife.bind(this); 46 | initView(); 47 | initData(); 48 | } 49 | 50 | private void initView() { 51 | mAdapter = new KChartAdapter(); 52 | mKChartView.setAdapter(mAdapter); 53 | mKChartView.setDateTimeFormatter(new DateFormatter()); 54 | mKChartView.setGridRows(4); 55 | mKChartView.setGridColumns(4); 56 | mKChartView.setOnSelectedChangedListener(new BaseKChartView.OnSelectedChangedListener() { 57 | @Override 58 | public void onSelectedChanged(BaseKChartView view, Object point, int index) { 59 | KLineEntity data = (KLineEntity) point; 60 | Log.i("onSelectedChanged", "index:" + index + " closePrice:" + data.getClosePrice()); 61 | } 62 | }); 63 | } 64 | 65 | private void initData() { 66 | mKChartView.showLoading(); 67 | mKChartView.setRefreshListener(this); 68 | onLoadMoreBegin(mKChartView); 69 | } 70 | 71 | @Override 72 | public void onConfigurationChanged(Configuration newConfig) { 73 | super.onConfigurationChanged(newConfig); 74 | if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 75 | mLlStatus.setVisibility(View.GONE); 76 | mKChartView.setGridRows(3); 77 | mKChartView.setGridColumns(8); 78 | } else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 79 | mLlStatus.setVisibility(View.VISIBLE); 80 | mKChartView.setGridRows(4); 81 | mKChartView.setGridColumns(4); 82 | } 83 | } 84 | 85 | @Override 86 | public void onLoadMoreBegin(KChartView chart) { 87 | new Thread(new Runnable() { 88 | @Override 89 | public void run() { 90 | final List data = DataRequest.getData(LoadMoreActivity.this, mAdapter.getCount(), 500); 91 | try { 92 | Thread.sleep(1000); 93 | } catch (InterruptedException e) { 94 | e.printStackTrace(); 95 | } 96 | if (!data.isEmpty()) { 97 | Log.i("onLoadMoreBegin", "start:" + data.get(0).getDatetime() + " stop:" + data.get(data.size() - 1).getDatetime()); 98 | } 99 | runOnUiThread(new Runnable() { 100 | @Override 101 | public void run() { 102 | //第一次加载时开始动画 103 | if (mAdapter.getCount() == 0) { 104 | mKChartView.startAnimation(); 105 | } 106 | mAdapter.addFooterData(data); 107 | //加载完成,还有更多数据 108 | if (data.size() > 0) { 109 | mKChartView.refreshComplete(); 110 | } 111 | //加载完成,没有更多数据 112 | else { 113 | mKChartView.refreshEnd(); 114 | } 115 | } 116 | }); 117 | } 118 | }).start(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | 9 | /** 10 | * Created by tifezh on 2017/6/30. 11 | */ 12 | 13 | public class MainActivity extends AppCompatActivity implements View.OnClickListener{ 14 | @Override 15 | protected void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | } 19 | 20 | @Override 21 | public void onClick(View v) { 22 | Intent intent=new Intent(); 23 | switch (v.getId()) 24 | { 25 | case R.id.btn_style1: 26 | intent.setClass(this,ExampleActivity.class); 27 | intent.putExtra("type",0); 28 | break; 29 | case R.id.btn_style2: 30 | intent.setClass(this,ExampleActivity.class); 31 | intent.putExtra("type",1); 32 | break; 33 | case R.id.btn_loadmore: 34 | intent.setClass(this,LoadMoreActivity.class); 35 | break; 36 | case R.id.btn_minute: 37 | intent.setClass(this,MinuteChartActivity.class); 38 | break; 39 | } 40 | startActivity(intent); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/MinuteChartActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.Window; 8 | import android.view.WindowManager; 9 | 10 | import com.github.tifezh.kchart.chart.MinuteLineEntity; 11 | import com.github.tifezh.kchartlib.chart.MinuteChartView; 12 | import com.github.tifezh.kchartlib.utils.DateUtil; 13 | 14 | import java.text.ParseException; 15 | import java.util.Date; 16 | import java.util.List; 17 | 18 | 19 | import butterknife.BindView; 20 | import butterknife.ButterKnife; 21 | 22 | /** 23 | * Created by tifezh on 2017/7/20. 24 | */ 25 | 26 | public class MinuteChartActivity extends AppCompatActivity { 27 | 28 | 29 | @BindView(R.id.minuteChartView) 30 | MinuteChartView mMinuteChartView; 31 | 32 | @Override 33 | public void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_minute_chart); 36 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 37 | Window window = getWindow(); 38 | window.setFlags( 39 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, 40 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 41 | } 42 | ButterKnife.bind(this); 43 | initView(); 44 | initData(); 45 | } 46 | 47 | private void initView() { 48 | } 49 | 50 | private void initData() { 51 | try { 52 | //整体开始时间 53 | Date startTime = DateUtil.shortTimeFormat.parse("09:30"); 54 | //整体的结束时间 55 | Date endTime = DateUtil.shortTimeFormat.parse("15:00"); 56 | //休息开始时间 57 | Date firstEndTime = DateUtil.shortTimeFormat.parse("11:30"); 58 | //休息结束时间 59 | Date secondStartTime = DateUtil.shortTimeFormat.parse("13:00"); 60 | //获取随机生成的数据 61 | List minuteData = 62 | DataRequest.getMinuteData(startTime, 63 | endTime, 64 | firstEndTime, 65 | secondStartTime); 66 | mMinuteChartView.initData(minuteData, 67 | startTime, 68 | endTime, 69 | firstEndTime, 70 | secondStartTime, 71 | (float) (minuteData.get(0).price - 0.5 + Math.random())); 72 | } catch (ParseException e) { 73 | e.printStackTrace(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/chart/KChartAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart.chart; 2 | 3 | import com.github.tifezh.kchartlib.chart.BaseKChartAdapter; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | /** 10 | * 数据适配器 11 | * Created by tifezh on 2016/6/18. 12 | */ 13 | 14 | public class KChartAdapter extends BaseKChartAdapter { 15 | 16 | private List datas = new ArrayList<>(); 17 | 18 | public KChartAdapter() { 19 | 20 | } 21 | 22 | @Override 23 | public int getCount() { 24 | return datas.size(); 25 | } 26 | 27 | @Override 28 | public Object getItem(int position) { 29 | return datas.get(position); 30 | } 31 | 32 | @Override 33 | public Date getDate(int position) { 34 | try { 35 | String s = datas.get(position).Date; 36 | String[] split = s.split("/"); 37 | Date date = new Date(); 38 | date.setYear(Integer.parseInt(split[0]) - 1900); 39 | date.setMonth(Integer.parseInt(split[1]) - 1); 40 | date.setDate(Integer.parseInt(split[2])); 41 | return date; 42 | } catch (Exception e) { 43 | e.printStackTrace(); 44 | } 45 | return null; 46 | } 47 | 48 | /** 49 | * 向头部添加数据 50 | */ 51 | public void addHeaderData(List data) { 52 | if (data != null && !data.isEmpty()) { 53 | datas.addAll(data); 54 | notifyDataSetChanged(); 55 | } 56 | } 57 | 58 | /** 59 | * 向尾部添加数据 60 | */ 61 | public void addFooterData(List data) { 62 | if (data != null && !data.isEmpty()) { 63 | datas.addAll(0, data); 64 | notifyDataSetChanged(); 65 | } 66 | } 67 | 68 | /** 69 | * 改变某个点的值 70 | * @param position 索引值 71 | */ 72 | public void changeItem(int position,KLineEntity data) 73 | { 74 | datas.set(position,data); 75 | notifyDataSetChanged(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/chart/KLineEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart.chart; 2 | 3 | import com.github.tifezh.kchartlib.chart.entity.IKLine; 4 | 5 | 6 | /** 7 | * K线实体 8 | * Created by tifezh on 2016/5/16. 9 | */ 10 | 11 | public class KLineEntity implements IKLine { 12 | 13 | public String getDatetime() { 14 | return Date; 15 | } 16 | 17 | @Override 18 | public float getOpenPrice() { 19 | return Open; 20 | } 21 | 22 | @Override 23 | public float getHighPrice() { 24 | return High; 25 | } 26 | 27 | @Override 28 | public float getLowPrice() { 29 | return Low; 30 | } 31 | 32 | @Override 33 | public float getClosePrice() { 34 | return Close; 35 | } 36 | 37 | @Override 38 | public float getMA5Price() { 39 | return MA5Price; 40 | } 41 | 42 | @Override 43 | public float getMA10Price() { 44 | return MA10Price; 45 | } 46 | 47 | @Override 48 | public float getMA20Price() { 49 | return MA20Price; 50 | } 51 | 52 | @Override 53 | public float getDea() { 54 | return dea; 55 | } 56 | 57 | @Override 58 | public float getDif() { 59 | return dif; 60 | } 61 | 62 | @Override 63 | public float getMacd() { 64 | return macd; 65 | } 66 | 67 | @Override 68 | public float getK() { 69 | return k; 70 | } 71 | 72 | @Override 73 | public float getD() { 74 | return d; 75 | } 76 | 77 | @Override 78 | public float getJ() { 79 | return j; 80 | } 81 | 82 | @Override 83 | public float getRsi1() { 84 | return rsi1; 85 | } 86 | 87 | @Override 88 | public float getRsi2() { 89 | return rsi2; 90 | } 91 | 92 | @Override 93 | public float getRsi3() { 94 | return rsi3; 95 | } 96 | 97 | @Override 98 | public float getUp() { 99 | return up; 100 | } 101 | 102 | @Override 103 | public float getMb() { 104 | return mb; 105 | } 106 | 107 | @Override 108 | public float getDn() { 109 | return dn; 110 | } 111 | 112 | @Override 113 | public float getVolume() { 114 | return Volume; 115 | } 116 | 117 | @Override 118 | public float getMA5Volume() { 119 | return MA5Volume; 120 | } 121 | 122 | @Override 123 | public float getMA10Volume() { 124 | return MA10Volume; 125 | } 126 | 127 | public String Date; 128 | public float Open; 129 | public float High; 130 | public float Low; 131 | public float Close; 132 | public float Volume; 133 | 134 | public float MA5Price; 135 | 136 | public float MA10Price; 137 | 138 | public float MA20Price; 139 | 140 | public float dea; 141 | 142 | public float dif; 143 | 144 | public float macd; 145 | 146 | public float k; 147 | 148 | public float d; 149 | 150 | public float j; 151 | 152 | public float rsi1; 153 | 154 | public float rsi2; 155 | 156 | public float rsi3; 157 | 158 | public float up; 159 | 160 | public float mb; 161 | 162 | public float dn; 163 | 164 | public float MA5Volume; 165 | 166 | public float MA10Volume; 167 | 168 | 169 | 170 | 171 | } 172 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/tifezh/kchart/chart/MinuteLineEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.tifezh.kchart.chart; 2 | 3 | import com.github.tifezh.kchartlib.chart.entity.IMinuteLine; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * 分时图实体 9 | * Created by tifezh on 2017/7/20. 10 | */ 11 | 12 | public class MinuteLineEntity implements IMinuteLine { 13 | /** 14 | * time : 09:30 15 | * price : 3.53 16 | * avg : 3.5206 17 | * vol : 9251 18 | */ 19 | 20 | public Date time; 21 | public float price; 22 | public float avg; 23 | public float volume; 24 | 25 | @Override 26 | public float getAvgPrice() { 27 | return avg; 28 | } 29 | 30 | @Override 31 | public float getPrice() { 32 | return price; 33 | } 34 | 35 | @Override 36 | public Date getDate() { 37 | return time; 38 | } 39 | 40 | @Override 41 | public float getVolume() { 42 | return volume; 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/res/color/tab_light_text_color_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_example.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 22 | 23 | 24 | 28 | 29 | 37 | 38 | 44 | 45 | 53 | 54 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 111 | 112 | 113 | 114 | 115 | 119 | 120 | 125 | 126 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_example_light.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 22 | 23 | 24 | 28 | 29 | 37 | 38 | 44 | 45 | 53 | 54 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 111 | 112 | 113 | 114 | 115 | 119 | 120 | 158 | 159 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 |