This commit is contained in:
coco
2026-07-03 15:56:07 +08:00
commit caef23209c
5767 changed files with 1004268 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild
+1
View File
@@ -0,0 +1 @@
/build
+36
View File
@@ -0,0 +1,36 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.github.fujianlian.klinechartdemo"
minSdkVersion 17
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(':KLineChartLib')
implementation 'com.google.code.gson:gson:2.2.4'
implementation 'org.jetbrains.anko:anko-sdk19:+'
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package com.github.fujianlian.klinechartdemo
import android.support.test.InstrumentationRegistry
import android.support.test.runner.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
assertEquals("com.github.fujianlian.klinechartdemo", appContext.packageName)
}
}
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.fujianlian.klinechartdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,66 @@
package com.github.fujianlian.klinechartdemo;
import android.content.Context;
import com.github.fujianlian.klinechart.DataHelper;
import com.github.fujianlian.klinechart.KLineEntity;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 模拟网络请求
* Created by tifezh on 2017/7/3.
*/
public class DataRequest {
private static List<KLineEntity> datas = null;
public static String getStringFromAssert(Context context, String fileName) {
try {
InputStream in = context.getResources().getAssets().open(fileName);
int length = in.available();
byte[] buffer = new byte[length];
in.read(buffer);
return new String(buffer, 0, buffer.length, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static List<KLineEntity> getALL(Context context) {
if (datas == null) {
final List<KLineEntity> data = new Gson().fromJson(getStringFromAssert(context, "ibm.json"), new TypeToken<List<KLineEntity>>() {
}.getType());
DataHelper.calculate(data);
datas = data;
}
return datas;
}
/**
* 分页查询
*
* @param context
* @param offset 开始的索引
* @param size 每次查询的条数
*/
public static List<KLineEntity> getData(Context context, int offset, int size) {
List<KLineEntity> all = getALL(context);
List<KLineEntity> data = new ArrayList<>();
int start = Math.max(0, all.size() - 1 - offset - size);
int stop = Math.min(all.size(), all.size() - offset);
for (int i = start; i < stop; i++) {
data.add(all.get(i));
}
return data;
}
}
@@ -0,0 +1,116 @@
package com.github.fujianlian.klinechartdemo
import android.graphics.Color
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.TextView
import com.github.fujianlian.klinechart.DataHelper
import com.github.fujianlian.klinechart.KLineChartAdapter
import com.github.fujianlian.klinechart.KLineEntity
import com.github.fujianlian.klinechart.draw.Status
import com.github.fujianlian.klinechart.formatter.DateFormatter
import kotlinx.android.synthetic.main.activity_main.*
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.textColor
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var datas: List<KLineEntity>
private val adapter by lazy { KLineChartAdapter() }
private val subTexts: ArrayList<TextView> by lazy { arrayListOf(macdText, kdjText, rsiText, wrText) }
// 主图指标下标
private var mainIndex = 0
// 副图指标下标
private var subIndex = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
kLineChartView.adapter = adapter
kLineChartView.dateTimeFormatter = DateFormatter()
kLineChartView.setGridRows(4)
kLineChartView.setGridColumns(4)
initData()
initListener()
}
private fun initData() {
kLineChartView.justShowLoading()
doAsync {
datas = DataRequest.getALL(this@MainActivity).subList(0, 500)
DataHelper.calculate(datas)
runOnUiThread {
adapter.addFooterData(datas)
adapter.notifyDataSetChanged()
kLineChartView.startAnimation()
kLineChartView.refreshEnd()
}
}
}
private fun initListener() {
maText.setOnClickListener {
if (mainIndex != 0) {
kLineChartView.hideSelectData()
mainIndex = 0
maText.textColor = Color.parseColor("#eeb350")
bollText.textColor = Color.WHITE
kLineChartView.changeMainDrawType(Status.MA)
}
}
bollText.setOnClickListener {
if (mainIndex != 1) {
kLineChartView.hideSelectData()
mainIndex = 1
bollText.textColor = Color.parseColor("#eeb350")
maText.textColor = Color.WHITE
kLineChartView.changeMainDrawType(Status.BOLL)
}
}
mainHide.setOnClickListener {
if (mainIndex != -1) {
kLineChartView.hideSelectData()
mainIndex = -1
bollText.textColor = Color.WHITE
maText.textColor = Color.WHITE
kLineChartView.changeMainDrawType(Status.NONE)
}
}
for ((index, text) in subTexts.withIndex()) {
text.setOnClickListener {
if (subIndex != index) {
kLineChartView.hideSelectData()
if (subIndex != -1) {
subTexts[subIndex].textColor = Color.WHITE
}
subIndex = index
text.textColor = Color.parseColor("#eeb350")
kLineChartView.setChildDraw(subIndex)
}
}
}
subHide.setOnClickListener {
if (subIndex != -1) {
kLineChartView.hideSelectData()
subTexts[subIndex].textColor = Color.WHITE
subIndex = -1
kLineChartView.hideChildDraw()
}
}
fenText.setOnClickListener {
kLineChartView.hideSelectData()
fenText.textColor = Color.parseColor("#eeb350")
kText.textColor = Color.WHITE
kLineChartView.setMainDrawLine(true)
}
kText.setOnClickListener {
kLineChartView.hideSelectData()
kText.textColor = Color.parseColor("#eeb350")
fenText.textColor = Color.WHITE
kLineChartView.setMainDrawLine(false)
}
}
}
@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context=".MainActivity">
<TextView
android:id="@+id/text1"
android:layout_width="50dp"
android:layout_height="40dp"
android:gravity="center"
android:text="主图"
android:textColor="@color/white" />
<TextView
android:id="@+id/maText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/text1"
android:gravity="center"
android:text="MA"
android:textColor="@color/colorAccent" />
<TextView
android:id="@+id/bollText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/maText"
android:gravity="center"
android:text="BOLL"
android:textColor="@color/white" />
<TextView
android:id="@+id/mainHide"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/bollText"
android:gravity="center"
android:text="隐藏"
android:textColor="@color/white" />
<TextView
android:id="@+id/text2"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_below="@+id/text1"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="副图"
android:textColor="@color/white" />
<TextView
android:id="@+id/macdText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_alignBottom="@+id/text2"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/text2"
android:gravity="center"
android:text="MACD"
android:textColor="@color/white" />
<TextView
android:id="@+id/kdjText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_alignBottom="@+id/macdText"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/macdText"
android:gravity="center"
android:text="KDJ"
android:textColor="@color/white" />
<TextView
android:id="@+id/rsiText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_alignBottom="@+id/kdjText"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/kdjText"
android:gravity="center"
android:text="RSI"
android:textColor="@color/white" />
<TextView
android:id="@+id/wrText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_alignBottom="@+id/rsiText"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/rsiText"
android:gravity="center"
android:text="WR"
android:textColor="@color/white" />
<TextView
android:id="@+id/subHide"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_alignBottom="@+id/wrText"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/wrText"
android:gravity="center"
android:text="隐藏"
android:textColor="@color/white" />
<TextView
android:id="@+id/fenText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_below="@+id/text2"
android:gravity="center"
android:text="分时"
android:textColor="@color/white" />
<TextView
android:id="@+id/kText"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_below="@+id/text2"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@+id/text1"
android:gravity="center"
android:text="k线图"
android:textColor="@color/colorAccent" />
<View
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_below="@+id/fenText"
android:background="#17181f" />
<com.github.fujianlian.klinechart.KLineChartView
android:id="@+id/kLineChartView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/view2"
android:layout_below="@+id/view1" />
<View
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="10dp"
android:layout_alignParentBottom="true"
android:background="#17181f" />
</RelativeLayout>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#1d1f26</color>
<color name="colorPrimaryDark">#1d1f26</color>
<color name="colorAccent">#efb350</color>
<color name="white">#ffffff</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">KLineChart</string>
</resources>
@@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
@@ -0,0 +1,17 @@
package com.github.fujianlian.klinechartdemo
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+1
View File
@@ -0,0 +1 @@
/build
+32
View File
@@ -0,0 +1,32 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 16
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,26 @@
package com.github.fujianlian.klinechart;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.github.fujianlian.klinechart.test", appContext.getPackageName());
}
}
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.fujianlian.klinechart" />
@@ -0,0 +1,36 @@
package com.github.fujianlian.klinechart;
import android.database.DataSetObservable;
import android.database.DataSetObserver;
import com.github.fujianlian.klinechart.base.IAdapter;
/**
* k线图的数据适配器
* Created by tifezh on 2016/6/9.
*/
public abstract class BaseKLineChartAdapter implements IAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
@Override
public void notifyDataSetChanged() {
if (getCount() > 0) {
mDataSetObservable.notifyChanged();
} else {
mDataSetObservable.notifyInvalidated();
}
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
}
@@ -0,0 +1,302 @@
package com.github.fujianlian.klinechart;
import java.util.List;
/**
* 数据辅助类 计算macd rsi等
* Created by tifezh on 2016/11/26.
*/
public class DataHelper {
/**
* 计算RSI
*
* @param dataList
*/
static void calculateRSI(List<KLineEntity> dataList) {
Float rsi;
float rsiABSEma = 0;
float rsiMaxEma = 0;
for (int i = 0; i < dataList.size(); i++) {
KLineEntity point = dataList.get(i);
final float closePrice = point.getClosePrice();
if (i == 0) {
rsi = 0f;
rsiABSEma = 0;
rsiMaxEma = 0;
} else {
float Rmax = Math.max(0, closePrice - dataList.get(i - 1).getClosePrice());
float RAbs = Math.abs(closePrice - dataList.get(i - 1).getClosePrice());
rsiMaxEma = (Rmax + (14f - 1) * rsiMaxEma) / 14f;
rsiABSEma = (RAbs + (14f - 1) * rsiABSEma) / 14f;
rsi = (rsiMaxEma / rsiABSEma) * 100;
}
if (i < 13) {
rsi = 0f;
}
if (rsi.isNaN())
rsi = 0f;
point.rsi = rsi;
}
}
/**
* 计算kdj
*
* @param dataList
*/
static void calculateKDJ(List<KLineEntity> dataList) {
float k = 0;
float d = 0;
for (int i = 0; i < dataList.size(); i++) {
KLineEntity point = dataList.get(i);
final float closePrice = point.getClosePrice();
int startIndex = i - 13;
if (startIndex < 0) {
startIndex = 0;
}
float max14 = Float.MIN_VALUE;
float min14 = Float.MAX_VALUE;
for (int index = startIndex; index <= i; index++) {
max14 = Math.max(max14, dataList.get(index).getHighPrice());
min14 = Math.min(min14, dataList.get(index).getLowPrice());
}
Float rsv = 100f * (closePrice - min14) / (max14 - min14);
if (rsv.isNaN()) {
rsv = 0f;
}
if (i == 0) {
k = 50;
d = 50;
} else {
k = (rsv + 2f * k) / 3f;
d = (k + 2f * d) / 3f;
}
if (i < 13) {
point.k = 0;
point.d = 0;
point.j = 0;
} else if (i == 13 || i == 14) {
point.k = k;
point.d = 0;
point.j = 0;
} else {
point.k = k;
point.d = d;
point.j = 3f * k - 2 * d;
}
}
}
/**
* 计算wr
*
* @param dataList
*/
static void calculateWR(List<KLineEntity> dataList) {
Float r;
for (int i = 0; i < dataList.size(); i++) {
KLineEntity point = dataList.get(i);
int startIndex = i - 14;
if (startIndex < 0) {
startIndex = 0;
}
float max14 = Float.MIN_VALUE;
float min14 = Float.MAX_VALUE;
for (int index = startIndex; index <= i; index++) {
max14 = Math.max(max14, dataList.get(index).getHighPrice());
min14 = Math.min(min14, dataList.get(index).getLowPrice());
}
if (i < 13) {
point.r = -10;
} else {
r = -100 * (max14 - dataList.get(i).getClosePrice()) / (max14 - min14);
if (r.isNaN()) {
point.r = 0;
} else {
point.r = r;
}
}
}
}
/**
* 计算macd
*
* @param dataList
*/
static void calculateMACD(List<KLineEntity> dataList) {
float ema12 = 0;
float ema26 = 0;
float dif = 0;
float dea = 0;
float macd = 0;
for (int i = 0; i < dataList.size(); i++) {
KLineEntity point = dataList.get(i);
final float closePrice = point.getClosePrice();
if (i == 0) {
ema12 = closePrice;
ema26 = closePrice;
} else {
// EMA12 = 前一日EMA12 X 11/13 + 今日收盘价 X 2/13
ema12 = ema12 * 11f / 13f + closePrice * 2f / 13f;
// EMA26 = 前一日EMA26 X 25/27 + 今日收盘价 X 2/27
ema26 = ema26 * 25f / 27f + closePrice * 2f / 27f;
}
// DIF = EMA12 - EMA26
// 今日DEA = (前一日DEA X 8/10 + 今日DIF X 2/10
// 用(DIF-DEA*2即为MACD柱状图。
dif = ema12 - ema26;
dea = dea * 8f / 10f + dif * 2f / 10f;
macd = (dif - dea) * 2f;
point.dif = dif;
point.dea = dea;
point.macd = macd;
}
}
/**
* 计算 BOLL 需要在计算ma之后进行
*
* @param dataList
*/
static void calculateBOLL(List<KLineEntity> dataList) {
for (int i = 0; i < dataList.size(); i++) {
KLineEntity point = dataList.get(i);
if (i < 19) {
point.mb = 0;
point.up = 0;
point.dn = 0;
} else {
int n = 20;
float md = 0;
for (int j = i - n + 1; j <= i; j++) {
float c = dataList.get(j).getClosePrice();
float m = point.getMA20Price();
float value = c - m;
md += value * value;
}
md = md / (n - 1);
md = (float) Math.sqrt(md);
point.mb = point.getMA20Price();
point.up = point.mb + 2f * md;
point.dn = point.mb - 2f * md;
}
}
}
/**
* 计算ma
*
* @param dataList
*/
static void calculateMA(List<KLineEntity> dataList) {
float ma5 = 0;
float ma10 = 0;
float ma20 = 0;
float ma30 = 0;
float ma60 = 0;
for (int i = 0; i < dataList.size(); i++) {
KLineEntity point = dataList.get(i);
final float closePrice = point.getClosePrice();
ma5 += closePrice;
ma10 += closePrice;
ma20 += closePrice;
ma30 += closePrice;
ma60 += closePrice;
if (i == 4) {
point.MA5Price = ma5 / 5f;
} else if (i >= 5) {
ma5 -= dataList.get(i - 5).getClosePrice();
point.MA5Price = ma5 / 5f;
} else {
point.MA5Price = 0f;
}
if (i == 9) {
point.MA10Price = ma10 / 10f;
} else if (i >= 10) {
ma10 -= dataList.get(i - 10).getClosePrice();
point.MA10Price = ma10 / 10f;
} else {
point.MA10Price = 0f;
}
if (i == 19) {
point.MA20Price = ma20 / 20f;
} else if (i >= 20) {
ma20 -= dataList.get(i - 20).getClosePrice();
point.MA20Price = ma20 / 20f;
} else {
point.MA20Price = 0f;
}
if (i == 29) {
point.MA30Price = ma30 / 30f;
} else if (i >= 30) {
ma30 -= dataList.get(i - 30).getClosePrice();
point.MA30Price = ma30 / 30f;
} else {
point.MA30Price = 0f;
}
if (i == 59) {
point.MA60Price = ma60 / 60f;
} else if (i >= 60) {
ma60 -= dataList.get(i - 60).getClosePrice();
point.MA60Price = ma60 / 60f;
} else {
point.MA60Price = 0f;
}
}
}
/**
* 计算MA BOLL RSI KDJ MACD
*
* @param dataList
*/
public static void calculate(List<KLineEntity> dataList) {
calculateMA(dataList);
calculateMACD(dataList);
calculateBOLL(dataList);
calculateRSI(dataList);
calculateKDJ(dataList);
calculateWR(dataList);
calculateVolumeMA(dataList);
}
private static void calculateVolumeMA(List<KLineEntity> entries) {
float volumeMa5 = 0;
float volumeMa10 = 0;
for (int i = 0; i < entries.size(); i++) {
KLineEntity entry = entries.get(i);
volumeMa5 += entry.getVolume();
volumeMa10 += entry.getVolume();
if (i == 4) {
entry.MA5Volume = (volumeMa5 / 5f);
} else if (i > 4) {
volumeMa5 -= entries.get(i - 5).getVolume();
entry.MA5Volume = volumeMa5 / 5f;
} else {
entry.MA5Volume = 0f;
}
if (i == 9) {
entry.MA10Volume = volumeMa10 / 10f;
} else if (i > 9) {
volumeMa10 -= entries.get(i - 10).getVolume();
entry.MA10Volume = volumeMa10 / 10f;
} else {
entry.MA10Volume = 0f;
}
}
}
}
@@ -0,0 +1,70 @@
package com.github.fujianlian.klinechart;
import java.util.ArrayList;
import java.util.List;
/**
* 数据适配器
* Created by tifezh on 2016/6/18.
*/
public class KLineChartAdapter extends BaseKLineChartAdapter {
private List<KLineEntity> datas = new ArrayList<>();
public KLineChartAdapter() {
}
@Override
public int getCount() {
return datas.size();
}
@Override
public Object getItem(int position) {
return datas.get(position);
}
@Override
public String getDate(int position) {
return datas.get(position).Date;
}
/**
* 向头部添加数据
*/
public void addHeaderData(List<KLineEntity> data) {
if (data != null && !data.isEmpty()) {
datas.clear();
datas.addAll(data);
}
}
/**
* 向尾部添加数据
*/
public void addFooterData(List<KLineEntity> data) {
if (data != null && !data.isEmpty()) {
datas.clear();
datas.addAll(0, data);
}
}
/**
* 改变某个点的值
*
* @param position 索引值
*/
public void changeItem(int position, KLineEntity data) {
datas.set(position, data);
notifyDataSetChanged();
}
/**
* 数据清除
*/
public void clearData() {
datas.clear();
notifyDataSetChanged();
}
}
@@ -0,0 +1,466 @@
package com.github.fujianlian.klinechart;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ProgressBar;
import com.github.fujianlian.klinechart.draw.KDJDraw;
import com.github.fujianlian.klinechart.draw.MACDDraw;
import com.github.fujianlian.klinechart.draw.MainDraw;
import com.github.fujianlian.klinechart.draw.RSIDraw;
import com.github.fujianlian.klinechart.draw.VolumeDraw;
import com.github.fujianlian.klinechart.draw.WRDraw;
/**
* k线图
* Created by tian on 2016/5/20.
*/
public class KLineChartView extends BaseKLineChartView {
ProgressBar mProgressBar;
private boolean isRefreshing = false;
private boolean isLoadMoreEnd = false;
private boolean mLastScrollEnable;
private boolean mLastScaleEnable;
private KChartRefreshListener mRefreshListener;
private MACDDraw mMACDDraw;
private RSIDraw mRSIDraw;
private MainDraw mMainDraw;
private KDJDraw mKDJDraw;
private WRDraw mWRDraw;
private VolumeDraw mVolumeDraw;
public KLineChartView(Context context) {
this(context, null);
}
public KLineChartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public KLineChartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
initAttrs(attrs);
}
private void initView() {
mProgressBar = new ProgressBar(getContext());
LayoutParams layoutParams = new LayoutParams(dp2px(50), dp2px(50));
layoutParams.addRule(CENTER_IN_PARENT);
addView(mProgressBar, layoutParams);
mProgressBar.setVisibility(GONE);
mVolumeDraw = new VolumeDraw(this);
mMACDDraw = new MACDDraw(this);
mWRDraw = new WRDraw(this);
mKDJDraw = new KDJDraw(this);
mRSIDraw = new RSIDraw(this);
mMainDraw = new MainDraw(this);
addChildDraw(mMACDDraw);
addChildDraw(mKDJDraw);
addChildDraw(mRSIDraw);
addChildDraw(mWRDraw);
setVolDraw(mVolumeDraw);
setMainDraw(mMainDraw);
}
private void initAttrs(AttributeSet attrs) {
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.KLineChartView);
if (array != null) {
try {
//public
setPointWidth(array.getDimension(R.styleable.KLineChartView_kc_point_width, getDimension(R.dimen.chart_point_width)));
setTextSize(array.getDimension(R.styleable.KLineChartView_kc_text_size, getDimension(R.dimen.chart_text_size)));
setTextColor(array.getColor(R.styleable.KLineChartView_kc_text_color, getColor(R.color.chart_text)));
setMTextSize(array.getDimension(R.styleable.KLineChartView_kc_text_size, getDimension(R.dimen.chart_text_size)));
setMTextColor(array.getColor(R.styleable.KLineChartView_kc_text_color, getColor(R.color.chart_white)));
setLineWidth(array.getDimension(R.styleable.KLineChartView_kc_line_width, getDimension(R.dimen.chart_line_width)));
setBackgroundColor(array.getColor(R.styleable.KLineChartView_kc_background_color, getColor(R.color.chart_bac)));
setSelectPointColor(array.getColor(R.styleable.KLineChartView_kc_background_color, getColor(R.color.chart_point_bac)));
setSelectedXLineColor(Color.WHITE);
setSelectedXLineWidth(getDimension(R.dimen.chart_line_width));
setSelectedYLineColor(Color.parseColor("#8040424D"));
setSelectedYLineWidth(getDimension(R.dimen.chart_point_width));
setGridLineWidth(array.getDimension(R.styleable.KLineChartView_kc_grid_line_width, getDimension(R.dimen.chart_grid_line_width)));
setGridLineColor(array.getColor(R.styleable.KLineChartView_kc_grid_line_color, getColor(R.color.chart_grid_line)));
//macd
setMACDWidth(array.getDimension(R.styleable.KLineChartView_kc_macd_width, getDimension(R.dimen.chart_candle_width)));
setDIFColor(array.getColor(R.styleable.KLineChartView_kc_dif_color, getColor(R.color.chart_ma5)));
setDEAColor(array.getColor(R.styleable.KLineChartView_kc_dea_color, getColor(R.color.chart_ma10)));
setMACDColor(array.getColor(R.styleable.KLineChartView_kc_macd_color, getColor(R.color.chart_ma30)));
//kdj
setKColor(array.getColor(R.styleable.KLineChartView_kc_dif_color, getColor(R.color.chart_ma5)));
setDColor(array.getColor(R.styleable.KLineChartView_kc_dea_color, getColor(R.color.chart_ma10)));
setJColor(array.getColor(R.styleable.KLineChartView_kc_macd_color, getColor(R.color.chart_ma30)));
//wr
setRColor(array.getColor(R.styleable.KLineChartView_kc_dif_color, getColor(R.color.chart_ma5)));
//rsi
setRSI1Color(array.getColor(R.styleable.KLineChartView_kc_dif_color, getColor(R.color.chart_ma5)));
setRSI2Color(array.getColor(R.styleable.KLineChartView_kc_dea_color, getColor(R.color.chart_ma10)));
setRSI3Color(array.getColor(R.styleable.KLineChartView_kc_macd_color, getColor(R.color.chart_ma30)));
//main
setMa5Color(array.getColor(R.styleable.KLineChartView_kc_dif_color, getColor(R.color.chart_ma5)));
setMa10Color(array.getColor(R.styleable.KLineChartView_kc_dea_color, getColor(R.color.chart_ma10)));
setMa30Color(array.getColor(R.styleable.KLineChartView_kc_macd_color, getColor(R.color.chart_ma30)));
setCandleWidth(array.getDimension(R.styleable.KLineChartView_kc_candle_width, getDimension(R.dimen.chart_candle_width)));
setCandleLineWidth(array.getDimension(R.styleable.KLineChartView_kc_candle_line_width, getDimension(R.dimen.chart_candle_line_width)));
setSelectorBackgroundColor(array.getColor(R.styleable.KLineChartView_kc_selector_background_color, getColor(R.color.chart_selector)));
setSelectorTextSize(array.getDimension(R.styleable.KLineChartView_kc_selector_text_size, getDimension(R.dimen.chart_selector_text_size)));
setCandleSolid(array.getBoolean(R.styleable.KLineChartView_kc_candle_solid, true));
} catch (Exception e) {
e.printStackTrace();
} finally {
array.recycle();
}
}
}
private float getDimension(@DimenRes int resId) {
return getResources().getDimension(resId);
}
private int getColor(@ColorRes int resId) {
return ContextCompat.getColor(getContext(), resId);
}
@Override
public void onLeftSide() {
showLoading();
}
@Override
public void onRightSide() {
}
public void showLoading() {
if (!isLoadMoreEnd && !isRefreshing) {
isRefreshing = true;
if (mProgressBar != null) {
mProgressBar.setVisibility(View.VISIBLE);
}
if (mRefreshListener != null) {
mRefreshListener.onLoadMoreBegin(this);
}
mLastScaleEnable = isScaleEnable();
mLastScrollEnable = isScrollEnable();
super.setScrollEnable(false);
super.setScaleEnable(false);
}
}
public void justShowLoading() {
if (!isRefreshing) {
isLongPress = false;
isRefreshing = true;
if (mProgressBar != null) {
mProgressBar.setVisibility(View.VISIBLE);
}
if (mRefreshListener != null) {
mRefreshListener.onLoadMoreBegin(this);
}
mLastScaleEnable = isScaleEnable();
mLastScrollEnable = isScrollEnable();
super.setScrollEnable(false);
super.setScaleEnable(false);
}
}
private void hideLoading() {
if (mProgressBar != null) {
mProgressBar.setVisibility(View.GONE);
}
super.setScrollEnable(mLastScrollEnable);
super.setScaleEnable(mLastScaleEnable);
}
/**
* 隐藏选择器内容
*/
public void hideSelectData() {
isLongPress = false;
invalidate();
}
/**
* 刷新完成
*/
public void refreshComplete() {
isRefreshing = false;
hideLoading();
}
/**
* 刷新完成,没有数据
*/
public void refreshEnd() {
isLoadMoreEnd = true;
isRefreshing = false;
hideLoading();
}
/**
* 重置加载更多
*/
public void resetLoadMoreEnd() {
isLoadMoreEnd = false;
}
public void setLoadMoreEnd() {
isLoadMoreEnd = true;
}
public interface KChartRefreshListener {
/**
* 加载更多
*
* @param chart
*/
void onLoadMoreBegin(KLineChartView chart);
}
@Override
public void setScaleEnable(boolean scaleEnable) {
if (isRefreshing) {
throw new IllegalStateException("请勿在刷新状态设置属性");
}
super.setScaleEnable(scaleEnable);
}
@Override
public void setScrollEnable(boolean scrollEnable) {
if (isRefreshing) {
throw new IllegalStateException("请勿在刷新状态设置属性");
}
super.setScrollEnable(scrollEnable);
}
/**
* 设置DIF颜色
*/
public void setDIFColor(int color) {
mMACDDraw.setDIFColor(color);
}
/**
* 设置DEA颜色
*/
public void setDEAColor(int color) {
mMACDDraw.setDEAColor(color);
}
/**
* 设置MACD颜色
*/
public void setMACDColor(int color) {
mMACDDraw.setMACDColor(color);
}
/**
* 设置MACD的宽度
*
* @param MACDWidth
*/
public void setMACDWidth(float MACDWidth) {
mMACDDraw.setMACDWidth(MACDWidth);
}
/**
* 设置K颜色
*/
public void setKColor(int color) {
mKDJDraw.setKColor(color);
}
/**
* 设置D颜色
*/
public void setDColor(int color) {
mKDJDraw.setDColor(color);
}
/**
* 设置J颜色
*/
public void setJColor(int color) {
mKDJDraw.setJColor(color);
}
/**
* 设置R颜色
*/
public void setRColor(int color) {
mWRDraw.setRColor(color);
}
/**
* 设置ma5颜色
*
* @param color
*/
public void setMa5Color(int color) {
mMainDraw.setMa5Color(color);
mVolumeDraw.setMa5Color(color);
}
/**
* 设置ma10颜色
*
* @param color
*/
public void setMa10Color(int color) {
mMainDraw.setMa10Color(color);
mVolumeDraw.setMa10Color(color);
}
/**
* 设置ma20颜色
*
* @param color
*/
public void setMa30Color(int color) {
mMainDraw.setMa30Color(color);
}
/**
* 设置选择器文字大小
*
* @param textSize
*/
public void setSelectorTextSize(float textSize) {
mMainDraw.setSelectorTextSize(textSize);
}
/**
* 设置选择器背景
*
* @param color
*/
public void setSelectorBackgroundColor(int color) {
mMainDraw.setSelectorBackgroundColor(color);
}
/**
* 设置蜡烛宽度
*
* @param candleWidth
*/
public void setCandleWidth(float candleWidth) {
mMainDraw.setCandleWidth(candleWidth);
}
/**
* 设置蜡烛线宽度
*
* @param candleLineWidth
*/
public void setCandleLineWidth(float candleLineWidth) {
mMainDraw.setCandleLineWidth(candleLineWidth);
}
/**
* 蜡烛是否空心
*/
public void setCandleSolid(boolean candleSolid) {
mMainDraw.setCandleSolid(candleSolid);
}
public void setRSI1Color(int color) {
mRSIDraw.setRSI1Color(color);
}
public void setRSI2Color(int color) {
mRSIDraw.setRSI2Color(color);
}
public void setRSI3Color(int color) {
mRSIDraw.setRSI3Color(color);
}
@Override
public void setTextSize(float textSize) {
super.setTextSize(textSize);
mMainDraw.setTextSize(textSize);
mRSIDraw.setTextSize(textSize);
mMACDDraw.setTextSize(textSize);
mKDJDraw.setTextSize(textSize);
mWRDraw.setTextSize(textSize);
mVolumeDraw.setTextSize(textSize);
}
@Override
public void setLineWidth(float lineWidth) {
super.setLineWidth(lineWidth);
mMainDraw.setLineWidth(lineWidth);
mRSIDraw.setLineWidth(lineWidth);
mMACDDraw.setLineWidth(lineWidth);
mKDJDraw.setLineWidth(lineWidth);
mWRDraw.setLineWidth(lineWidth);
mVolumeDraw.setLineWidth(lineWidth);
}
@Override
public void setTextColor(int color) {
super.setTextColor(color);
mMainDraw.setSelectorTextColor(color);
}
/**
* 设置刷新监听
*/
public void setRefreshListener(KChartRefreshListener refreshListener) {
mRefreshListener = refreshListener;
}
public void setMainDrawLine(boolean isLine) {
mMainDraw.setLine(isLine);
invalidate();
}
private int startX;
private int startY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) ev.getX();
startY = (int) ev.getY();
break;
case MotionEvent.ACTION_MOVE:
int dX = (int) (ev.getX() - startX);
int dY = (int) (ev.getY() - startX);
if (Math.abs(dX) > Math.abs(dY)) {
//左右滑动
return true;
} else {
//上下滑动
return false;
}
case MotionEvent.ACTION_UP:
break;
default:
}
return super.onInterceptTouchEvent(ev);
}
@Override
public void onLongPress(MotionEvent e) {
if (!isRefreshing) {
super.onLongPress(e);
}
}
}
@@ -0,0 +1,174 @@
package com.github.fujianlian.klinechart;
import com.github.fujianlian.klinechart.entity.IKLine;
/**
* K线实体
* Created by tifezh on 2016/5/16.
*/
public class KLineEntity implements IKLine {
public String getDate() {
return Date;
}
@Override
public float getOpenPrice() {
return Open;
}
@Override
public float getHighPrice() {
return High;
}
@Override
public float getLowPrice() {
return Low;
}
@Override
public float getClosePrice() {
return Close;
}
@Override
public float getMA5Price() {
return MA5Price;
}
@Override
public float getMA10Price() {
return MA10Price;
}
@Override
public float getMA20Price() {
return MA20Price;
}
@Override
public float getMA30Price() {
return MA30Price;
}
@Override
public float getMA60Price() {
return MA60Price;
}
@Override
public float getDea() {
return dea;
}
@Override
public float getDif() {
return dif;
}
@Override
public float getMacd() {
return macd;
}
@Override
public float getK() {
return k;
}
@Override
public float getD() {
return d;
}
@Override
public float getJ() {
return j;
}
@Override
public float getR() {
return r;
}
@Override
public float getRsi() {
return rsi;
}
@Override
public float getUp() {
return up;
}
@Override
public float getMb() {
return mb;
}
@Override
public float getDn() {
return dn;
}
@Override
public float getVolume() {
return Volume;
}
@Override
public float getMA5Volume() {
return MA5Volume;
}
@Override
public float getMA10Volume() {
return MA10Volume;
}
public String Date;
public float Open;
public float High;
public float Low;
public float Close;
public float Volume;
public float MA5Price;
public float MA10Price;
public float MA20Price;
public float MA30Price;
public float MA60Price;
public float dea;
public float dif;
public float macd;
public float k;
public float d;
public float j;
public float r;
public float rsi;
public float up;
public float mb;
public float dn;
public float MA5Volume;
public float MA10Volume;
}
@@ -0,0 +1,327 @@
package com.github.fujianlian.klinechart;
import android.content.Context;
import android.support.v4.view.GestureDetectorCompat;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.OverScroller;
import android.widget.RelativeLayout;
/**
* 可以滑动和放大的view
* Created by tian on 2016/5/3.
*/
public abstract class ScrollAndScaleView extends RelativeLayout implements
GestureDetector.OnGestureListener,
ScaleGestureDetector.OnScaleGestureListener {
protected int mScrollX = 0;
protected GestureDetectorCompat mDetector;
protected ScaleGestureDetector mScaleDetector;
protected boolean isLongPress = false;
private OverScroller mScroller;
protected boolean touch = false;
protected float mScaleX = 1;
protected float mScaleXMax = 2f;
protected float mScaleXMin = 0.5f;
private boolean mMultipleTouch = false;
private boolean mScrollEnable = true;
private boolean mScaleEnable = true;
public ScrollAndScaleView(Context context) {
super(context);
init();
}
public ScrollAndScaleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ScrollAndScaleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setWillNotDraw(false);
mDetector = new GestureDetectorCompat(getContext(), this);
mScaleDetector = new ScaleGestureDetector(getContext(), this);
mScroller = new OverScroller(getContext());
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isLongPress && !isMultipleTouch()) {
scrollBy(Math.round(distanceX), 0);
return true;
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
isLongPress = true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (!isTouch() && isScrollEnable()) {
mScroller.fling(mScrollX, 0
, Math.round(velocityX / mScaleX), 0,
Integer.MIN_VALUE, Integer.MAX_VALUE,
0, 0);
}
return true;
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
if (!isTouch()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
} else {
mScroller.forceFinished(true);
}
}
}
@Override
public void scrollBy(int x, int y) {
scrollTo(mScrollX - Math.round(x / mScaleX), 0);
}
@Override
public void scrollTo(int x, int y) {
if (!isScrollEnable()) {
mScroller.forceFinished(true);
return;
}
int oldX = mScrollX;
mScrollX = x;
if (mScrollX < getMinScrollX()) {
mScrollX = getMinScrollX();
onRightSide();
mScroller.forceFinished(true);
} else if (mScrollX > getMaxScrollX()) {
mScrollX = getMaxScrollX();
onLeftSide();
mScroller.forceFinished(true);
}
onScrollChanged(mScrollX, 0, oldX, 0);
invalidate();
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (!isScaleEnable()) {
return false;
}
float oldScale = mScaleX;
mScaleX *= detector.getScaleFactor();
if (mScaleX < mScaleXMin) {
mScaleX = mScaleXMin;
} else if (mScaleX > mScaleXMax) {
mScaleX = mScaleXMax;
} else {
onScaleChanged(mScaleX, oldScale);
}
return true;
}
protected void onScaleChanged(float scale, float oldScale) {
invalidate();
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
float x;
@Override
public boolean onTouchEvent(MotionEvent event) {
// 按压手指超过1个
if (event.getPointerCount() > 1) {
isLongPress = false;
}
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
touch = true;
x = event.getX();
break;
case MotionEvent.ACTION_MOVE:
//长按之后移动
if (isLongPress) {
onLongPress(event);
}
break;
case MotionEvent.ACTION_POINTER_UP:
invalidate();
break;
case MotionEvent.ACTION_UP:
if (x == event.getX()) {
if (isLongPress) {
isLongPress = false;
}
}
touch = false;
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
isLongPress = false;
touch = false;
invalidate();
break;
default:
break;
}
mMultipleTouch = event.getPointerCount() > 1;
this.mDetector.onTouchEvent(event);
this.mScaleDetector.onTouchEvent(event);
return true;
}
/**
* 滑到了最左边
*/
abstract public void onLeftSide();
/**
* 滑到了最右边
*/
abstract public void onRightSide();
/**
* 是否在触摸中
*
* @return
*/
public boolean isTouch() {
return touch;
}
/**
* 获取位移的最小值
*
* @return
*/
public abstract int getMinScrollX();
/**
* 获取位移的最大值
*
* @return
*/
public abstract int getMaxScrollX();
/**
* 设置ScrollX
*
* @param scrollX
*/
public void setScrollX(int scrollX) {
this.mScrollX = scrollX;
scrollTo(scrollX, 0);
}
/**
* 是否是多指触控
*
* @return
*/
public boolean isMultipleTouch() {
return mMultipleTouch;
}
protected void checkAndFixScrollX() {
if (mScrollX < getMinScrollX()) {
mScrollX = getMinScrollX();
mScroller.forceFinished(true);
} else if (mScrollX > getMaxScrollX()) {
mScrollX = getMaxScrollX();
mScroller.forceFinished(true);
}
}
public float getScaleXMax() {
return mScaleXMax;
}
public float getScaleXMin() {
return mScaleXMin;
}
public boolean isScrollEnable() {
return mScrollEnable;
}
public boolean isScaleEnable() {
return mScaleEnable;
}
/**
* 设置缩放的最大值
*/
public void setScaleXMax(float scaleXMax) {
mScaleXMax = scaleXMax;
}
/**
* 设置缩放的最小值
*/
public void setScaleXMin(float scaleXMin) {
mScaleXMin = scaleXMin;
}
/**
* 设置是否可以滑动
*/
public void setScrollEnable(boolean scrollEnable) {
mScrollEnable = scrollEnable;
}
/**
* 设置是否可以缩放
*/
public void setScaleEnable(boolean scaleEnable) {
mScaleEnable = scaleEnable;
}
@Override
public float getScaleX() {
return mScaleX;
}
}
@@ -0,0 +1,52 @@
package com.github.fujianlian.klinechart.base;
import android.database.DataSetObserver;
/**
* 数据适配器
* Created by tifezh on 2016/6/14.
*/
public interface IAdapter {
/**
* 获取点的数目
*
* @return
*/
int getCount();
/**
* 通过序号获取item
*
* @param position 对应的序号
* @return 数据实体
*/
Object getItem(int position);
/**
* 通过序号获取时间
*
* @param position
* @return
*/
String getDate(int position);
/**
* 注册一个数据观察者
*
* @param observer 数据观察者
*/
void registerDataSetObserver(DataSetObserver observer);
/**
* 移除一个数据观察者
*
* @param observer 数据观察者
*/
void unregisterDataSetObserver(DataSetObserver observer);
/**
* 当数据发生变化时调用
*/
void notifyDataSetChanged();
}
@@ -0,0 +1,58 @@
package com.github.fujianlian.klinechart.base;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.github.fujianlian.klinechart.BaseKLineChartView;
/**
* 画图的基类 根据实体来画图形
* Created by tifezh on 2016/6/14.
*/
public interface IChartDraw<T> {
/**
* 需要滑动 物体draw方法
*
* @param canvas canvas
* @param view k线图View
* @param position 当前点的位置
* @param lastPoint 上一个点
* @param curPoint 当前点
* @param lastX 上一个点的x坐标
* @param curX 当前点的X坐标
*/
void drawTranslated(@Nullable T lastPoint, @NonNull T curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position);
/**
* @param canvas
* @param view
* @param position 该点的位置
* @param x x的起始坐标
* @param y y的起始坐标
*/
void drawText(@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position, float x, float y);
/**
* 获取当前实体中最大的值
*
* @param point
* @return
*/
float getMaxValue(T point);
/**
* 获取当前实体中最小的值
*
* @param point
* @return
*/
float getMinValue(T point);
/**
* 获取value格式化器
*/
IValueFormatter getValueFormatter();
}
@@ -0,0 +1,12 @@
package com.github.fujianlian.klinechart.base;
import java.util.Date;
/**
* 时间格式化接口
* Created by tifezh on 2016/6/21.
*/
public interface IDateTimeFormatter {
String format(Date date);
}
@@ -0,0 +1,16 @@
package com.github.fujianlian.klinechart.base;
/**
* Value格式化接口
* Created by tifezh on 2016/6/21.
*/
public interface IValueFormatter {
/**
* 格式化value
*
* @param value 传入的value值
* @return 返回字符串
*/
String format(float value);
}
@@ -0,0 +1,114 @@
package com.github.fujianlian.klinechart.draw;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.github.fujianlian.klinechart.BaseKLineChartView;
import com.github.fujianlian.klinechart.base.IChartDraw;
import com.github.fujianlian.klinechart.base.IValueFormatter;
import com.github.fujianlian.klinechart.entity.IKDJ;
import com.github.fujianlian.klinechart.formatter.ValueFormatter;
/**
* KDJ实现类
* Created by tifezh on 2016/6/19.
*/
public class KDJDraw implements IChartDraw<IKDJ> {
private Paint mKPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mDPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mJPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public KDJDraw(BaseKLineChartView view) {
}
@Override
public void drawTranslated(@Nullable IKDJ lastPoint, @NonNull IKDJ curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position) {
if (lastPoint.getK() != 0) {
view.drawChildLine(canvas, mKPaint, lastX, lastPoint.getK(), curX, curPoint.getK());
}
if (lastPoint.getD() != 0) {
view.drawChildLine(canvas, mDPaint, lastX, lastPoint.getD(), curX, curPoint.getD());
}
if (lastPoint.getJ() != 0) {
view.drawChildLine(canvas, mJPaint, lastX, lastPoint.getJ(), curX, curPoint.getJ());
}
}
@Override
public void drawText(@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position, float x, float y) {
IKDJ point = (IKDJ) view.getItem(position);
if (point.getK() != 0) {
String text = "KDJ(14,1,3) ";
canvas.drawText(text, x, y, view.getTextPaint());
x += view.getTextPaint().measureText(text);
text = "K:" + view.formatValue(point.getK()) + " ";
canvas.drawText(text, x, y, mKPaint);
x += mKPaint.measureText(text);
if (point.getD() != 0) {
text = "D:" + view.formatValue(point.getD()) + " ";
canvas.drawText(text, x, y, mDPaint);
x += mDPaint.measureText(text);
text = "J:" + view.formatValue(point.getJ()) + " ";
canvas.drawText(text, x, y, mJPaint);
}
}
}
@Override
public float getMaxValue(IKDJ point) {
return Math.max(point.getK(), Math.max(point.getD(), point.getJ()));
}
@Override
public float getMinValue(IKDJ point) {
return Math.min(point.getK(), Math.min(point.getD(), point.getJ()));
}
@Override
public IValueFormatter getValueFormatter() {
return new ValueFormatter();
}
/**
* 设置K颜色
*/
public void setKColor(int color) {
mKPaint.setColor(color);
}
/**
* 设置D颜色
*/
public void setDColor(int color) {
mDPaint.setColor(color);
}
/**
* 设置J颜色
*/
public void setJColor(int color) {
mJPaint.setColor(color);
}
/**
* 设置曲线宽度
*/
public void setLineWidth(float width) {
mKPaint.setStrokeWidth(width);
mDPaint.setStrokeWidth(width);
mJPaint.setStrokeWidth(width);
}
/**
* 设置文字大小
*/
public void setTextSize(float textSize) {
mKPaint.setTextSize(textSize);
mDPaint.setTextSize(textSize);
mJPaint.setTextSize(textSize);
}
}
@@ -0,0 +1,144 @@
package com.github.fujianlian.klinechart.draw;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import com.github.fujianlian.klinechart.R;
import com.github.fujianlian.klinechart.BaseKLineChartView;
import com.github.fujianlian.klinechart.base.IChartDraw;
import com.github.fujianlian.klinechart.base.IValueFormatter;
import com.github.fujianlian.klinechart.entity.IMACD;
import com.github.fujianlian.klinechart.formatter.ValueFormatter;
/**
* macd实现类
* Created by tifezh on 2016/6/19.
*/
public class MACDDraw implements IChartDraw<IMACD> {
private Paint mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mGreenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mDIFPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mDEAPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mMACDPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**
* macd 中柱子的宽度
*/
private float mMACDWidth = 0;
public MACDDraw(BaseKLineChartView view) {
Context context = view.getContext();
mRedPaint.setColor(ContextCompat.getColor(context, R.color.chart_red));
mGreenPaint.setColor(ContextCompat.getColor(context, R.color.chart_green));
}
@Override
public void drawTranslated(@Nullable IMACD lastPoint, @NonNull IMACD curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position) {
drawMACD(canvas, view, curX, curPoint.getMacd());
view.drawChildLine(canvas, mDIFPaint, lastX, lastPoint.getDea(), curX, curPoint.getDea());
view.drawChildLine(canvas, mDEAPaint, lastX, lastPoint.getDif(), curX, curPoint.getDif());
}
@Override
public void drawText(@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position, float x, float y) {
IMACD point = (IMACD) view.getItem(position);
String text = "MACD(12,26,9) ";
canvas.drawText(text, x, y, view.getTextPaint());
x += view.getTextPaint().measureText(text);
text = "MACD:" + view.formatValue(point.getMacd()) + " ";
canvas.drawText(text, x, y, mMACDPaint);
x += mMACDPaint.measureText(text);
text = "DIF:" + view.formatValue(point.getDif()) + " ";
canvas.drawText(text, x, y, mDEAPaint);
x += mDIFPaint.measureText(text);
text = "DEA:" + view.formatValue(point.getDea());
canvas.drawText(text, x, y, mDIFPaint);
}
@Override
public float getMaxValue(IMACD point) {
return Math.max(point.getMacd(), Math.max(point.getDea(), point.getDif()));
}
@Override
public float getMinValue(IMACD point) {
return Math.min(point.getMacd(), Math.min(point.getDea(), point.getDif()));
}
@Override
public IValueFormatter getValueFormatter() {
return new ValueFormatter();
}
/**
* 画macd
*
* @param canvas
* @param x
* @param macd
*/
private void drawMACD(Canvas canvas, BaseKLineChartView view, float x, float macd) {
float macdy = view.getChildY(macd);
float r = mMACDWidth / 2;
float zeroy = view.getChildY(0);
if (macd > 0) {
// left top right bottom
canvas.drawRect(x - r, macdy, x + r, zeroy, mRedPaint);
} else {
canvas.drawRect(x - r, zeroy, x + r, macdy, mGreenPaint);
}
}
/**
* 设置DIF颜色
*/
public void setDIFColor(int color) {
this.mDIFPaint.setColor(color);
}
/**
* 设置DEA颜色
*/
public void setDEAColor(int color) {
this.mDEAPaint.setColor(color);
}
/**
* 设置MACD颜色
*/
public void setMACDColor(int color) {
this.mMACDPaint.setColor(color);
}
/**
* 设置MACD的宽度
*
* @param MACDWidth
*/
public void setMACDWidth(float MACDWidth) {
mMACDWidth = MACDWidth;
}
/**
* 设置曲线宽度
*/
public void setLineWidth(float width) {
mDEAPaint.setStrokeWidth(width);
mDIFPaint.setStrokeWidth(width);
mMACDPaint.setStrokeWidth(width);
}
/**
* 设置文字大小
*/
public void setTextSize(float textSize) {
mDEAPaint.setTextSize(textSize);
mDIFPaint.setTextSize(textSize);
mMACDPaint.setTextSize(textSize);
}
}
@@ -0,0 +1,410 @@
package com.github.fujianlian.klinechart.draw;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import com.github.fujianlian.klinechart.R;
import com.github.fujianlian.klinechart.BaseKLineChartView;
import com.github.fujianlian.klinechart.KLineChartView;
import com.github.fujianlian.klinechart.base.IChartDraw;
import com.github.fujianlian.klinechart.base.IValueFormatter;
import com.github.fujianlian.klinechart.entity.ICandle;
import com.github.fujianlian.klinechart.entity.IKLine;
import com.github.fujianlian.klinechart.formatter.ValueFormatter;
import com.github.fujianlian.klinechart.utils.ViewUtil;
import java.util.ArrayList;
import java.util.List;
/**
* 主图的实现类
* Created by tifezh on 2016/6/14.
*/
public class MainDraw implements IChartDraw<ICandle> {
private float mCandleWidth = 0;
private float mCandleLineWidth = 0;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mGreenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ma5Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ma10Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ma30Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mSelectorTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mSelectorBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Context mContext;
private boolean mCandleSolid = true;
// 是否分时
private boolean isLine = false;
private Status status = Status.MA;
private KLineChartView kChartView;
public MainDraw(BaseKLineChartView view) {
Context context = view.getContext();
kChartView = (KLineChartView) view;
mContext = context;
mRedPaint.setColor(ContextCompat.getColor(context, R.color.chart_red));
mGreenPaint.setColor(ContextCompat.getColor(context, R.color.chart_green));
mLinePaint.setColor(ContextCompat.getColor(context, R.color.chart_line));
paint.setColor(ContextCompat.getColor(context, R.color.chart_line_background));
}
public void setStatus(Status status) {
this.status = status;
}
public Status getStatus() {
return status;
}
@Override
public void drawTranslated(@Nullable ICandle lastPoint, @NonNull ICandle curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position) {
if (isLine) {
view.drawMainLine(canvas, mLinePaint, lastX, lastPoint.getClosePrice(), curX, curPoint.getClosePrice());
view.drawMainMinuteLine(canvas, paint, lastX, lastPoint.getClosePrice(), curX, curPoint.getClosePrice());
if (status == Status.MA) {
//画ma60
if (lastPoint.getMA60Price() != 0) {
view.drawMainLine(canvas, ma10Paint, lastX, lastPoint.getMA60Price(), curX, curPoint.getMA60Price());
}
} else if (status == Status.BOLL) {
//画boll
if (lastPoint.getMb() != 0) {
view.drawMainLine(canvas, ma10Paint, lastX, lastPoint.getMb(), curX, curPoint.getMb());
}
}
} else {
drawCandle(view, canvas, curX, curPoint.getHighPrice(), curPoint.getLowPrice(), curPoint.getOpenPrice(), curPoint.getClosePrice());
if (status == Status.MA) {
//画ma5
if (lastPoint.getMA5Price() != 0) {
view.drawMainLine(canvas, ma5Paint, lastX, lastPoint.getMA5Price(), curX, curPoint.getMA5Price());
}
//画ma10
if (lastPoint.getMA10Price() != 0) {
view.drawMainLine(canvas, ma10Paint, lastX, lastPoint.getMA10Price(), curX, curPoint.getMA10Price());
}
//画ma30
if (lastPoint.getMA30Price() != 0) {
view.drawMainLine(canvas, ma30Paint, lastX, lastPoint.getMA30Price(), curX, curPoint.getMA30Price());
}
} else if (status == Status.BOLL) {
//画boll
if (lastPoint.getUp() != 0) {
view.drawMainLine(canvas, ma5Paint, lastX, lastPoint.getUp(), curX, curPoint.getUp());
}
if (lastPoint.getMb() != 0) {
view.drawMainLine(canvas, ma10Paint, lastX, lastPoint.getMb(), curX, curPoint.getMb());
}
if (lastPoint.getDn() != 0) {
view.drawMainLine(canvas, ma30Paint, lastX, lastPoint.getDn(), curX, curPoint.getDn());
}
}
}
}
@Override
public void drawText(@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position, float x, float y) {
ICandle point = (IKLine) view.getItem(position);
y = y - 5;
if (isLine) {
if (status == Status.MA) {
if (point.getMA60Price() != 0) {
String text = "MA60:" + view.formatValue(point.getMA60Price()) + " ";
canvas.drawText(text, x, y, ma10Paint);
}
} else if (status == Status.BOLL) {
if (point.getMb() != 0) {
String text = "BOLL:" + view.formatValue(point.getMb()) + " ";
canvas.drawText(text, x, y, ma10Paint);
}
}
} else {
if (status == Status.MA) {
String text;
if (point.getMA5Price() != 0) {
text = "MA5:" + view.formatValue(point.getMA5Price()) + " ";
canvas.drawText(text, x, y, ma5Paint);
x += ma5Paint.measureText(text);
}
if (point.getMA10Price() != 0) {
text = "MA10:" + view.formatValue(point.getMA10Price()) + " ";
canvas.drawText(text, x, y, ma10Paint);
x += ma10Paint.measureText(text);
}
if (point.getMA20Price() != 0) {
text = "MA30:" + view.formatValue(point.getMA30Price());
canvas.drawText(text, x, y, ma30Paint);
}
} else if (status == Status.BOLL) {
if (point.getMb() != 0) {
String text = "BOLL:" + view.formatValue(point.getMb()) + " ";
canvas.drawText(text, x, y, ma10Paint);
x += ma5Paint.measureText(text);
text = "UB:" + view.formatValue(point.getUp()) + " ";
canvas.drawText(text, x, y, ma5Paint);
x += ma10Paint.measureText(text);
text = "LB:" + view.formatValue(point.getDn());
canvas.drawText(text, x, y, ma30Paint);
}
}
}
if (view.isLongPress()) {
drawSelector(view, canvas);
}
}
@Override
public float getMaxValue(ICandle point) {
if (status == Status.BOLL) {
if (Float.isNaN(point.getUp())) {
if (point.getMb() == 0) {
return point.getHighPrice();
} else {
return point.getMb();
}
} else if (point.getUp() == 0) {
return point.getHighPrice();
} else {
return point.getUp();
}
} else {
return Math.max(point.getHighPrice(), point.getMA30Price());
}
}
@Override
public float getMinValue(ICandle point) {
if (status == Status.BOLL) {
if (point.getDn() == 0) {
return point.getLowPrice();
} else {
return point.getDn();
}
} else {
if (point.getMA30Price() == 0f) {
return point.getLowPrice();
} else {
return Math.min(point.getMA30Price(), point.getLowPrice());
}
}
}
@Override
public IValueFormatter getValueFormatter() {
return new ValueFormatter();
}
/**
* 画Candle
*
* @param canvas
* @param x x轴坐标
* @param high 最高价
* @param low 最低价
* @param open 开盘价
* @param close 收盘价
*/
private void drawCandle(BaseKLineChartView view, Canvas canvas, float x, float high, float low, float open, float close) {
high = view.getMainY(high);
low = view.getMainY(low);
open = view.getMainY(open);
close = view.getMainY(close);
float r = mCandleWidth / 2;
float lineR = mCandleLineWidth / 2;
if (open > close) {
//实心
if (mCandleSolid) {
canvas.drawRect(x - r, close, x + r, open, mRedPaint);
canvas.drawRect(x - lineR, high, x + lineR, low, mRedPaint);
} else {
mRedPaint.setStrokeWidth(mCandleLineWidth);
canvas.drawLine(x, high, x, close, mRedPaint);
canvas.drawLine(x, open, x, low, mRedPaint);
canvas.drawLine(x - r + lineR, open, x - r + lineR, close, mRedPaint);
canvas.drawLine(x + r - lineR, open, x + r - lineR, close, mRedPaint);
mRedPaint.setStrokeWidth(mCandleLineWidth * view.getScaleX());
canvas.drawLine(x - r, open, x + r, open, mRedPaint);
canvas.drawLine(x - r, close, x + r, close, mRedPaint);
}
} else if (open < close) {
canvas.drawRect(x - r, open, x + r, close, mGreenPaint);
canvas.drawRect(x - lineR, high, x + lineR, low, mGreenPaint);
} else {
canvas.drawRect(x - r, open, x + r, close + 1, mRedPaint);
canvas.drawRect(x - lineR, high, x + lineR, low, mRedPaint);
}
}
/**
* draw选择器
*
* @param view
* @param canvas
*/
private void drawSelector(BaseKLineChartView view, Canvas canvas) {
Paint.FontMetrics metrics = mSelectorTextPaint.getFontMetrics();
float textHeight = metrics.descent - metrics.ascent;
int index = view.getSelectedIndex();
float padding = ViewUtil.Dp2Px(mContext, 5);
float margin = ViewUtil.Dp2Px(mContext, 5);
float width = 0;
float left;
float top = margin + view.getTopPadding();
float height = padding * 8 + textHeight * 5;
ICandle point = (ICandle) view.getItem(index);
List<String> strings = new ArrayList<>();
strings.add(view.getAdapter().getDate(index));
strings.add("高:" + point.getHighPrice());
strings.add("低:" + point.getLowPrice());
strings.add("开:" + point.getOpenPrice());
strings.add("收:" + point.getClosePrice());
for (String s : strings) {
width = Math.max(width, mSelectorTextPaint.measureText(s));
}
width += padding * 2;
float x = view.translateXtoX(view.getX(index));
if (x > view.getChartWidth() / 2) {
left = margin;
} else {
left = view.getChartWidth() - width - margin;
}
RectF r = new RectF(left, top, left + width, top + height);
canvas.drawRoundRect(r, padding, padding, mSelectorBackgroundPaint);
float y = top + padding * 2 + (textHeight - metrics.bottom - metrics.top) / 2;
for (String s : strings) {
canvas.drawText(s, left + padding, y, mSelectorTextPaint);
y += textHeight + padding;
}
}
/**
* 设置蜡烛宽度
*
* @param candleWidth
*/
public void setCandleWidth(float candleWidth) {
mCandleWidth = candleWidth;
}
/**
* 设置蜡烛线宽度
*
* @param candleLineWidth
*/
public void setCandleLineWidth(float candleLineWidth) {
mCandleLineWidth = candleLineWidth;
}
/**
* 设置ma5颜色
*
* @param color
*/
public void setMa5Color(int color) {
this.ma5Paint.setColor(color);
}
/**
* 设置ma10颜色
*
* @param color
*/
public void setMa10Color(int color) {
this.ma10Paint.setColor(color);
}
/**
* 设置ma30颜色
*
* @param color
*/
public void setMa30Color(int color) {
this.ma30Paint.setColor(color);
}
/**
* 设置选择器文字颜色
*
* @param color
*/
public void setSelectorTextColor(int color) {
mSelectorTextPaint.setColor(color);
}
/**
* 设置选择器文字大小
*
* @param textSize
*/
public void setSelectorTextSize(float textSize) {
mSelectorTextPaint.setTextSize(textSize);
}
/**
* 设置选择器背景
*
* @param color
*/
public void setSelectorBackgroundColor(int color) {
mSelectorBackgroundPaint.setColor(color);
}
/**
* 设置曲线宽度
*/
public void setLineWidth(float width) {
ma30Paint.setStrokeWidth(width);
ma10Paint.setStrokeWidth(width);
ma5Paint.setStrokeWidth(width);
mLinePaint.setStrokeWidth(width);
}
/**
* 设置文字大小
*/
public void setTextSize(float textSize) {
ma30Paint.setTextSize(textSize);
ma10Paint.setTextSize(textSize);
ma5Paint.setTextSize(textSize);
}
/**
* 蜡烛是否实心
*/
public void setCandleSolid(boolean candleSolid) {
mCandleSolid = candleSolid;
}
public void setLine(boolean line) {
if (isLine != line) {
isLine = line;
if (isLine) {
kChartView.setCandleWidth(kChartView.dp2px(7f));
} else {
kChartView.setCandleWidth(kChartView.dp2px(6f));
}
}
}
public boolean isLine() {
return isLine;
}
}
@@ -0,0 +1,91 @@
package com.github.fujianlian.klinechart.draw;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.github.fujianlian.klinechart.BaseKLineChartView;
import com.github.fujianlian.klinechart.base.IChartDraw;
import com.github.fujianlian.klinechart.base.IValueFormatter;
import com.github.fujianlian.klinechart.entity.IRSI;
import com.github.fujianlian.klinechart.formatter.ValueFormatter;
/**
* RSI实现类
* Created by tifezh on 2016/6/19.
*/
public class RSIDraw implements IChartDraw<IRSI> {
private Paint mRSI1Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mRSI2Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mRSI3Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
public RSIDraw(BaseKLineChartView view) {
}
@Override
public void drawTranslated(@Nullable IRSI lastPoint, @NonNull IRSI curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position) {
if (lastPoint.getRsi() != 0) {
view.drawChildLine(canvas, mRSI1Paint, lastX, lastPoint.getRsi(), curX, curPoint.getRsi());
}
}
@Override
public void drawText(@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position, float x, float y) {
IRSI point = (IRSI) view.getItem(position);
if (point.getRsi() != 0) {
String text = "RSI(14) ";
canvas.drawText(text, x, y, view.getTextPaint());
x += view.getTextPaint().measureText(text);
text = view.formatValue(point.getRsi());
canvas.drawText(text, x, y, mRSI1Paint);
}
}
@Override
public float getMaxValue(IRSI point) {
return point.getRsi();
}
@Override
public float getMinValue(IRSI point) {
return point.getRsi();
}
@Override
public IValueFormatter getValueFormatter() {
return new ValueFormatter();
}
public void setRSI1Color(int color) {
mRSI1Paint.setColor(color);
}
public void setRSI2Color(int color) {
mRSI2Paint.setColor(color);
}
public void setRSI3Color(int color) {
mRSI3Paint.setColor(color);
}
/**
* 设置曲线宽度
*/
public void setLineWidth(float width) {
mRSI1Paint.setStrokeWidth(width);
mRSI2Paint.setStrokeWidth(width);
mRSI3Paint.setStrokeWidth(width);
}
/**
* 设置文字大小
*/
public void setTextSize(float textSize) {
mRSI2Paint.setTextSize(textSize);
mRSI3Paint.setTextSize(textSize);
mRSI1Paint.setTextSize(textSize);
}
}
@@ -0,0 +1,9 @@
package com.github.fujianlian.klinechart.draw;
/**
* @author fujianlian Created on 2018/8/20 11:09
* @descripe MainDraw当前子视图
*/
public enum Status {
MA, BOLL, NONE
}
@@ -0,0 +1,127 @@
package com.github.fujianlian.klinechart.draw;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import com.github.fujianlian.klinechart.R;
import com.github.fujianlian.klinechart.BaseKLineChartView;
import com.github.fujianlian.klinechart.base.IChartDraw;
import com.github.fujianlian.klinechart.base.IValueFormatter;
import com.github.fujianlian.klinechart.entity.IVolume;
import com.github.fujianlian.klinechart.formatter.BigValueFormatter;
import com.github.fujianlian.klinechart.utils.ViewUtil;
/**
* Created by hjm on 2017/11/14 17:49.
*/
public class VolumeDraw implements IChartDraw<IVolume> {
private Paint mRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mGreenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ma5Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint ma10Paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int pillarWidth = 0;
public VolumeDraw(BaseKLineChartView view) {
Context context = view.getContext();
mRedPaint.setColor(ContextCompat.getColor(context, R.color.chart_red));
mGreenPaint.setColor(ContextCompat.getColor(context, R.color.chart_green));
pillarWidth = ViewUtil.Dp2Px(context, 4);
}
@Override
public void drawTranslated(
@Nullable IVolume lastPoint, @NonNull IVolume curPoint, float lastX, float curX,
@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position) {
drawHistogram(canvas, curPoint, lastPoint, curX, view, position);
if (lastPoint.getMA5Volume() != 0f) {
view.drawVolLine(canvas, ma5Paint, lastX, lastPoint.getMA5Volume(), curX, curPoint.getMA5Volume());
}
if (lastPoint.getMA10Volume() != 0f) {
view.drawVolLine(canvas, ma10Paint, lastX, lastPoint.getMA10Volume(), curX, curPoint.getMA10Volume());
}
}
private void drawHistogram(
Canvas canvas, IVolume curPoint, IVolume lastPoint, float curX,
BaseKLineChartView view, int position) {
float r = pillarWidth / 2;
float top = view.getVolY(curPoint.getVolume());
int bottom = view.getVolRect().bottom;
if (curPoint.getClosePrice() >= curPoint.getOpenPrice()) {//涨
canvas.drawRect(curX - r, top, curX + r, bottom, mRedPaint);
} else {
canvas.drawRect(curX - r, top, curX + r, bottom, mGreenPaint);
}
}
@Override
public void drawText(
@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position, float x, float y) {
IVolume point = (IVolume) view.getItem(position);
String text = "VOL:" + getValueFormatter().format(point.getVolume()) + " ";
canvas.drawText(text, x, y, view.getTextPaint());
x += view.getTextPaint().measureText(text);
text = "MA5:" + getValueFormatter().format(point.getMA5Volume()) + " ";
canvas.drawText(text, x, y, ma5Paint);
x += ma5Paint.measureText(text);
text = "MA10:" + getValueFormatter().format(point.getMA10Volume());
canvas.drawText(text, x, y, ma10Paint);
}
@Override
public float getMaxValue(IVolume point) {
return Math.max(point.getVolume(), Math.max(point.getMA5Volume(), point.getMA10Volume()));
}
@Override
public float getMinValue(IVolume point) {
return Math.min(point.getVolume(), Math.min(point.getMA5Volume(), point.getMA10Volume()));
}
@Override
public IValueFormatter getValueFormatter() {
return new BigValueFormatter();
}
/**
* 设置 MA5 线的颜色
*
* @param color
*/
public void setMa5Color(int color) {
this.ma5Paint.setColor(color);
}
/**
* 设置 MA10 线的颜色
*
* @param color
*/
public void setMa10Color(int color) {
this.ma10Paint.setColor(color);
}
public void setLineWidth(float width) {
this.ma5Paint.setStrokeWidth(width);
this.ma10Paint.setStrokeWidth(width);
}
/**
* 设置文字大小
*
* @param textSize
*/
public void setTextSize(float textSize) {
this.ma5Paint.setTextSize(textSize);
this.ma10Paint.setTextSize(textSize);
}
}
@@ -0,0 +1,79 @@
package com.github.fujianlian.klinechart.draw;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.github.fujianlian.klinechart.BaseKLineChartView;
import com.github.fujianlian.klinechart.base.IChartDraw;
import com.github.fujianlian.klinechart.base.IValueFormatter;
import com.github.fujianlian.klinechart.entity.IWR;
import com.github.fujianlian.klinechart.formatter.ValueFormatter;
/**
* KDJ实现类
* Created by tifezh on 2016/6/19.
*/
public class WRDraw implements IChartDraw<IWR> {
private Paint mRPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public WRDraw(BaseKLineChartView view) {
}
@Override
public void drawTranslated(@Nullable IWR lastPoint, @NonNull IWR curPoint, float lastX, float curX, @NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position) {
if (lastPoint.getR() != -10) {
view.drawChildLine(canvas, mRPaint, lastX, lastPoint.getR(), curX, curPoint.getR());
}
}
@Override
public void drawText(@NonNull Canvas canvas, @NonNull BaseKLineChartView view, int position, float x, float y) {
IWR point = (IWR) view.getItem(position);
if (point.getR() != -10) {
String text = "WR(14):";
canvas.drawText(text, x, y, view.getTextPaint());
x += view.getTextPaint().measureText(text);
text = view.formatValue(point.getR()) + " ";
canvas.drawText(text, x, y, mRPaint);
}
}
@Override
public float getMaxValue(IWR point) {
return point.getR();
}
@Override
public float getMinValue(IWR point) {
return point.getR();
}
@Override
public IValueFormatter getValueFormatter() {
return new ValueFormatter();
}
/**
* 设置%R颜色
*/
public void setRColor(int color) {
mRPaint.setColor(color);
}
/**
* 设置曲线宽度
*/
public void setLineWidth(float width) {
mRPaint.setStrokeWidth(width);
}
/**
* 设置文字大小
*/
public void setTextSize(float textSize) {
mRPaint.setTextSize(textSize);
}
}
@@ -0,0 +1,72 @@
package com.github.fujianlian.klinechart.entity;
/**
* 蜡烛图实体接口
* Created by tifezh on 2016/6/9.
*/
public interface ICandle {
/**
* 开盘价
*/
float getOpenPrice();
/**
* 最高价
*/
float getHighPrice();
/**
* 最低价
*/
float getLowPrice();
/**
* 收盘价
*/
float getClosePrice();
// 以下为MA数据
/**
* 五(月,日,时,分,5分等)均价
*/
float getMA5Price();
/**
* 十(月,日,时,分,5分等)均价
*/
float getMA10Price();
/**
* 二十(月,日,时,分,5分等)均价
*/
float getMA20Price();
/**
* 三十(月,日,时,分,5分等)均价
*/
float getMA30Price();
/**
* 六十(月,日,时,分,5分等)均价
*/
float getMA60Price();
// 以下为BOLL数据
/**
* 上轨线
*/
float getUp();
/**
* 中轨线
*/
float getMb();
/**
* 下轨线
*/
float getDn();
}
@@ -0,0 +1,25 @@
package com.github.fujianlian.klinechart.entity;
/**
* @author fujianlian Created on 2018/8/20 16:08
* @descripe KDJ指标(随机指标)接口
* 相关说明:https://baike.baidu.com/item/KDJ%E6%8C%87%E6%A0%87/6328421?fr=aladdin&fromid=3423560&fromtitle=kdj
*/
public interface IKDJ {
/**
* K值
*/
float getK();
/**
* D值
*/
float getD();
/**
* J值
*/
float getJ();
}
@@ -0,0 +1,8 @@
package com.github.fujianlian.klinechart.entity;
/**
* KDJ指标(随机指标)接口
* Created by tifezh on 2016/6/9.
*/
public interface IKLine extends ICandle, IMACD, IKDJ, IRSI, IVolume, IWR {
}
@@ -0,0 +1,27 @@
package com.github.fujianlian.klinechart.entity;
/**
* MACD指标(指数平滑移动平均线)接口
* @see <a href="https://baike.baidu.com/item/MACD指标"/>相关说明</a>
* Created by tifezh on 2016/6/10.
*/
public interface IMACD {
/**
* DEA值
*/
float getDea();
/**
* DIF值
*/
float getDif();
/**
* MACD值
*/
float getMacd();
}
@@ -0,0 +1,16 @@
package com.github.fujianlian.klinechart.entity;
/**
* RSI指标接口
* @see <a href="https://baike.baidu.com/item/RSI%E6%8C%87%E6%A0%87"/>相关说明</a>
* Created by tifezh on 2016/6/10.
*/
public interface IRSI {
/**
* RSI值
*/
float getRsi();
}
@@ -0,0 +1,34 @@
package com.github.fujianlian.klinechart.entity;
/**
* 成交量接口
* Created by hjm on 2017/11/14 17:46.
*/
public interface IVolume {
/**
* 开盘价
*/
float getOpenPrice();
/**
* 收盘价
*/
float getClosePrice();
/**
* 成交量
*/
float getVolume();
/**
* 五(月,日,时,分,5分等)均量
*/
float getMA5Volume();
/**
* 十(月,日,时,分,5分等)均量
*/
float getMA10Volume();
}
@@ -0,0 +1,14 @@
package com.github.fujianlian.klinechart.entity;
/**
* @author fujianlian Created on 2018/8/20 15:16
* @descripe WR指标(随机指标)接口
*/
public interface IWR {
/**
* %R值
*/
float getR();
}
@@ -0,0 +1,33 @@
package com.github.fujianlian.klinechart.formatter;
import com.github.fujianlian.klinechart.base.IValueFormatter;
import java.util.Locale;
/**
* 对较大数据进行格式化
* Created by tifezh on 2017/12/13.
*/
public class BigValueFormatter implements IValueFormatter{
//必须是排好序的
private int[] values={10000,1000000,100000000};
private String[] units={"","百万","亿"};
@Override
public String format(float value) {
String unit="";
int i=values.length-1;
while (i>=0)
{
if(value>values[i]) {
value /= values[i];
unit = units[i];
break;
}
i--;
}
return String.format(Locale.getDefault(),"%.2f", value)+unit;
}
}
@@ -0,0 +1,22 @@
package com.github.fujianlian.klinechart.formatter;
import com.github.fujianlian.klinechart.base.IDateTimeFormatter;
import com.github.fujianlian.klinechart.utils.DateUtil;
import java.util.Date;
/**
* 时间格式化器
* Created by tifezh on 2016/6/21.
*/
public class DateFormatter implements IDateTimeFormatter {
@Override
public String format(Date date) {
if (date != null) {
return DateUtil.DateFormat.format(date);
} else {
return "";
}
}
}
@@ -0,0 +1,21 @@
package com.github.fujianlian.klinechart.formatter;
import com.github.fujianlian.klinechart.base.IDateTimeFormatter;
import com.github.fujianlian.klinechart.utils.DateUtil;
import java.util.Date;
/**
* 时间格式化器
* Created by tifezh on 2016/6/21.
*/
public class TimeFormatter implements IDateTimeFormatter {
@Override
public String format(Date date) {
if (date == null) {
return "";
}
return DateUtil.shortTimeFormat.format(date);
}
}
@@ -0,0 +1,15 @@
package com.github.fujianlian.klinechart.formatter;
import com.github.fujianlian.klinechart.base.IValueFormatter;
/**
* Value格式化类
* Created by tifezh on 2016/6/21.
*/
public class ValueFormatter implements IValueFormatter {
@Override
public String format(float value) {
return String.format("%.2f", value);
}
}
@@ -0,0 +1,14 @@
package com.github.fujianlian.klinechart.utils;
import java.text.SimpleDateFormat;
/**
* 时间工具类
* Created by tifezh on 2016/4/27.
*/
public class DateUtil {
public static SimpleDateFormat longTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public static SimpleDateFormat shortTimeFormat = new SimpleDateFormat("HH:mm");
public static SimpleDateFormat DateFormat = new SimpleDateFormat("yyyy/MM/dd");
}
@@ -0,0 +1,18 @@
package com.github.fujianlian.klinechart.utils;
import android.content.Context;
/**
* Created by tian on 2016/4/11.
*/
public class ViewUtil {
static public int Dp2Px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
static public int Px2Dp(Context context, float px) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (px / scale + 0.5f);
}
}
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="KLineChartView">
<!--公共的-->
<attr name="kc_text_size" format="dimension"/>
<attr name="kc_text_color" format="color"/>
<attr name="kc_line_width" format="dimension"/>
<attr name="kc_background_color" format="color"/>
<attr name="kc_selected_line_color" format="color"/>
<attr name="kc_selected_line_width" format="dimension"/>
<attr name="kc_grid_line_width" format="dimension"/>
<attr name="kc_grid_line_color" format="color"/>
<attr name="kc_point_width" format="dimension"/>
<!--macd-->
<attr name="kc_macd_width" format="dimension"/>
<attr name="kc_dif_color" format="color"/>
<attr name="kc_dea_color" format="color"/>
<attr name="kc_macd_color" format="color"/>
<!--kdj-->
<attr name="kc_k_color" format="color"/>
<attr name="kc_d_color" format="color"/>
<attr name="kc_j_color" format="color"/>
<!--rsi-->
<attr name="kc_rsi1_color" format="color"/>
<attr name="kc_rsi2_color" format="color"/>
<attr name="kc_ris3_color" format="color"/>
<!--boll-->
<attr name="kc_up_color" format="color"/>
<attr name="kc_mb_color" format="color"/>
<attr name="kc_dn_color" format="color"/>
<!--main-->
<attr name="kc_ma5_color" format="color"/>
<attr name="kc_ma10_color" format="color"/>
<attr name="kc_ma20_color" format="color"/>
<attr name="kc_candle_width" format="dimension"/>
<attr name="kc_candle_line_width" format="dimension"/>
<attr name="kc_selector_background_color" format="color"/>
<attr name="kc_selector_text_size" format="dimension"/>
<attr name="kc_candle_solid" format="boolean"/>
<!--tab-->
<attr name="kc_tab_background_color" format="color"/>
<attr name="kc_tab_text_color" format="color"/>
<attr name="kc_tab_indicator_color" format="color"/>
</declare-styleable>
</resources>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="chart_red">#26BF66</color>
<color name="chart_green">#FD6433</color>
<color name="chart_line">#C9933E</color>
<color name="chart_line_background">#1aC9933E</color>
<color name="chart_ma5">#DA8AE5</color>
<color name="chart_ma10">#39B0E8</color>
<color name="chart_ma30">#FFC76D</color>
<color name="chart_white">#ffffff</color>
<color name="chart_background">#202326</color>
<color name="chart_bac">#00FFFFFF</color>
<color name="chart_point_bac">#202326</color>
<color name="chart_grid_line">#1AFFFFFF</color>
<color name="chart_text">#818596</color>
<color name="chart_selector">#202326</color>
<color name="chart_tab_background">#30343C</color>
<color name="chart_tab_indicator">#FF6601</color>
</resources>
@@ -0,0 +1,13 @@
<resources>
<dimen name="chart_text_size">10sp</dimen>
<dimen name="chart_selector_text_size">12sp</dimen>
<dimen name="chart_candle_line_width">1dp</dimen>
<dimen name="chart_candle_width">6dp</dimen>
<dimen name="chart_line_width">1dp</dimen>
<dimen name="chart_grid_line_width">0.8dp</dimen>
<dimen name="chart_0.5dp">0.5dp</dimen>
<dimen name="chart_point_width">7dp</dimen>
<dimen name="chart_top_padding">15dp</dimen>
<dimen name="chart_bottom_padding">15dp</dimen>
<dimen name="child_top_padding">10dp</dimen>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">KLineChartLib</string>
</resources>
@@ -0,0 +1,17 @@
package com.github.fujianlian.klinechart;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}
+124
View File
@@ -0,0 +1,124 @@
# KLineChart(不再维护)
Android仿火币K线图实现(包含MA,BOLL,MACD,KDJ,RSI,WR指标)
本项目是在 [tifezh的KChartView](https://github.com/tifezh/KChartView) 基础上进行修改的,新增了KDJ、WR指标、最大值和最小值展示,对UI展示进行了修改。
建议使用之前先查看[常见问题](./problem.md)
## 项目运行效果
![gif](https://github.com/fujianlian/KLineChart/raw/master/img/effect.gif)
![截图](https://github.com/fujianlian/KLineChart/raw/master/img/1.png)
## app下载
![二维码](https://github.com/fujianlian/KLineChart/raw/master/img/qrcode.png)
## 配置使用
```xml
<com.github.fujianlian.klinechart.KLineChartView
android:id="@+id/kLineChartView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
```
主图和附图初始化
```java
// KLineChartView
private void initView() {
...
// 依次添加副图子视图
addChildDraw(mMACDDraw);
addChildDraw(mKDJDraw);
addChildDraw(mRSIDraw);
addChildDraw(mWRDraw);
// 设置成交量视图
setVolDraw(mVolumeDraw);
// 设置主视图
setMainDraw(mMainDraw);
}
```
BaseKLineChartView
```java
// 主图显示隐藏调用
public void changeMainDrawType(Status status) {
if (mainDraw != null && mainDraw.getStatus() != status) {
mainDraw.setStatus(status);
invalidate();
}
}
// 主视图当前子视图
public enum Status {
MA, BOLL, NONE
}
// 设置子视图,position依据初始化添加先后顺序下标
public void setChildDraw(int position) {
if (mChildDrawPosition != position) {
if (!isShowChild) {
isShowChild = true;
initRect();
}
mChildDraw = mChildDraws.get(position);
mChildDrawPosition = position;
isWR = position == 5;
invalidate();
}
}
// 子视图隐藏
public void hideChildDraw() {
mChildDrawPosition = -1;
isShowChild = false;
mChildDraw = null;
initRect();
invalidate();
}
```
KLineChartView
```java
// 是否显示为分时图
public void setMainDrawLine(boolean isLine) {
mMainDraw.setLine(isLine);
}
```
## 自定义颜色
```xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="chart_red">#26BF66</color>
<color name="chart_green">#FD6433</color>
<color name="chart_line">#C9933E</color>
<color name="chart_line_background">#1aC9933E</color>
<color name="chart_ma5">#DA8AE5</color>
<color name="chart_ma10">#39B0E8</color>
<color name="chart_ma30">#FFC76D</color>
<color name="chart_white">#ffffff</color>
<color name="chart_background">#202326</color>
<color name="chart_bac">#00FFFFFF</color>
<color name="chart_point_bac">#202326</color>
<color name="chart_grid_line">#1AFFFFFF</color>
<color name="chart_text">#818596</color>
<color name="chart_selector">#202326</color>
<color name="chart_tab_background">#30343C</color>
<color name="chart_tab_indicator">#FF6601</color>
</resources>
```
布局时修改KLineChartView的相关颜色即可
## 具体使用参照KLineChartDemo
+27
View File
@@ -0,0 +1,27 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.20'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+14
View File
@@ -0,0 +1,14 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.enableAapt2=true
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#Fri Jul 26 21:09:55 CST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
+84
View File
@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+56
View File
@@ -0,0 +1,56 @@
## 展示5分钟、10分钟等分时图
https://github.com/fujianlian/KLineChart/issues/5
## 在NestedScrollView或ScrollView左右滑动冲突问题
```kotlin
// layout为与kChartView引起滑动冲突的view
kChartView.setOnTouchListener { _, event ->
if (event.pointerCount == 2) {
layout.requestDisallowInterceptTouchEvent(true)
} else {
layout.requestDisallowInterceptTouchEvent(false)
}
false
}
```
或者参考以下
https://github.com/fujianlian/KLineChart/issues/13
由于滑动冲突的情况比较多,若无法解决,请自查相关滑动冲突解决文章
## 向尾部添加数据时,尾部MA线形成一条竖线?
https://github.com/fujianlian/KLineChart/issues/12
## 关于新增一条数据addHeaderData后指标线不见了
https://github.com/fujianlian/KLineChart/issues/1
## 当数值过小时,macd图 只显示一条横线
https://github.com/fujianlian/KLineChart/issues/6
## 单击显示详情
https://github.com/fujianlian/KLineChart/issues/10
https://github.com/fujianlian/KLineChart/issues/19
## 数据很少从左边开始显示
https://github.com/fujianlian/KLineChart/issues/9
## 时间轴挤在一起了
https://github.com/fujianlian/KLineChart/issues/14
+1
View File
@@ -0,0 +1 @@
include ':KLineChartDemo', ':KLineChartLib'