├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── colors.xml
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── loading.png
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── kline_back_port.png
│ │ │ │ ├── kline_go_land.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ ├── toolbar_back.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── drawable
│ │ │ │ ├── sls_color_blue_gray_8b.xml
│ │ │ │ └── sls_color_green_red.xml
│ │ │ ├── anim
│ │ │ │ └── rotate.xml
│ │ │ ├── layout
│ │ │ │ └── item_tab_kline.xml
│ │ │ ├── layout-land
│ │ │ │ └── activity_main.xml
│ │ │ └── layout-port
│ │ │ │ └── activity_main.xml
│ │ ├── java
│ │ │ └── cn
│ │ │ │ └── qianlan
│ │ │ │ └── klinedemo
│ │ │ │ ├── AppContext.java
│ │ │ │ ├── chart
│ │ │ │ ├── InBoundYAxisRenderer.java
│ │ │ │ ├── CoupleChartValueSelectedListener.java
│ │ │ │ ├── ChartFingerTouchListener.java
│ │ │ │ ├── InBoundXAxisRenderer.java
│ │ │ │ ├── HighlightCombinedRenderer.java
│ │ │ │ ├── OffsetBarBuffer.java
│ │ │ │ ├── CoupleChartGestureListener.java
│ │ │ │ ├── HighlightLineRenderer.java
│ │ │ │ ├── HighlightCandleRenderer.java
│ │ │ │ └── OffsetBarRenderer.java
│ │ │ │ ├── KlineEntity.java
│ │ │ │ ├── LoadingDialog.java
│ │ │ │ ├── OkHttpUtil.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── cn
│ │ │ └── qianlan
│ │ │ └── klinedemo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── cn
│ │ └── qianlan
│ │ └── klinedemo
│ │ └── ExampleInstrumentedTest.java
├── build.gradle
└── proguard-rules.pro
├── settings.gradle
├── README.md
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 博客地址:https://blog.csdn.net/shilyhm/article/category/7922086
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | KlineDemo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xhdpi/loading.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/kline_back_port.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xhdpi/kline_back_port.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/kline_go_land.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xhdpi/kline_go_land.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/toolbar_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xxhdpi/toolbar_back.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Qianlan77/KlineDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Aug 07 16:57:52 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sls_color_blue_gray_8b.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sls_color_green_red.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/AppContext.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo;
2 |
3 | import android.app.Application;
4 |
5 | public class AppContext extends Application {
6 |
7 | @Override
8 | public void onCreate() {
9 | super.onCreate();
10 | OkHttpUtil.initOkHttp();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/rotate.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_tab_kline.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/cn/qianlan/klinedemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1F232C
4 | #1F232C
5 | #477dd7
6 | #fff
7 | #0f121b
8 | #0F131C
9 | #3E424B
10 | #878d9b
11 | #477dd7
12 | #eb5350
13 | #4da64c
14 | #FFBB77
15 | #B230ED
16 | #EFBB40
17 |
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/cn/qianlan/klinedemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("cn.qianlan.klinedemo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | buildToolsVersion "27.0.3"
6 |
7 | defaultConfig {
8 | applicationId "cn.qianlan.klinedemo"
9 | minSdkVersion 17
10 | targetSdkVersion 27
11 | versionCode 1
12 | versionName "1.0.0"
13 | }
14 |
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
26 | compile 'com.android.support:appcompat-v7:27.0.1'
27 | compile 'com.android.support:design:27.0.1'
28 | compile 'com.google.code.gson:gson:2.8.4'
29 | compile 'com.squareup.okhttp3:okhttp:3.10.0'
30 | compile 'com.github.PhilJay:MPAndroidChart:v3.0.3'
31 | testCompile 'junit:junit:4.12'
32 | }
33 |
--------------------------------------------------------------------------------
/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 E:\Android\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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/InBoundYAxisRenderer.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import android.graphics.Canvas;
4 |
5 | import com.github.mikephil.charting.components.YAxis;
6 | import com.github.mikephil.charting.renderer.YAxisRenderer;
7 | import com.github.mikephil.charting.utils.Transformer;
8 | import com.github.mikephil.charting.utils.Utils;
9 | import com.github.mikephil.charting.utils.ViewPortHandler;
10 |
11 | /**
12 | * 自定义Y轴标签渲染器,使其不出界
13 | * 只修改了第31行 使最后一个标签处于刻度下方 其余标签处于刻度上方
14 | */
15 | public class InBoundYAxisRenderer extends YAxisRenderer {
16 |
17 | public InBoundYAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
18 | super(viewPortHandler, yAxis, trans);
19 | }
20 |
21 | @Override
22 | protected void drawYLabels(Canvas c, float fixedPosition, float[] positions, float offset) {
23 | final int from = mYAxis.isDrawBottomYLabelEntryEnabled() ? 0 : 1;
24 | final int to = mYAxis.isDrawTopYLabelEntryEnabled() ? mYAxis.mEntryCount : (mYAxis.mEntryCount - 1);
25 |
26 | // draw
27 | int labelHeight = Utils.calcTextHeight(mAxisLabelPaint, "A");
28 | for (int i = from; i < to; i++) {
29 | String text = mYAxis.getFormattedLabel(i);
30 | float os = i == mYAxis.mEntryCount - 1 ? -0.9F * labelHeight : 0.7F * labelHeight;
31 | c.drawText(text, fixedPosition, positions[i * 2 + 1] + offset - os, mAxisLabelPaint);
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/KlineEntity.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * K线数据
7 | */
8 | public class KlineEntity {
9 |
10 | /**
11 | * ok : true
12 | * code : 1
13 | * msg :
14 | * data : {}
15 | */
16 |
17 | private boolean ok;
18 | private int code;
19 | private String msg;
20 | private DataEntity data;
21 |
22 | public boolean isOk() {
23 | return ok;
24 | }
25 |
26 | public void setOk(boolean ok) {
27 | this.ok = ok;
28 | }
29 |
30 | public int getCode() {
31 | return code;
32 | }
33 |
34 | public void setCode(int code) {
35 | this.code = code;
36 | }
37 |
38 | public String getMsg() {
39 | return msg;
40 | }
41 |
42 | public void setMsg(String msg) {
43 | this.msg = msg;
44 | }
45 |
46 | public DataEntity getData() {
47 | return data;
48 | }
49 |
50 | public void setData(DataEntity data) {
51 | this.data = data;
52 | }
53 |
54 | public static class DataEntity {
55 | private List columns;
56 | private List> lists;
57 |
58 | public List getColumns() {
59 | return columns;
60 | }
61 |
62 | public void setColumns(List columns) {
63 | this.columns = columns;
64 | }
65 |
66 | public List> getLists() {
67 | return lists;
68 | }
69 |
70 | public void setLists(List> lists) {
71 | this.lists = lists;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/LoadingDialog.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo;
2 |
3 | import android.app.Dialog;
4 | import android.app.DialogFragment;
5 | import android.app.FragmentManager;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.os.Bundle;
9 | import android.support.annotation.Nullable;
10 | import android.support.v4.app.FragmentActivity;
11 | import android.view.Gravity;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.view.Window;
16 | import android.view.animation.AnimationUtils;
17 | import android.widget.ImageView;
18 |
19 | /**
20 | * 加载中对话框
21 | */
22 | public class LoadingDialog extends DialogFragment {
23 |
24 | private ImageView iv;
25 |
26 | public static LoadingDialog newInstance() {
27 | return new LoadingDialog();
28 | }
29 |
30 | @Nullable
31 | @Override
32 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
33 | if (iv == null) {
34 | iv = new ImageView(getActivity());
35 | iv.setImageResource(R.mipmap.loading);
36 | }
37 | iv.startAnimation(AnimationUtils.loadAnimation(getActivity(), R.anim.rotate));
38 |
39 | getDialog().setCanceledOnTouchOutside(false);//对话框外不可取消
40 | getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);//取消标题显示
41 | Window window = getDialog().getWindow();
42 | window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
43 | window.setGravity(Gravity.CENTER);
44 |
45 | return iv;
46 | }
47 |
48 | public LoadingDialog show(FragmentActivity context) {
49 | return show(context.getFragmentManager());
50 | }
51 |
52 | public LoadingDialog show(FragmentManager manager) {
53 | Dialog dialog = getDialog();
54 | if (dialog == null || !dialog.isShowing()) {
55 | show(manager, "dialog");
56 | }
57 | return this;
58 | }
59 |
60 | @Override
61 | public void dismiss() {
62 | if (iv != null && iv.getAnimation() != null) {
63 | iv.clearAnimation();
64 | }
65 | super.dismiss();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/CoupleChartValueSelectedListener.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import com.github.mikephil.charting.charts.BarChart;
4 | import com.github.mikephil.charting.charts.BarLineChartBase;
5 | import com.github.mikephil.charting.charts.CombinedChart;
6 | import com.github.mikephil.charting.data.Entry;
7 | import com.github.mikephil.charting.highlight.Highlight;
8 | import com.github.mikephil.charting.listener.OnChartValueSelectedListener;
9 |
10 | /**
11 | * 图表联动高亮监听
12 | */
13 | public class CoupleChartValueSelectedListener implements OnChartValueSelectedListener {
14 |
15 | private BarLineChartBase srcChart;
16 | private BarLineChartBase[] dstCharts;
17 | private ValueSelectedListener mListener;
18 |
19 | public CoupleChartValueSelectedListener(BarLineChartBase srcChart, BarLineChartBase... dstCharts) {
20 | this(null, srcChart, dstCharts);
21 | }
22 |
23 | public CoupleChartValueSelectedListener(ValueSelectedListener mListener,
24 | BarLineChartBase srcChart, BarLineChartBase... dstCharts) {
25 | this.mListener = mListener;
26 | this.srcChart = srcChart;
27 | this.dstCharts = dstCharts;
28 | }
29 |
30 | @Override
31 | public void onValueSelected(Entry e, Highlight h) {
32 | if (dstCharts != null) {
33 | for (BarLineChartBase chart : dstCharts) {
34 | float touchY = h.getDrawY();//手指接触点在srcChart上的Y坐标,即手势监听器中保存数据
35 | float y = h.getY();
36 | if (chart instanceof BarChart) {
37 | y = touchY - srcChart.getHeight();
38 | } else if (chart instanceof CombinedChart) {
39 | y = touchY + chart.getHeight();
40 | }
41 | Highlight hl = new Highlight(h.getX(), Float.NaN, h.getDataSetIndex());
42 | hl.setDraw(h.getX(), y);
43 | chart.highlightValues(new Highlight[]{hl});
44 | }
45 | }
46 | if (mListener != null) {
47 | mListener.valueSelected(e);
48 | }
49 | }
50 |
51 | @Override
52 | public void onNothingSelected() {
53 | if (dstCharts != null) {
54 | for (BarLineChartBase chart : dstCharts) {
55 | chart.highlightValues(null);
56 | }
57 | }
58 | if (mListener != null) {
59 | mListener.nothingSelected();
60 | }
61 | }
62 |
63 | public interface ValueSelectedListener {
64 | void valueSelected(Entry e);
65 | void nothingSelected();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/ChartFingerTouchListener.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import android.view.GestureDetector;
4 | import android.view.MotionEvent;
5 | import android.view.View;
6 |
7 | import com.github.mikephil.charting.charts.BarLineChartBase;
8 | import com.github.mikephil.charting.highlight.Highlight;
9 |
10 | /**
11 | * 图表长按及滑动手指监听
12 | */
13 | public class ChartFingerTouchListener implements View.OnTouchListener {
14 |
15 | private BarLineChartBase mChart;
16 | private GestureDetector mDetector;
17 | private HighlightListener mListener;
18 | private boolean mIsLongPress = false;
19 |
20 | public ChartFingerTouchListener(BarLineChartBase chart, HighlightListener listener) {
21 | mChart = chart;
22 | mListener = listener;
23 | mDetector = new GestureDetector(mChart.getContext(), new GestureDetector.SimpleOnGestureListener() {
24 | @Override
25 | public void onLongPress(MotionEvent e) {
26 | super.onLongPress(e);
27 | mIsLongPress = true;
28 | if (mListener != null) {
29 | mListener.enableHighlight();
30 | }
31 | Highlight h = mChart.getHighlightByTouchPoint(e.getX(), e.getY());
32 | if (h != null) {
33 | h.setDraw(e.getX(), e.getY());
34 | mChart.highlightValue(h, true);
35 | mChart.disableScroll();
36 | }
37 | }
38 | });
39 | }
40 |
41 | @Override
42 | public boolean onTouch(View v, MotionEvent event) {
43 | mDetector.onTouchEvent(event);
44 | if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
45 | mIsLongPress = false;
46 | mChart.highlightValue(null, true);
47 | if (mListener != null) {
48 | mListener.disableHighlight();
49 | }
50 | mChart.enableScroll();
51 | }
52 | if (mIsLongPress && event.getAction() == MotionEvent.ACTION_MOVE) {
53 | if (mListener != null) {
54 | mListener.enableHighlight();
55 | }
56 | Highlight h = mChart.getHighlightByTouchPoint(event.getX(), event.getY());
57 | if (h != null) {
58 | h.setDraw(event.getX(), event.getY());
59 | mChart.highlightValue(h, true);
60 | mChart.disableScroll();
61 | }
62 | return true;
63 | }
64 | return false;
65 | }
66 |
67 | public interface HighlightListener {
68 | void enableHighlight();
69 | void disableHighlight();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/InBoundXAxisRenderer.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import android.graphics.Canvas;
4 |
5 | import com.github.mikephil.charting.components.XAxis;
6 | import com.github.mikephil.charting.renderer.XAxisRenderer;
7 | import com.github.mikephil.charting.utils.MPPointF;
8 | import com.github.mikephil.charting.utils.Transformer;
9 | import com.github.mikephil.charting.utils.Utils;
10 | import com.github.mikephil.charting.utils.ViewPortHandler;
11 |
12 | /**
13 | * 自定义X轴标签渲染器,使其不出界
14 | * {@link #interval} -- 左右两侧边缘处的标签 距离边缘的间隔
15 | * 修改了第55行 使第一个标签向右偏移一半、最后一个标签向左偏移一半
16 | */
17 | public class InBoundXAxisRenderer extends XAxisRenderer {
18 |
19 | protected int interval;
20 |
21 | public InBoundXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) {
22 | this(viewPortHandler, xAxis, trans, 0);
23 | }
24 |
25 | public InBoundXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans,
26 | int interval) {
27 | super(viewPortHandler, xAxis, trans);
28 | this.interval = interval;
29 | }
30 |
31 | @Override
32 | protected void drawLabels(Canvas c, float pos, MPPointF anchor) {
33 | final float labelRotationAngleDegrees = mXAxis.getLabelRotationAngle();
34 | boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled();
35 |
36 | float[] positions = new float[mXAxis.mEntryCount * 2];
37 | for (int i = 0; i < positions.length; i += 2) {
38 | // only fill x values
39 | if (centeringEnabled) {
40 | positions[i] = mXAxis.mCenteredEntries[i / 2];
41 | } else {
42 | positions[i] = mXAxis.mEntries[i / 2];
43 | }
44 | }
45 | mTrans.pointValuesToPixel(positions);
46 |
47 | for (int i = 0; i < positions.length; i += 2) {
48 | float x = positions[i];
49 | if (mViewPortHandler.isInBoundsX(x)) {
50 | String label = mXAxis.getValueFormatter().getFormattedValue(mXAxis.mEntries[i / 2], mXAxis);
51 | if (mXAxis.isAvoidFirstLastClippingEnabled()) {
52 | // avoid clipping of the last
53 | float width = Utils.calcTextWidth(mAxisLabelPaint, label);
54 | if (i == mXAxis.mEntryCount * 2 - 2 && mXAxis.mEntryCount > 1) {
55 | x -= width / 2 + interval;
56 | // avoid clipping of the first
57 | } else if (i == 0) {
58 | x += width / 2 + interval;
59 | }
60 | }
61 |
62 | drawLabel(c, label, x, pos, anchor, labelRotationAngleDegrees);
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/HighlightCombinedRenderer.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import com.github.mikephil.charting.animation.ChartAnimator;
4 | import com.github.mikephil.charting.charts.CombinedChart;
5 | import com.github.mikephil.charting.charts.CombinedChart.DrawOrder;
6 | import com.github.mikephil.charting.renderer.BarChartRenderer;
7 | import com.github.mikephil.charting.renderer.BubbleChartRenderer;
8 | import com.github.mikephil.charting.renderer.CombinedChartRenderer;
9 | import com.github.mikephil.charting.renderer.ScatterChartRenderer;
10 | import com.github.mikephil.charting.utils.ViewPortHandler;
11 |
12 | /**
13 | * 自定义CombinedChartRenderer 把Candle图、Line图 的渲染器替换成自定义渲染器
14 | */
15 | public class HighlightCombinedRenderer extends CombinedChartRenderer {
16 |
17 | private float highlightSize;//图表高亮文字大小 单位:px
18 |
19 | public HighlightCombinedRenderer(CombinedChart chart, ChartAnimator animator,
20 | ViewPortHandler viewPortHandler, float highlightSize) {
21 | super(chart, animator, viewPortHandler);
22 | this.highlightSize = highlightSize;
23 | }
24 |
25 | @Override
26 | public void createRenderers() {
27 | mRenderers.clear();
28 | CombinedChart chart = (CombinedChart)mChart.get();
29 | if (chart == null)
30 | return;
31 | DrawOrder[] orders = chart.getDrawOrder();
32 | for (DrawOrder order : orders) {
33 | switch (order) {
34 | case BAR:
35 | if (chart.getBarData() != null)
36 | mRenderers.add(new BarChartRenderer(chart, mAnimator, mViewPortHandler));
37 | break;
38 | case BUBBLE:
39 | if (chart.getBubbleData() != null)
40 | mRenderers.add(new BubbleChartRenderer(chart, mAnimator, mViewPortHandler));
41 | break;
42 | case LINE:
43 | if (chart.getLineData() != null)
44 | mRenderers.add(new HighlightLineRenderer(chart, mAnimator, mViewPortHandler)
45 | .setHighlightSize(highlightSize));
46 | break;
47 | case CANDLE:
48 | if (chart.getCandleData() != null)
49 | mRenderers.add(new HighlightCandleRenderer(chart, mAnimator, mViewPortHandler)
50 | .setHighlightSize(highlightSize));
51 | break;
52 | case SCATTER:
53 | if (chart.getScatterData() != null)
54 | mRenderers.add(new ScatterChartRenderer(chart, mAnimator, mViewPortHandler));
55 | break;
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/OffsetBarBuffer.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import com.github.mikephil.charting.buffer.BarBuffer;
4 | import com.github.mikephil.charting.data.BarEntry;
5 | import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
6 |
7 | /**
8 | * 自定义BarChart的Bar数据 绘制Bar时添加偏移
9 | */
10 | public class OffsetBarBuffer extends BarBuffer {
11 |
12 | protected float barOffset;//BarChart绘制时偏移多少个单位 --小于0时向左偏移
13 |
14 | public OffsetBarBuffer(int size, int dataSetCount, boolean containsStacks, float barOffset) {
15 | super(size, dataSetCount, containsStacks);
16 | this.barOffset = barOffset;
17 | }
18 |
19 | @Override
20 | public void feed(IBarDataSet data) {
21 | float size = data.getEntryCount() * phaseX;
22 | float barWidthHalf = mBarWidth / 2f;
23 | for (int i = 0; i < size; i++) {
24 | BarEntry e = data.getEntryForIndex(i);
25 | if(e == null)
26 | continue;
27 | float x = e.getX();
28 | float y = e.getY();
29 | float[] vals = e.getYVals();
30 |
31 | if (!mContainsStacks || vals == null) {
32 | float left = x - barWidthHalf + barOffset;
33 | float right = x + barWidthHalf + barOffset;
34 | float bottom, top;
35 |
36 | if (mInverted) {
37 | bottom = y >= 0 ? y : 0;
38 | top = y <= 0 ? y : 0;
39 | } else {
40 | top = y >= 0 ? y : 0;
41 | bottom = y <= 0 ? y : 0;
42 | }
43 |
44 | // multiply the height of the rect with the phase
45 | if (top > 0)
46 | top *= phaseY;
47 | else
48 | bottom *= phaseY;
49 |
50 | addBar(left, top, right, bottom);
51 | } else {
52 | float posY = 0f;
53 | float negY = -e.getNegativeSum();
54 | float yStart = 0f;
55 |
56 | // fill the stack
57 | for (int k = 0; k < vals.length; k++) {
58 | float value = vals[k];
59 | if (value == 0.0f && (posY == 0.0f || negY == 0.0f)) {
60 | // Take care of the situation of a 0.0 value, which overlaps a non-zero bar
61 | y = value;
62 | yStart = y;
63 | } else if (value >= 0.0f) {
64 | y = posY;
65 | yStart = posY + value;
66 | posY = yStart;
67 | } else {
68 | y = negY;
69 | yStart = negY + Math.abs(value);
70 | negY += Math.abs(value);
71 | }
72 |
73 | float left = x - barWidthHalf + barOffset;
74 | float right = x + barWidthHalf + barOffset;
75 | float bottom, top;
76 |
77 | if (mInverted) {
78 | bottom = y >= yStart ? y : yStart;
79 | top = y <= yStart ? y : yStart;
80 | } else {
81 | top = y >= yStart ? y : yStart;
82 | bottom = y <= yStart ? y : yStart;
83 | }
84 |
85 | // multiply the height of the rect with the phase
86 | top *= phaseY;
87 | bottom *= phaseY;
88 | addBar(left, top, right, bottom);
89 | }
90 | }
91 | }
92 | reset();
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/OkHttpUtil.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo;
2 |
3 | import android.os.Handler;
4 | import android.text.TextUtils;
5 | import android.util.Log;
6 |
7 | import java.io.IOException;
8 | import java.security.SecureRandom;
9 | import java.security.cert.CertificateException;
10 | import java.security.cert.X509Certificate;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | import javax.net.ssl.HostnameVerifier;
14 | import javax.net.ssl.SSLContext;
15 | import javax.net.ssl.SSLSession;
16 | import javax.net.ssl.TrustManager;
17 | import javax.net.ssl.X509TrustManager;
18 |
19 | import okhttp3.Call;
20 | import okhttp3.Callback;
21 | import okhttp3.OkHttpClient;
22 | import okhttp3.Request;
23 | import okhttp3.RequestBody;
24 | import okhttp3.Response;
25 |
26 | /**
27 | * OkHttp的工具类
28 | */
29 | public class OkHttpUtil {
30 |
31 | private static OkHttpClient okHttpClient;
32 | private static Handler handler = new Handler();
33 |
34 | public static void initOkHttp() {
35 | OkHttpClient.Builder builder = new OkHttpClient.Builder()
36 | .connectTimeout(60, TimeUnit.MINUTES)
37 | .readTimeout(60, TimeUnit.MINUTES)
38 | .writeTimeout(60, TimeUnit.MINUTES)
39 | .retryOnConnectionFailure(false);
40 |
41 | //处理https协议
42 | SSLContext sc;
43 | try {
44 | sc = SSLContext.getInstance("SSL");
45 | sc.init(null, new TrustManager[]{new X509TrustManager() {
46 | @Override
47 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
48 |
49 | @Override
50 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
51 |
52 | @Override
53 | public X509Certificate[] getAcceptedIssuers() {
54 | return new X509Certificate[]{};
55 | }
56 | }}, new SecureRandom());
57 | } catch (Exception e) {
58 | e.printStackTrace();
59 | sc = null;
60 | }
61 | if (sc != null) {
62 | okHttpClient = builder.hostnameVerifier(new HostnameVerifier() {
63 | @Override
64 | public boolean verify(String hostname, SSLSession session) {
65 | return true;
66 | }
67 | })
68 | .sslSocketFactory(sc.getSocketFactory())
69 | .build();
70 | } else {
71 | okHttpClient = builder.build();
72 | }
73 | }
74 |
75 | /**
76 | * 取消所有请求
77 | */
78 | public static void cancel() {
79 | okHttpClient.dispatcher().cancelAll();
80 | }
81 |
82 | /**
83 | * 下载json
84 | */
85 | public static void getJSON(String url, OnDataListener dataListener) {
86 | if (!TextUtils.isEmpty(url)) {
87 | Request request = new Request.Builder()
88 | .url(url)
89 | .build();
90 | okHttpClient.newCall(request).enqueue(new OkHttpCallback(url, dataListener));
91 | }
92 | }
93 |
94 | private static void postJson(String url, RequestBody body, OnDataListener dataListener) {
95 | if (!TextUtils.isEmpty(url)) {
96 | Request request = new Request.Builder()
97 | .url(url)
98 | .post(body)
99 | .build();
100 | okHttpClient.newCall(request).enqueue(new OkHttpCallback(url, dataListener));
101 | }
102 | }
103 |
104 |
105 | /**
106 | * 结果回调
107 | */
108 | static class OkHttpCallback implements Callback {
109 |
110 | private String url;
111 | private OnDataListener dataListener;
112 |
113 | public OkHttpCallback(String url, OnDataListener dataListener) {
114 | this.url = url;
115 | this.dataListener = dataListener;
116 | }
117 |
118 | @Override
119 | public void onFailure(Call call, IOException e) {
120 | onFail(dataListener, url, e);
121 | }
122 |
123 | @Override
124 | public void onResponse(Call call, Response response) throws IOException {
125 | onResp(dataListener, url, response.body().string());
126 | }
127 | }
128 |
129 | private static void onFail(final OnDataListener dataListener, final String url, final IOException e) {
130 | handler.post(new Runnable() {
131 | @Override
132 | public void run() {
133 | try {
134 | if (dataListener != null) {
135 | Log.e("loge", "onFailure: " + url + "\n" + e.getMessage());
136 | dataListener.onFailure(url, e.getMessage());
137 | }
138 | } catch (Exception e) {
139 | Log.e("loge", "E: " + e.getMessage());
140 | }
141 | }
142 | });
143 | }
144 |
145 | private static void onResp(final OnDataListener dataListener, final String url, final String json) {
146 | handler.post(new Runnable() {
147 | @Override
148 | public void run() {
149 | try {
150 | if (dataListener != null && !TextUtils.isEmpty(json)) {
151 | dataListener.onResponse(url, json);
152 | }
153 | } catch (Exception e) {
154 | Log.e("loge", json + "\nE: " + e.getMessage());
155 | if (dataListener != null) {
156 | dataListener.onFailure(url, e.getMessage());
157 | }
158 | }
159 | }
160 | });
161 | }
162 |
163 | public interface OnDataListener {
164 | void onResponse(String url, String json);
165 | void onFailure(String url, String error);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/CoupleChartGestureListener.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import android.graphics.Matrix;
4 | import android.view.MotionEvent;
5 |
6 | import com.github.mikephil.charting.charts.BarLineChartBase;
7 | import com.github.mikephil.charting.charts.Chart;
8 | import com.github.mikephil.charting.listener.ChartTouchListener;
9 | import com.github.mikephil.charting.listener.OnChartGestureListener;
10 |
11 | /**
12 | * 图表联动交互监听
13 | */
14 | public class CoupleChartGestureListener implements OnChartGestureListener {
15 |
16 | private BarLineChartBase srcChart;
17 | private Chart[] dstCharts;
18 |
19 | private OnEdgeListener edgeListener;//滑动到边缘的监听器
20 | private boolean isLoadMore, isHighlight;//是否加载更多、是否高亮 -- 高亮时不再加载更多
21 | private boolean canLoad;//K线图手指交互已停止,正在惯性滑动
22 |
23 | public CoupleChartGestureListener(BarLineChartBase srcChart, Chart... dstCharts) {
24 | this.srcChart = srcChart;
25 | this.dstCharts = dstCharts;
26 | isLoadMore = false;
27 | }
28 |
29 | public CoupleChartGestureListener(OnEdgeListener edgeListener, BarLineChartBase srcChart,
30 | Chart... dstCharts) {
31 | this.edgeListener = edgeListener;
32 | this.srcChart = srcChart;
33 | this.dstCharts = dstCharts;
34 | isLoadMore = true;
35 | }
36 |
37 | @Override
38 | public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
39 | canLoad = false;
40 | syncCharts();
41 | chartGestureStart(me, lastPerformedGesture);
42 | }
43 |
44 | @Override
45 | public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {
46 | if (isHighlight) {
47 | isHighlight = false;
48 | } else {
49 | if (isLoadMore) {
50 | float leftX = srcChart.getLowestVisibleX();
51 | float rightX = srcChart.getHighestVisibleX();
52 | if (leftX == srcChart.getXChartMin()) {//滑到最左端
53 | canLoad = false;
54 | if (edgeListener != null) {
55 | edgeListener.edgeLoad(leftX, true);
56 | }
57 | } else if (rightX == srcChart.getXChartMax()) {//滑到最右端
58 | canLoad = false;
59 | if (edgeListener != null) {
60 | edgeListener.edgeLoad(rightX, false);
61 | }
62 | } else {
63 | canLoad = true;
64 | }
65 | }
66 | }
67 | syncCharts();
68 | chartGestureEnd(me, lastPerformedGesture);
69 | }
70 |
71 | @Override
72 | public void onChartLongPressed(MotionEvent me) {
73 | syncCharts();
74 | chartLongPressed(me);
75 | }
76 |
77 | @Override
78 | public void onChartDoubleTapped(MotionEvent me) {
79 | syncCharts();
80 | chartDoubleTapped(me);
81 | }
82 |
83 | @Override
84 | public void onChartSingleTapped(MotionEvent me) {
85 | syncCharts();
86 | chartSingleTapped(me);
87 | }
88 |
89 | @Override
90 | public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
91 | syncCharts();
92 | }
93 |
94 | @Override
95 | public void onChartScale(MotionEvent me, float scaleX, float scaleY) {
96 | syncCharts();
97 | }
98 |
99 | /**
100 | * 由于在外部设置了禁止惯性甩动(因为和Chart的move方法有冲突),
101 | * if中的语句实际上不会执行(整个手势交互结束后,最后回调的方法是onChartGestureEnd,而不是onChartTranslate),
102 | * 这样写是为了统一允许惯性甩动的情况
103 | */
104 | @Override
105 | public void onChartTranslate(MotionEvent me, float dX, float dY) {
106 | if (canLoad) {
107 | float leftX = srcChart.getLowestVisibleX();
108 | float rightX = srcChart.getHighestVisibleX();
109 | if (leftX == srcChart.getXChartMin()) {//滑到最左端
110 | canLoad = false;
111 | if (edgeListener != null) {
112 | edgeListener.edgeLoad(leftX, true);
113 | }
114 | } else if (rightX == srcChart.getXChartMax()) {//滑到最右端
115 | canLoad = false;
116 | if (edgeListener != null) {
117 | edgeListener.edgeLoad(rightX, false);
118 | }
119 | }
120 | }
121 | syncCharts();
122 | chartTranslate(me, dX, dY);
123 | }
124 |
125 | //以下5个方法仅为了:方便在外部根据需要自行重写
126 | public void chartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {}
127 | public void chartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) {}
128 | public void chartLongPressed(MotionEvent me) {}
129 | public void chartDoubleTapped(MotionEvent me) {}
130 | public void chartSingleTapped(MotionEvent me) {}
131 | public void chartTranslate(MotionEvent me, float dX, float dY) {}
132 |
133 | private void syncCharts() {
134 | Matrix srcMatrix;
135 | float[] srcVals = new float[9];
136 | Matrix dstMatrix;
137 | float[] dstVals = new float[9];
138 | // get src chart translation matrix:
139 | srcMatrix = srcChart.getViewPortHandler().getMatrixTouch();
140 | srcMatrix.getValues(srcVals);
141 | // apply X axis scaling and position to dst charts:
142 | for (Chart dstChart : dstCharts) {
143 | dstMatrix = dstChart.getViewPortHandler().getMatrixTouch();
144 | dstMatrix.getValues(dstVals);
145 |
146 | dstVals[Matrix.MSCALE_X] = srcVals[Matrix.MSCALE_X];
147 | dstVals[Matrix.MSKEW_X] = srcVals[Matrix.MSKEW_X];
148 | dstVals[Matrix.MTRANS_X] = srcVals[Matrix.MTRANS_X];
149 | dstVals[Matrix.MSKEW_Y] = srcVals[Matrix.MSKEW_Y];
150 | dstVals[Matrix.MSCALE_Y] = srcVals[Matrix.MSCALE_Y];
151 | dstVals[Matrix.MTRANS_Y] = srcVals[Matrix.MTRANS_Y];
152 | dstVals[Matrix.MPERSP_0] = srcVals[Matrix.MPERSP_0];
153 | dstVals[Matrix.MPERSP_1] = srcVals[Matrix.MPERSP_1];
154 | dstVals[Matrix.MPERSP_2] = srcVals[Matrix.MPERSP_2];
155 |
156 | dstMatrix.setValues(dstVals);
157 | dstChart.getViewPortHandler().refresh(dstMatrix, dstChart, true);
158 | }
159 | }
160 |
161 | public void setHighlight(boolean highlight) {
162 | isHighlight = highlight;
163 | }
164 |
165 | public interface OnEdgeListener {
166 | void edgeLoad(float x, boolean left);
167 | }
168 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/HighlightLineRenderer.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Paint;
5 | import android.graphics.RectF;
6 | import android.text.TextUtils;
7 |
8 | import com.github.mikephil.charting.animation.ChartAnimator;
9 | import com.github.mikephil.charting.charts.CombinedChart;
10 | import com.github.mikephil.charting.components.YAxis;
11 | import com.github.mikephil.charting.data.Entry;
12 | import com.github.mikephil.charting.data.LineData;
13 | import com.github.mikephil.charting.highlight.Highlight;
14 | import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
15 | import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
16 | import com.github.mikephil.charting.renderer.DataRenderer;
17 | import com.github.mikephil.charting.renderer.LineChartRenderer;
18 | import com.github.mikephil.charting.utils.MPPointD;
19 | import com.github.mikephil.charting.utils.Utils;
20 | import com.github.mikephil.charting.utils.ViewPortHandler;
21 |
22 | import java.text.DecimalFormat;
23 |
24 | /**
25 | * 自定义LineChart渲染器 绘制高亮 -- 绘制方式和自定义CandleStickChart渲染器相同
26 | * 使用方法: 1.先设置渲染器 {@link CombinedChart#setRenderer(DataRenderer)}
27 | * 传入自定义渲染器 将其中Line图的渲染器替换成此渲染器
28 | * 2.设置数据时 调用 {@link Entry#Entry(float, float, Object)}
29 | * 传入String类型的data 以绘制x的值 -- 如未设置 则只绘制竖线
30 | */
31 | public class HighlightLineRenderer extends LineChartRenderer {
32 |
33 | private float highlightSize;//图表高亮文字大小 单位:px
34 | private DecimalFormat format = new DecimalFormat("0.0000");
35 | private Highlight[] indices;
36 |
37 | public HighlightLineRenderer(LineDataProvider chart, ChartAnimator animator,
38 | ViewPortHandler viewPortHandler) {
39 | super(chart, animator, viewPortHandler);
40 | }
41 |
42 | public HighlightLineRenderer setHighlightSize(float textSize) {
43 | highlightSize = textSize;
44 | return this;
45 | }
46 |
47 | @Override
48 | public void drawHighlighted(Canvas c, Highlight[] indices) {
49 | this.indices = indices;
50 | }
51 |
52 | protected float getYPixelForValues(float x, float y) {
53 | MPPointD pixels = mChart.getTransformer(YAxis.AxisDependency.LEFT).getPixelForValues(x, y);
54 | return (float) pixels.y;
55 | }
56 |
57 | @Override
58 | public void drawExtras(Canvas c) {
59 | if (indices == null) {
60 | return;
61 | }
62 |
63 | LineData lineData = mChart.getLineData();
64 | for (Highlight high : indices) {
65 | ILineDataSet set = lineData.getDataSetByIndex(high.getDataSetIndex());
66 | if (set == null || !set.isHighlightEnabled())
67 | continue;
68 |
69 | Entry e = set.getEntryForXValue(high.getX(), high.getY());
70 | if (!isInBoundsX(e, set))
71 | continue;
72 |
73 | MPPointD pix = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(e.getX(),
74 | e.getY() * mAnimator.getPhaseY());
75 | float xp = (float) pix.x;
76 |
77 | mHighlightPaint.setColor(set.getHighLightColor());
78 | mHighlightPaint.setStrokeWidth(set.getHighlightLineWidth());
79 | mHighlightPaint.setTextSize(highlightSize);
80 |
81 | float xMin = mViewPortHandler.contentLeft();
82 | float xMax = mViewPortHandler.contentRight();
83 | float contentBottom = mViewPortHandler.contentBottom();
84 | //画竖线
85 | int halfPaddingVer = 5;//竖向半个padding
86 | int halfPaddingHor = 8;
87 | float textXHeight = 0;
88 |
89 | String textX;//高亮点的X显示文字
90 | Object data = e.getData();
91 | if (data != null && data instanceof String) {
92 | textX = (String) data;
93 | } else {
94 | textX = e.getX() + "";
95 | }
96 | if (!TextUtils.isEmpty(textX)) {//绘制x的值
97 | //先绘制文字框
98 | mHighlightPaint.setStyle(Paint.Style.STROKE);
99 | int width = Utils.calcTextWidth(mHighlightPaint, textX);
100 | int height = Utils.calcTextHeight(mHighlightPaint, textX);
101 | float left = Math.max(xMin, xp - width / 2F - halfPaddingVer);//考虑间隙
102 | float right = left + width + halfPaddingHor * 2;
103 | if (right > xMax) {
104 | right = xMax;
105 | left = right - width - halfPaddingHor * 2;
106 | }
107 | textXHeight = height + halfPaddingVer * 2;
108 | RectF rect = new RectF(left, 0, right, textXHeight);
109 | c.drawRect(rect, mHighlightPaint);
110 | //再绘制文字
111 | mHighlightPaint.setStyle(Paint.Style.FILL);
112 | Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
113 | float baseY = (height + halfPaddingVer * 2 - metrics.top - metrics.bottom) / 2;
114 | c.drawText(textX, left + halfPaddingHor, baseY, mHighlightPaint);
115 | }
116 | //绘制竖线
117 | c.drawLine(xp, textXHeight, xp, mChart.getHeight(), mHighlightPaint);
118 |
119 | //判断是否画横线
120 | float y = high.getDrawY();
121 | float yMaxValue = mChart.getYChartMax();
122 | float yMinValue = mChart.getYChartMin();
123 | float yMin = getYPixelForValues(xp, yMaxValue);
124 | float yMax = getYPixelForValues(xp, yMinValue);
125 | if (y >= 0 && y <= contentBottom) {//在区域内即绘制横线
126 | //先绘制文字框
127 | mHighlightPaint.setStyle(Paint.Style.STROKE);
128 | float yValue = (yMax - y) / (yMax - yMin) * (yMaxValue - yMinValue) + yMinValue;
129 | String text = format.format(yValue);
130 | int width = Utils.calcTextWidth(mHighlightPaint, text);
131 | int height = Utils.calcTextHeight(mHighlightPaint, text);
132 | float top = Math.max(0, y - height / 2F - halfPaddingVer);//考虑间隙
133 | float bottom = top + height + halfPaddingVer * 2;
134 | if (bottom > contentBottom) {
135 | bottom = contentBottom;
136 | top = bottom - height - halfPaddingVer * 2;
137 | }
138 | RectF rect = new RectF(xMax - width - halfPaddingHor * 2, top, xMax, bottom);
139 | c.drawRect(rect, mHighlightPaint);
140 | //再绘制文字
141 | mHighlightPaint.setStyle(Paint.Style.FILL);
142 | Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
143 | float baseY = (top + bottom - metrics.top - metrics.bottom) / 2;
144 | c.drawText(text, xMax - width - halfPaddingHor, baseY, mHighlightPaint);
145 | //绘制横线
146 | c.drawLine(0, y, xMax - width - halfPaddingHor * 2, y, mHighlightPaint);
147 | }
148 | }
149 | indices = null;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/HighlightCandleRenderer.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Paint;
5 | import android.graphics.RectF;
6 | import android.text.TextUtils;
7 |
8 | import com.github.mikephil.charting.animation.ChartAnimator;
9 | import com.github.mikephil.charting.charts.CombinedChart;
10 | import com.github.mikephil.charting.components.YAxis;
11 | import com.github.mikephil.charting.data.CandleData;
12 | import com.github.mikephil.charting.data.CandleEntry;
13 | import com.github.mikephil.charting.highlight.Highlight;
14 | import com.github.mikephil.charting.interfaces.dataprovider.CandleDataProvider;
15 | import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet;
16 | import com.github.mikephil.charting.renderer.CandleStickChartRenderer;
17 | import com.github.mikephil.charting.renderer.DataRenderer;
18 | import com.github.mikephil.charting.utils.MPPointD;
19 | import com.github.mikephil.charting.utils.Utils;
20 | import com.github.mikephil.charting.utils.ViewPortHandler;
21 |
22 | import java.text.DecimalFormat;
23 |
24 | /**
25 | * 自定义CandleStickChart渲染器 绘制高亮 -- 绘制方式和自定义LineChart渲染器相同
26 | * 使用方法: 1.先设置渲染器 {@link CombinedChart#setRenderer(DataRenderer)}
27 | * 传入自定义渲染器 将其中Candle图的渲染器替换成此渲染器
28 | * 2.设置数据时 调用 {@link CandleEntry#CandleEntry(float, float, float, float, float, Object)}
29 | * 传入String类型的data 以绘制x的值 -- 如未设置 则只绘制竖线
30 | */
31 | public class HighlightCandleRenderer extends CandleStickChartRenderer {
32 |
33 | private float highlightSize;//图表高亮文字大小 单位:px
34 | private DecimalFormat format = new DecimalFormat("0.0000");
35 | private Highlight[] indices;
36 |
37 | public HighlightCandleRenderer(CandleDataProvider chart, ChartAnimator animator,
38 | ViewPortHandler viewPortHandler) {
39 | super(chart, animator, viewPortHandler);
40 | }
41 |
42 | public HighlightCandleRenderer setHighlightSize(float textSize) {
43 | highlightSize = textSize;
44 | return this;
45 | }
46 |
47 | @Override
48 | public void drawHighlighted(Canvas c, Highlight[] indices) {
49 | this.indices = indices;
50 | }
51 |
52 | protected float getYPixelForValues(float x, float y) {
53 | MPPointD pixels = mChart.getTransformer(YAxis.AxisDependency.LEFT).getPixelForValues(x, y);
54 | return (float) pixels.y;
55 | }
56 |
57 | @Override
58 | public void drawExtras(Canvas c) {
59 | if (indices == null) {
60 | return;
61 | }
62 |
63 | CandleData candleData = mChart.getCandleData();
64 | for (Highlight high : indices) {
65 | ICandleDataSet set = candleData.getDataSetByIndex(high.getDataSetIndex());
66 | if (set == null || !set.isHighlightEnabled())
67 | continue;
68 |
69 | CandleEntry e = set.getEntryForXValue(high.getX(), high.getY());
70 | if (!isInBoundsX(e, set))
71 | continue;
72 |
73 | float lowValue = e.getLow() * mAnimator.getPhaseY();
74 | float highValue = e.getHigh() * mAnimator.getPhaseY();
75 | MPPointD pix = mChart.getTransformer(set.getAxisDependency())
76 | .getPixelForValues(e.getX(), (lowValue + highValue) / 2f);
77 | float xp = (float) pix.x;
78 |
79 | mHighlightPaint.setColor(set.getHighLightColor());
80 | mHighlightPaint.setStrokeWidth(set.getHighlightLineWidth());
81 | mHighlightPaint.setTextSize(highlightSize);
82 |
83 | float xMin = mViewPortHandler.contentLeft();
84 | float xMax = mViewPortHandler.contentRight();
85 | float contentBottom = mViewPortHandler.contentBottom();
86 | //画竖线
87 | int halfPaddingVer = 5;//竖向半个padding
88 | int halfPaddingHor = 8;
89 | float textXHeight = 0;
90 |
91 | String textX;//高亮点的X显示文字
92 | Object data = e.getData();
93 | if (data != null && data instanceof String) {
94 | textX = (String) data;
95 | } else {
96 | textX = e.getX() + "";
97 | }
98 | if (!TextUtils.isEmpty(textX)) {//绘制x的值
99 | //先绘制文字框
100 | mHighlightPaint.setStyle(Paint.Style.STROKE);
101 | int width = Utils.calcTextWidth(mHighlightPaint, textX);
102 | int height = Utils.calcTextHeight(mHighlightPaint, textX);
103 | float left = Math.max(xMin, xp - width / 2F - halfPaddingVer);//考虑间隙
104 | float right = left + width + halfPaddingHor * 2;
105 | if (right > xMax) {
106 | right = xMax;
107 | left = right - width - halfPaddingHor * 2;
108 | }
109 | textXHeight = height + halfPaddingVer * 2;
110 | RectF rect = new RectF(left, 0, right, textXHeight);
111 | c.drawRect(rect, mHighlightPaint);
112 | //再绘制文字
113 | mHighlightPaint.setStyle(Paint.Style.FILL);
114 | Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
115 | float baseY = (height + halfPaddingVer * 2 - metrics.top - metrics.bottom) / 2;
116 | c.drawText(textX, left + halfPaddingHor, baseY, mHighlightPaint);
117 | }
118 | //绘制竖线
119 | c.drawLine(xp, textXHeight, xp, mChart.getHeight(), mHighlightPaint);
120 |
121 | //判断是否画横线
122 | float y = high.getDrawY();
123 | float yMaxValue = mChart.getYChartMax();
124 | float yMinValue = mChart.getYChartMin();
125 | float yMin = getYPixelForValues(xp, yMaxValue);
126 | float yMax = getYPixelForValues(xp, yMinValue);
127 | if (y >= 0 && y <= contentBottom) {//在区域内即绘制横线
128 | //先绘制文字框
129 | mHighlightPaint.setStyle(Paint.Style.STROKE);
130 | float yValue = (yMax - y) / (yMax - yMin) * (yMaxValue - yMinValue) + yMinValue;
131 | String text = format.format(yValue);
132 | int width = Utils.calcTextWidth(mHighlightPaint, text);
133 | int height = Utils.calcTextHeight(mHighlightPaint, text);
134 | float top = Math.max(0, y - height / 2F - halfPaddingVer);//考虑间隙
135 | float bottom = top + height + halfPaddingVer * 2;
136 | if (bottom > contentBottom) {
137 | bottom = contentBottom;
138 | top = bottom - height - halfPaddingVer * 2;
139 | }
140 | RectF rect = new RectF(xMax - width - halfPaddingHor * 2, top, xMax, bottom);
141 | c.drawRect(rect, mHighlightPaint);
142 | //再绘制文字
143 | mHighlightPaint.setStyle(Paint.Style.FILL);
144 | Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
145 | float baseY = (top + bottom - metrics.top - metrics.bottom) / 2;
146 | c.drawText(text, xMax - width - halfPaddingHor, baseY, mHighlightPaint);
147 | //绘制横线
148 | c.drawLine(0, y, xMax - width - halfPaddingHor * 2, y, mHighlightPaint);
149 | }
150 | }
151 | indices = null;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/chart/OffsetBarRenderer.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo.chart;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Color;
5 | import android.graphics.Paint;
6 | import android.graphics.RectF;
7 |
8 | import com.github.mikephil.charting.animation.ChartAnimator;
9 | import com.github.mikephil.charting.buffer.BarBuffer;
10 | import com.github.mikephil.charting.charts.BarChart;
11 | import com.github.mikephil.charting.components.YAxis;
12 | import com.github.mikephil.charting.data.BarData;
13 | import com.github.mikephil.charting.data.BarDataSet;
14 | import com.github.mikephil.charting.data.BarEntry;
15 | import com.github.mikephil.charting.highlight.Highlight;
16 | import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider;
17 | import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
18 | import com.github.mikephil.charting.renderer.BarChartRenderer;
19 | import com.github.mikephil.charting.renderer.DataRenderer;
20 | import com.github.mikephil.charting.utils.MPPointD;
21 | import com.github.mikephil.charting.utils.Transformer;
22 | import com.github.mikephil.charting.utils.Utils;
23 | import com.github.mikephil.charting.utils.ViewPortHandler;
24 |
25 | import java.text.DecimalFormat;
26 |
27 | /**
28 | * 自定义BarChart渲染器 使Bar的颜色根据取值来实现 自定义高亮
29 | * 只修改 {@link #drawDataSet(Canvas, IBarDataSet, int)} 中设置多种颜色的情况
30 | * 使用方法: 1.先设置渲染器 {@link BarChart#setRenderer(DataRenderer)} 传入此渲染器
31 | * 2.再调用 {@link BarDataSet#setColors(int...)} 设置多种颜色;
32 | * 3.设置数据时 调用 {@link BarEntry#BarEntry(float, float, Object)} 传入Integer类型的data指明第几种颜色.
33 | */
34 | public class OffsetBarRenderer extends BarChartRenderer {
35 |
36 | protected float barOffset;//BarChart绘制时偏移多少个单位 --小于0时向左偏移
37 | protected float highlightWidth, highlightSize;//高亮线宽度 单位:dp / 高亮文字大小 单位:px
38 | private RectF mBarShadowRectBuffer = new RectF();
39 | private DecimalFormat format = new DecimalFormat("0.0000");//保留小数点后四位
40 |
41 | public OffsetBarRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
42 | this(chart, animator, viewPortHandler, 0);
43 | }
44 |
45 | public OffsetBarRenderer(BarDataProvider chart, ChartAnimator animator,
46 | ViewPortHandler viewPortHandler, float barOffsetCount) {
47 | super(chart, animator, viewPortHandler);
48 | barOffset = barOffsetCount;
49 | }
50 |
51 | @Override
52 | public void initBuffers() {
53 | BarData barData = mChart.getBarData();
54 | mBarBuffers = new OffsetBarBuffer[barData.getDataSetCount()];
55 |
56 | for (int i = 0; i < mBarBuffers.length; i++) {
57 | IBarDataSet set = barData.getDataSetByIndex(i);
58 | mBarBuffers[i] = new OffsetBarBuffer(set.getEntryCount() * 4 *
59 | (set.isStacked() ? set.getStackSize() : 1), barData.getDataSetCount(),
60 | set.isStacked(), barOffset);
61 | }
62 | }
63 |
64 | @Override
65 | protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
66 | Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
67 | mBarBorderPaint.setColor(dataSet.getBarBorderColor());
68 | mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth()));
69 |
70 | final boolean drawBorder = dataSet.getBarBorderWidth() > 0.f;
71 | float phaseX = mAnimator.getPhaseX();
72 | float phaseY = mAnimator.getPhaseY();
73 |
74 | // draw the bar shadow before the values
75 | if (mChart.isDrawBarShadowEnabled()) {
76 | mShadowPaint.setColor(dataSet.getBarShadowColor());
77 |
78 | BarData barData = mChart.getBarData();
79 | final float barWidth = barData.getBarWidth();
80 | final float barWidthHalf = barWidth / 2.0f;
81 | float x;
82 |
83 | for (int i = 0, count = Math.min((int)(Math.ceil((float)(dataSet.getEntryCount()) * phaseX)),
84 | dataSet.getEntryCount()); i < count; i++) {
85 |
86 | BarEntry e = dataSet.getEntryForIndex(i);
87 | x = e.getX();
88 | mBarShadowRectBuffer.left = x - barWidthHalf;
89 | mBarShadowRectBuffer.right = x + barWidthHalf;
90 | trans.rectValueToPixel(mBarShadowRectBuffer);
91 |
92 | if (!mViewPortHandler.isInBoundsLeft(mBarShadowRectBuffer.right))
93 | continue;
94 | if (!mViewPortHandler.isInBoundsRight(mBarShadowRectBuffer.left))
95 | break;
96 |
97 | mBarShadowRectBuffer.top = mViewPortHandler.contentTop();
98 | mBarShadowRectBuffer.bottom = mViewPortHandler.contentBottom();
99 | c.drawRect(mBarShadowRectBuffer, mShadowPaint);
100 | }
101 | }
102 |
103 | // initialize the buffer
104 | BarBuffer buffer = mBarBuffers[index];
105 | buffer.setPhases(phaseX, phaseY);
106 | buffer.setDataSet(index);
107 | buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
108 | buffer.setBarWidth(mChart.getBarData().getBarWidth());
109 |
110 | buffer.feed(dataSet);
111 | trans.pointValuesToPixel(buffer.buffer);
112 |
113 | int size = dataSet.getColors().size();
114 | final boolean isSingleColor = size == 1;
115 | if (isSingleColor) {
116 | mRenderPaint.setColor(dataSet.getColor());
117 | }
118 |
119 | for (int j = 0; j < buffer.size(); j += 4) {
120 | if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2]))
121 | continue;
122 | if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))
123 | break;
124 | if (!isSingleColor) {
125 | // Set the color for the currently drawn value. If the index
126 | // is out of bounds, reuse colors.
127 | BarEntry entry = dataSet.getEntryForIndex(j / 4);
128 | Object data = entry.getData();
129 | if (data == null || !(data instanceof Integer)) {
130 | mRenderPaint.setColor(dataSet.getColor(j / 4));
131 | } else {
132 | int i = (int) data;
133 | mRenderPaint.setColor(size > 1 ? dataSet.getColors().get(i % size) : Color.BLACK);
134 | }
135 | }
136 |
137 | c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
138 | buffer.buffer[j + 3], mRenderPaint);
139 | if (drawBorder) {
140 | c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
141 | buffer.buffer[j + 3], mBarBorderPaint);
142 | }
143 | }
144 | }
145 |
146 | public OffsetBarRenderer setHighlightWidthSize(float width, float textSize) {
147 | highlightWidth = Utils.convertDpToPixel(width);
148 | highlightSize = textSize;
149 | return this;
150 | }
151 |
152 | @Override
153 | public void drawHighlighted(Canvas c, Highlight[] indices) {
154 | BarData barData = mChart.getBarData();
155 | for (Highlight high : indices) {
156 | IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex());
157 | if (set == null || !set.isHighlightEnabled())
158 | continue;
159 | BarEntry e = set.getEntryForXValue(high.getX(), high.getY());
160 | if (!isInBoundsX(e, set))
161 | continue;
162 |
163 | mHighlightPaint.setColor(set.getHighLightColor());
164 | mHighlightPaint.setStrokeWidth(highlightWidth);
165 | mHighlightPaint.setTextSize(highlightSize);
166 |
167 | //画竖线
168 | float barWidth = barData.getBarWidth();
169 | Transformer trans = mChart.getTransformer(set.getAxisDependency());
170 | prepareBarHighlight(e.getX() + barOffset, 0, 0, barWidth / 2, trans);
171 |
172 | float xp = mBarRect.centerX();
173 | c.drawLine(xp, mViewPortHandler.getContentRect().bottom, xp, 0, mHighlightPaint);
174 |
175 | //判断是否画横线
176 | float y = high.getDrawY();
177 | float yMaxValue = mChart.getYChartMax();
178 | float yMin = getYPixelForValues(xp, yMaxValue);
179 | float yMax = getYPixelForValues(xp, 0);
180 | if (y >= 0 && y <= yMax) {//在区域内即绘制横线
181 | float xMax = mChart.getWidth();
182 | int halfPaddingVer = 5;//竖向半个padding
183 | int halfPaddingHor = 8;
184 | //先绘制文字框
185 | mHighlightPaint.setStyle(Paint.Style.STROKE);
186 | float yValue = (yMax - y) / (yMax - yMin) * yMaxValue;
187 | String text = format.format(yValue);
188 | int width = Utils.calcTextWidth(mHighlightPaint, text);
189 | int height = Utils.calcTextHeight(mHighlightPaint, text);
190 | float top = Math.max(0, y - height / 2F - halfPaddingVer);//考虑间隙
191 | float bottom = top + height + halfPaddingVer * 2;
192 | if (bottom > yMax) {
193 | bottom = yMax;
194 | top = bottom - height - halfPaddingVer * 2;
195 | }
196 | RectF rect = new RectF(xMax - width - halfPaddingHor * 2, top, xMax, bottom);
197 | c.drawRect(rect, mHighlightPaint);
198 | //再绘制文字
199 | mHighlightPaint.setStyle(Paint.Style.FILL);
200 | Paint.FontMetrics metrics = mHighlightPaint.getFontMetrics();
201 | float baseY = (top + bottom - metrics.top - metrics.bottom) / 2;
202 | c.drawText(text, xMax - width - halfPaddingHor, baseY, mHighlightPaint);
203 | //绘制横线
204 | c.drawLine(0, y, xMax - width - halfPaddingHor * 2, y, mHighlightPaint);
205 | }
206 | }
207 | }
208 |
209 | protected float getYPixelForValues(float x, float y) {
210 | MPPointD pixels = mChart.getTransformer(YAxis.AxisDependency.LEFT).getPixelForValues(x, y);
211 | return (float) pixels.y;
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
13 |
24 |
29 |
40 |
49 |
58 |
66 |
67 |
76 |
87 |
98 |
107 |
118 |
127 |
138 |
139 |
148 |
163 |
173 |
185 |
197 |
207 |
217 |
228 |
240 |
251 |
261 |
272 |
283 |
284 |
285 |
289 |
293 |
301 |
302 |
307 |
308 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-port/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
13 |
25 |
34 |
45 |
54 |
63 |
71 |
72 |
82 |
93 |
104 |
115 |
124 |
135 |
144 |
159 |
169 |
180 |
192 |
202 |
212 |
223 |
235 |
245 |
255 |
266 |
277 |
278 |
279 |
283 |
287 |
294 |
302 |
303 |
308 |
309 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/qianlan/klinedemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package cn.qianlan.klinedemo;
2 |
3 | import android.content.pm.ActivityInfo;
4 | import android.content.res.Configuration;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.support.constraint.ConstraintLayout;
10 | import android.support.design.widget.TabLayout;
11 | import android.support.v4.content.ContextCompat;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.text.Html;
14 | import android.text.Spanned;
15 | import android.text.TextUtils;
16 | import android.util.Log;
17 | import android.util.TypedValue;
18 | import android.view.LayoutInflater;
19 | import android.view.MotionEvent;
20 | import android.view.View;
21 | import android.widget.ImageView;
22 | import android.widget.TextView;
23 |
24 | import com.github.mikephil.charting.charts.BarChart;
25 | import com.github.mikephil.charting.charts.CombinedChart;
26 | import com.github.mikephil.charting.components.AxisBase;
27 | import com.github.mikephil.charting.components.XAxis;
28 | import com.github.mikephil.charting.components.YAxis;
29 | import com.github.mikephil.charting.data.BarData;
30 | import com.github.mikephil.charting.data.BarDataSet;
31 | import com.github.mikephil.charting.data.BarEntry;
32 | import com.github.mikephil.charting.data.CandleData;
33 | import com.github.mikephil.charting.data.CandleDataSet;
34 | import com.github.mikephil.charting.data.CandleEntry;
35 | import com.github.mikephil.charting.data.CombinedData;
36 | import com.github.mikephil.charting.data.Entry;
37 | import com.github.mikephil.charting.data.LineData;
38 | import com.github.mikephil.charting.data.LineDataSet;
39 | import com.github.mikephil.charting.formatter.IAxisValueFormatter;
40 | import com.github.mikephil.charting.utils.Transformer;
41 | import com.google.gson.Gson;
42 | import com.google.gson.GsonBuilder;
43 |
44 | import java.text.DecimalFormat;
45 | import java.text.ParseException;
46 | import java.text.SimpleDateFormat;
47 | import java.util.ArrayList;
48 | import java.util.Date;
49 | import java.util.HashMap;
50 | import java.util.List;
51 | import java.util.Locale;
52 | import java.util.Map;
53 |
54 | import cn.qianlan.klinedemo.chart.ChartFingerTouchListener;
55 | import cn.qianlan.klinedemo.chart.CoupleChartGestureListener;
56 | import cn.qianlan.klinedemo.chart.CoupleChartValueSelectedListener;
57 | import cn.qianlan.klinedemo.chart.HighlightCombinedRenderer;
58 | import cn.qianlan.klinedemo.chart.InBoundXAxisRenderer;
59 | import cn.qianlan.klinedemo.chart.InBoundYAxisRenderer;
60 | import cn.qianlan.klinedemo.chart.OffsetBarRenderer;
61 |
62 | public class MainActivity extends AppCompatActivity implements View.OnClickListener,
63 | TabLayout.OnTabSelectedListener, OkHttpUtil.OnDataListener, CoupleChartGestureListener.OnEdgeListener,
64 | CoupleChartValueSelectedListener.ValueSelectedListener, ChartFingerTouchListener.HighlightListener {
65 |
66 | private ImageView ivBack;
67 | private ImageView ivOri;
68 |
69 | private TabLayout tabLayout;
70 | private ConstraintLayout clHl;
71 |
72 | private TextView tvOpen;
73 | private TextView tvClose;
74 | private TextView tvHigh;
75 | private TextView tvLow;
76 | private TextView tvVol;
77 | private TextView tvLine;
78 |
79 | private CombinedChart cc;
80 | private BarChart bc;
81 |
82 | private boolean toLeft;
83 | private int range = 52;//一屏显示Candle个数
84 | private int index = 2;//TabLayout选中下标
85 | private float highVisX;//切屏时X轴的最大值
86 |
87 | private List> dataList;
88 | private Map xValues;
89 | private LineDataSet lineSetMin;//分时线
90 | private LineDataSet lineSet5;
91 | private LineDataSet lineSet10;
92 | private CandleDataSet candleSet;
93 | private CombinedData combinedData;
94 | private BarDataSet barSet;
95 | private final float barOffset = -0.5F;//BarChart偏移量
96 | private CoupleChartGestureListener ccGesture;
97 | private CoupleChartGestureListener bcGesture;
98 |
99 | //K线接口 参考文档:https://github.com/Dragonexio/OpenApi/blob/master/docs/%E4%B8%AD%E6%96%87/1.%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3_v1.md
100 | private String URL = "https://openapi.dragonex.im/api/v1/market/kline/?symbol_id=103&st=%s&direction=%s&count=100&kline_type=%s";
101 | private final String[] KLINE = {"1m", "5m", "15m", "30m", "1h", "1d"};
102 | private int[] KL_TYPE = {1, 2, 3, 4, 5, 6};
103 | private int[] KL_INTERVAL = {1, 5, 15, 30, 60, 1440};//单位: Min
104 | private final long M1 = 60 * 1000L;//1 Min的毫秒数
105 |
106 | //5日均线、10日均线
107 | private String Kline5_10 = "MA5:%s MA10:%s";
108 |
109 | private LoadingDialog loadingDialog;
110 | private Gson gson = new GsonBuilder().create();
111 |
112 | private DecimalFormat format4p = new DecimalFormat("0.0000");//格式化数字,保留小数点后4位
113 | private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
114 |
115 | @Override
116 | protected void onCreate(Bundle savedInstanceState) {
117 | super.onCreate(savedInstanceState);
118 | setContentView(R.layout.activity_main);
119 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//默认竖屏
120 | initView();
121 | loadData();
122 | }
123 |
124 | private void initView() {
125 | ivBack = findViewById(R.id.iv_klBack);
126 | ivOri = findViewById(R.id.iv_klOrientation);
127 |
128 | tabLayout = findViewById(R.id.tl_kl);
129 | clHl = findViewById(R.id.cl_klHighlight);
130 |
131 | tvOpen = findViewById(R.id.tv_klOpen);
132 | tvClose = findViewById(R.id.tv_klClose);
133 | tvHigh = findViewById(R.id.tv_klHigh);
134 | tvLow = findViewById(R.id.tv_klLow);
135 | tvVol = findViewById(R.id.tv_klVol);
136 | tvLine = findViewById(R.id.tv_klLineInfo);
137 |
138 | cc = findViewById(R.id.cc_kl);
139 | bc = findViewById(R.id.bc_kl);
140 |
141 | ivBack.setOnClickListener(this);
142 | ivOri.setOnClickListener(this);
143 |
144 | for (int i = 0; i < KLINE.length; i++) {
145 | TextView v = (TextView) LayoutInflater.from(this).inflate(R.layout.item_tab_kline, null);
146 | v.setText(KLINE[i]);
147 | tabLayout.addTab(tabLayout.newTab().setCustomView(v), i == index);
148 | }
149 | tabLayout.addOnTabSelectedListener(this);
150 |
151 | initChart();
152 | }
153 |
154 | private void initChart() {
155 | int black = getColorById(R.color.black3B);
156 | int gray = getColorById(R.color.gray8B);
157 | int red = getColorById(R.color.redEB);
158 | int green = getColorById(R.color.green4C);
159 | int highlightColor = getColorById(R.color.brown);
160 | float highlightWidth = 0.5F;//高亮线的线宽
161 | float sp8 = sp2px(8);
162 | //K线
163 | cc.setNoDataTextColor(gray);//无数据时提示文字的颜色
164 | cc.setDescription(null);//取消描述
165 | cc.getLegend().setEnabled(false);//取消图例
166 | cc.setDragDecelerationEnabled(false);//不允许甩动惯性滑动 和moveView方法有冲突 设置为false
167 | cc.setMinOffset(0);//设置外边缘偏移量
168 | cc.setExtraBottomOffset(6);//设置底部外边缘偏移量 便于显示X轴
169 |
170 | cc.setScaleEnabled(false);//不可缩放
171 | cc.setAutoScaleMinMaxEnabled(true);//自适应最大最小值
172 | cc.setDrawOrder(new CombinedChart.DrawOrder[]{CombinedChart.DrawOrder.CANDLE,
173 | CombinedChart.DrawOrder.LINE});
174 | Transformer trans = cc.getTransformer(YAxis.AxisDependency.LEFT);
175 | //自定义X轴标签位置
176 | cc.setXAxisRenderer(new InBoundXAxisRenderer(cc.getViewPortHandler(), cc.getXAxis(), trans, 10));
177 | //自定义Y轴标签位置
178 | cc.setRendererLeftYAxis(new InBoundYAxisRenderer(cc.getViewPortHandler(), cc.getAxisLeft(), trans));
179 | //自定义渲染器 重绘高亮
180 | cc.setRenderer(new HighlightCombinedRenderer(cc, cc.getAnimator(), cc.getViewPortHandler(), sp8));
181 |
182 | //X轴
183 | XAxis xac = cc.getXAxis();
184 | xac.setPosition(XAxis.XAxisPosition.BOTTOM);
185 | xac.setGridColor(black);//网格线颜色
186 | xac.setTextColor(gray);//标签颜色
187 | xac.setTextSize(8);//标签字体大小
188 | xac.setAxisLineColor(black);//轴线颜色
189 | xac.disableAxisLineDashedLine();//取消轴线虚线设置
190 | xac.setAvoidFirstLastClipping(true);//避免首尾端标签被裁剪
191 | xac.setLabelCount(2, true);//强制显示2个标签
192 | xac.setValueFormatter(new IAxisValueFormatter() {//转换X轴的数字为文字
193 | @Override
194 | public String getFormattedValue(float value, AxisBase axis) {
195 | int v = (int) value;
196 | if (!xValues.containsKey(v) && xValues.containsKey(v - 1)) {
197 | v = v - 1;
198 | }
199 | String x = xValues.get(v);
200 | return TextUtils.isEmpty(x) ? "" : x;
201 | }
202 | });
203 |
204 | //左Y轴
205 | YAxis yac = cc.getAxisLeft();
206 | yac.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);//标签显示在内侧
207 | yac.setGridColor(black);
208 | yac.setTextColor(gray);
209 | yac.setTextSize(8);
210 | yac.setLabelCount(5, true);
211 | yac.enableGridDashedLine(5, 4, 0);//横向网格线设置为虚线
212 | yac.setValueFormatter(new IAxisValueFormatter() {
213 | @Override
214 | public String getFormattedValue(float value, AxisBase axis) {//只显示部分标签
215 | int index = getIndexY(value, axis.getAxisMinimum(), axis.getAxisMaximum());
216 | return index == 0 || index == 2 ? format4p.format(value) : "";//不显示的标签不能返回null
217 | }
218 | });
219 | //右Y轴
220 | cc.getAxisRight().setEnabled(false);
221 |
222 | //蜡烛图
223 | candleSet = new CandleDataSet(new ArrayList(), "Kline");
224 | candleSet.setAxisDependency(YAxis.AxisDependency.LEFT);
225 | candleSet.setDrawHorizontalHighlightIndicator(false);
226 | candleSet.setHighlightLineWidth(highlightWidth);
227 | candleSet.setHighLightColor(highlightColor);
228 | candleSet.setShadowWidth(0.7f);
229 | candleSet.setIncreasingColor(red);//上涨为红色
230 | candleSet.setIncreasingPaintStyle(Paint.Style.FILL);
231 | candleSet.setDecreasingColor(green);//下跌为绿色
232 | candleSet.setDecreasingPaintStyle(Paint.Style.STROKE);
233 | candleSet.setNeutralColor(red);
234 | candleSet.setShadowColorSameAsCandle(true);
235 | candleSet.setDrawValues(false);
236 | candleSet.setHighlightEnabled(false);
237 | //均线
238 | lineSet5 = new LineDataSet(new ArrayList(), "MA5");
239 | lineSet5.setAxisDependency(YAxis.AxisDependency.LEFT);
240 | lineSet5.setColor(getColorById(R.color.purple));
241 | lineSet5.setDrawCircles(false);
242 | lineSet5.setDrawValues(false);
243 | lineSet5.setHighlightEnabled(false);
244 | lineSet10 = new LineDataSet(new ArrayList(), "MA10");
245 | lineSet10.setAxisDependency(YAxis.AxisDependency.LEFT);
246 | lineSet10.setColor(getColorById(R.color.yellow));
247 | lineSet10.setDrawCircles(false);
248 | lineSet10.setDrawValues(false);
249 | lineSet10.setHighlightEnabled(false);
250 | //分时线
251 | lineSetMin = new LineDataSet(new ArrayList(), "Minutes");
252 | lineSetMin.setAxisDependency(YAxis.AxisDependency.LEFT);
253 | lineSetMin.setColor(Color.WHITE);
254 | lineSetMin.setDrawCircles(false);
255 | lineSetMin.setDrawValues(false);
256 | lineSetMin.setDrawFilled(true);
257 | lineSetMin.setHighlightEnabled(false);
258 | lineSetMin.setFillColor(gray);
259 | lineSetMin.setFillAlpha(60);
260 |
261 |
262 | //成交量
263 | bc.setNoDataTextColor(gray);
264 | bc.setDescription(null);
265 | bc.getLegend().setEnabled(false);
266 | bc.setDragDecelerationEnabled(false);//不允许甩动惯性滑动
267 | bc.setMinOffset(0);//设置外边缘偏移量
268 |
269 | bc.setScaleEnabled(false);//不可缩放
270 | bc.setAutoScaleMinMaxEnabled(true);//自适应最大最小值
271 | //自定义Y轴标签位置
272 | bc.setRendererLeftYAxis(new InBoundYAxisRenderer(bc.getViewPortHandler(), bc.getAxisLeft(),
273 | bc.getTransformer(YAxis.AxisDependency.LEFT)));
274 | //设置渲染器控制颜色、偏移,以及高亮
275 | bc.setRenderer(new OffsetBarRenderer(bc, bc.getAnimator(), bc.getViewPortHandler(), barOffset)
276 | .setHighlightWidthSize(highlightWidth, sp8));
277 |
278 | bc.getXAxis().setEnabled(false);
279 | YAxis yab = bc.getAxisLeft();
280 | yab.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);//标签显示在内侧
281 | yab.setDrawAxisLine(false);
282 | yab.setGridColor(black);
283 | yab.setTextColor(gray);
284 | yab.setTextSize(8);
285 | yab.setLabelCount(2, true);
286 | yab.setAxisMinimum(0);
287 | yab.setValueFormatter(new IAxisValueFormatter() {
288 | @Override
289 | public String getFormattedValue(float value, AxisBase axis) {
290 | return value == 0 ? "" : value + "";
291 | }
292 | });
293 | bc.getAxisRight().setEnabled(false);
294 |
295 | barSet = new BarDataSet(new ArrayList(), "VOL");
296 | barSet.setHighLightColor(highlightColor);
297 | barSet.setColors(red, green);
298 | barSet.setDrawValues(false);
299 | barSet.setHighlightEnabled(false);
300 |
301 | ccGesture = new CoupleChartGestureListener(this, cc, bc) {//设置成全局变量,后续要用到
302 | @Override
303 | public void chartDoubleTapped(MotionEvent me) {
304 | doubleTapped();
305 | }
306 | };
307 | cc.setOnChartGestureListener(ccGesture);//设置手势联动监听
308 | bcGesture = new CoupleChartGestureListener(this, bc, cc) {
309 | @Override
310 | public void chartDoubleTapped(MotionEvent me) {
311 | doubleTapped();
312 | }
313 | };
314 | bc.setOnChartGestureListener(bcGesture);
315 |
316 | cc.setOnChartValueSelectedListener(new CoupleChartValueSelectedListener(this, cc, bc));//设置高亮联动监听
317 | bc.setOnChartValueSelectedListener(new CoupleChartValueSelectedListener(this, bc, cc));
318 | cc.setOnTouchListener(new ChartFingerTouchListener(cc, this));//手指长按滑动高亮
319 | bc.setOnTouchListener(new ChartFingerTouchListener(bc, this));
320 | }
321 |
322 | /**
323 | * 计算value是当前Y轴的第几个
324 | */
325 | private int getIndexY(float value, float min, float max) {
326 | float piece = (max - min) / 4;
327 | return Math.round((value - min) / piece);
328 | }
329 |
330 | protected void loadData() {
331 | clearChart();
332 | toLeft = true;
333 | getData("0");
334 | }
335 |
336 | private void clearChart() {
337 | if (dataList == null) {
338 | dataList = new ArrayList<>();
339 | } else {
340 | dataList.clear();
341 | }
342 | if (xValues == null) {
343 | xValues = new HashMap<>();
344 | } else {
345 | xValues.clear();
346 | }
347 | cc.setNoDataText("加载中...");
348 | cc.clear();
349 | bc.setNoDataText("加载中...");
350 | bc.clear();
351 | }
352 |
353 | private void getData(String time) {
354 | String url = String.format(URL, time, toLeft ? "2" : "1", KL_TYPE[index]);
355 | OkHttpUtil.getJSON(url, this);
356 | }
357 |
358 | @Override
359 | public void onTabSelected(TabLayout.Tab tab) {
360 | index = tab.getPosition();
361 | loadData();
362 | }
363 |
364 | @Override
365 | public void onTabReselected(TabLayout.Tab tab) {
366 | onTabSelected(tab);
367 | }
368 |
369 | @Override
370 | public void onTabUnselected(TabLayout.Tab tab) {}
371 |
372 | @Override
373 | public void onResponse(String url, String json) {
374 | Log.e("loge", "Kline: " + json);
375 | KlineEntity kl = gson.fromJson(json, KlineEntity.class);
376 | if (kl.isOk()) {
377 | int size = xValues.size();
378 | List> lists = kl.getData().getLists();
379 | if (lists.size() <= 0) {
380 | dismissLoading();
381 | return;
382 | }
383 | if (lists.size() == 1) {
384 | long time = Long.parseLong(lists.get(0).get(6)) * 1000;
385 | String x = sdf.format(new Date(time));
386 | if (!xValues.containsValue(x)) {
387 | handleData(lists, size);
388 | }
389 | } else {
390 | handleData(lists, size);
391 | }
392 | }
393 | dismissLoading();
394 | }
395 |
396 | /**
397 | * size是指追加数据之前,已有的数据个数
398 | */
399 | private void handleData(List> lists, int size) {
400 | if (toLeft) {
401 | dataList.addAll(0, lists);//添加到左侧
402 | } else {
403 | dataList.addAll(lists);
404 | }
405 |
406 | configData();
407 | if (xValues.size() > 0) {
408 | int x = xValues.size() - (toLeft ? size : 0);
409 | //如果设置了惯性甩动 move方法将会无效
410 | if (!toLeft && size > 0) {
411 | cc.moveViewToAnimated(x, 0, YAxis.AxisDependency.LEFT, 200);
412 | bc.moveViewToAnimated(x + barOffset, 0, YAxis.AxisDependency.LEFT, 200);
413 | } else {
414 | cc.moveViewToX(x);
415 | bc.moveViewToX(x + barOffset);
416 | }
417 | cc.notifyDataSetChanged();
418 | bc.notifyDataSetChanged();
419 | }
420 | }
421 |
422 | private void configData() {
423 | if (dataList.size() == 0) {
424 | cc.setNoDataText("暂无相关数据");
425 | cc.clear();
426 | bc.setNoDataText("暂无相关数据");
427 | bc.clear();
428 | } else {
429 | if (combinedData == null) {
430 | combinedData = new CombinedData();
431 | }
432 | xValues.clear();
433 | List candleValues = candleSet.getValues();
434 | candleValues.clear();
435 | List ma5Values = lineSet5.getValues();
436 | ma5Values.clear();
437 | List ma10Values = lineSet10.getValues();
438 | ma10Values.clear();
439 | List minValues = lineSetMin.getValues();
440 | minValues.clear();
441 | List barValues = barSet.getValues();
442 | barValues.clear();
443 | for (int i = 0; i < dataList.size(); i++) {
444 | List k = dataList.get(i);
445 | Date d = new Date(Long.parseLong(k.get(6)) * 1000);//毫秒
446 | String x = sdf.format(d);//显示日期
447 | if (xValues.containsValue(x)) {//x重复
448 | dataList.remove(i);
449 | i--;
450 | } else {
451 | xValues.put(i, x);
452 | float open = Float.parseFloat(k.get(4));
453 | float close = Float.parseFloat(k.get(1));
454 | candleValues.add(new CandleEntry(i, Float.parseFloat(k.get(2)),
455 | Float.parseFloat(k.get(3)), open, close, x));
456 | minValues.add(new Entry(i, close, x));
457 | barValues.add(new BarEntry(i, Float.parseFloat(k.get(8)), close >= open ? 0 : 1));
458 | if (i >=4) {
459 | ma5Values.add(new Entry(i, getMA(i, 5)));
460 | if (i >= 9) {
461 | ma10Values.add(new Entry(i, getMA(i, 10)));
462 | }
463 | }
464 | }
465 | }
466 | candleSet.setValues(candleValues);
467 | lineSet5.setValues(ma5Values);
468 | lineSet10.setValues(ma10Values);
469 | lineSetMin.setValues(minValues);
470 | if (tabLayout.getSelectedTabPosition() == 0) {
471 | combinedData.removeDataSet(candleSet);//分时图时移除蜡烛图
472 | combinedData.setData(new LineData(lineSetMin));
473 | } else {
474 | combinedData.setData(new CandleData(candleSet));
475 | combinedData.setData(new LineData(lineSet5, lineSet10));
476 | }
477 |
478 | cc.setData(combinedData);
479 | float xMax = xValues.size() - 0.5F;//默认X轴最大值是 xValues.size() - 1
480 | cc.getXAxis().setAxisMaximum(xMax);//使最后一个显示完整
481 |
482 | barSet.setValues(barValues);
483 | BarData barData = new BarData(barSet);
484 | barData.setBarWidth(1 - candleSet.getBarSpace() * 2);//使Candle和Bar宽度一致
485 | bc.setData(barData);
486 | bc.getXAxis().setAxisMaximum(xMax + barOffset);//保持边缘对齐
487 |
488 | cc.setVisibleXRange(range, range);//设置显示X轴个数的上下限,竖屏固定52个
489 | bc.setVisibleXRange(range, range);
490 | }
491 | }
492 |
493 | private void dismissLoading() {
494 | if (loadingDialog != null) {
495 | loadingDialog.dismiss();
496 | }
497 | }
498 |
499 | @Override
500 | public void onFailure(String url, String error) {
501 | dismissLoading();
502 | cc.setNoDataText("加载失败 点击标签重试");
503 | cc.invalidate();
504 | bc.setNoDataText("加载失败");
505 | bc.invalidate();
506 | }
507 |
508 | private float getMA(int index, int maxCount) {
509 | int count = 1;
510 | float sum = Float.parseFloat(dataList.get(index).get(1));
511 | while (count < maxCount) {
512 | if (--index < 0) {
513 | break;
514 | }
515 | sum += Float.parseFloat(dataList.get(index).get(1));
516 | count++;
517 | }
518 | return sum / count;
519 | }
520 |
521 | @Override
522 | public void onClick(View v) {
523 | switch (v.getId()) {
524 | case R.id.iv_klBack://后退键
525 | onBackPressed();
526 | break;
527 | case R.id.iv_klOrientation://切换横竖屏
528 | highVisX = cc.getHighestVisibleX();
529 | setRequestedOrientation(isPort() ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
530 | ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
531 | break;
532 | }
533 | }
534 |
535 | /**
536 | * 滑动到边缘后加载更多
537 | */
538 | @Override
539 | public void edgeLoad(float x, boolean left) {
540 | int v = (int) x;
541 | if (!left && !xValues.containsKey(v) && xValues.containsKey(v - 1)) {
542 | v = v - 1;
543 | }
544 | String time = xValues.get(v);
545 | if (!TextUtils.isEmpty(time)) {
546 | try {
547 | long t = sdf.parse(time).getTime();
548 | if (!left) {//向右获取数据时判断时间间隔
549 | long interval = KL_INTERVAL[tabLayout.getSelectedTabPosition()] * M1;
550 | if (System.currentTimeMillis() - t < interval) {//不会有新数据
551 | return;
552 | }
553 | }
554 | loadingDialog = LoadingDialog.newInstance();
555 | loadingDialog.show(this);
556 | toLeft = left;
557 | getData(t * 1000000L + "");
558 | } catch (ParseException e) {
559 | e.printStackTrace();
560 | }
561 | }
562 | }
563 |
564 | /**
565 | * 双击图表
566 | */
567 | private void doubleTapped() {
568 | if (isPort()) {
569 | highVisX = cc.getHighestVisibleX();
570 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
571 | }
572 | }
573 |
574 | @Override
575 | public void valueSelected(Entry e) {
576 | float x = e.getX();
577 | clHl.setVisibility(View.VISIBLE);
578 | CandleEntry candle = candleSet.getEntryForXValue(x, 0);
579 | if (candle != null) {
580 | tvOpen.setText(format4p.format(candle.getOpen()));
581 | tvOpen.setSelected(candle.getOpen() < candle.getClose());
582 | tvClose.setText(format4p.format(candle.getClose()));
583 | tvClose.setSelected(candle.getOpen() >= candle.getClose());
584 |
585 | tvHigh.setText(format4p.format(candle.getHigh()));
586 | tvHigh.setSelected(false);
587 | tvLow.setText(format4p.format(candle.getLow()));
588 | tvLow.setSelected(true);
589 | }
590 | BarEntry bar = barSet.getEntryForXValue(x, 0);
591 | if (bar != null) {
592 | tvVol.setText(format4p.format(bar.getY()));
593 | }
594 |
595 | if (tabLayout.getSelectedTabPosition() != 0) {
596 | Entry line5 = lineSet5.getEntryForXValue(x, 0);
597 | Entry line10 = lineSet10.getEntryForXValue(x, 0);
598 | if (line5 != null && line10 != null) {
599 | tvLine.setVisibility(View.VISIBLE);
600 | String line = String.format(Kline5_10, format4p.format(line5.getY()),
601 | format4p.format(line10.getY()));
602 | tvLine.setText(fromHtml(line));
603 | }
604 | }
605 | }
606 |
607 | @Override
608 | public void nothingSelected() {
609 | clHl.setVisibility(View.GONE);
610 | tvLine.setVisibility(View.GONE);
611 | }
612 |
613 | @Override
614 | public void enableHighlight() {
615 | if (!barSet.isHighlightEnabled()) {
616 | candleSet.setHighlightEnabled(true);
617 | lineSetMin.setHighlightEnabled(true);
618 | barSet.setHighlightEnabled(true);
619 | }
620 | }
621 |
622 | @Override
623 | public void disableHighlight() {
624 | if (barSet.isHighlightEnabled()) {
625 | candleSet.setHighlightEnabled(false);
626 | lineSetMin.setHighlightEnabled(false);
627 | barSet.setHighlightEnabled(false);
628 | if (ccGesture != null) {
629 | ccGesture.setHighlight(true);
630 | }
631 | if (bcGesture != null) {
632 | bcGesture.setHighlight(true);
633 | }
634 | }
635 | }
636 |
637 | @Override
638 | public void onBackPressed() {
639 | if (isPort()) {
640 | super.onBackPressed();
641 | } else {
642 | highVisX = cc.getHighestVisibleX();
643 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
644 | }
645 | }
646 |
647 | @Override
648 | protected void onRestart() {
649 | super.onRestart();
650 | float rightX = cc.getHighestVisibleX();
651 | if (rightX == cc.getXChartMax()) {//停留在最右端
652 | edgeLoad(rightX, false);
653 | }
654 | }
655 |
656 | /**
657 | * 横竖屏切换
658 | */
659 | @Override
660 | public void onConfigurationChanged(Configuration newConfig) {
661 | super.onConfigurationChanged(newConfig);
662 | setContentView(R.layout.activity_main);
663 | initView();
664 | range = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT ? 52 : 86;//竖屏显示52个 横屏显示86个
665 | configData();
666 | if (xValues.size() > 0) {
667 | cc.post(new Runnable() {
668 | @Override
669 | public void run() {
670 | float x = highVisX - range;
671 | cc.moveViewToX(x);
672 | bc.moveViewToX(x + barOffset);
673 | cc.notifyDataSetChanged();
674 | bc.notifyDataSetChanged();
675 | }
676 | });
677 | }
678 | }
679 |
680 | /**
681 | * 当前是否是竖屏
682 | */
683 | public boolean isPort() {
684 | return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
685 | }
686 |
687 |
688 | public int getColorById(int colorId) {
689 | return ContextCompat.getColor(this, colorId);
690 | }
691 |
692 | public int sp2px(float spValue) {
693 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue,
694 | getResources().getDisplayMetrics());
695 | }
696 |
697 | public static Spanned fromHtml(String source) {
698 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
699 | return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
700 | } else {
701 | return Html.fromHtml(source);
702 | }
703 | }
704 | }
705 |
--------------------------------------------------------------------------------