Full Code of xiesuichao/KLineView for AI

master 94818338859e cached
41 files
202.4 KB
54.2k tokens
243 symbols
1 requests
Download .txt
Showing preview only (223K chars total). Download the full file or copy to clipboard to get everything.
Repository: xiesuichao/KLineView
Branch: master
Commit: 94818338859e
Files: 41
Total size: 202.4 KB

Directory structure:
gitextract_4lzolz3h/

├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   └── Project.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── README.md
├── apk/
│   └── app-debug.apk
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── example/
│       │               └── admin/
│       │                   └── klineview/
│       │                       └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── example/
│       │   │           └── admin/
│       │   │               └── klineview/
│       │   │                   ├── DepthActivity.java
│       │   │                   ├── MainActivity.java
│       │   │                   ├── Print.java
│       │   │                   ├── depth/
│       │   │                   │   ├── Depth.java
│       │   │                   │   └── DepthView.java
│       │   │                   └── kline/
│       │   │                       ├── KData.java
│       │   │                       ├── KLineView.java
│       │   │                       ├── Pointer.java
│       │   │                       ├── QuotaThread.java
│       │   │                       └── QuotaUtil.java
│       │   └── res/
│       │       ├── drawable/
│       │       │   └── ic_launcher_background.xml
│       │       ├── drawable-v24/
│       │       │   └── ic_launcher_foreground.xml
│       │       ├── layout/
│       │       │   ├── activity_depth.xml
│       │       │   └── activity_main.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       └── values/
│       │           ├── attrs.xml
│       │           ├── colors.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── example/
│                       └── admin/
│                           └── klineview/
│                               └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.release

================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <codeStyleSettings language="XML">
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
      <arrangement>
        <rules>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:android</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:id</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>style</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>ANDROID_ATTRIBUTE_ORDER</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>.*</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
        </rules>
      </arrangement>
    </codeStyleSettings>
  </code_scheme>
</component>

================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleMigrationSettings" migrationVersion="1" />
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="testRunner" value="PLATFORM" />
        <option name="distributionType" value="DEFAULT_WRAPPED" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
          </set>
        </option>
        <option name="resolveModulePerSourceSet" value="false" />
      </GradleProjectSettings>
    </option>
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="NullableNotNullManager">
    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
    <option name="myNullables">
      <value>
        <list size="12">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
          <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
          <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
          <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
          <item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
          <item index="6" class="java.lang.String" itemvalue="android.annotation.Nullable" />
          <item index="7" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
          <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
          <item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
          <item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
        </list>
      </value>
    </option>
    <option name="myNotNulls">
      <value>
        <list size="11">
          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
          <item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
          <item index="5" class="java.lang.String" itemvalue="android.annotation.NonNull" />
          <item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
          <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
          <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
          <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
          <item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
        </list>
      </value>
    </option>
  </component>
  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
        <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>

================================================
FILE: README.md
================================================
# KLineView
股票走势图K线控件  
[![](https://jitpack.io/v/xiesuichao/KLineView.svg)](https://jitpack.io/#xiesuichao/KLineView)  

主图指标:MA, EMA, BOLL      
副图指标:MACD, KDJ, RSI     
根目录下有个apk文件夹,内有最新的测试包,可以先安装看效果      
新增深度图控件,如下图所示,详情见demo         

支持实时刷新最后一条数据。  
支持添加最新的单条数据。  
支持滑动时的分页加载更多数据。     
支持惯性滑动。         
支持多指触控缩放。       
支持长按拖动。         
支持横屏显示         
支持xml布局自定义颜色,字体大小属性     

已对性能做优化,总数据量十万条以上对用户体验没有影响。   
首次加载5000条数据,页面初始化到加载完成,总共耗时400+ms,不超过0.5秒。         
分页加载5000条数据时,如果正在滑动过程中,添加数据的那一瞬间会稍微有一下卡顿,影响不大。        
经测试,800块的华为荣耀6A 每次添加4000条以下数据不会有卡顿,很流畅。         
建议每次添加数据在2000条左右。       
已对滑动事件冲突做处理,可上下滑动的父类(ScrollView、NestedScrollView等)无需再考虑滑动冲突       
             

![image](https://github.com/xiesuichao/KLineView/raw/master/image/KLineUI.png)
![image](https://github.com/xiesuichao/KLineView/raw/master/image/a5.png)
![image](https://github.com/xiesuichao/KLineView/raw/master/image/a2.png)
![image](https://github.com/xiesuichao/KLineView/raw/master/image/a3.png)

1.K线控件:
      
    //初始化控件加载数据(仅作初始化用,数据重置请调用resetDataList)
    mKLineView.initKDataList(getKDataList(5));

    //设置十字线移动模式,默认为0:固定指向收盘价
    mKLineView.setCrossHairMoveMode(KLineView.CROSS_HAIR_MOVE_FREE);
                
    //分页加载时添加多条数据
    mKLineView.addDataList(getKDataList(5));
                
    //实时刷新时添加单条数据
    mKLineView.addData(getKDataList(0.1).get(0));

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_kline_reset:
                //重置数据,可用于分时加载,是否需要定位到重置前的时间点请看方法注释
                //在做分时功能重新加载数据的时候,请务必调用该方法
                mKLineView.resetDataList(getKDataList(0.1));
                break;

            case R.id.btn_deputy:
                //是否显示副图
                mKLineView.setDeputyPicShow(!mKLineView.getVicePicShow());
                break;

            case R.id.btn_ma:
                //主图展示MA
                mKLineView.setMainImgType(KLineView.MAIN_IMG_MA);
                break;

            case R.id.btn_ema:
                //主图展示EMA
                mKLineView.setMainImgType(KLineView.MAIN_IMG_EMA);
                break;

            case R.id.btn_boll:
                //主图展示BOLL
                mKLineView.setMainImgType(KLineView.MAIN_IMG_BOLL);
                break;

            case R.id.btn_macd:
                //副图展示MACD
                mKLineView.setDeputyImgType(KLineView.DEPUTY_IMG_MACD);
                break;

            case R.id.btn_kdj:
                //副图展示KDJ
                mKLineView.setDeputyImgType(KLineView.DEPUTY_IMG_KDJ);
                break;

            case R.id.btn_rsi:
                //副图展示RSI
                mKLineView.setDeputyImgType(KLineView.DEPUTY_IMG_RSI);
                break;

            case R.id.btn_depth_activity:
                //跳转到深度图页面
                startActivity(new Intent(getApplicationContext(), DepthActivity.class));
                break;
        }
    }

    /**
     * 当控件显示数据属于总数据量的前三分之一时,会自动调用该接口,用于预加载数据,保证控件操作过程中的流畅性,
     * 虽然做了预加载,当总数据量较小时,也会出现用户滑到左边界了,但数据还未获取到,依然会有停顿。
     * 所以数据量越大,越不会出现停顿,也就越流畅
     */
    mKLineView.setOnRequestDataListListener(new KLineView.OnRequestDataListListener() {
        @Override
        public void requestData() {
            //请求数据
        }
    });


2.深度图控件:

    //添加购买数据
    depthView.setBuyDataList(getBuyDepthList());

    //添加出售数据
    depthView.setSellDataList(getSellDepthList());

    //重置深度数据
    depthView.resetAllData(getBuyDepthList(), getSellDepthList());

    //设置横坐标中间值
    depthView.setAbscissaCenterPrice(10.265);

    //设置数据详情的价钱说明
    depthView.setDetailPriceTitle("价格(BTC):");

    //设置数据详情的数量说明
    depthView.setDetailVolumeTitle("累积交易量:");

    //设置横坐标价钱小数位精度
    depthView.setPricePrecision(4);

    //是否显示竖线
    depthView.setShowDetailLine(true);

    //手指单击松开后,数据是否继续显示
    depthView.setShowDetailSingleClick(true);

    //手指长按松开后,数据是否继续显示
    depthView.setShowDetailLongPress(true);



================================================
FILE: app/.gitignore
================================================
/build


================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.admin.klineview"
        minSdkVersion 15
        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'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    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'
}


================================================
FILE: app/proguard-rules.pro
================================================
# 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


================================================
FILE: app/src/androidTest/java/com/example/admin/klineview/ExampleInstrumentedTest.java
================================================
package com.example.admin.klineview;

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() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("com.example.admin.klineview", appContext.getPackageName());
    }
}


================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.admin.klineview">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        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>

        <activity android:name=".DepthActivity"/>
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/example/admin/klineview/DepthActivity.java
================================================
package com.example.admin.klineview;

import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.example.admin.klineview.depth.Depth;
import com.example.admin.klineview.depth.DepthView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 深度图
 * Created by xiesuichao on 2018/9/24.
 */

public class DepthActivity extends AppCompatActivity {


    private DepthView depthView;
    private Button depthResetBtn;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_depth);

        initView();
        initData();
        setListener();

    }

    private void initView(){
        depthView = findViewById(R.id.dv_depth);
        depthResetBtn = findViewById(R.id.btn_depth_reset);
    }

    private void initData(){
        //添加购买数据
        depthView.setBuyDataList(getBuyDepthList());

        //添加出售数据
        depthView.setSellDataList(getSellDepthList());

        //设置横坐标中间值
        depthView.setAbscissaCenterPrice(10.265);

        //设置数据详情的价钱说明
        depthView.setDetailPriceTitle("价格(BTC):");

        //设置数据详情的数量说明
        depthView.setDetailVolumeTitle("累积交易量:");

        //设置横坐标价钱小数位精度
        depthView.setPricePrecision(4);

        //是否显示竖线
        depthView.setShowDetailLine(true);

        //手指单击松开后,数据是否继续显示
        depthView.setShowDetailSingleClick(true);

        //手指长按松开后,数据是否继续显示
        depthView.setShowDetailLongPress(true);

    }

    private void setListener(){
        depthResetBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //重置深度数据
                depthView.resetAllData(getBuyDepthList(), getSellDepthList());
            }
        });
    }

    //模拟深度数据
    private List<Depth> getBuyDepthList(){
        List<Depth> depthList = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            depthList.add(new Depth(100 - random.nextDouble() * 10,
                    random.nextInt(10) * random.nextInt(10) * random.nextInt(10) + random.nextDouble(), 0));
        }
        return depthList;
    }

    //模拟深度数据
    private List<Depth> getSellDepthList(){
        List<Depth> depthList = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            depthList.add(new Depth(100 + random.nextDouble() * 10,
                    random.nextInt(10) * random.nextInt(10) * random.nextInt(10) + random.nextDouble(), 1));
        }
        return depthList;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        depthView.cancelCallback();
    }
}


================================================
FILE: app/src/main/java/com/example/admin/klineview/MainActivity.java
================================================
package com.example.admin.klineview;

import android.content.Intent;
import android.content.res.Configuration;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;

import com.example.admin.klineview.depth.Depth;
import com.example.admin.klineview.depth.DepthView;
import com.example.admin.klineview.kline.KData;
import com.example.admin.klineview.kline.KLineView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Handler mHandler;
    private KLineView kLineView;
    private Button deputyBtn, maBtn, emaBtn, bollBtn, macdBtn, kdjBtn, depthJumpBtn, kLineResetBtn,
            rsiBtn, instantBtn;
    private Runnable dataListAddRunnable, singleDataAddRunnable;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initData();
        setListener();

        //切换横屏适配测试
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    dp2px(280));
            kLineView.setLayoutParams(params);
        } else {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    dp2px(380));
            kLineView.setLayoutParams(params);
        }
    }

    private void initView() {
        kLineView = findViewById(R.id.klv_main);
        deputyBtn = findViewById(R.id.btn_deputy);
        maBtn = findViewById(R.id.btn_ma);
        emaBtn = findViewById(R.id.btn_ema);
        bollBtn = findViewById(R.id.btn_boll);
        macdBtn = findViewById(R.id.btn_macd);
        kdjBtn = findViewById(R.id.btn_kdj);
        rsiBtn = findViewById(R.id.btn_rsi);
        kLineResetBtn = findViewById(R.id.btn_kline_reset);
        depthJumpBtn = findViewById(R.id.btn_depth_activity);
        instantBtn = findViewById(R.id.btn_instant);
    }

    private void initData() {
        //初始化控件加载数据,仅限于首次初始化赋值,不可用于更新数据
        kLineView.initKDataList(getKDataList(10));
        //设置十字线移动模式,默认为0:固定指向收盘价
        kLineView.setCrossHairMoveMode(KLineView.CROSS_HAIR_MOVE_OPEN);

        mHandler = new Handler();
        dataListAddRunnable = new Runnable() {
            @Override
            public void run() {
                //分页加载时添加多条数据
                kLineView.addPreDataList(getKDataList(10), true);
//                kLineView.addPreDataList(null, true);
            }
        };

        singleDataAddRunnable = new Runnable() {
            @Override
            public void run() {
                //实时刷新时添加单条数据
                /*KData kData = kLineView.getTotalDataList().get(kLineView.getTotalDataList().size() - 1);
                KData kData1 = new KData(kData.getTime(), kData.getOpenPrice(), kData.getOpenPrice(),
                        kData.getMaxPrice(), kData.getMinPrice(), kData.getVolume());
                kLineView.addSingleData(kData1);*/
                kLineView.addSingleData(getKDataList(0.1).get(0));
//                mHandler.postDelayed(this, 1000);
            }
        };
//        mHandler.postDelayed(singleDataAddRunnable, 2000);

    }

    private void setListener() {
        deputyBtn.setOnClickListener(this);
        maBtn.setOnClickListener(this);
        emaBtn.setOnClickListener(this);
        bollBtn.setOnClickListener(this);
        macdBtn.setOnClickListener(this);
        kdjBtn.setOnClickListener(this);
        rsiBtn.setOnClickListener(this);
        depthJumpBtn.setOnClickListener(this);
        kLineResetBtn.setOnClickListener(this);
        instantBtn.setOnClickListener(this);

        //当控件显示数据属于总数据量的前三分之一时,会自动调用该接口,用于预加载数据,保证控件操作过程中的流畅性,
        //虽然做了预加载,当总数据量较小时,也会出现用户滑到左边界了,但数据还未获取到,依然会有停顿。
        //所以数据量越大,越不会出现停顿,也就越流畅
        kLineView.setOnRequestDataListListener(new KLineView.OnRequestDataListListener() {
            @Override
            public void requestData() {
                //延时3秒执行,模拟网络请求耗时
                mHandler.postDelayed(dataListAddRunnable, 3000);
            }
        });
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_kline_reset:
                //重置数据,可用于分时加载,是否需要定位到重置前的时间点请看方法注释
                //在做分时功能重新加载数据的时候,请务必调用该方法
                kLineView.resetDataList(getKDataList(0.1));
                break;

            case R.id.btn_deputy:
                //是否显示副图
                kLineView.setDeputyPicShow(!kLineView.getVicePicShow());
                break;

            case R.id.btn_ma:
                //主图展示MA
                kLineView.setMainImgType(KLineView.MAIN_IMG_MA);
                break;

            case R.id.btn_ema:
                //主图展示EMA
                kLineView.setMainImgType(KLineView.MAIN_IMG_EMA);
                break;

            case R.id.btn_boll:
                //主图展示BOLL
                kLineView.setMainImgType(KLineView.MAIN_IMG_BOLL);
                break;

            case R.id.btn_macd:
                //副图展示MACD
                kLineView.setDeputyImgType(KLineView.DEPUTY_IMG_MACD);
                break;

            case R.id.btn_kdj:
                //副图展示KDJ
                kLineView.setDeputyImgType(KLineView.DEPUTY_IMG_KDJ);
                break;

            case R.id.btn_rsi:
                //副图展示RSI
                kLineView.setDeputyImgType(KLineView.DEPUTY_IMG_RSI);
                break;

            case R.id.btn_instant:
                kLineView.setShowInstant(!kLineView.isShowInstant());
                break;

            case R.id.btn_depth_activity:
                //跳转到深度图页面
                startActivity(new Intent(getApplicationContext(), DepthActivity.class));
                break;

            default:
                break;
        }
    }

    //模拟K线数据
    private List<KData> getKDataList(double num) {
        long start = System.currentTimeMillis();

        Random random = new Random();
        List<KData> dataList = new ArrayList<>();
        double openPrice = 100;
        double closePrice;
        double maxPrice;
        double minPrice;
        double volume;

        /*for (int i = 0; i < 2000; i++) {
            start += 60 * 1000 * 5;
            closePrice = 150;
            maxPrice = 200;
            minPrice = 80;
            volume = 300;
            dataList.add(new KData(start, openPrice, closePrice, maxPrice, minPrice, volume));
        }*/

        for (int x = 0; x < num * 10; x++) {
            for (int i = 0; i < 12; i++) {
                start += 60 * 1000 * 5;
                closePrice = openPrice + getAddRandomDouble();
                maxPrice = closePrice + getAddRandomDouble();
                minPrice = openPrice - getSubRandomDouble();
                volume = random.nextInt(100) * 1000 + random.nextInt(10) * 10 + random.nextInt(10) + random.nextDouble();
                dataList.add(new KData(start, openPrice, closePrice, maxPrice, minPrice, volume));
                openPrice = closePrice;
            }

            for (int i = 0; i < 8; i++) {
                start += 60 * 1000 * 5;
                closePrice = openPrice - getSubRandomDouble();
                maxPrice = openPrice + getAddRandomDouble();
                minPrice = closePrice - getSubRandomDouble();
                volume = random.nextInt(100) * 1000 + random.nextInt(10) * 10 + random.nextInt(10) + random.nextDouble();
                dataList.add(new KData(start, openPrice, closePrice, maxPrice, minPrice, volume));
                openPrice = closePrice;
            }

        }
        long end = System.currentTimeMillis();
        return dataList;
    }

    private double getAddRandomDouble() {
        Random random = new Random();
        return random.nextInt(5) * 5 + random.nextDouble();
    }

    private double getSubRandomDouble() {
        Random random = new Random();
        return random.nextInt(5) * 5 - random.nextDouble();
    }

    private int dp2px(float dpValue) {
        final float scale = getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //退出页面时停止子线程并置空,便于回收,避免内存泄露
        kLineView.cancelQuotaThread();
    }

}


================================================
FILE: app/src/main/java/com/example/admin/klineview/Print.java
================================================
package com.example.admin.klineview;


import android.util.Log;

/**
 * Created by xiesuichao on 2016/10/18.
 */
public class Print {

    private static boolean printAvailable = true;
    private static final String TAG_LOG = "KLine";

    public static void log(Object obj) {
        if (printAvailable){
            Log.w(TAG_LOG, "--x--" + getCurrentClassName() + "--:" + obj);
        }
    }

    public static void log(String title, Object obj) {
        if (printAvailable){
            Log.w(TAG_LOG, "--x--" + getCurrentClassName() + "--:" + title + ":" + obj);
        }
    }

    private static String getCurrentClassName() {
        int level = 2;
        StackTraceElement[] stacks = new Throwable().getStackTrace();
        String className = stacks[level].getClassName();
        return className.substring(className.lastIndexOf(".") + 1);
    }

}


================================================
FILE: app/src/main/java/com/example/admin/klineview/depth/Depth.java
================================================
package com.example.admin.klineview.depth;

import android.support.annotation.NonNull;

/**
 * 深度数据
 * Created by xiesuichao on 2018/9/23.
 */

public class Depth implements Comparable<Depth>{

    private double price;
    private double volume;
    //buy:0, sell:1
    private int tradeType;
    private float x;
    private float y;

    public Depth() {
    }

    public Depth(double price, double volume, int tradeType) {
        this.price = price;
        this.volume = volume;
        this.tradeType = tradeType;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public double getVolume() {
        return volume;
    }

    public void setVolume(double volume) {
        this.volume = volume;
    }

    public int getTradeType() {
        return tradeType;
    }

    public void setTradeType(int tradeType) {
        this.tradeType = tradeType;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    @Override
    public int compareTo(@NonNull Depth o) {
        double diff = this.getPrice() - o.getPrice();
        if (diff > 0){
            return 1;
        }else if (diff < 0){
            return -1;
        }else {
            return 0;
        }
    }

    @Override
    public String toString() {
        return "Depth{" +
                "price=" + price +
                ", volume=" + volume +
                ", tradeType=" + tradeType +
                ", x=" + x +
                ", y=" + y +
                '}';
    }
}


================================================
FILE: app/src/main/java/com/example/admin/klineview/depth/DepthView.java
================================================
package com.example.admin.klineview.depth;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.example.admin.klineview.R;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 深度控件
 * Created by xiesuichao on 2018/9/23.
 */

public class DepthView extends View {

    //是否显示详情
    private boolean isShowDetail = false;
    //是否长按
    private boolean isLongPress = false;
    //是否显示竖线
    private boolean isShowDetailLine = true;
    //手指单击松开后,数据是否继续显示
    private boolean isShowDetailSingleClick = true;
    //单击松开,数据延时消失,单位毫秒
    private final int DISAPPEAR_TIME = 500;
    //手指长按松开后,数据是否继续显示
    private boolean isShowDetailLongPress = true;
    //长按触发时长,单位毫秒
    private final int LONG_PRESS_TIME_OUT = 300;
    //横坐标中间值
    private double abscissaCenterPrice = -1;
    private boolean isHorizontalMove;
    private Depth clickDepth;
    private String detailPriceTitle;
    private String detailVolumeTitle;
    private Paint strokePaint, fillPaint;
    private Rect textRect;
    private Path linePath;
    private List<Depth> buyDataList, sellDataList;
    private double maxVolume, avgVolumeSpace, avgOrdinateSpace, depthImgHeight;
    private float leftStart, topStart, rightEnd, bottomEnd, longPressDownX, longPressDownY,
            singleClickDownX, singleClickDownY, detailLineWidth, dispatchDownX;
    private int buyLineCol, buyBgCol, sellLineCol, sellBgCol, ordinateTextCol, ordinateTextSize,
            abscissaTextCol, abscissaTextSize, detailBgCol, detailTextCol, detailTextSize, ordinateNum,
            buyLineStrokeWidth, sellLineStrokeWidth, detailLineCol, detailPointRadius, pricePrecision,
            moveLimitDistance;
    private Runnable longPressRunnable;
    private Runnable singleClickDisappearRunnable;
    private String leftPriceStr;
    private String rightPriceStr;

    public DepthView(Context context) {
        this(context, null);
    }

    public DepthView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DepthView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    /**
     * 设置购买数据
     */
    public void setBuyDataList(List<Depth> depthList) {
        buyDataList.clear();
        buyDataList.addAll(depthList);
        //如果数据是无序的,则按价格进行排序。如果是有序的,则注释掉
        Collections.sort(buyDataList);
        //计算累积交易量
        for (int i = buyDataList.size() - 1; i >= 0; i--) {
            if (i < buyDataList.size() - 1) {
                buyDataList.get(i).setVolume(buyDataList.get(i).getVolume() + buyDataList.get(i + 1).getVolume());
            }
        }
        requestLayout();
        invalidate();
    }

    /**
     * 设置出售数据
     */
    public void setSellDataList(List<Depth> depthList) {
        sellDataList.clear();
        sellDataList.addAll(depthList);
        //如果数据是无序的,则按价格进行排序。如果是有序的,则注释掉
        Collections.sort(sellDataList);
        //计算累积交易量
        for (int i = 0; i < sellDataList.size(); i++) {
            if (i > 0) {
                sellDataList.get(i).setVolume(sellDataList.get(i).getVolume() + sellDataList.get(i - 1).getVolume());
            }
        }
        requestLayout();
        invalidate();
    }

    /**
     * 重置深度数据
     */
    public void resetAllData(List<Depth> buyDataList, List<Depth> sellDataList) {
        setBuyDataList(buyDataList);
        setSellDataList(sellDataList);
        isShowDetail = false;
        isLongPress = false;
        requestLayout();
    }

    /**
     * 设置横坐标中间值
     */
    public void setAbscissaCenterPrice(double centerPrice) {
        this.abscissaCenterPrice = centerPrice;
    }

    /**
     * 是否显示竖线
     */
    public void setShowDetailLine(boolean isShowLine) {
        this.isShowDetailLine = isShowLine;
    }

    /**
     * 手指单击松开后,数据是否继续显示
     */
    public void setShowDetailSingleClick(boolean isShowDetailSingleClick) {
        this.isShowDetailSingleClick = isShowDetailSingleClick;
    }

    /**
     * 手指长按松开后,数据是否继续显示
     */
    public void setShowDetailLongPress(boolean isShowDetailLongPress) {
        this.isShowDetailLongPress = isShowDetailLongPress;
    }

    /**
     * 设置横坐标价钱小数位精度
     */
    public void setPricePrecision(int pricePrecision) {
        this.pricePrecision = pricePrecision;
    }

    /**
     * 设置数据详情的价钱说明
     */
    public void setDetailPriceTitle(String priceTitle) {
        this.detailPriceTitle = priceTitle;
    }

    /**
     * 设置数据详情的数量说明
     */
    public void setDetailVolumeTitle(String volumeTitle) {
        this.detailVolumeTitle = volumeTitle;
    }

    /**
     * 移除runnable
     */
    public void cancelCallback() {
        removeCallbacks(longPressRunnable);
        removeCallbacks(singleClickDisappearRunnable);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DepthView);
            buyLineCol = typedArray.getColor(R.styleable.DepthView_dvBuyLineCol, 0xff2BB8AB);
            buyLineStrokeWidth = typedArray.getInt(R.styleable.DepthView_dvBuyLineStrokeWidth, 1);
            buyBgCol = typedArray.getColor(R.styleable.DepthView_dvBuyBgCol, 0x662BB8AB);
            sellLineCol = typedArray.getColor(R.styleable.DepthView_dvSellLineCol, 0xffFF5442);
            sellLineStrokeWidth = typedArray.getInt(R.styleable.DepthView_dvSellLineStrokeWidth, 1);
            sellBgCol = typedArray.getColor(R.styleable.DepthView_dvSellBgCol, 0x66FF5442);
            ordinateTextCol = typedArray.getColor(R.styleable.DepthView_dvOrdinateCol, 0xff808F9E);
            ordinateTextSize = typedArray.getInt(R.styleable.DepthView_dvOrdinateTextSize, 8);
            ordinateNum = typedArray.getInt(R.styleable.DepthView_dvOrdinateNum, 5);
            abscissaTextCol = typedArray.getColor(R.styleable.DepthView_dvAbscissaCol, ordinateTextCol);
            abscissaTextSize = typedArray.getInt(R.styleable.DepthView_dvAbscissaTextSize, ordinateTextSize);
            detailBgCol = typedArray.getColor(R.styleable.DepthView_dvDetailBgCol, 0x99F3F4F6);
            detailTextCol = typedArray.getColor(R.styleable.DepthView_dvDetailTextCol, 0xff294058);
            detailTextSize = typedArray.getInt(R.styleable.DepthView_dvDetailTextSize, 10);
            detailLineCol = typedArray.getColor(R.styleable.DepthView_dvDetailLineCol, 0xff828EA2);
            detailLineWidth = typedArray.getFloat(R.styleable.DepthView_dvDetailLineWidth, 0);
            detailPointRadius = typedArray.getInt(R.styleable.DepthView_dvDetailPointRadius, 3);
            detailPriceTitle = typedArray.getString(R.styleable.DepthView_dvDetailPriceTitle);
            detailVolumeTitle = typedArray.getString(R.styleable.DepthView_dvDetailVolumeTitle);
            typedArray.recycle();
        }

        buyDataList = new ArrayList<>();
        sellDataList = new ArrayList<>();

        strokePaint = new Paint();
        strokePaint.setAntiAlias(true);
        strokePaint.setStyle(Paint.Style.STROKE);

        fillPaint = new Paint();
        fillPaint.setAntiAlias(true);
        fillPaint.setStyle(Paint.Style.FILL);

        textRect = new Rect();
        linePath = new Path();

        pricePrecision = 8;

        if (TextUtils.isEmpty(detailPriceTitle)) {
            detailPriceTitle = "价格(BTC):";
        }
        if (TextUtils.isEmpty(detailVolumeTitle)) {
            detailVolumeTitle = "累积交易量:";
        }

        moveLimitDistance = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        longPressRunnable = new Runnable() {
            @Override
            public void run() {
                isLongPress = true;
                isShowDetail = true;
                getClickDepth(longPressDownX);
                invalidate();
            }
        };
        singleClickDisappearRunnable = new Runnable() {
            @Override
            public void run() {
                isShowDetail = false;
                invalidate();
            }
        };

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        leftStart = getPaddingLeft() + 1;
        topStart = getPaddingTop() + 1;
        rightEnd = getMeasuredWidth() - getPaddingRight() - 1;
        bottomEnd = getMeasuredHeight() - getPaddingBottom() - 1;

        double maxBuyVolume;
        double minBuyVolume;
        double maxSellVolume;
        double minSellVolume;

        if (!buyDataList.isEmpty()) {
            maxBuyVolume = buyDataList.get(0).getVolume();
            minBuyVolume = buyDataList.get(buyDataList.size() - 1).getVolume();
        } else {
            maxBuyVolume = minBuyVolume = 0;
        }

        if (!sellDataList.isEmpty()) {
            maxSellVolume = sellDataList.get(sellDataList.size() - 1).getVolume();
            minSellVolume = sellDataList.get(0).getVolume();
        } else {
            maxSellVolume = minSellVolume = 0;
        }

        maxVolume = Math.max(maxBuyVolume, maxSellVolume);
        double minVolume = Math.min(minBuyVolume, minSellVolume);

        resetStrokePaint(abscissaTextCol, abscissaTextSize, 0);

        if (!buyDataList.isEmpty()) {
            leftPriceStr = setPrecision(buyDataList.get(0).getPrice(), pricePrecision);
        } else if (!sellDataList.isEmpty()) {
            leftPriceStr = setPrecision(sellDataList.get(0).getPrice(), pricePrecision);
        } else {
            leftPriceStr = "0";
        }

        if (!sellDataList.isEmpty()) {
            rightPriceStr = setPrecision(sellDataList.get(sellDataList.size() - 1).getPrice(), pricePrecision);
        } else if (!buyDataList.isEmpty()) {
            rightPriceStr = setPrecision(buyDataList.get(buyDataList.size() - 1).getPrice(), pricePrecision);
        } else {
            rightPriceStr = "0";
        }

        strokePaint.getTextBounds(leftPriceStr, 0, leftPriceStr.length(), textRect);
        depthImgHeight = bottomEnd - topStart - textRect.height() - dp2px(4);
        double avgHeightPerVolume = depthImgHeight / (maxVolume - minVolume);
        double avgWidthPerSize = (rightEnd - leftStart) / (buyDataList.size() + sellDataList.size());
        avgVolumeSpace = maxVolume / ordinateNum;
        avgOrdinateSpace = depthImgHeight / ordinateNum;

        for (int i = 0; i < buyDataList.size(); i++) {
            buyDataList.get(i).setX(leftStart + (float) avgWidthPerSize * i);
            buyDataList.get(i).setY(topStart + (float) ((maxVolume - buyDataList.get(i).getVolume()) * avgHeightPerVolume));
        }

        for (int i = sellDataList.size() - 1; i >= 0; i--) {
            sellDataList.get(i).setX(rightEnd - (float) (avgWidthPerSize * (sellDataList.size() - 1 - i)));
            sellDataList.get(i).setY(topStart + (float) ((maxVolume - sellDataList.get(i).getVolume()) * avgHeightPerVolume));
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (buyDataList.isEmpty() && sellDataList.isEmpty()) {
            return;
        }
        drawLineAndBg(canvas);
        drawCoordinateValue(canvas);
        drawDetailData(canvas);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            longPressDownX = event.getX();
            longPressDownY = event.getY();
            dispatchDownX = event.getX();
            isLongPress = false;
            postDelayed(longPressRunnable, LONG_PRESS_TIME_OUT);

        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            //长按控制
            float dispatchMoveX = event.getX();
            float dispatchMoveY = event.getY();
            float diffDispatchMoveX = Math.abs(dispatchMoveX - longPressDownX);
            float diffDispatchMoveY = Math.abs(dispatchMoveY - longPressDownY);
            float moveDistanceX = Math.abs(event.getX() - dispatchDownX);

            getParent().requestDisallowInterceptTouchEvent(true);

            if (isHorizontalMove || (diffDispatchMoveX > diffDispatchMoveY + dp2px(5)
                    && diffDispatchMoveX > moveLimitDistance)) {
                isHorizontalMove = true;
                removeCallbacks(longPressRunnable);

                if (isLongPress && moveDistanceX > 2) {
                    getClickDepth(event.getX());
                    if (clickDepth != null) {
                        invalidate();
                    }
                }
                dispatchDownX = event.getX();
                return isLongPress || super.dispatchTouchEvent(event);

            } else if (!isHorizontalMove && diffDispatchMoveY > diffDispatchMoveX + dp2px(5)
                    && diffDispatchMoveY > moveLimitDistance) {
                removeCallbacks(longPressRunnable);
                getParent().requestDisallowInterceptTouchEvent(false);
                return false;
            }

        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            isHorizontalMove = false;
            removeCallbacks(longPressRunnable);
            if (!isShowDetailLongPress) {
                isShowDetail = false;
                invalidate();
            }
            getParent().requestDisallowInterceptTouchEvent(false);
        }
        return isLongPress || super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                singleClickDownX = event.getX();
                singleClickDownY = event.getY();
                break;

            case MotionEvent.ACTION_UP:
                float diffTouchMoveX = event.getX() - singleClickDownX;
                float diffTouchMoveY = event.getY() - singleClickDownY;
                if (diffTouchMoveY < moveLimitDistance && diffTouchMoveX < moveLimitDistance) {
                    isShowDetail = true;
                    getClickDepth(singleClickDownX);
                    if (clickDepth != null) {
                        invalidate();
                    }
                }
                if (!isShowDetailSingleClick) {
                    postDelayed(singleClickDisappearRunnable, DISAPPEAR_TIME);
                }
                break;
        }
        return true;
    }

    //获取单击位置数据
    private void getClickDepth(float clickX) {
        clickDepth = null;
        if (sellDataList.isEmpty()) {
            for (int i = 0; i < buyDataList.size(); i++) {
                if (i + 1 < buyDataList.size() && clickX >= buyDataList.get(i).getX()
                        && clickX < buyDataList.get(i + 1).getX()) {
                    clickDepth = buyDataList.get(i);
                    break;
                } else if (i == buyDataList.size() - 1 && clickX >= buyDataList.get(i).getX()) {
                    clickDepth = buyDataList.get(i);
                    break;
                }
            }
        } else if (clickX < sellDataList.get(0).getX()) {
            for (int i = 0; i < buyDataList.size(); i++) {
                if (i + 1 < buyDataList.size() && clickX >= buyDataList.get(i).getX()
                        && clickX < buyDataList.get(i + 1).getX()) {
                    clickDepth = buyDataList.get(i);
                    break;
                } else if (i == buyDataList.size() - 1 && clickX >= buyDataList.get(i).getX()
                        && clickX < sellDataList.get(0).getX()) {
                    clickDepth = buyDataList.get(i);
                    break;
                }
            }
        } else {
            for (int i = 0; i < sellDataList.size(); i++) {
                if (i + 1 < sellDataList.size() && clickX >= sellDataList.get(i).getX()
                        && clickX < sellDataList.get(i + 1).getX()) {
                    clickDepth = sellDataList.get(i);
                    break;
                } else if (i == sellDataList.size() - 1 && clickX >= sellDataList.get(i).getX()) {
                    clickDepth = sellDataList.get(i);
                    break;
                }
            }
        }
    }

    //坐标轴
    private void drawCoordinateValue(Canvas canvas) {
        //横轴
        resetStrokePaint(abscissaTextCol, abscissaTextSize, 0);

        strokePaint.getTextBounds(rightPriceStr, 0, rightPriceStr.length(), textRect);
        //左边价格
        canvas.drawText(leftPriceStr,
                leftStart,
                bottomEnd - dp2px(2),
                strokePaint);
        //右边价格
        canvas.drawText(rightPriceStr,
                rightEnd - textRect.width(),
                bottomEnd - dp2px(2),
                strokePaint);
        //中间价格
        if (abscissaCenterPrice != -1) {
            canvas.drawText(setPrecision(abscissaCenterPrice, pricePrecision),
                    getWidth() / 2 - strokePaint.measureText(setPrecision(abscissaCenterPrice, pricePrecision)) / 2,
                    bottomEnd - dp2px(2),
                    strokePaint);
        }

        //纵轴
        resetStrokePaint(ordinateTextCol, ordinateTextSize, 0);
        strokePaint.getTextBounds(maxVolume + "", 0, (maxVolume + "").length(), textRect);
        for (int i = 0; i < ordinateNum; i++) {
            String ordinateStr = formatNum(maxVolume - i * avgVolumeSpace);
            canvas.drawText(ordinateStr,
                    rightEnd - strokePaint.measureText(ordinateStr),
                    (float) (topStart + textRect.height() + i * avgOrdinateSpace),
                    strokePaint);
        }
    }

    private void drawLineAndBg(Canvas canvas) {
        //买方背景
        if (!buyDataList.isEmpty()) {
            linePath.reset();
            for (int i = 0; i < buyDataList.size(); i++) {
                if (i == 0) {
                    linePath.moveTo(buyDataList.get(i).getX(), buyDataList.get(i).getY());
                } else {
                    linePath.lineTo(buyDataList.get(i).getX(), buyDataList.get(i).getY());
                }
            }
            if (!buyDataList.isEmpty() && buyDataList.get(buyDataList.size() - 1).getY() < topStart + depthImgHeight) {
                linePath.lineTo(buyDataList.get(buyDataList.size() - 1).getX(), (float) (topStart + depthImgHeight));
            }
            linePath.lineTo(leftStart, (float) (topStart + depthImgHeight));
            linePath.close();
            fillPaint.setColor(buyBgCol);
            canvas.drawPath(linePath, fillPaint);

            //买方线条
            linePath.reset();
            for (int i = 0; i < buyDataList.size(); i++) {
                if (i == 0) {
                    linePath.moveTo(buyDataList.get(i).getX(), buyDataList.get(i).getY());
                } else {
                    linePath.lineTo(buyDataList.get(i).getX(), buyDataList.get(i).getY());
                }
            }
            resetStrokePaint(buyLineCol, 0, buyLineStrokeWidth);
            canvas.drawPath(linePath, strokePaint);
        }

        //卖方背景
        if (!sellDataList.isEmpty()) {
            linePath.reset();
            for (int i = sellDataList.size() - 1; i >= 0; i--) {
                if (i == sellDataList.size() - 1) {
                    linePath.moveTo(sellDataList.get(i).getX(), sellDataList.get(i).getY());
                } else {
                    linePath.lineTo(sellDataList.get(i).getX(), sellDataList.get(i).getY());
                }
            }
            if (!sellDataList.isEmpty() && sellDataList.get(0).getY() < (float) (topStart + depthImgHeight)) {
                linePath.lineTo(sellDataList.get(0).getX(), (float) (topStart + depthImgHeight));
            }
            linePath.lineTo(rightEnd, (float) (topStart + depthImgHeight));
            linePath.close();
            fillPaint.setColor(sellBgCol);
            canvas.drawPath(linePath, fillPaint);

            //卖方线条
            linePath.reset();
            for (int i = 0; i < sellDataList.size(); i++) {
                if (i == 0) {
                    linePath.moveTo(sellDataList.get(i).getX(), sellDataList.get(i).getY());
                } else {
                    linePath.lineTo(sellDataList.get(i).getX(), sellDataList.get(i).getY());
                }
            }
            resetStrokePaint(sellLineCol, 0, sellLineStrokeWidth);
            canvas.drawPath(linePath, strokePaint);
        }

    }

    private void drawDetailData(Canvas canvas) {
        if (!isShowDetail || clickDepth == null) {
            return;
        }
        //游标线
        if (isShowDetailLine) {
            resetStrokePaint(detailLineCol, 0, detailLineWidth);
            canvas.drawLine(clickDepth.getX(), topStart, clickDepth.getX(), topStart + (float) depthImgHeight,
                    strokePaint);
        }

        if (sellDataList.isEmpty() || clickDepth.getX() < sellDataList.get(0).getX()) {
            fillPaint.setColor(buyLineCol);
        } else if (buyDataList.isEmpty() || clickDepth.getX() >= sellDataList.get(0).getX()) {
            fillPaint.setColor(sellLineCol);
        }
        canvas.drawCircle(clickDepth.getX(), clickDepth.getY(), dp2px(detailPointRadius), fillPaint);

        resetStrokePaint(detailTextCol, detailTextSize, 0);
        fillPaint.setColor(detailBgCol);
        String clickPriceStr = detailPriceTitle + formatNum(clickDepth.getPrice());
        String clickVolumeStr = detailVolumeTitle + formatNum(clickDepth.getVolume());
        strokePaint.getTextBounds(clickPriceStr, 0, clickPriceStr.length(), textRect);
        int priceStrWidth = textRect.width();
        int priceStrHeight = textRect.height();
        strokePaint.getTextBounds(clickVolumeStr, 0, clickVolumeStr.length(), textRect);
        int volumeStrWidth = textRect.width();
        int maxWidth = Math.max(priceStrWidth, volumeStrWidth);

        float bgLeft, bgTop, bgRight, bgBottom, priceStrX, priceStrY, volumeStrY;

        if (clickDepth.getX() <= maxWidth + dp2px(15)) {
            bgLeft = clickDepth.getX() + dp2px(5);
            bgRight = clickDepth.getX() + dp2px(15) + maxWidth;
            priceStrX = clickDepth.getX() + dp2px(10);

        } else {
            bgLeft = clickDepth.getX() - dp2px(15) - maxWidth;
            bgRight = clickDepth.getX() - dp2px(5);
            priceStrX = clickDepth.getX() - dp2px(10) - maxWidth;
        }

        if (clickDepth.getY() < topStart + dp2px(7) + priceStrHeight) {
            bgTop = topStart;
            bgBottom = topStart + dp2px(14) + priceStrHeight * 2;
            priceStrY = topStart + dp2px(3) + priceStrHeight;
            volumeStrY = topStart + dp2px(7) + priceStrHeight * 2;

        } else if (clickDepth.getY() > topStart + depthImgHeight - dp2px(7) - priceStrHeight) {
            bgTop = topStart + (float) depthImgHeight - dp2px(14) - priceStrHeight * 2;
            bgBottom = topStart + (float) depthImgHeight;
            priceStrY = topStart + (float) depthImgHeight - dp2px(9) - priceStrHeight;
            volumeStrY = topStart + (float) depthImgHeight - dp2px(5);

        } else {
            bgTop = clickDepth.getY() - dp2px(10) - priceStrHeight;
            bgBottom = clickDepth.getY() + dp2px(10) + priceStrHeight;
            priceStrY = clickDepth.getY() - dp2px(2);
            volumeStrY = clickDepth.getY() + priceStrHeight;
        }

        RectF rectF = new RectF(bgLeft, bgTop, bgRight, bgBottom);
        canvas.drawRoundRect(rectF, 6, 6, fillPaint);
        canvas.drawText(clickPriceStr, priceStrX, priceStrY, strokePaint);
        canvas.drawText(clickVolumeStr, priceStrX, volumeStrY, strokePaint);

    }

    /**
     * 设置小数位精度
     *
     * @param num
     * @param scale 保留几位小数
     */
    private String setPrecision(Double num, int scale) {
        BigDecimal bigDecimal = new BigDecimal(num);
        return bigDecimal.setScale(scale, BigDecimal.ROUND_DOWN).toPlainString();
    }

    /**
     * 按量级格式化数量
     */
    private String formatNum(double num) {
        if (num < 1) {
            return setPrecision(num, 6);
        } else if (num < 10) {
            return setPrecision(num, 4);
        } else if (num < 100) {
            return setPrecision(num, 3);
        } else if (num < 10000) {
            return setPrecision(num / 1000, 1) + "K";
        } else {
            return setPrecision(num / 10000, 2) + "万";
        }
    }

    private void resetStrokePaint(int colorId, int textSize, float strokeWidth) {
        strokePaint.setColor(colorId);
        strokePaint.setTextSize(sp2px(textSize));
        strokePaint.setStrokeWidth(dp2px(strokeWidth));
    }

    private int dp2px(float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    private int sp2px(float spValue) {
        final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }


}


================================================
FILE: app/src/main/java/com/example/admin/klineview/kline/KData.java
================================================
package com.example.admin.klineview.kline;

/**
 * K线数据
 * Created by xiesuichao on 2018/6/29.
 */

public class KData {

    private long time;//时间戳
    private double openPrice;
    private double closePrice;
    private double maxPrice;
    private double minPrice;
    private double volume;
    private double upDnAmount;//涨跌额
    private double upDnRate;//涨跌幅
    private double priceMa5;
    private double priceMa10;
    private double priceMa30;
    private double ema5;
    private double ema10;
    private double ema30;
    private double ema;
    private double volumeMa5;
    private double volumeMa10;
    private double bollMb;
    private double bollUp;
    private double bollDn;
    private double macd;
    private double dea;
    private double dif;
    private double k;
    private double d;
    private double j;
    private double rs1;
    private double rs2;
    private double rs3;
    private double leftX;
    private double rightX;
    private double centerX;
    private double closeY;
    private double openY;
    private boolean initFinish;

    public KData() {
    }

    public KData(double openPrice, double closedPrice, double maxPrice, double minPrice, double volume) {
        this.openPrice = openPrice;
        this.closePrice = closedPrice;
        this.maxPrice = maxPrice;
        this.minPrice = minPrice;
        this.volume = volume;
    }

    public KData(long time, double openPrice, double closePrice, double maxPrice, double minPrice, double volume){
        this.time = time;
        this.openPrice = openPrice;
        this.closePrice = closePrice;
        this.maxPrice = maxPrice;
        this.minPrice = minPrice;
        this.volume = volume;
    }

    public double getCenterX() {
        return centerX;
    }

    public void setCenterX(double centerX) {
        this.centerX = centerX;
    }

    public double getEma() {
        return ema;
    }

    public void setEma(double ema) {
        this.ema = ema;
    }

    public double getOpenPrice() {
        return openPrice;
    }

    public void setOpenPrice(double openPrice) {
        this.openPrice = openPrice;
    }

    public double getClosePrice() {
        return closePrice;
    }

    public void setClosePrice(double closePrice) {
        this.closePrice = closePrice;
    }

    public double getMaxPrice() {
        return maxPrice;
    }

    public void setMaxPrice(double maxPrice) {
        this.maxPrice = maxPrice;
    }

    public double getMinPrice() {
        return minPrice;
    }

    public void setMinPrice(double minPrice) {
        this.minPrice = minPrice;
    }

    public double getLeftX() {
        return leftX;
    }

    public void setLeftX(double leftX) {
        this.leftX = leftX;
    }

    public double getRightX() {
        return rightX;
    }

    public void setRightX(double rightX) {
        this.rightX = rightX;
    }

    public long getTime() {
        return time;
    }

    public void setTime(long time) {
        this.time = time;
    }

    public double getVolume() {
        return volume;
    }

    public void setVolume(double volume) {
        this.volume = volume;
    }

    public double getPriceMa5() {
        return priceMa5;
    }

    public void setPriceMa5(double priceMa5) {
        this.priceMa5 = priceMa5;
    }

    public double getPriceMa10() {
        return priceMa10;
    }

    public void setPriceMa10(double priceMa10) {
        this.priceMa10 = priceMa10;
    }

    public double getPriceMa30() {
        return priceMa30;
    }

    public void setPriceMa30(double priceMa30) {
        this.priceMa30 = priceMa30;
    }

    public double getVolumeMa5() {
        return volumeMa5;
    }

    public void setVolumeMa5(double volumeMa5) {
        this.volumeMa5 = volumeMa5;
    }

    public double getVolumeMa10() {
        return volumeMa10;
    }

    public void setVolumeMa10(double volumeMa10) {
        this.volumeMa10 = volumeMa10;
    }

    public double getEma5() {
        return ema5;
    }

    public void setEma5(double ema5) {
        this.ema5 = ema5;
    }

    public double getEma10() {
        return ema10;
    }

    public void setEma10(double ema10) {
        this.ema10 = ema10;
    }

    public double getEma30() {
        return ema30;
    }

    public void setEma30(double ema30) {
        this.ema30 = ema30;
    }

    public double getBollMb() {
        return bollMb;
    }

    public void setBollMb(double bollMb) {
        this.bollMb = bollMb;
    }

    public double getBollUp() {
        return bollUp;
    }

    public void setBollUp(double bollUp) {
        this.bollUp = bollUp;
    }

    public double getBollDn() {
        return bollDn;
    }

    public void setBollDn(double bollDn) {
        this.bollDn = bollDn;
    }

    public double getMacd() {
        return macd;
    }

    public void setMacd(double macd) {
        this.macd = macd;
    }

    public double getDea() {
        return dea;
    }

    public void setDea(double dea) {
        this.dea = dea;
    }

    public double getDif() {
        return dif;
    }

    public void setDif(double dif) {
        this.dif = dif;
    }

    public double getK() {
        return k;
    }

    public void setK(double k) {
        this.k = k;
    }

    public double getD() {
        return d;
    }

    public void setD(double d) {
        this.d = d;
    }

    public double getJ() {
        return j;
    }

    public void setJ(double j) {
        this.j = j;
    }

    public double getRs1() {
        return rs1;
    }

    public void setRs1(double rs1) {
        this.rs1 = rs1;
    }

    public double getRs2() {
        return rs2;
    }

    public void setRs2(double rs2) {
        this.rs2 = rs2;
    }

    public double getRs3() {
        return rs3;
    }

    public void setRs3(double rs3) {
        this.rs3 = rs3;
    }

    public double getUpDnAmount() {
        return closePrice - openPrice;
    }

    public void setUpDnAmount(double upDnAmount) {
        this.upDnAmount = upDnAmount;
    }

    public double getUpDnRate() {
        return (closePrice - openPrice) / openPrice;
    }

    public void setUpDnRate(double upDnRate) {
        this.upDnRate = upDnRate;
    }

    public double getCloseY() {
        return closeY;
    }

    public void setCloseY(double closeY) {
        this.closeY = closeY;
    }

    public double getOpenY() {
        return openY;
    }

    public void setOpenY(double openY) {
        this.openY = openY;
    }

    public boolean isInitFinish() {
        return initFinish;
    }

    public void setInitFinish(boolean initFinish) {
        this.initFinish = initFinish;
    }

    @Override
    public String toString() {
        return "KData{" +
                "time=" + time +
                ", openPrice=" + openPrice +
                ", closePrice=" + closePrice +
                ", maxPrice=" + maxPrice +
                ", minPrice=" + minPrice +
                ", volume=" + volume +
                ", upDnAmount=" + upDnAmount +
                ", upDnRate=" + upDnRate +
                ", priceMa5=" + priceMa5 +
                ", priceMa10=" + priceMa10 +
                ", priceMa30=" + priceMa30 +
                ", ema5=" + ema5 +
                ", ema10=" + ema10 +
                ", ema30=" + ema30 +
                ", ema=" + ema +
                ", volumeMa5=" + volumeMa5 +
                ", volumeMa10=" + volumeMa10 +
                ", bollMb=" + bollMb +
                ", bollUp=" + bollUp +
                ", bollDn=" + bollDn +
                ", macd=" + macd +
                ", dea=" + dea +
                ", dif=" + dif +
                ", k=" + k +
                ", d=" + d +
                ", j=" + j +
                ", rs1=" + rs1 +
                ", rs2=" + rs2 +
                ", rs3=" + rs3 +
                ", leftX=" + leftX +
                ", rightX=" + rightX +
                ", closeY=" + closeY +
                ", openY=" + openY +
                ", initFinish=" + initFinish +
                '}';
    }
}


================================================
FILE: app/src/main/java/com/example/admin/klineview/kline/KLineView.java
================================================
package com.example.admin.klineview.kline;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import com.example.admin.klineview.Print;
import com.example.admin.klineview.R;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 股票走势图 K线控件
 * Created by xiesuichao on 2018/6/29.
 */

public class KLineView extends View implements View.OnTouchListener, Handler.Callback {

    //view显示的第一条数据在总数据list中的position
    private int startDataNum = 0;
    //首次加载显示的数据条数,可自行修改
    private final int VIEW_DATA_NUM_INIT = 34;
    //放大时最少显示的数据条数,可自行修改
    private final int VIEW_DATA_NUM_MIN = 18;
    //缩小时最多显示的数据条数,可自行修改
    private final int VIEW_DATA_NUM_MAX = 140;
    //view显示的最大数据条数
    private int maxViewDataNum = VIEW_DATA_NUM_INIT;
    //是否显示副图
    private boolean isShowDeputy = false;
    //是否显示详情
    private boolean isShowDetail = false;
    //是否长按
    private boolean isLongPress = false;
    //长按触发时长
    private final int LONG_PRESS_TIME_OUT = 300;
    //是否水平移动
    private boolean isHorizontalMove = false;
    //是否需要请求前期的数据
    private boolean isNeedRequestPreData = true;
    //是否双指触控
    private boolean isDoubleFinger = false;
    //是否显示分时图
    private boolean isShowInstant = false;
    //主图数据类型 0:MA, 1:EMA 2:BOLL
    public static final int MAIN_IMG_MA = 0;
    public static final int MAIN_IMG_EMA = 1;
    public static final int MAIN_IMG_BOLL = 2;
    private int mainImgType = MAIN_IMG_MA;
    //副图数据类型 0:MACD, 1:KDJ, 2:RSI
    public static final int DEPUTY_IMG_MACD = 0;
    public static final int DEPUTY_IMG_KDJ = 1;
    public static final int DEPUTY_IMG_RSI = 2;
    private int deputyImgType = DEPUTY_IMG_MACD;
    //十字线横线上下移动模式 0:固定指向收盘价,1:固定指向开盘价,2:上下自由滑动
    public static final int CROSS_HAIR_MOVE_CLOSE = 0;
    public static final int CROSS_HAIR_MOVE_OPEN = 1;
    public static final int CROSS_HAIR_MOVE_FREE = 2;
    private int crossHairMoveMode = CROSS_HAIR_MOVE_CLOSE;

    private final String STR_MA5 = "Ma5:";
    private final String STR_MA10 = "Ma10:";
    private final String STR_MA30 = "Ma30:";
    private final String STR_VOL = "VOL:";
    private final String STR_MACD_TITLE = "MACD(12,26,9)";
    private final String STR_MACD = "MACD:";
    private final String STR_DIF = "DIF:";
    private final String STR_DEA = "DEA:";
    private final String STR_KDJ_TITLE = "KDJ(9,3,3)";
    private final String STR_K = "K:";
    private final String STR_D = "D:";
    private final String STR_J = "J:";
    private final String STR_RSI_TITLE = "RSI(6,12,24)";
    private final String STR_RS1 = "RS1:";
    private final String STR_RS2 = "RS2:";
    private final String STR_RS3 = "RS3:";

    private int initTotalListSize = 0;

    private Paint strokePaint, fillPaint, instantFillPaint;
    private Path curvePath, instantPath;

    private Rect topMa5Rect = new Rect();
    private Rect topMa10Rect = new Rect();
    private Rect topMa30Rect = new Rect();
    private Rect detailTextRect = new Rect();

    private String[] detailLeftTitleArr;
    private List<KData> totalDataList = new ArrayList<>();
    private List<KData> viewDataList = new ArrayList<>();
    private List<KData> endDataList = new ArrayList<>();

    private List<String> detailRightDataList = new ArrayList<>();

    //水平线纵坐标
    private List<Float> horizontalYList = new ArrayList<>();
    //垂直线横坐标
    private List<Float> verticalXList = new ArrayList<>();

    private List<Pointer> mainMa5PointList = new ArrayList<>();
    private List<Pointer> mainMa10PointList = new ArrayList<>();
    private List<Pointer> mainMa30PointList = new ArrayList<>();

    private List<Pointer> deputyMa5PointList = new ArrayList<>();
    private List<Pointer> deputyMa10PointList = new ArrayList<>();
    private List<Pointer> deputyMa30PointList = new ArrayList<>();

    private List<Pointer> volumeMa5PointList = new ArrayList<>();
    private List<Pointer> volumeMa10PointList = new ArrayList<>();

    private KData lastKData;
    private OnRequestDataListListener requestListener;
    private QuotaThread quotaThread;
    private Runnable mDelayRunnable;
    private Runnable longPressRunnable;
    private GestureDetector gestureDetector;

    private int priceIncreaseCol, priceFallCol, priceMa5Col, priceMa10Col, priceMa30Col,
            priceMaxLabelCol, priceMinLabelCol, volumeTextCol, volumeMa5Col, volumeMa10Col, macdTextCol,
            macdPositiveCol, macdNegativeCol, difLineCol, deaLineCol, kLineCol, dLineCol,
            jLineCol, abscissaTextCol, ordinateTextCol, crossHairCol, crossHairRightLabelCol,
            crossHairBottomLabelCol, crossHairRightLabelTextCol, detailFrameCol, detailTextCol, tickMarkCol,
            detailBgCol, detailRectWidth, abscissaTextSize, volumeTextSize, crossHairBottomLabelTextCol,
            priceMaxLabelTextCol, priceMinLabelTextCol, priceMaxLabelTextSize, priceMinLabelTextSize,
            crossHairRightLabelTextSize, crossHairBottomLabelTextSize, ordinateTextSize, detailTextSize,
            topMaTextSize, detailRectHeight, moveLimitDistance;

    private float leftStart, topStart, rightEnd, bottomEnd, mulFirstDownX, mulFirstDownY, lastDiffMoveX,
            lastDiffMoveY, singleClickDownX, detailTextVerticalSpace, longPressMoveY, dispatchDownY,
            volumeImgBot, verticalSpace, flingVelocityX, priceImgBot, deputyTopY, deputyCenterY,
            singleClickDownY, mulSecondDownX, longPressDownX, longPressDownY, dispatchDownX;

    private double maxPrice, topPrice, maxPriceX, minPrice, botPrice, minPriceX, maxVolume, avgHeightPerPrice,
            avgPriceRectWidth, avgHeightPerVolume, avgHeightMacd, avgHeightDea, avgHeightDif,
            avgHeightK, avgHeightD, avgHeightJ, mMaxPriceY, avgHeightRSI,
            mMinPriceY, mMaxMacd, mMinMacd, mMaxK;


    public KLineView(Context context) {
        this(context, null);
    }

    public KLineView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public KLineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initAttrs(context, attrs);
        initData();
    }

    public interface OnRequestDataListListener {
        void requestData();
    }

    public void setOnRequestDataListListener(OnRequestDataListListener requestListener) {
        this.requestListener = requestListener;
    }

    /**
     * ---仅限于首次初始化赋值,不可用于更新数据,如要更新,请调用resetDataList---
     * 控件初始化时添加的数据量,建议每次添加数据在2000条左右
     * 已对性能做优化,总数据量十万条以上对用户体验没有影响
     * 首次加载5000条数据,页面初始化到加载完成,总共耗时400+ms,不超过0.5秒。
     * 分页加载5000条数据时,如果正在滑动过程中,添加数据的那一瞬间会稍微有一下卡顿,影响不大。
     * 经测试,800块的华为荣耀6A 每次添加4000条以下数据不会有卡顿,很流畅。
     */
    public void initKDataList(List<KData> dataList) {
        if (dataList == null || dataList.isEmpty() || totalDataList == null
                || totalDataList.size() > 0) {
            return;
        }
        this.totalDataList.addAll(dataList);
        startDataNum = totalDataList.size() - maxViewDataNum;
        QuotaUtil.initMa(totalDataList, false);
        resetViewData();
    }

    /**
     * 获取总数据list
     */
    public List<KData> getTotalDataList(){
        return totalDataList;
    }

    /**
     * 获取显示的数据list
     */
    public List<KData> getViewDataList(){
        return viewDataList;
    }

    /**
     * 添加最新的单条数据
     */
    public void addSingleData(KData data) {
        if (data == null || endDataList == null || totalDataList == null) {
            return;
        }
        endDataList.clear();
        int startIndex;
        if (totalDataList.size() >= 30) {
            startIndex = totalDataList.size() - 30;
        } else {
            startIndex = 0;
        }
        endDataList.addAll(totalDataList.subList(startIndex, totalDataList.size()));
        endDataList.add(data);
        if (quotaThread != null) {
            quotaThread.quotaSingleCalculate(endDataList);
        }
    }

    /**
     * 分页加载,向前期滑动时,进行分页加载添加数据,建议每次添加数据在2000条左右
     * 配合setOnRequestDataListListener接口使用实现自动分页加载
     *
     * @param isNeedReqPre 下次向前期滑动到边界时,是否需要自动调用接口请求数据
     */
    public void addPreDataList(List<KData> dataList, boolean isNeedReqPre) {
        if (dataList == null || dataList.isEmpty() || totalDataList == null) {
            return;
        }
        isNeedRequestPreData = isNeedReqPre;
        totalDataList.addAll(0, dataList);
        startDataNum += dataList.size();
        if (quotaThread != null) {
            quotaThread.quotaListCalculate(totalDataList);
        }
    }

    /**
     * 分页加载,向前期滑动时,进行分页加载添加数据,建议每次添加数据在2000条左右
     * 配合setOnRequestDataListListener接口使用实现自动分页加载
     * 首次调用该方法后会记录该次list.size,后续继续调用时会将传进来的list.size与首次
     * 的进行比较,如果比首次的size小,则判定为数据已拿完,不再自动调用接口请求数据。
     * <p>
     * ---该方法仅在能保证每次分页加载拿到的数据list.size相同的情况下调用,否则,请调用
     * 上面的方法,手动传入isNeedReqPre用来判断是否需要继续自动调用接口请求数据
     */
    public void addPreDataList(List<KData> dataList) {
        if (dataList == null || dataList.isEmpty() || totalDataList == null) {
            return;
        }
        if (initTotalListSize == 0) {
            initTotalListSize = dataList.size();
        }
        isNeedRequestPreData = dataList.size() >= initTotalListSize;
        totalDataList.addAll(0, dataList);
        startDataNum += dataList.size();
        if (quotaThread != null) {
            quotaThread.quotaListCalculate(totalDataList);
        }
    }

    /**
     * 重置所有数据,默认不作定位
     */
    public void resetDataList(List<KData> dataList) {
        if (dataList == null || dataList.isEmpty()){
            return;
        }
        resetDataList(dataList, false);
    }

    /**
     * 重置所有数据
     *
     * @param isNeedLocateCurrent 重置后的数据是否需要定位到重置前移动到的时间点,例如:
     *                            重置前已经滑动到9月20号,true则在重置后会将新数据定位到9月20号。
     *                            false则不作定位,view右边直接显示为最新的数据
     */
    public void resetDataList(List<KData> dataList, boolean isNeedLocateCurrent) {
        if (dataList == null || dataList.isEmpty() || viewDataList == null || totalDataList == null) {
            return;
        }
        long currentStartTime = 0;
        if (viewDataList.size() > 0) {
            currentStartTime = viewDataList.get(0).getTime();
        }
        isShowDetail = false;

        this.totalDataList.clear();
        this.totalDataList.addAll(dataList);
        QuotaUtil.initMa(totalDataList, false);
        switch (mainImgType) {
            case MAIN_IMG_EMA:
                QuotaUtil.initEma(totalDataList, false);
                break;

            case MAIN_IMG_BOLL:
                QuotaUtil.initBoll(totalDataList, false);
                break;

            default:
                break;
        }
        switch (deputyImgType) {
            case DEPUTY_IMG_MACD:
                QuotaUtil.initMACD(totalDataList, false);
                break;

            case DEPUTY_IMG_KDJ:
                QuotaUtil.initKDJ(totalDataList, false);
                break;

            case DEPUTY_IMG_RSI:
                QuotaUtil.initRSI(totalDataList, false);
                break;

            default:
                break;
        }

        if (isNeedLocateCurrent) {
            int halfSizeNum = totalDataList.size() / 2;
            startDataNum = -1;
            if (totalDataList.get(0).getTime() <= currentStartTime
                    && totalDataList.get(halfSizeNum).getTime() > currentStartTime) {
                for (int i = 0; i < halfSizeNum; i++) {
                    if (i + 1 < totalDataList.size() && totalDataList.get(i).getTime() <= currentStartTime
                            && totalDataList.get(i + 1).getTime() > currentStartTime) {
                        startDataNum = i;
                        break;
                    }
                }
            } else if (totalDataList.get(halfSizeNum).getTime() <= currentStartTime
                    && totalDataList.get(totalDataList.size() - 1).getTime() >= currentStartTime) {
                for (int i = halfSizeNum; i < totalDataList.size(); i++) {
                    if (i + 1 < totalDataList.size() && totalDataList.get(i).getTime() <= currentStartTime
                            && totalDataList.get(i + 1).getTime() > currentStartTime) {
                        startDataNum = i;
                        break;
                    }
                }
            }

            if (totalDataList.size() < maxViewDataNum) {
                startDataNum = 0;
            } else if (totalDataList.size() - startDataNum < maxViewDataNum || startDataNum == -1) {
                startDataNum = totalDataList.size() - maxViewDataNum;
            }

        } else {
            if (totalDataList.size() < maxViewDataNum) {
                startDataNum = 0;
            } else {
                startDataNum = totalDataList.size() - maxViewDataNum;
            }
        }

        resetViewData();
    }

    /**
     * 设置主图显示类型
     * MA: MAIN_IMG_MA
     * EMA: MAIN_IMG_EMA
     * BOLL: MAIN_IMG_BOLL
     */
    public void setMainImgType(int type) {
        switch (type) {
            case MAIN_IMG_MA:
                QuotaUtil.initMa(totalDataList, false);
                break;

            case MAIN_IMG_EMA:
                QuotaUtil.initEma(totalDataList, false);
                break;

            case MAIN_IMG_BOLL:
                QuotaUtil.initBoll(totalDataList, false);
                break;

            default:
                break;
        }
        this.mainImgType = type;
        invalidate();
    }

    /**
     * 是否显示副图
     */
    public void setDeputyPicShow(boolean showState) {
        this.isShowDeputy = showState;
        if (isShowDeputy) {
            setDeputyImgType(deputyImgType);
        }
        invalidate();
    }

    /**
     * 设置副图显示类型
     * MACD: DEPUTY_IMG_MACD
     * KDJ: DEPUTY_IMG_KDJ
     * RSI: DEPUTY_IMG_RSI
     */
    public void setDeputyImgType(int type) {
        this.deputyImgType = type;
        switch (deputyImgType) {
            case DEPUTY_IMG_MACD:
                QuotaUtil.initMACD(totalDataList, false);
                break;

            case DEPUTY_IMG_KDJ:
                QuotaUtil.initKDJ(totalDataList, false);
                break;

            case DEPUTY_IMG_RSI:
                QuotaUtil.initRSI(totalDataList, false);
                break;

            default:
                break;
        }
        invalidate();
    }

    /**
     * 设置十字线的横线上下移动模式
     * 固定指向收盘价: CROSS_HAIR_MOVE_CLOSE
     * 固定指向开盘价: CROSS_HAIR_MOVE_OPEN
     * 上下自由滑动: CROSS_HAIR_MOVE_FREE
     */
    public void setCrossHairMoveMode(int moveMode) {
        this.crossHairMoveMode = moveMode;
    }

    /**
     * 获取副图是否显示
     */
    public boolean getVicePicShow() {
        return this.isShowDeputy;
    }

    /**
     * 退出页面时停止子线程并置空,便于回收,避免内存泄露
     */
    public void cancelQuotaThread() {
        if (quotaThread != null) {
            quotaThread.setUIHandler(null);
            quotaThread.quit();
            quotaThread = null;
        }
        removeCallbacks(mDelayRunnable);
        removeCallbacks(longPressRunnable);
    }

    /**
     * 是否显示分时图
     */
    public void setShowInstant(boolean state){
        this.isShowInstant = state;
        invalidate();
    }

    /**
     * 获取分时图是否显示
     */
    public boolean isShowInstant(){
        return this.isShowInstant;
    }

    private void initAttrs(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.KLineView);
            tickMarkCol = typedArray.getColor(R.styleable.KLineView_klTickMarkLineCol, 0xffF7F7FB);
            abscissaTextCol = typedArray.getColor(R.styleable.KLineView_klAbscissaTextCol, 0xff9BACBD);
            abscissaTextSize = typedArray.getInt(R.styleable.KLineView_klAbscissaTextSize, 8);
            ordinateTextCol = typedArray.getColor(R.styleable.KLineView_klOrdinateTextCol, abscissaTextCol);
            ordinateTextSize = typedArray.getInt(R.styleable.KLineView_klOrdinateTextSize, abscissaTextSize);
            topMaTextSize = typedArray.getInt(R.styleable.KLineView_klTopMaTextSize, 10);
            priceIncreaseCol = typedArray.getColor(R.styleable.KLineView_klPriceIncreaseCol, 0xffFF5442);
            priceFallCol = typedArray.getColor(R.styleable.KLineView_klPriceFallCol, 0xff2BB8AB);
            priceMa5Col = typedArray.getColor(R.styleable.KLineView_klPriceMa5LineCol, 0xffFFA800);
            priceMa10Col = typedArray.getColor(R.styleable.KLineView_klPriceMa10LineCol, 0xff2668FF);
            priceMa30Col = typedArray.getColor(R.styleable.KLineView_klPriceMa30LineCol, 0xffFF45A1);
            priceMaxLabelCol = typedArray.getColor(R.styleable.KLineView_klPriceMaxLabelCol, 0xffC0C6C9);
            priceMaxLabelTextCol = typedArray.getColor(R.styleable.KLineView_klPriceMaxLabelTextCol, 0xffffffff);
            priceMaxLabelTextSize = typedArray.getInt(R.styleable.KLineView_klPriceMaxLabelTextSize, 10);
            priceMinLabelCol = typedArray.getColor(R.styleable.KLineView_klPriceMinLabelCol, priceMaxLabelCol);
            priceMinLabelTextCol = typedArray.getColor(R.styleable.KLineView_klPriceMinLabelTextCol, priceMaxLabelTextCol);
            priceMinLabelTextSize = typedArray.getInt(R.styleable.KLineView_klPriceMinLabelTextSize, 10);
            volumeTextCol = typedArray.getColor(R.styleable.KLineView_klVolumeTextCol, 0xff9BACBD);
            volumeTextSize = typedArray.getInt(R.styleable.KLineView_klVolumeTextSize, 10);
            volumeMa5Col = typedArray.getColor(R.styleable.KLineView_klVolumeMa5LineCol, priceMa5Col);
            volumeMa10Col = typedArray.getColor(R.styleable.KLineView_klVolumeMa10LineCol, priceMa10Col);
            macdTextCol = typedArray.getColor(R.styleable.KLineView_klMacdTextCol, volumeTextCol);
            macdPositiveCol = typedArray.getColor(R.styleable.KLineView_klMacdPositiveCol, priceIncreaseCol);
            macdNegativeCol = typedArray.getColor(R.styleable.KLineView_klMacdNegativeCol, priceFallCol);
            difLineCol = typedArray.getColor(R.styleable.KLineView_klDifLineCol, priceMa10Col);
            deaLineCol = typedArray.getColor(R.styleable.KLineView_klDeaLineCol, priceMa30Col);
            kLineCol = typedArray.getColor(R.styleable.KLineView_klKLineCol, priceMa5Col);
            dLineCol = typedArray.getColor(R.styleable.KLineView_klDLineCol, priceMa10Col);
            jLineCol = typedArray.getColor(R.styleable.KLineView_klJLineCol, priceMa30Col);
            crossHairCol = typedArray.getColor(R.styleable.KLineView_klCrossHairCol, 0xff828EA2);
            crossHairRightLabelCol = typedArray.getColor(R.styleable.KLineView_klCrossHairRightLabelCol, 0xff3193FF);
            crossHairRightLabelTextCol = typedArray.getColor(R.styleable.KLineView_klCrossHairRightLabelTextCol, 0xffffffff);
            crossHairRightLabelTextSize = typedArray.getInt(R.styleable.KLineView_klCrossHairRightLabelTextSize, 10);
            crossHairBottomLabelCol = typedArray.getColor(R.styleable.KLineView_klCrossHairBottomLabelCol, priceMaxLabelCol);
            crossHairBottomLabelTextCol = typedArray.getColor(R.styleable.KLineView_klCrossHairBottomLabelTextCol, 0xffffffff);
            crossHairBottomLabelTextSize = typedArray.getInt(R.styleable.KLineView_klCrossHairBottomLabelTextSize, 8);
            detailFrameCol = typedArray.getColor(R.styleable.KLineView_klDetailFrameCol, 0xffB5C0D0);
            detailTextCol = typedArray.getColor(R.styleable.KLineView_klDetailTextCol, 0xff808F9E);
            detailTextSize = typedArray.getInt(R.styleable.KLineView_klDetailTextSize, 10);
            detailBgCol = typedArray.getColor(R.styleable.KLineView_klDetailBgCol, 0xe6ffffff);
            typedArray.recycle();
        }
    }

    private void initData() {
        super.setOnTouchListener(this);
        super.setClickable(true);
        super.setFocusable(true);
        gestureDetector = new GestureDetector(getContext(), new CustomGestureListener());
        moveLimitDistance = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        detailRectWidth = dp2px(103);
        detailRectHeight = dp2px(120);
        detailTextVerticalSpace = (detailRectHeight - dp2px(4)) / 8;
        detailLeftTitleArr = new String[]{"时间", "开", "高", "低", "收", "涨跌额", "涨跌幅", "成交量"};
        initQuotaThread();
        initStopDelay();

        strokePaint = new Paint();
        strokePaint.setAntiAlias(true);
        strokePaint.setTextSize(sp2px(abscissaTextSize));
        strokePaint.setStyle(Paint.Style.STROKE);

        fillPaint = new Paint();
        fillPaint.setAntiAlias(true);
        fillPaint.setStyle(Paint.Style.FILL);

        instantFillPaint = new Paint();
        instantFillPaint.setAntiAlias(true);
        instantFillPaint.setStyle(Paint.Style.FILL);

        curvePath = new Path();
        instantPath = new Path();

        longPressRunnable = new Runnable() {
            @Override
            public void run() {
                isLongPress = true;
                isShowDetail = true;
                getClickKData(longPressDownX);
                invalidate();
            }
        };
    }

    private void initQuotaThread() {
        Handler uiHandler = new Handler(this);
        quotaThread = new QuotaThread("quotaThread", Process.THREAD_PRIORITY_BACKGROUND);
        quotaThread.setUIHandler(uiHandler);
        quotaThread.start();
    }

    @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == QuotaThread.HANDLER_QUOTA_LIST) {
            invalidate();
        } else if (msg.what == QuotaThread.HANDLER_QUOTA_SINGLE) {
            if (endDataList == null || totalDataList == null) {
                return false;
            }
            KData endLastData = endDataList.get(endDataList.size() - 1);
            int totalSize = totalDataList.size();
            KData totalLastData = totalDataList.get(totalSize - 1);
            if (endLastData.getTime() == totalLastData.getTime()) {
                totalDataList.remove(totalSize - 1);
            }
            totalDataList.add(endLastData);
            if (totalSize >= maxViewDataNum
                    && startDataNum == totalSize - maxViewDataNum - 1) {
                startDataNum++;
                resetViewData();
            } else {
                resetViewData();
            }
        }
        return false;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        leftStart = getPaddingLeft();
        topStart = getPaddingTop();
        rightEnd = getMeasuredWidth() - getPaddingRight();
        bottomEnd = getMeasuredHeight() - getPaddingBottom();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (totalDataList.isEmpty() || viewDataList.isEmpty()) {
            return;
        }
        resetData();

        drawTickMark(canvas);
        drawAbscissa(canvas);
        drawOrdinate(canvas);
        drawCrossHairLine(canvas);
        drawVolume(canvas);

        if (isShowInstant){
            crossHairMoveMode = CROSS_HAIR_MOVE_CLOSE;
            drawInstant(canvas);
        } else {
            drawMainDeputyRect(canvas);
            drawBezierCurve(canvas);
            drawTopPriceMAData(canvas);
            drawBotMAData(canvas);
            drawMaxMinPriceLabel(canvas);
            drawDetailData(canvas);
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            longPressDownX = event.getX();
            longPressDownY = event.getY();
            dispatchDownX = event.getX();
            dispatchDownY = event.getY();
            isLongPress = false;
            postDelayed(longPressRunnable, LONG_PRESS_TIME_OUT);

        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            //长按控制
            float diffDispatchMoveX = Math.abs(event.getX() - longPressDownX);
            float diffDispatchMoveY = Math.abs(event.getY() - longPressDownY);
            float moveDistanceX = Math.abs(event.getX() - dispatchDownX);
            float moveDistanceY = Math.abs(event.getY() - dispatchDownY);
            longPressMoveY = event.getY();
            if (getParent() != null) {
                getParent().requestDisallowInterceptTouchEvent(true);
            }

            if (isHorizontalMove || (diffDispatchMoveX > diffDispatchMoveY + dp2px(5)
                    && diffDispatchMoveX > moveLimitDistance)
                    || (isLongPress && diffDispatchMoveY > moveLimitDistance)) {
                isHorizontalMove = true;
                removeCallbacks(longPressRunnable);

                if (isLongPress && (moveDistanceX > 1 || moveDistanceY > 1)) {
                    getClickKData(event.getX());
                    if (lastKData != null) {
                        invalidate();
                    }
                }

                dispatchDownX = event.getX();
                dispatchDownY = event.getY();
                return isLongPress || super.dispatchTouchEvent(event);

            } else if (!isLongPress && !isHorizontalMove && !isDoubleFinger
                    && diffDispatchMoveY > diffDispatchMoveX + dp2px(5)
                    && diffDispatchMoveY > moveLimitDistance) {
                removeCallbacks(longPressRunnable);
                if (getParent() != null) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                return false;
            }

        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            isHorizontalMove = false;
            removeCallbacks(longPressRunnable);
            if (getParent() != null) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
        }

        return isLongPress || super.dispatchTouchEvent(event);

    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                singleClickDownX = event.getX();
                singleClickDownY = event.getY();
                flingVelocityX = 0;
                mulFirstDownX = event.getX(0);
                mulFirstDownY = event.getY(0);
                break;

            case MotionEvent.ACTION_POINTER_DOWN:
                isShowDetail = false;
                isDoubleFinger = true;
                removeCallbacks(longPressRunnable);
                mulSecondDownX = event.getX(1);
                float mulSecondDownY = event.getY(1);
                lastDiffMoveX = Math.abs(mulSecondDownX - mulFirstDownX);
                lastDiffMoveY = Math.abs(mulSecondDownY - mulFirstDownY);
                break;

            case MotionEvent.ACTION_MOVE:
                if (event.getPointerCount() > 1) {
                    float mulFirstMoveX = event.getX(0);
                    float mulFirstMoveY = event.getY(0);
                    float mulSecondMoveX = event.getX(1);
                    float mulSecondMoveY = event.getY(1);
                    float diffMoveX = Math.abs(mulSecondMoveX - mulFirstMoveX);
                    float diffMoveY = Math.abs(mulSecondMoveY - mulFirstMoveY);

                    //双指分开,放大显示
                    if ((diffMoveX >= diffMoveY && diffMoveX - lastDiffMoveX > 1)
                            || (diffMoveY >= diffMoveX && diffMoveY - lastDiffMoveY > 1)) {

                        if (maxViewDataNum <= VIEW_DATA_NUM_MIN) {
                            maxViewDataNum = VIEW_DATA_NUM_MIN;

                            //如果view中显示的数据量小于当前最大数据条数,则不管双指如何落点,都向左放大
                        } else if (viewDataList != null && viewDataList.size() < maxViewDataNum) {
                            maxViewDataNum -= 2;
                            startDataNum = totalDataList.size() - maxViewDataNum;

                            //如果双指起始落点都在中线左侧,则左边不动,放大右边
                        } else if (verticalXList != null && mulFirstDownX < verticalXList.get(2)
                                && mulSecondDownX <= verticalXList.get(2)) {
                            maxViewDataNum -= 2;

                            //如果双指起始落点在中线左右两侧,则左右同时放大
                        } else if ((verticalXList != null && mulFirstDownX <= verticalXList.get(2)
                                && mulSecondDownX >= verticalXList.get(2))
                                || (verticalXList != null && mulFirstDownX >= verticalXList.get(2)
                                && mulSecondDownX <= verticalXList.get(2))) {
                            maxViewDataNum -= 2;
                            startDataNum += 1;

                            //如果双指起始落点在中线右侧,则右边不动,放大左边
                        } else if (verticalXList != null && mulFirstDownX >= verticalXList.get(2)
                                && mulSecondDownX > verticalXList.get(2)) {
                            maxViewDataNum -= 2;
                            startDataNum += 2;
                        }
                        resetViewData();

                        //双指靠拢,缩小显示
                    } else if ((diffMoveX >= diffMoveY && diffMoveX - lastDiffMoveX < -1)
                            || (diffMoveY >= diffMoveX && diffMoveY - lastDiffMoveY < -1)) {

                        if (maxViewDataNum >= VIEW_DATA_NUM_MAX) {
                            maxViewDataNum = VIEW_DATA_NUM_MAX;

                            //如果view显示的数据是totalDataList最末尾的数据,则只向左边缩小
                        } else if (totalDataList != null
                                && startDataNum + maxViewDataNum >= totalDataList.size()) {
                            maxViewDataNum += 2;
                            startDataNum = totalDataList.size() - maxViewDataNum;

                            //如果view显示的数据是totalDataList最开始的数据,则只向右边缩小
                        } else if (startDataNum <= 0) {
                            startDataNum = 0;
                            maxViewDataNum += 2;

                            //如果双指起始落点都在中线左侧,则左边不动,右边缩小
                        } else if (verticalXList != null && mulFirstDownX < verticalXList.get(2)
                                && mulSecondDownX <= verticalXList.get(2)) {
                            maxViewDataNum += 2;

                            //如果双指起始落点在中线左右两侧,则左右同时缩小
                        } else if ((verticalXList != null && mulFirstDownX <= verticalXList.get(2)
                                && mulSecondDownX >= verticalXList.get(2))
                                || (verticalXList != null && mulFirstDownX >= verticalXList.get(2)
                                && mulSecondDownX <= verticalXList.get(2))) {
                            maxViewDataNum += 2;
                            startDataNum -= 1;

                            //如果双指起始落点都在中线右侧,则右边不动,左边缩小
                        } else if (verticalXList != null && mulFirstDownX >= verticalXList.get(2)
                                && mulSecondDownX > verticalXList.get(2)) {
                            maxViewDataNum += 2;
                            startDataNum -= 2;
                        }
                        resetViewData();

                    }
                    lastDiffMoveX = Math.abs(mulSecondMoveX - mulFirstMoveX);
                    lastDiffMoveY = Math.abs(mulSecondMoveY - mulFirstMoveY);
                }
                break;

            case MotionEvent.ACTION_UP:
                if (!isDoubleFinger) {
                    float diffTouchMoveX = Math.abs(event.getX() - singleClickDownX);
                    float diffTouchMoveY = Math.abs(event.getY() - singleClickDownY);
                    if (diffTouchMoveY < moveLimitDistance && diffTouchMoveX < moveLimitDistance) {
                        isShowDetail = true;
                        if (crossHairMoveMode == CROSS_HAIR_MOVE_FREE) {
                            longPressMoveY = event.getY();
                        }
                        getClickKData(singleClickDownX);
                        if (lastKData != null) {
                            invalidate();
                        }
                    }
                }
                isDoubleFinger = false;
                break;

            case MotionEvent.ACTION_CANCEL:
                isDoubleFinger = false;
                break;

        }
        return true;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return !isDoubleFinger && gestureDetector.onTouchEvent(event);
    }

    private class CustomGestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if ((startDataNum == 0 && distanceX < 0)
                    || (totalDataList != null && startDataNum == totalDataList.size() - maxViewDataNum  && distanceX > 0)
                    || startDataNum < 0
                    || (viewDataList != null && viewDataList.size() < maxViewDataNum)) {
                if (isShowDetail) {
                    isShowDetail = false;
                    if (!viewDataList.isEmpty()) {
                        lastKData = viewDataList.get(viewDataList.size() - 1);
                    }
                    invalidate();
                }
                return true;
            } else {
                isShowDetail = false;
                if (Math.abs(distanceX) > 1) {
                    moveData(distanceX);
                    invalidate();
                }
            }
            return true;
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (totalDataList != null && startDataNum > 0
                    && startDataNum < totalDataList.size() - 1 - maxViewDataNum) {
                if (velocityX > 8000) {
                    flingVelocityX = 8000;
                } else if (velocityX < -8000) {
                    flingVelocityX = -8000;
                } else {
                    flingVelocityX = velocityX;
                }
                stopDelay();
            }
            return true;
        }
    }

    private void stopDelay() {
        post(mDelayRunnable);
    }

    private void initStopDelay() {
        mDelayRunnable = new Runnable() {
            @Override
            public void run() {
                if (flingVelocityX < -200) {
                    if (flingVelocityX < -6000) {
                        startDataNum += 6;
                    } else if (flingVelocityX < -4000) {
                        startDataNum += 5;
                    } else if (flingVelocityX < -2500) {
                        startDataNum += 4;
                    } else if (flingVelocityX < -1000) {
                        startDataNum += 3;
                    } else {
                        startDataNum++;
                    }
                    flingVelocityX += 200;
                    if (startDataNum > totalDataList.size() - maxViewDataNum) {
                        startDataNum = totalDataList.size() - maxViewDataNum;
                    }
                } else if (flingVelocityX > 200) {
                    if (flingVelocityX > 6000) {
                        startDataNum -= 6;
                    } else if (flingVelocityX > 4000) {
                        startDataNum -= 5;
                    } else if (flingVelocityX > 2500) {
                        startDataNum -= 4;
                    } else if (flingVelocityX > 1000) {
                        startDataNum -= 3;
                    } else {
                        startDataNum--;
                    }
                    flingVelocityX -= 200;
                    if (startDataNum < 0) {
                        startDataNum = 0;
                    }
                }
                resetViewData();
                requestNewData();

                if (Math.abs(flingVelocityX) > 200) {
                    postDelayed(this, 15);
                }
            }
        };
    }

    private void moveData(float distanceX) {
        if (maxViewDataNum < 60) {
            setSpeed(distanceX, 10);
        } else {
            setSpeed(distanceX, 3.5);
        }
        if (startDataNum < 0) {
            startDataNum = 0;
        }
        if (totalDataList != null){
            int size = totalDataList.size();
            if (startDataNum > size - maxViewDataNum) {
                startDataNum = size - maxViewDataNum;
            }
        }
        requestNewData();
        resetViewData();
    }

    private void setSpeed(float distanceX, double num) {
        if (Math.abs(distanceX) > 1 && Math.abs(distanceX) < 2) {
            startDataNum += (int) (distanceX * 10) % 2;
        } else if (Math.abs(distanceX) < 10) {
            startDataNum += (int) distanceX % 2;
        } else {
            startDataNum += (int) distanceX / num;
        }
    }

    private void requestNewData() {
        if (totalDataList != null && startDataNum <= totalDataList.size() / 3 && isNeedRequestPreData) {
            isNeedRequestPreData = false;
            if (requestListener != null){
                requestListener.requestData();
            }
        }
    }

    private void resetViewData() {
        if (viewDataList == null || totalDataList == null){
            return;
        }
        viewDataList.clear();
        int currentViewDataNum = Math.min(maxViewDataNum, totalDataList.size());
        if (startDataNum >= 0) {
            for (int i = 0; i < currentViewDataNum; i++) {
                if (i + startDataNum < totalDataList.size()) {
                    viewDataList.add(totalDataList.get(i + startDataNum));
                }
            }
        } else {
            for (int i = 0; i < currentViewDataNum; i++) {
                viewDataList.add(totalDataList.get(i));
            }
        }
        if (viewDataList.size() > 0 && !isShowDetail) {
            lastKData = viewDataList.get(viewDataList.size() - 1);
        } else if (viewDataList.isEmpty()) {
            lastKData = null;
        }
        invalidate();
    }

    private void resetData() {
        if (verticalXList == null || horizontalYList == null || rightEnd == 0) {
            return;
        }
        //垂直刻度线
        float horizontalSpace = (rightEnd - leftStart - (dp2px(46))) / 4;
        verticalXList.clear();
        for (int i = 0; i < 5; i++) {
            verticalXList.add(leftStart + horizontalSpace * (i) + dp2px(6));
        }
        //水平刻度线
        verticalSpace = (bottomEnd - topStart - dp2px(38)) / 5;
        horizontalYList.clear();
        for (int i = 0; i < 6; i++) {
            horizontalYList.add(topStart + verticalSpace * i + dp2px(18));
        }
        //副图顶线
        deputyTopY = horizontalYList.get(4) + dp2px(12);

        if (verticalXList == null || horizontalYList == null || viewDataList == null) {
            return;
        }
        avgPriceRectWidth = (verticalXList.get(verticalXList.size() - 1)
                - verticalXList.get(0)) / maxViewDataNum;
        maxPrice = viewDataList.get(0).getMaxPrice();
        minPrice = viewDataList.get(0).getMinPrice();
        maxVolume = viewDataList.get(0).getVolume();
        mMaxMacd = viewDataList.get(0).getMacd();
        mMinMacd = viewDataList.get(0).getMacd();
        double maxDea = viewDataList.get(0).getDea();
        double minDea = viewDataList.get(0).getDea();
        double maxDif = viewDataList.get(0).getDif();
        double minDif = viewDataList.get(0).getDif();
        mMaxK = viewDataList.get(0).getK();
        double maxD = viewDataList.get(0).getD();
        double maxJ = viewDataList.get(0).getJ();

        int viewDataSize = viewDataList.size();
        for (int i = 0; i < viewDataSize; i++) {
            KData viewKData = viewDataList.get(i);
            double rightX = verticalXList.get(verticalXList.size() - 1)
                    - (viewDataList.size() - i - 1) * avgPriceRectWidth;
            double leftX = rightX - avgPriceRectWidth;
            double centerX = rightX - avgPriceRectWidth/2;
            viewKData.setLeftX(leftX);
            viewKData.setRightX(rightX);
            viewKData.setCenterX(centerX);

            if (viewKData.getMaxPrice() >= maxPrice) {
                maxPrice = viewKData.getMaxPrice();
                maxPriceX = viewKData.getLeftX() + avgPriceRectWidth / 2;
            }
            if (viewKData.getMinPrice() <= minPrice) {
                minPrice = viewKData.getMinPrice();
                minPriceX = viewKData.getLeftX() + avgPriceRectWidth / 2;
            }
            if (viewKData.getVolume() >= maxVolume) {
                maxVolume = viewKData.getVolume();
            }

            if (!isShowDeputy || isShowInstant){
                continue;
            }
            if (deputyImgType == DEPUTY_IMG_MACD) {
                if (viewKData.getMacd() >= mMaxMacd) {
                    mMaxMacd = viewKData.getMacd();
                }
                if (viewKData.getMacd() <= mMinMacd) {
                    mMinMacd = viewKData.getMacd();
                }
                if (viewKData.getDea() >= maxDea) {
                    maxDea = viewKData.getDea();
                }
                if (viewKData.getDea() <= minDea) {
                    minDea = viewKData.getDea();
                }
                if (viewKData.getDif() >= maxDif) {
                    maxDif = viewKData.getDif();
                }
                if (viewKData.getDif() <= minDif) {
                    minDif = viewKData.getDif();
                }

            } else if (deputyImgType == DEPUTY_IMG_KDJ) {
                if (viewKData.getK() >= mMaxK) {
                    mMaxK = viewKData.getK();
                }
                if (viewKData.getD() >= maxD) {
                    maxD = viewKData.getD();
                }
                if (viewKData.getJ() >= maxJ) {
                    maxJ = viewKData.getJ();
                }
            }
        }

        topPrice = maxPrice + (maxPrice - minPrice) * 0.1;
        botPrice = minPrice - (maxPrice - minPrice) * 0.1;

        if (!isShowDeputy) {
            priceImgBot = horizontalYList.get(4);
            volumeImgBot = horizontalYList.get(5);
        } else {
            priceImgBot = horizontalYList.get(3);
            volumeImgBot = horizontalYList.get(4);
        }
        //priceData
        avgHeightPerPrice = (priceImgBot - horizontalYList.get(0)) / (topPrice - botPrice);
        mMaxPriceY = (horizontalYList.get(0) + (topPrice - maxPrice) * avgHeightPerPrice);
        mMinPriceY = (horizontalYList.get(0) + (topPrice - minPrice) * avgHeightPerPrice);

        //volumeData
        avgHeightPerVolume = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY) / maxVolume;

        for (KData kData : viewDataList) {
            double openPrice = kData.getOpenPrice();
            double closedPrice = kData.getClosePrice();
            kData.setCloseY((float) (horizontalYList.get(0) + (topPrice - closedPrice) * avgHeightPerPrice));
            kData.setOpenY((float) (horizontalYList.get(0) + (topPrice - openPrice) * avgHeightPerPrice));
        }

        if (!isShowDeputy){
            return;
        }
        switch (deputyImgType) {
            case DEPUTY_IMG_MACD:
                //MACD
                if (mMaxMacd > 0 && mMinMacd < 0) {
                    avgHeightMacd = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY) / Math.abs(mMaxMacd - mMinMacd);
                    deputyCenterY = (float) (deputyTopY + mMaxMacd * avgHeightMacd);
                } else if (mMaxMacd <= 0) {
                    avgHeightMacd = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY) / Math.abs(mMinMacd);
                    deputyCenterY = deputyTopY;
                } else if (mMinMacd >= 0) {
                    avgHeightMacd = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY) / Math.abs(mMaxMacd);
                    deputyCenterY = horizontalYList.get(horizontalYList.size() - 1);
                }
                //DEA
                if (maxDea > 0 && minDea < 0) {
                    avgHeightDea = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(24)) / (maxDea - minDea);
                } else if (maxDea <= 0) {
                    avgHeightDea = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(24)) / Math.abs(minDea);
                } else if (minDea >= 0) {
                    avgHeightDea = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(24)) / Math.abs(maxDea);
                }
                //DIF
                if (maxDif > 0 && minDif < 0) {
                    avgHeightDif = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(24)) / (maxDif - minDif);
                } else if (maxDif <= 0) {
                    avgHeightDif = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(24)) / Math.abs(minDif);
                } else if (minDif >= 0) {
                    avgHeightDif = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(24)) / Math.abs(maxDif);
                }
                break;

            case DEPUTY_IMG_KDJ:
                //K
                avgHeightK = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(12)) / mMaxK;
                //D
                avgHeightD = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(12)) / maxD;
                //J
                avgHeightJ = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY - dp2px(12)) / maxJ;
                break;

            case DEPUTY_IMG_RSI:
                avgHeightRSI = (horizontalYList.get(horizontalYList.size() - 1) - deputyTopY) / 100;
                break;
        }
    }

    //刻度线
    private void drawTickMark(Canvas canvas) {
        if (verticalXList == null || horizontalYList == null){
            return;
        }
        resetStrokePaint(tickMarkCol, 0);
        for (Float aFloat : verticalXList) {
            canvas.drawLine(aFloat, topStart + dp2px(18), aFloat, bottomEnd - dp2px(20), strokePaint);
        }
        float horizontalRightEnd;
        for (int i = 0; i < horizontalYList.size(); i++) {
            if (i == 0 || i == 5 || i == 4 || (isShowDeputy && i == 3)) {
                horizontalRightEnd = rightEnd;
            } else {
                horizontalRightEnd = verticalXList.get(verticalXList.size() - 1);
            }
            canvas.drawLine(leftStart + dp2px(6),
                    horizontalYList.get(i),
                    horizontalRightEnd,
                    horizontalYList.get(i),
                    strokePaint);
        }

        canvas.drawLine(leftStart + dp2px(6),
                horizontalYList.get(4) + verticalSpace / 2,
                verticalXList.get(verticalXList.size() - 1),
                horizontalYList.get(4) + verticalSpace / 2,
                strokePaint);
        //数量中线
        if (isShowDeputy) {
            canvas.drawLine(leftStart + dp2px(6),
                    horizontalYList.get(3) + verticalSpace / 2,
                    verticalXList.get(verticalXList.size() - 1),
                    horizontalYList.get(3) + verticalSpace / 2,
                    strokePaint);
        }
    }

    //主副图蜡烛图
    private void drawMainDeputyRect(Canvas canvas) {
        int viewDataSize = viewDataList.size();
        //drawPriceRectAndLine
        for (KData viewKData : viewDataList) {
            double openPrice = viewKData.getOpenPrice();
            double closedPrice = viewKData.getClosePrice();
            double higherPrice;
            double lowerPrice;
            if (openPrice >= closedPrice) {
                higherPrice = openPrice;
                lowerPrice = closedPrice;
                fillPaint.setColor(priceFallCol);
                resetStrokePaint(priceFallCol, 0);

            } else {
                higherPrice = closedPrice;
                lowerPrice = openPrice;
                fillPaint.setColor(priceIncreaseCol);
                resetStrokePaint(priceIncreaseCol, 0);
            }

            //如果开盘价==收盘价,则给1px的高度
            float upPriceCoordinate = (float) (mMaxPriceY + (maxPrice - higherPrice) * avgHeightPerPrice);
            float downPriceCoordinate = (float) (mMaxPriceY + (maxPrice - lowerPrice) * avgHeightPerPrice);
            if (upPriceCoordinate == downPriceCoordinate){
                downPriceCoordinate = upPriceCoordinate + 1;
            }

            //priceRect
            canvas.drawRect((float) viewKData.getLeftX() + dp2px(0.5f),
                    upPriceCoordinate,
                    (float) viewKData.getRightX() - dp2px(0.5f),
                    downPriceCoordinate,
                    fillPaint);

            //priceLine
            canvas.drawLine((float) (viewKData.getCenterX()),
                    (float) (mMaxPriceY + (maxPrice - viewKData.getMaxPrice()) * avgHeightPerPrice),
                    (float) (viewKData.getCenterX()),
                    (float) (mMaxPriceY + (maxPrice - viewKData.getMinPrice()) * avgHeightPerPrice),
                    strokePaint);

            //MACD
            if (isShowDeputy && deputyImgType == DEPUTY_IMG_MACD) {
                double macd = viewKData.getMacd();
                if (macd > 0) {
                    fillPaint.setColor(macdPositiveCol);
                    canvas.drawRect((float) (viewKData.getLeftX() + dp2px(0.5f)),
                            (float) (deputyCenterY - macd * avgHeightMacd),
                            (float) viewKData.getRightX() - dp2px(0.5f),
                            deputyCenterY,
                            fillPaint);

                } else {
                    fillPaint.setColor(macdNegativeCol);
                    canvas.drawRect((float) (viewKData.getLeftX() + dp2px(0.5f)),
                            deputyCenterY,
                            (float) viewKData.getRightX() - dp2px(0.5f),
                            (float) (deputyCenterY + Math.abs(macd) * avgHeightMacd),
                            fillPaint);
                }
            }
        }
    }

    private void drawVolume(Canvas canvas){
        for (KData kData : viewDataList) {
            //volumeRect
            if (!isShowInstant){
                double openPrice = kData.getOpenPrice();
                double closedPrice = kData.getClosePrice();
                if (openPrice >= closedPrice) {
                    fillPaint.setColor(priceFallCol);
                } else {
                    fillPaint.setColor(priceIncreaseCol);
                }
            } else {
                fillPaint.setColor(0xff4db7f3);
            }
            canvas.drawRect((float) (kData.getLeftX() + dp2px(0.5f)),
                    (float) (volumeImgBot - kData.getVolume() * avgHeightPerVolume),
                    (float) kData.getRightX() - dp2px(0.5f),
                    volumeImgBot,
                    fillPaint);
        }
    }

    //贝塞尔曲线
    private void drawBezierCurve(Canvas canvas) {
        mainMa5PointList.clear();
        mainMa10PointList.clear();
        mainMa30PointList.clear();

        volumeMa5PointList.clear();
        volumeMa10PointList.clear();

        deputyMa5PointList.clear();
        deputyMa10PointList.clear();
        deputyMa30PointList.clear();

        for (KData kData : viewDataList) {
            if (!kData.isInitFinish()) {
                break;
            }
            //volumeMA
            Pointer volumeMa5Point = new Pointer();
            if (kData.getVolumeMa5() > 0) {
                volumeMa5Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                volumeMa5Point.setY((float) (volumeImgBot
                        - kData.getVolumeMa5() * avgHeightPerVolume));
                volumeMa5PointList.add(volumeMa5Point);
            }

            Pointer volumeMa10Point = new Pointer();
            if (kData.getVolumeMa10() > 0) {
                volumeMa10Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                volumeMa10Point.setY((float) (volumeImgBot
                        - kData.getVolumeMa10() * avgHeightPerVolume));
                volumeMa10PointList.add(volumeMa10Point);
            }

            switch (mainImgType) {
                //priceMA
                case MAIN_IMG_MA:
                    Pointer priceMa5Point = new Pointer();
                    if (kData.getPriceMa5() > 0) {
                        priceMa5Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        priceMa5Point.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getPriceMa5()) * avgHeightPerPrice));
                        mainMa5PointList.add(priceMa5Point);
                    }

                    Pointer priceMa10Point = new Pointer();
                    if (kData.getPriceMa10() > 0) {
                        priceMa10Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        priceMa10Point.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getPriceMa10()) * avgHeightPerPrice));
                        mainMa10PointList.add(priceMa10Point);
                    }

                    Pointer priceMa30Point = new Pointer();
                    if (kData.getPriceMa30() > 0) {
                        priceMa30Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        priceMa30Point.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getPriceMa30()) * avgHeightPerPrice));
                        mainMa30PointList.add(priceMa30Point);
                    }
                    break;

                //priceEMA
                case MAIN_IMG_EMA:
                    Pointer ema5Point = new Pointer();
                    if (kData.getEma5() > 0) {
                        ema5Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        ema5Point.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getEma5()) * avgHeightPerPrice));
                        mainMa5PointList.add(ema5Point);
                    }

                    Pointer ema10Point = new Pointer();
                    if (kData.getEma10() > 0) {
                        ema10Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        ema10Point.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getEma10()) * avgHeightPerPrice));
                        mainMa10PointList.add(ema10Point);
                    }

                    Pointer ema30Point = new Pointer();
                    if (kData.getEma30() > 0) {
                        ema30Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        ema30Point.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getEma30()) * avgHeightPerPrice));
                        mainMa30PointList.add(ema30Point);
                    }
                    break;

                //priceBOLL
                case MAIN_IMG_BOLL:
                    Pointer bollMbPoint = new Pointer();
                    if (kData.getBollMb() > 0) {
                        bollMbPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        bollMbPoint.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getBollMb()) * avgHeightPerPrice));
                        mainMa5PointList.add(bollMbPoint);
                    }

                    Pointer bollUpPoint = new Pointer();
                    if (kData.getBollUp() > 0) {
                        bollUpPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        bollUpPoint.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getBollUp()) * avgHeightPerPrice));
                        mainMa10PointList.add(bollUpPoint);
                    }

                    Pointer bollDnPoint = new Pointer();
                    if (kData.getBollDn() > 0) {
                        bollDnPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                        bollDnPoint.setY((float) (mMaxPriceY
                                + (maxPrice - kData.getBollDn()) * avgHeightPerPrice));
                        mainMa30PointList.add(bollDnPoint);
                    }
                    break;
            }

            if (isShowDeputy && deputyImgType == DEPUTY_IMG_MACD) {
                Pointer difPoint = new Pointer();
                if (kData.getDif() > 0) {
                    difPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    difPoint.setY((float) (deputyCenterY - kData.getDif() * avgHeightDif));
                } else {
                    difPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    difPoint.setY((float) (deputyCenterY + Math.abs(kData.getDif() * avgHeightDif)));
                }
                deputyMa10PointList.add(difPoint);

                Pointer deaPoint = new Pointer();
                if (kData.getDea() > 0) {
                    deaPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    deaPoint.setY((float) (deputyCenterY - kData.getDea() * avgHeightDea));
                } else {
                    deaPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    deaPoint.setY((float) (deputyCenterY + Math.abs(kData.getDea() * avgHeightDea)));
                }
                deputyMa30PointList.add(deaPoint);

            } else if (isShowDeputy && deputyImgType == DEPUTY_IMG_KDJ) {
                Pointer kPoint = new Pointer();
                if (kData.getK() > 0) {
                    kPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    kPoint.setY((float) (horizontalYList.get(5) - kData.getK() * avgHeightK));
                    deputyMa5PointList.add(kPoint);
                }

                Pointer dPoint = new Pointer();
                if (kData.getD() > 0) {
                    dPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    dPoint.setY((float) (horizontalYList.get(5) - kData.getD() * avgHeightD));
                    deputyMa10PointList.add(dPoint);
                }

                Pointer jPoint = new Pointer();
                if (kData.getJ() > 0) {
                    jPoint.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    jPoint.setY((float) (horizontalYList.get(5) - kData.getJ() * avgHeightJ));
                    deputyMa30PointList.add(jPoint);
                }

            } else if (isShowDeputy && deputyImgType == DEPUTY_IMG_RSI) {
                Pointer rs1Point = new Pointer();
                if (kData.getRs1() >= 0) {
                    rs1Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    rs1Point.setY((float) (horizontalYList.get(5) - kData.getRs1() * avgHeightRSI));
                    deputyMa5PointList.add(rs1Point);
                }

                Pointer rs2Point = new Pointer();
                if (kData.getRs2() >= 0) {
                    rs2Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    rs2Point.setY((float) (horizontalYList.get(5) - kData.getRs2() * avgHeightRSI));
                    deputyMa10PointList.add(rs2Point);
                }

                Pointer rs3Point = new Pointer();
                if (kData.getRs3() >= 0) {
                    rs3Point.setX((float) (kData.getLeftX() + avgPriceRectWidth / 2));
                    rs3Point.setY((float) (horizontalYList.get(5) - kData.getRs3() * avgHeightRSI));
                    deputyMa30PointList.add(rs3Point);
                }
            }
        }

        drawVolumeBezierCurve(canvas);
        drawMainBezierCurve(canvas);
        if (isShowDeputy) {
            drawDeputyCurve(canvas);
        }
    }

    //主图 MA曲线
    private void drawMainBezierCurve(@NonNull Canvas canvas) {
        QuotaUtil.setBezierPath(mainMa5PointList, curvePath);
        resetStrokePaint(priceMa5Col, 0);
        canvas.drawPath(curvePath, strokePaint);

        QuotaUtil.setBezierPath(mainMa10PointList, curvePath);
        resetStrokePaint(priceMa10Col, 0);
        canvas.drawPath(curvePath, strokePaint);

        QuotaUtil.setBezierPath(mainMa30PointList, curvePath);
        resetStrokePaint(priceMa30Col, 0);
        canvas.drawPath(curvePath, strokePaint);
    }

    //volume MA曲线
    private void drawVolumeBezierCurve(@NonNull Canvas canvas) {
        QuotaUtil.setBezierPath(volumeMa5PointList, curvePath);
        resetStrokePaint(priceMa5Col, 0);
        canvas.drawPath(curvePath, strokePaint);

        QuotaUtil.setBezierPath(volumeMa10PointList, curvePath);
        resetStrokePaint(priceMa10Col, 0);
        canvas.drawPath(curvePath, strokePaint);
    }

    //副图 曲线
    private void drawDeputyCurve(@NonNull Canvas canvas) {
        QuotaUtil.setLinePath(deputyMa5PointList, curvePath);
        resetStrokePaint(priceMa5Col, 0);
        canvas.drawPath(curvePath, strokePaint);

        QuotaUtil.setLinePath(deputyMa10PointList, curvePath);
        resetStrokePaint(priceMa10Col, 0);
        canvas.drawPath(curvePath, strokePaint);

        QuotaUtil.setLinePath(deputyMa30PointList, curvePath);
        resetStrokePaint(priceMa30Col, 0);
        canvas.drawPath(curvePath, strokePaint);
    }

    //获取单击位置的数据
    private void getClickKData(float clickX) {
        if (isShowDetail) {
            detailRightDataList.clear();
            for (int i = 0; i < viewDataList.size(); i++) {
                if (viewDataList.get(i).getLeftX() <= clickX
                        && viewDataList.get(i).getRightX() >= clickX) {
                    lastKData = viewDataList.get(i);
                    detailRightDataList.add(formatDate(lastKData.getTime()));
                    detailRightDataList.add(setPrecision(lastKData.getOpenPrice(), 2));
                    detailRightDataList.add(setPrecision(lastKData.getMaxPrice(), 2));
                    detailRightDataList.add(setPrecision(lastKData.getMinPrice(), 2));
                    detailRightDataList.add(setPrecision(lastKData.getClosePrice(), 2));
                    double upDnAmount = lastKData.getUpDnAmount();
                    if (upDnAmount > 0) {
                        detailRightDataList.add("+" + setPrecision(upDnAmount, 2));
                        detailRightDataList.add("+" + setPrecision(lastKData.getUpDnRate() * 100, 2) + "%");
                    } else {
                        detailRightDataList.add(setPrecision(upDnAmount, 2));
                        detailRightDataList.add(setPrecision(lastKData.getUpDnRate() * 100, 2) + "%");
                    }
                    detailRightDataList.add(setPrecision(lastKData.getVolume(), 2));
                    break;

                } else {
                    lastKData = null;
                }
            }

        } else {
            lastKData = viewDataList.get(viewDataList.size() - 1);
        }
    }

    //十字线
    private void drawCrossHairLine(Canvas canvas) {
        if (lastKData == null || !isShowDetail) {
            return;
        }
        //垂直
        resetStrokePaint(crossHairCol, 0);
        canvas.drawLine((float) (lastKData.getLeftX() + avgPriceRectWidth / 2),
                horizontalYList.get(0),
                (float) (lastKData.getLeftX() + avgPriceRectWidth / 2),
                horizontalYList.get(horizontalYList.size() - 1),
                strokePaint);

        //水平
        double moveY;
        switch (crossHairMoveMode) {
            case CROSS_HAIR_MOVE_OPEN:
                moveY = lastKData.getOpenY();
                break;

            case CROSS_HAIR_MOVE_FREE:
                moveY = longPressMoveY;
                break;

            case CROSS_HAIR_MOVE_CLOSE:
            default:
                moveY = lastKData.getCloseY();
                break;
        }
        if (moveY < horizontalYList.get(0)) {
            moveY = horizontalYList.get(0);
        } else if (moveY > priceImgBot) {
            moveY = priceImgBot;
        }

        resetStrokePaint(crossHairCol, 0);
        canvas.drawLine(verticalXList.get(0),
                (float) moveY,
                verticalXList.get(verticalXList.size() - 1),
                (float) moveY,
                strokePaint);

        //底部标签
        RectF grayRectF = new RectF((float) (lastKData.getLeftX() + avgPriceRectWidth / 2 - dp2px(25)),
                bottomEnd - dp2px(20),
                (float) (lastKData.getLeftX() + avgPriceRectWidth / 2 + dp2px(25)),
                bottomEnd);
        fillPaint.setColor(crossHairBottomLabelCol);
        canvas.drawRoundRect(grayRectF, 4, 4, fillPaint);

        //底部标签text
        String moveTime = formatDate(lastKData.getTime());
        resetStrokePaint(crossHairBottomLabelTextCol, crossHairBottomLabelTextSize);
        canvas.drawText(moveTime,
                (float) (lastKData.getLeftX() + avgPriceRectWidth / 2 - strokePaint.measureText(moveTime) / 2),
                bottomEnd - dp2px(7),
                strokePaint);

        //右侧标签
        RectF blueRectF = new RectF(rightEnd - dp2px(38),
                (float) moveY - dp2px(7),
                rightEnd - dp2px(1),
                (float) moveY + dp2px(7));
        fillPaint.setColor(crossHairRightLabelCol);
        canvas.drawRoundRect(blueRectF, 4, 4, fillPaint);

        curvePath.reset();
        curvePath.moveTo(verticalXList.get(verticalXList.size() - 1), (float) moveY);
        curvePath.lineTo(rightEnd - dp2px(37), (float) moveY - dp2px(3));
        curvePath.lineTo(rightEnd - dp2px(37), (float) moveY + dp2px(3));
        curvePath.close();
        canvas.drawPath(curvePath, fillPaint);

        double avgPricePerHeight;
        if (!isShowDeputy) {
            avgPricePerHeight = (topPrice - botPrice)
                    / (horizontalYList.get(4) - horizontalYList.get(0));
        } else {
            avgPricePerHeight = (topPrice - botPrice)
                    / (horizontalYList.get(3) - horizontalYList.get(0));
        }

        String movePrice = formatKDataNum(topPrice
                - avgPricePerHeight * ((float) moveY - horizontalYList.get(0)));
        Rect textRect = new Rect();
        resetStrokePaint(crossHairRightLabelTextCol, crossHairRightLabelTextSize);
        strokePaint.getTextBounds(movePrice, 0, movePrice.length(), textRect);
        canvas.drawText(movePrice,
                rightEnd - dp2px(38) + (blueRectF.width() - textRect.width()) / 2,
                (float) moveY + dp2px(7) - (blueRectF.height() - textRect.height()) / 2,
                strokePaint);
    }

    //最高价、最低价标签
    private void drawMaxMinPriceLabel(Canvas canvas) {
        //maxPrice
        Rect maxPriceRect = new Rect();
        String maxPriceStr = setPrecision(maxPrice, 2);
        resetStrokePaint(priceMaxLabelTextCol, priceMaxLabelTextSize);
        strokePaint.getTextBounds(maxPriceStr, 0, maxPriceStr.length(), maxPriceRect);

        RectF maxRectF;
        float maxPriceTextX;
        if (maxPriceX + maxPriceRect.width() + dp2px(8) < verticalXList.get(verticalXList.size() - 1)) {
            maxRectF = new RectF((float) (maxPriceX + dp2px(3)),
                    (float) mMaxPriceY - dp2px(7),
                    (float) (maxPriceX + maxPriceRect.width() + dp2px(8)),
                    (float) mMaxPriceY + dp2px(7));

            curvePath.reset();
            curvePath.moveTo((float) maxPriceX, (float) mMaxPriceY);
            curvePath.lineTo((float) (maxPriceX + dp2px(4)), (float) mMaxPriceY - dp2px(3));
            curvePath.lineTo((float) (maxPriceX + dp2px(4)), (float) mMaxPriceY + dp2px(3));
            curvePath.close();

            maxPriceTextX = (float) (maxPriceX + dp2px(5));

        } else {
            maxRectF = new RectF((float) (maxPriceX - dp2px(3)),
                    (float) mMaxPriceY - dp2px(7),
                    (float) (maxPriceX - maxPriceRect.width() - dp2px(8)),
                    (float) mMaxPriceY + dp2px(7));

            curvePath.reset();
            curvePath.moveTo((float) maxPriceX, (float) mMaxPriceY);
            curvePath.lineTo((float) (maxPriceX - dp2px(4)), (float) mMaxPriceY - dp2px(3));
            curvePath.lineTo((float) (maxPriceX - dp2px(4)), (float) mMaxPriceY + dp2px(3));
            curvePath.close();

            maxPriceTextX = (float) (maxPriceX - dp2px(5)) - maxPriceRect.width();
        }

        fillPaint.setColor(priceMaxLabelCol);
        canvas.drawRoundRect(maxRectF, 4, 4, fillPaint);
        canvas.drawPath(curvePath, fillPaint);

        resetStrokePaint(priceMaxLabelTextCol, priceMaxLabelTextSize);
        canvas.drawText(maxPriceStr,
                maxPriceTextX,
                (float) mMaxPriceY + maxPriceRect.height() / 2f,
                strokePaint);

        //minPrice
        Rect minPriceRect = new Rect();
        String minPriceStr = setPrecision(minPrice, 2);
        resetStrokePaint(priceMinLabelTextCol, priceMinLabelTextSize);
        strokePaint.getTextBounds(minPriceStr, 0, minPriceStr.length(), minPriceRect);

        RectF minRectF;
        float minPriceTextX;
        if (minPriceX + minPriceRect.width() + dp2px(8) < verticalXList.get(verticalXList.size() - 1)) {
            minRectF = new RectF((float) (minPriceX + dp2px(3)),
                    (float) mMinPriceY - dp2px(7),
                    (float) (minPriceX + minPriceRect.width() + dp2px(8)),
                    (float) mMinPriceY + dp2px(7));

            curvePath.reset();
            curvePath.moveTo((float) minPriceX, (float) mMinPriceY);
            curvePath.lineTo((float) (minPriceX + dp2px(4)), (float) mMinPriceY - dp2px(3));
            curvePath.lineTo((float) (minPriceX + dp2px(4)), (float) mMinPriceY + dp2px(3));
            curvePath.close();

            minPriceTextX = (float) (minPriceX + dp2px(5));

        } else {
            minRectF = new RectF((float) (minPriceX - dp2px(3)),
                    (float) mMinPriceY - dp2px(7),
                    (float) (minPriceX - minPriceRect.width() - dp2px(8)),
                    (float) mMinPriceY + dp2px(7));

            curvePath.reset();
            curvePath.moveTo((float) minPriceX, (float) mMinPriceY);
            curvePath.lineTo((float) (minPriceX - dp2px(4)), (float) mMinPriceY - dp2px(3));
            curvePath.lineTo((float) (minPriceX - dp2px(4)), (float) mMinPriceY + dp2px(3));
            curvePath.close();

            minPriceTextX = (float) (minPriceX - dp2px(5)) - minPriceRect.width();
        }

        fillPaint.setColor(priceMinLabelCol);
        canvas.drawRoundRect(minRectF, 4, 4, fillPaint);
        canvas.drawPath(curvePath, fillPaint);

        resetStrokePaint(priceMinLabelTextCol, priceMinLabelTextSize);
        canvas.drawText(minPriceStr,
                minPriceTextX,
                (float) mMinPriceY + minPriceRect.height() / 2f,
                strokePaint);
    }

    private void drawDetailData(Canvas canvas) {
        if (lastKData == null || !isShowDetail) {
            return;
        }
        resetStrokePaint(detailTextCol, detailTextSize);
        strokePaint.getTextBounds(detailLeftTitleArr[0], 0, detailLeftTitleArr[0].length(), detailTextRect);

        if (lastKData.getLeftX() + avgPriceRectWidth / 2 <= getMeasuredWidth() / 2f) {
            //边框(右侧)
            fillPaint.setColor(detailBgCol);
            canvas.drawRect(verticalXList.get(verticalXList.size() - 1) - detailRectWidth,
                    horizontalYList.get(0),
                    verticalXList.get(verticalXList.size() - 1),
                    horizontalYList.get(0) + detailRectHeight,
                    fillPaint);

            resetStrokePaint(detailFrameCol, 0);
            canvas.drawLine(verticalXList.get(verticalXList.size() - 1) - detailRectWidth,
                    horizontalYList.get(0),
                    verticalXList.get(verticalXList.size() - 1) - detailRectWidth,
                    horizontalYList.get(0) + detailRectHeight,
                    strokePaint);

            canvas.drawLine(verticalXList.get(verticalXList.size() - 1) - detailRectWidth,
                    horizontalYList.get(0),
                    verticalXList.get(verticalXList.size() - 1),
                    horizontalYList.get(0),
                    strokePaint);

            canvas.drawLine(verticalXList.get(verticalXList.size() - 1),
                    horizontalYList.get(0),
                    verticalXList.get(verticalXList.size() - 1),
                    horizontalYList.get(0) + detailRectHeight,
                    strokePaint);

            canvas.drawLine(verticalXList.get(verticalXList.size() - 1) - detailRectWidth,
                    horizontalYList.get(0) + detailRectHeight,
                    verticalXList.get(verticalXList.size() - 1),
                    horizontalYList.get(0) + detailRectHeight,
                    strokePaint);

            //详情字段
            resetStrokePaint(detailTextCol, detailTextSize);
            for (int i = 0; i < detailLeftTitleArr.length; i++) {
                canvas.drawText(detailLeftTitleArr[i],
                        verticalXList.get(verticalXList.size() - 1) - detailRectWidth + dp2px(4),
                        horizontalYList.get(0) + detailTextVerticalSpace * i
                                + detailTextRect.height() + (detailTextVerticalSpace - detailTextRect.height()) / 2,
                        strokePaint);
            }

            //详情数据
            for (int i = 0; i < detailRightDataList.size(); i++) {
                if (i == 5 || i == 6) {
                    if (lastKData.getUpDnAmount() > 0) {
                        resetStrokePaint(priceIncreaseCol, detailTextSize);
                    } else {
                        resetStrokePaint(priceFallCol, detailTextSize);
                    }
                } else {
                    resetStrokePaint(detailTextCol, detailTextSize);
                }
                canvas.drawText(detailRightDataList.get(i),
                        verticalXList.get(verticalXList.size() - 1) - dp2px(4)
                                - strokePaint.measureText(detailRightDataList.get(i)),
                        horizontalYList.get(0) + detailTextVerticalSpace * i
                                + detailTextRect.height() + (detailTextVerticalSpace - detailTextRect.height()) / 2,
                        strokePaint);
            }

        } else {
            //边框(左侧)
            fillPaint.setColor(detailBgCol);
            canvas.drawRect(verticalXList.get(0),
                    horizontalYList.get(0),
                    verticalXList.get(0) + detailRectWidth,
                    horizontalYList.get(0) + detailRectHeight,
                    fillPaint);

            resetStrokePaint(detailFrameCol, 0);
            canvas.drawLine(verticalXList.get(0),
                    horizontalYList.get(0),
                    verticalXList.get(0),
                    horizontalYList.get(0) + detailRectHeight,
                    strokePaint);

            canvas.drawLine(verticalXList.get(0),
                    horizontalYList.get(0),
                    verticalXList.get(0) + detailRectWidth,
                    horizontalYList.get(0),
                    strokePaint);

            canvas.drawLine(verticalXList.get(0) + detailRectWidth,
                    horizontalYList.get(0),
                    verticalXList.get(0) + detailRectWidth,
                    horizontalYList.get(0) + detailRectHeight,
                    strokePaint);

            canvas.drawLine(verticalXList.get(0),
                    horizontalYList.get(0) + detailRectHeight,
                    verticalXList.get(0) + detailRectWidth,
                    horizontalYList.get(0) + detailRectHeight,
                    strokePaint);

            //文字详情
            resetStrokePaint(detailTextCol, detailTextSize);
            for (int i = 0; i < detailLeftTitleArr.length; i++) {
                canvas.drawText(detailLeftTitleArr[i],
                        verticalXList.get(0) + dp2px(4),
                        horizontalYList.get(0) + detailTextVerticalSpace * i
                                + detailTextRect.height() + (detailTextVerticalSpace - detailTextRect.height()) / 2,
                        strokePaint);
            }

            //详情数据
            for (int i = 0; i < detailRightDataList.size(); i++) {
                if (i == 5 || i == 6) {
                    if (lastKData.getUpDnAmount() > 0) {
                        resetStrokePaint(priceIncreaseCol, detailTextSize);
                    } else {
                        resetStrokePaint(priceFallCol, detailTextSize);
                    }
                } else {
                    resetStrokePaint(detailTextCol, detailTextSize);
                }
                canvas.drawText(detailRightDataList.get(i),
                        verticalXList.get(0) + detailRectWidth - dp2px(4)
                                - strokePaint.measureText(detailRightDataList.get(i)),
                        horizontalYList.get(0) + detailTextVerticalSpace * i
                                + detailTextRect.height() + (detailTextVerticalSpace - detailTextRect.height()) / 2,
                        strokePaint);
            }
        }
    }

    //顶部价格MA
    private void drawTopPriceMAData(Canvas canvas) {
        if (lastKData == null) {
            return;
        }
        String ma5Str = STR_MA5 + setPrecision(lastKData.getPriceMa5(), 2);
        String ma10Str = STR_MA10 + setPrecision(lastKData.getPriceMa10(), 2);
        String ma30Str = STR_MA30 + setPrecision(lastKData.getPriceMa30(), 2);

        resetStrokePaint(priceMa5Col, topMaTextSize);
        strokePaint.getTextBounds(ma5Str, 0, ma5Str.length(), topMa5Rect);
        canvas.drawText(ma5Str,
                leftStart + dp2px(6),
                topStart + topMa5Rect.height() + dp2px(6),
                strokePaint);

        resetStrokePaint(priceMa10Col, topMaTextSize);
        strokePaint.getTextBounds(ma10Str, 0, ma10Str.length(), topMa10Rect);
        canvas.drawText(ma10Str,
                leftStart + dp2px(6) + topMa5Rect.width() + dp2px(10),
                topStart + topMa5Rect.height() + dp2px(6),
                strokePaint);

        resetStrokePaint(priceMa30Col, topMaTextSize);
        strokePaint.getTextBounds(ma30Str, 0, ma30Str.length(), topMa30Rect);
        canvas.drawText(ma30Str,
                leftStart + dp2px(6) + topMa5Rect.width() + topMa10Rect.width() + dp2px(10) * 2,
                topStart + topMa5Rect.height() + dp2px(6),
                strokePaint);
    }

    //数量MA
    private void drawBotMAData(Canvas canvas) {
        if (lastKData == null) {
            return;
        }
        //VOL
        String volStr = STR_VOL + setPrecision(lastKData.getVolume(), 2);
        Rect volRect = new Rect();
        resetStrokePaint(volumeTextCol, volumeTextSize);
        strokePaint.getTextBounds(volStr, 0, volStr.length(), volRect);
        canvas.drawText(volStr,
                verticalXList.get(0),
                priceImgBot + volRect.height() + dp2px(2),
                strokePaint);

        String ma5Str = STR_MA5 + setPrecision(lastKData.getVolumeMa5(), 2);
        Rect volMa5Rect = new Rect();
        resetStrokePaint(priceMa5Col, volumeTextSize);
        strokePaint.getTextBounds(ma5Str, 0, ma5Str.length(), volMa5Rect);
        canvas.drawText(ma5Str,
                verticalXList.get(0) + volRect.width() + dp2px(10),
                priceImgBot + volRect.height() + dp2px(2),
                strokePaint);

        String ma10Str = STR_MA10 + setPrecision(lastKData.getVolumeMa10(), 2);
        resetStrokePaint(priceMa10Col, volumeTextSize);
        canvas.drawText(ma10Str,
                verticalXList.get(0) + volMa5Rect.width() + volRect.width() + dp2px(10) * 2,
                priceImgBot + volRect.height() + dp2px(2),
                strokePaint);

        String titleStr = "";
        String firstStr = "";
        String secondStr = "";
        String thirdStr = "";
        if (isShowDeputy && deputyImgType == DEPUTY_IMG_MACD) {
            titleStr = STR_MACD_TITLE;
            firstStr = STR_MACD + setPrecision(lastKData.getMacd(), 2);
            secondStr = STR_DIF + setPrecision(lastKData.getDif(), 2);
            thirdStr = STR_DEA + setPrecision(lastKData.getDea(), 2);

        } else if (isShowDeputy && deputyImgType == DEPUTY_IMG_KDJ) {
            titleStr = STR_KDJ_TITLE;
            firstStr = STR_K + setPrecision(lastKData.getK(), 2);
            secondStr = STR_D + setPrecision(lastKData.getD(), 2);
            thirdStr = STR_J + setPrecision(lastKData.getJ(), 2);

        } else if (isShowDeputy && deputyImgType == DEPUTY_IMG_RSI) {
            titleStr = STR_RSI_TITLE;
            firstStr = STR_RS1 + setPrecision(lastKData.getRs1(), 2);
            secondStr = STR_RS2 + setPrecision(lastKData.getRs2(), 2);
            thirdStr = STR_RS3 + setPrecision(lastKData.getRs3(), 2);
        }

        Rect titleRect = new Rect();
        resetStrokePaint(volumeTextCol, volumeTextSize);
        strokePaint.getTextBounds(titleStr, 0, titleStr.length(), titleRect);
        canvas.drawText(titleStr,
                verticalXList.get(0),
                horizontalYList.get(4) + titleRect.height(),
                strokePaint);

        resetStrokePaint(priceMa5Col, volumeTextSize);
        canvas.drawText(firstStr,
                verticalXList.get(0) + titleRect.width() + dp2px(10),
                horizontalYList.get(4) + titleRect.height(),
                strokePaint);
        float firstWidth = strokePaint.measureText(firstStr);

        resetStrokePaint(priceMa10Col, volumeTextSize);
        canvas.drawText(secondStr,
                verticalXList.get(0) + titleRect.width() + dp2px(20) + firstWidth,
                horizontalYList.get(4) + titleRect.height(),
                strokePaint);
        float secondWidth = strokePaint.measureText(secondStr);

        resetStrokePaint(priceMa30Col, volumeTextSize);
        canvas.drawText(thirdStr,
                verticalXList.get(0) + titleRect.width() + dp2px(30) + firstWidth + secondWidth,
                horizontalYList.get(4) + titleRect.height(),
                strokePaint);
    }

    //横坐标
    private void drawAbscissa(Canvas canvas) {
        resetStrokePaint(abscissaTextCol, abscissaTextSize);
        for (int i = 0; i < verticalXList.size(); i++) {
            if (i == 0 && viewDataList.get(0).getLeftX() <= verticalXList.get(0) + avgPriceRectWidth / 2
                    && viewDataList.get(0).getRightX() > verticalXList.get(0)) {
                canvas.drawText(formatDate(viewDataList.get(0).getTime()),
                        leftStart + dp2px(6),
                        bottomEnd - dp2px(7),
                        strokePaint);

            } else if (i == verticalXList.size() - 1) {
                String dateStr = formatDate(viewDataList.get(viewDataList.size() - 1).getTime());
                canvas.drawText(dateStr,
                        rightEnd - dp2px(41) - strokePaint.measureText(dateStr),
                        bottomEnd - dp2px(7),
                        strokePaint);
            } else {
                for (KData data : viewDataList) {
                    if (data.getLeftX() <= verticalXList.get(i) && data.getRightX() >= verticalXList.get(i)) {
                        String dateStr = formatDate(data.getTime());
                        canvas.drawText(dateStr,
                                verticalXList.get(i) - strokePaint.measureText(dateStr) / 2,
                                bottomEnd - dp2px(7),
                                strokePaint);
                        break;
                    }
                }
            }
        }
    }

    //纵坐标
    private void drawOrdinate(@NonNull Canvas canvas) {
        Rect rect = new Rect();
        resetStrokePaint(ordinateTextCol, ordinateTextSize);
        //最高价
        strokePaint.getTextBounds(formatKDataNum(topPrice), 0, formatKDataNum(topPrice).length(), rect);
        canvas.drawText(formatKDataNum(topPrice),
                verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                horizontalYList.get(0) + rect.height() + dp2px(2),
                strokePaint);

        //最低价
        strokePaint.getTextBounds(formatKDataNum(botPrice), 0, formatKDataNum(botPrice).length(), rect);
        canvas.drawText(formatKDataNum(botPrice),
                verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                priceImgBot - dp2px(2),
                strokePaint);

        if (!isShowDeputy) {
            double avgPrice = (topPrice - botPrice) / 4;
            for (int i = 0; i < 3; i++) {
                canvas.drawText(formatKDataNum(topPrice - avgPrice * (i + 1)),
                        verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                        horizontalYList.get(i + 1) + rect.height() / 2f,
                        strokePaint);
            }

        } else {
            double avgPrice = (topPrice - botPrice) / 3;
            for (int i = 0; i < 2; i++) {
                canvas.drawText(formatKDataNum(topPrice - avgPrice * (i + 1)),
                        verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                        horizontalYList.get(i + 1) + rect.height() / 2f,
                        strokePaint);
            }

            String topDeputy = "";
            String botDeputy = "";
            String centerDeputy = "";
            if (deputyImgType == DEPUTY_IMG_MACD) {
                if (mMaxMacd > 0 && mMinMacd < 0) {
                    topDeputy = setPrecision(mMaxMacd, 2);
                    botDeputy = setPrecision(mMinMacd, 2);
                    centerDeputy = setPrecision((mMaxMacd - mMinMacd) / 2, 2);
                } else if (mMaxMacd <= 0) {
                    topDeputy = "0";
                    botDeputy = setPrecision(mMinMacd, 2);
                    centerDeputy = setPrecision((mMinMacd - mMaxMacd) / 2, 2);
                } else if (mMinMacd >= 0) {
                    topDeputy = setPrecision(mMaxMacd, 2);
                    botDeputy = "0";
                    centerDeputy = setPrecision((mMaxMacd - mMinMacd) / 2, 2);
                }

            } else if (deputyImgType == DEPUTY_IMG_KDJ) {
                topDeputy = setPrecision(mMaxK, 2);
                centerDeputy = setPrecision(mMaxK / 2, 2);
                botDeputy = "0";

            } else if (deputyImgType == DEPUTY_IMG_RSI) {
                topDeputy = "100";
                centerDeputy = "50";
                botDeputy = "0";
            }

            canvas.drawText(topDeputy,
                    verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                    horizontalYList.get(horizontalYList.size() - 2) + rect.height() + dp2px(2),
                    strokePaint);

            canvas.drawText(centerDeputy,
                    verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                    horizontalYList.get(horizontalYList.size() - 1) - verticalSpace / 2 + rect.height() / 2f,
                    strokePaint);

            canvas.drawText(botDeputy,
                    verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                    horizontalYList.get(horizontalYList.size() - 1) - dp2px(2),
                    strokePaint);
        }

        //最高量
        strokePaint.getTextBounds(formatVolNum(maxVolume), 0, formatVolNum(maxVolume).length(), rect);
        canvas.drawText(formatVolNum(maxVolume),
                verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                priceImgBot + rect.height() + dp2px(2),
                strokePaint);

        //最高量/2
        canvas.drawText(formatVolNum(maxVolume / 2),
                verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                volumeImgBot - verticalSpace / 2 + rect.height() / 2f,
                strokePaint);

        //数量 0
        canvas.drawText("0",
                verticalXList.get(verticalXList.size() - 1) + dp2px(4),
                volumeImgBot - dp2px(2),
                strokePaint);

    }

    //分时图
    private void drawInstant(Canvas canvas){
        if (canvas == null || curvePath == null || viewDataList == null || strokePaint == null){
            return;
        }
        curvePath.reset();
        instantPath.reset();
        float startX = (float) viewDataList.get(0).getCenterX();
        float startY = (float) viewDataList.get(0).getCloseY();
        curvePath.moveTo(startX, startY);
        instantPath.moveTo(startX, startY);
        int viewDataSize = viewDataList.size();
        for (int i = 1; i < viewDataSize; i++) {
            KData viewData = viewDataList.get(i);
            curvePath.lineTo((float) viewData.getCenterX(), (float) viewData.getCloseY());
            instantPath.lineTo((float) viewData.getCenterX(), (float) viewData.getCloseY());
            if (i == viewDataSize - 1){
                instantPath.lineTo(verticalXList.get(verticalXList.size() - 1), (float) viewData.getCloseY());
            }
        }
        resetStrokePaint(0xff1aa3f0, 0);
        canvas.drawPath(curvePath, strokePaint);

        instantPath.lineTo(verticalXList.get(verticalXList.size() - 1), horizontalYList.get(horizontalYList.size() - 2));
        instantPath.lineTo(startX, horizontalYList.get(horizontalYList.size() - 2));
        instantPath.close();
        LinearGradient gradient = new LinearGradient(0, (int)mMaxPriceY, 0,
                horizontalYList.get(horizontalYList.size() - 2), 0x801aa3f0, 0x0d1aa3f0, Shader.TileMode.CLAMP);
        instantFillPaint.setShader(gradient);
        canvas.drawPath(instantPath, instantFillPaint);
    }

    private int dp2px(float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    private int sp2px(float spValue) {
        final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }

    private String formatDate(long timeStamp) {
        if (timeStamp <= 0) {
            return "";
        }
        SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm");
        return format.format(new Date(timeStamp));
    }

    /**
     * 设置小数位精度
     *
     * @param scale 保留几位小数
     */
    private String setPrecision(Double num, int scale) {
        BigDecimal bigDecimal = new BigDecimal(num);
        return bigDecimal.setScale(scale, BigDecimal.ROUND_DOWN).toPlainString();
    }

    /**
     * 按量级格式化价格
     */
    private String formatKDataNum(double num) {
        if (num < 1) {
            return setPrecision(num, 6);
        } else if (num < 10) {
            return setPrecision(num, 4);
        } else if (num < 100) {
            return setPrecision(num, 3);
        } else if (num < 10000) {
            return setPrecision(num, 2);
        } else if (num < 100000000) {
            return setPrecision(num / 10000, 2) + "万";
        } else {
            return setPrecision(num / 100000000, 2) + "亿";
        }
    }

    /**
     * 按量级格式化数量
     */
    private String formatVolNum(double num) {
        if (num < 10000) {
            return setPrecision(num, 2);
        } else if (num < 100000000) {
            return setPrecision(num / 10000, 2) + "万";
        } else {
            return setPrecision(num / 100000000, 2) + "亿";
        }
    }

    private void resetStrokePaint(int colorId, int textSize) {
        strokePaint.setColor(colorId);
        strokePaint.setTextSize(sp2px(textSize));
    }

}


================================================
FILE: app/src/main/java/com/example/admin/klineview/kline/Pointer.java
================================================
package com.example.admin.klineview.kline;

/**
 * Created by xiesuichao on 2018/7/3.
 */

public class Pointer {
    
    private float x;
    private float y;

    public Pointer() {
    }

    public Pointer(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Pointer{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}


================================================
FILE: app/src/main/java/com/example/admin/klineview/kline/QuotaThread.java
================================================
package com.example.admin.klineview.kline;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.annotation.NonNull;

import java.util.List;

/**
 * 子线程计算五项数据
 * Created by xiesuichao on 2018/8/18.
 */

public class QuotaThread extends HandlerThread implements Handler.Callback {

    public static final int HANDLER_QUOTA_LIST = 100;
    public static final int HANDLER_QUOTA_SINGLE = 101;
    private Handler uiHandler;
    private Handler workHandler;

    public QuotaThread(String name, int priority) {
        super(name, priority);
    }

    public void setUIHandler(Handler uiHandler) {
        this.uiHandler = uiHandler;
    }

    public void quotaListCalculate(List<KData> dataList) {
        if (workHandler == null) {
            return;
        }
        Message message = Message.obtain(null, HANDLER_QUOTA_LIST);
        message.obj = dataList;
        workHandler.sendMessage(message);
    }

    public void quotaSingleCalculate(List<KData> dataList){
        if (workHandler == null) {
            return;
        }
        Message message = Message.obtain(null, HANDLER_QUOTA_SINGLE);
        message.obj = dataList;
        workHandler.sendMessage(message);
    }

    private void calculateKDataQuota(List<KData> dataList, boolean isEndData) {
        QuotaUtil.initEma(dataList, isEndData);
        QuotaUtil.initBoll(dataList, isEndData);
        QuotaUtil.initMACD(dataList, isEndData);
        QuotaUtil.initKDJ(dataList, isEndData);
        QuotaUtil.initRSI(dataList, isEndData);
        QuotaUtil.initMa(dataList, isEndData);
    }

    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        this.workHandler = new Handler(getLooper(), this);
    }

    @Override
    public boolean handleMessage(@NonNull Message msg) {
        if (msg.what == HANDLER_QUOTA_LIST) {
            handleData(msg, HANDLER_QUOTA_LIST, false);
        }else if (msg.what == HANDLER_QUOTA_SINGLE){
            handleData(msg, HANDLER_QUOTA_SINGLE, true);
        }
        return true;
    }

    private void handleData(Message msg, int whatId, boolean isEndData){
        if (msg == null || uiHandler == null) {
            return;
        }
        try {
            List<KData> dataList = (List<KData>) msg.obj;
            calculateKDataQuota(dataList, isEndData);
            Message message = Message.obtain(null, whatId);
            uiHandler.sendMessage(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}


================================================
FILE: app/src/main/java/com/example/admin/klineview/kline/QuotaUtil.java
================================================
package com.example.admin.klineview.kline;

import android.graphics.Path;

import java.util.ArrayList;
import java.util.List;

/**
 * 五项数据计算公式
 * Created by xiesuichao on 2018/8/12.
 */

public class QuotaUtil {

    private static final int QUOTA_DAY5 = 5;
    private static final int QUOTA_DAY10 = 10;
    private static final int QUOTA_DAY30 = 30;
    private static final float BEZIER_RATIO = 0.16f;
    private static List<KData> cacheList = new ArrayList<>();

    /**
     * 初始化 MA5,MA10, MA30
     *
     * @param isEndData 是否是添加到list末尾的数据
     */
    public static void initMa(List<KData> dataList, boolean isEndData) {
        if (dataList == null || dataList.isEmpty() || cacheList == null) {
            return;
        }
        cacheList.clear();
        cacheList.addAll(dataList);
        int size = dataList.size();
        for (int i = 0; i < size; i++) {
            if (i + QUOTA_DAY5 <= size) {
                //priceMa5
                dataList.get(i + QUOTA_DAY5 - 1).setPriceMa5(getPriceMa(cacheList.subList(i, i + QUOTA_DAY5)));
                //volumeMa5
                dataList.get(i + QUOTA_DAY5 - 1).setVolumeMa5(getVolumeMa(cacheList.subList(i, i + QUOTA_DAY5)));
            }
            if (i + QUOTA_DAY10 <= size) {
                //priceMa10
                dataList.get(i + QUOTA_DAY10 - 1).setPriceMa10(getPriceMa(cacheList.subList(i, i + QUOTA_DAY10)));
                //volumeMa10
                dataList.get(i + QUOTA_DAY10 - 1).setVolumeMa10(getVolumeMa(cacheList.subList(i, i + QUOTA_DAY10)));
            }
            if (i + QUOTA_DAY30 <= size) {
                //priceMa30
                if (dataList.get(i + QUOTA_DAY30 - 1).getPriceMa30() != 0 && !isEndData) {
                    break;
                } else {
                    dataList.get(i + QUOTA_DAY30 - 1).setPriceMa30(getPriceMa(cacheList.subList(i, i + QUOTA_DAY30)));
                }
            }
            dataList.get(i).setInitFinish(true);
        }
    }

    private static double getVolumeMa(List<KData> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            return -1;
        }
        double sum = 0;
        for (KData data : dataList) {
            sum += data.getVolume();
        }
        return sum / dataList.size();
    }

    private static double getPriceMa(List<KData> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            return -1;
        }
        double sum = 0;
        for (KData data : dataList) {
            sum += data.getClosePrice();
        }
        return sum / dataList.size();
    }

    /**
     * 初始化 EMA5, EMA10, EMA30
     */
    public static void initEma(List<KData> dataList, boolean isEndData) {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }
        double lastEma5 = dataList.get(0).getClosePrice();
        double lastEma10 = dataList.get(0).getClosePrice();
        double lastEma30 = dataList.get(0).getClosePrice();
        dataList.get(0).setEma5(lastEma5);
        dataList.get(0).setEma10(lastEma10);
        dataList.get(0).setEma30(lastEma30);

        int size = dataList.size();
        for (int i = 1; i < size; i++) {
            if (dataList.get(i).getEma30() != 0 && !isEndData) {
                break;
            }
            double currentEma5 = 2 * (dataList.get(i).getClosePrice() - lastEma5) / (QUOTA_DAY5 + 1) + lastEma5;
            double currentEma10 = 2 * (dataList.get(i).getClosePrice() - lastEma10) / (QUOTA_DAY10 + 1) + lastEma10;
            double currentEma30 = 2 * (dataList.get(i).getClosePrice() - lastEma30) / (QUOTA_DAY30 + 1) + lastEma30;

            dataList.get(i).setEma5(currentEma5);
            dataList.get(i).setEma10(currentEma10);
            dataList.get(i).setEma30(currentEma30);

            lastEma5 = currentEma5;
            lastEma10 = currentEma10;
            lastEma30 = currentEma30;
        }
    }

    /**
     * BOLL(n)计算公式:
     * MA=n日内的收盘价之和÷n。
     * MD=(n-1)日的平方根(C-MA)的两次方之和除以n
     * MB=(n-1)日的MA
     * UP=MB+k×MD
     * DN=MB-k×MD
     * K为参数,可根据股票的特性来做相应的调整,一般默认为2
     *
     * @param dataList 数据集合
     * @param period   周期,一般为26
     * @param k        参数,可根据股票的特性来做相应的调整,一般默认为2
     */
    public static void initBOLL(List<KData> dataList, int period, int k, boolean isEndData) {
        if (dataList == null || dataList.isEmpty()
                || period < 0 || period > dataList.size() - 1) {
            return;
        }
        double mb;//上轨线
        double up;//中轨线
        double dn;//下轨线
        //n日
        double sum = 0;
        //n-1日
        double sum2 = 0;
        //n日MA
        double ma;
        //n-1日MA
        double ma2;
        double md;

        int size = dataList.size();
        for (int i = 0; i < size; i++) {
            if (dataList.get(i).getBollMb() != 0 && !isEndData) {
                break;
            }
            KData quotes = dataList.get(i);
            sum += quotes.getClosePrice();
            sum2 += quotes.getClosePrice();
            if (i > period - 1)
                sum -= dataList.get(i - period).getClosePrice();
            if (i > period - 2)
                sum2 -= dataList.get(i - period + 1).getClosePrice();

            //这个范围不计算,在View上的反应就是不显示这个范围的boll线
            if (i < period - 1)
                continue;

            ma = sum / period;
            ma2 = sum2 / (period - 1);
            md = 0;
            for (int j = i + 1 - period; j <= i; j++) {
                //n-1日
                md += Math.pow(dataList.get(j).getClosePrice() - ma, 2);
            }
            md = Math.sqrt(md / period);
            //(n-1)日的MA
            mb = ma2;
            up = mb + k * md;
            dn = mb - k * md;

            quotes.setBollMb(mb);
            quotes.setBollUp(up);
            quotes.setBollDn(dn);
        }
    }

    public static void initBoll(List<KData> dataList, boolean isEndData) {
        initBOLL(dataList, 26, 2, isEndData);
    }

    /**
     * MACD
     *
     * @param dataList
     * @param fastPeriod   日快线移动平均,标准为12,按照标准即可
     * @param slowPeriod   日慢线移动平均,标准为26,可理解为天数
     * @param signalPeriod 日移动平均,标准为9,按照标准即可
     */
    public static void initMACD(List<KData> dataList, int fastPeriod, int slowPeriod, int signalPeriod, boolean isEndData) {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }
        double preEma_12 = 0;
        double preEma_26 = 0;
        double preDEA = 0;

        double ema_12 = 0;
        double ema_26 = 0;

        double dea = 0;
        double dif = 0;
        double macd = 0;
        int size = dataList.size();
        for (int i = 0; i < size; i++) {
            if (dataList.get(i).getMacd() != 0 && !isEndData) {
                break;
            }
            ema_12 = preEma_12 * (fastPeriod - 1) / (fastPeriod + 1) + dataList.get(i).getClosePrice() * 2 / (fastPeriod + 1);
            ema_26 = preEma_26 * (slowPeriod - 1) / (slowPeriod + 1) + dataList.get(i).getClosePrice() * 2 / (slowPeriod + 1);
            dif = ema_12 - ema_26;
            dea = preDEA * (signalPeriod - 1) / (signalPeriod + 1) + dif * 2 / (signalPeriod + 1);
            macd = 2 * (dif - dea);

            preEma_12 = ema_12;
            preEma_26 = ema_26;
            preDEA = dea;

            dataList.get(i).setMacd(macd);
            dataList.get(i).setDea(dea);
            dataList.get(i).setDif(dif);
        }
    }

    public static void initMACD(List<KData> dataList, boolean isEndData) {
        initMACD(dataList, 12, 26, 9, isEndData);
    }

    /**
     * KDJ
     *
     * @param n1 标准值9
     * @param n2 标准值3
     * @param n3 标准值3
     */
    public static void initKDJ(List<KData> dataList, int n1, int n2, int n3, boolean isEndData) {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }
        //K值
        double[] mK = new double[dataList.size()];
        //D值
        double[] mD = new double[dataList.size()];
        //J值
        double jValue;
        double highValue = dataList.get(0).getMaxPrice();
        double lowValue = dataList.get(0).getMinPrice();
        //记录最高价位置
        int highPosition = 0;
        //记录最低价位置
        int lowPosition = 0;
        double rSV = 0.0;
        int size = dataList.size();
        for (int i = 0; i < size; i++) {
            if (dataList.get(i).getK() != 0 && !isEndData) {
                break;
            }
            if (i == 0) {
//                mK[0] = 33.33;
//                mD[0] = 11.11;
//                jValue = 77.78;
                mK[0] = 50;
                mD[0] = 50;
                jValue = 50;
            } else {
                //对最高价和最低价赋值
                if (highValue <= dataList.get(i).getMaxPrice()) {
                    highValue = dataList.get(i).getMaxPrice();
                    highPosition = i;
                }
                if (lowValue >= dataList.get(i).getMinPrice()) {
                    lowValue = dataList.get(i).getMinPrice();
                    lowPosition = i;
                }
                if (i > (n1 - 1)) {
                    //判断存储的最高价是否高于当前最高价
                    if (highValue > dataList.get(i).getMaxPrice()) {
                        //判断最高价是不是在最近n天内,若不在最近n天内,则从最近n天找出最高价并赋值
                        if (highPosition < (i - (n1 - 1))) {
                            highValue = dataList.get(i - (n1 - 1)).getMaxPrice();
                            for (int j = (i - (n1 - 2)); j <= i; j++) {
                                if (highValue <= dataList.get(j).getMaxPrice()) {
                                    highValue = dataList.get(j).getMaxPrice();
                                    highPosition = j;
                                }
                            }
                        }
                    }
                    if ((lowValue < dataList.get(i).getMinPrice())) {
                        if (lowPosition < i - (n1 - 1)) {
                            lowValue = dataList.get(i).getMinPrice();
                            for (int k = i - (n1 - 2); k <= i; k++) {
                                if (lowValue >= dataList.get(k).getMinPrice()) {
                                    lowValue = dataList.get(k).getMinPrice();
                                    lowPosition = k;
                                }
                            }
                        }
                    }
                }
                if (highValue != lowValue) {
                    rSV = (dataList.get(i).getClosePrice() - lowValue) / (highValue - lowValue) * 100;
                }
                mK[i] = (mK[i - 1] * (n2 - 1) + rSV) / n2;
                mD[i] = (mD[i - 1] * (n3 - 1) + mK[i]) / n3;
                jValue = 3 * mK[i] - 2 * mD[i];
            }
            dataList.get(i).setK(mK[i]);
            dataList.get(i).setD(mD[i]);
            dataList.get(i).setJ(jValue);
        }
    }

    public static void initKDJ(List<KData> dataList, boolean isEndData) {
        initKDJ(dataList, 9, 3, 3, isEndData);
    }

    /**
     * 初始化RSI
     *
     * @param period1 标准值6
     * @param period2 标准值12
     * @param period3 标准值24
     */
    public static void initRSI(List<KData> dataList, int period1, int period2, int period3, boolean isEndData) {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }
        double upRateSum;
        int upRateCount;
        double dnRateSum;
        int dnRateCount;
        int size = dataList.size();
        for (int i = 0; i < size; i++) {
            if (dataList.get(i).getRs3() != 0 && !isEndData) {
                break;
            }
            upRateSum = 0;
            upRateCount = 0;
            dnRateSum = 0;
            dnRateCount = 0;
            if (i >= period1 - 1) {
                for (int x = i; x >= i + 1 - period1; x--) {
                    if (dataList.get(x).getUpDnRate() >= 0) {
                        upRateSum += dataList.get(x).getUpDnRate();
                        upRateCount++;
                    } else {
                        dnRateSum += dataList.get(x).getUpDnRate();
                        dnRateCount++;
                    }
                }
                double avgUpRate = 0;
                double avgDnRate = 0;
                if (upRateSum > 0) {
                    avgUpRate = upRateSum / upRateCount;
                }
                if (dnRateSum < 0) {
                    avgDnRate = dnRateSum / dnRateCount;
                }
                dataList.get(i).setRs1(avgUpRate / (avgUpRate + Math.abs(avgDnRate)) * 100);
            }

            upRateSum = 0;
            upRateCount = 0;
            dnRateSum = 0;
            dnRateCount = 0;
            if (i >= period2 - 1) {
                for (int x = i; x >= i + 1 - period2; x--) {
                    if (dataList.get(x).getUpDnRate() >= 0) {
                        upRateSum += dataList.get(x).getUpDnRate();
                        upRateCount++;
                    } else {
                        dnRateSum += dataList.get(x).getUpDnRate();
                        dnRateCount++;
                    }
                }
                double avgUpRate = 0;
                double avgDnRate = 0;
                if (upRateSum > 0) {
                    avgUpRate = upRateSum / upRateCount;
                }
                if (dnRateSum < 0) {
                    avgDnRate = dnRateSum / dnRateCount;
                }
                dataList.get(i).setRs2(avgUpRate / (avgUpRate + Math.abs(avgDnRate)) * 100);
            }

            upRateSum = 0;
            upRateCount = 0;
            dnRateSum = 0;
            dnRateCount = 0;
            if (i >= period3 - 1) {
                for (int x = i; x >= i + 1 - period3; x--) {
                    if (dataList.get(x).getUpDnRate() >= 0) {
                        upRateSum += dataList.get(x).getUpDnRate();
                        upRateCount++;
                    } else {
                        dnRateSum += dataList.get(x).getUpDnRate();
                        dnRateCount++;
                    }
                }
                double avgUpRate = 0;
                double avgDnRate = 0;
                if (upRateSum > 0) {
                    avgUpRate = upRateSum / upRateCount;
                }
                if (dnRateSum < 0) {
                    avgDnRate = dnRateSum / dnRateCount;
                }
                dataList.get(i).setRs3(avgUpRate / (avgUpRate + Math.abs(avgDnRate)) * 100);
            }
        }
    }

    public static void initRSI(List<KData> dataList, boolean isEndData) {
        initRSI(dataList, 6, 12, 24, isEndData);
    }

    /**
     * 三阶贝塞尔曲线控制点
     *
     * @param pointList
     * @param path
     */
    public static void setBezierPath(List<Pointer> pointList, Path path) {
        if (path == null){
            return;
        }
        path.reset();
        if (pointList == null || pointList.isEmpty()) {
            return;
        }
        path.moveTo(pointList.get(0).getX(), pointList.get(0).getY());
        Pointer leftControlPointer = new Pointer();
        Pointer rightControlPointer = new Pointer();

        int size = pointList.size();
        for (int i = 0; i < size; i++) {
            if (i == 0 && size > 2) {
                leftControlPointer.setX(pointList.get(i).getX() + BEZIER_RATIO * (pointList.get(i + 1).getX()
                        - pointList.get(0).getX()));
                leftControlPointer.setY(pointList.get(i).getY() + BEZIER_RATIO * (pointList.get(i + 1).getY()
                        - pointList.get(0).getY()));
                rightControlPointer.setX(pointList.get(i + 1).getX() - BEZIER_RATIO * (pointList.get(i + 2).getX()
                        - pointList.get(i).getX()));
                rightControlPointer.setY(pointList.get(i + 1).getY() - BEZIER_RATIO * (pointList.get(i + 2).getY()
                        - pointList.get(i).getY()));

            } else if (i == size - 2 && i > 1) {
                leftControlPointer.setX(pointList.get(i).getX() + BEZIER_RATIO * (pointList.get(i + 1).getX()
                        - pointList.get(i - 1).getX()));
                leftControlPointer.setY(pointList.get(i).getY() + BEZIER_RATIO * (pointList.get(i + 1).getY()
                        - pointList.get(i - 1).getY()));
                rightControlPointer.setX(pointList.get(i + 1).getX() - BEZIER_RATIO * (pointList.get(i + 1).getX()
                        - pointList.get(i).getX()));
                rightControlPointer.setY(pointList.get(i + 1).getY() - BEZIER_RATIO * (pointList.get(i + 1).getY()
                        - pointList.get(i).getY()));

            } else if (i > 0 && i < size - 2) {
                leftControlPointer.setX(pointList.get(i).getX() + BEZIER_RATIO * (pointList.get(i + 1).getX()
                        - pointList.get(i - 1).getX()));
                leftControlPointer.setY(pointList.get(i).getY() + BEZIER_RATIO * (pointList.get(i + 1).getY()
                        - pointList.get(i - 1).getY()));
                rightControlPointer.setX(pointList.get(i + 1).getX() - BEZIER_RATIO * (pointList.get(i + 2).getX()
                        - pointList.get(i).getX()));
                rightControlPointer.setY(pointList.get(i + 1).getY() - BEZIER_RATIO * (pointList.get(i + 2).getY()
                        - pointList.get(i).getY()));
            }

            if (i < size - 1) {
                path.cubicTo(leftControlPointer.getX(), leftControlPointer.getY(),
                        rightControlPointer.getX(), rightControlPointer.getY(),
                        pointList.get(i + 1).getX(), pointList.get(i + 1).getY());
            }
        }
    }

    public static void setLinePath(List<Pointer> pointerList, Path path) {
        if (path == null){
            return;
        }
        if (pointerList == null || pointerList.size() <= 1) {
            return;
        }
        path.moveTo(pointerList.get(0).getX(), pointerList.get(0).getY());
        int size = pointerList.size();
        for (int i = 1; i < size; i++) {
            path.lineTo(pointerList.get(i).getX(), pointerList.get(i).getY());
        }

    }


}


================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?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>


================================================
FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
<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>


================================================
FILE: app/src/main/res/layout/activity_depth.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#ffffff">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.example.admin.klineview.depth.DepthView
            android:id="@+id/dv_depth"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            app:dvBuyLineStrokeWidth="1"
            app:dvSellLineStrokeWidth="1"
            app:dvDetailLineWidth="0"
            />

        <Button
            android:id="@+id/btn_depth_reset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="reset"
            />

    </LinearLayout>

</android.support.v4.widget.NestedScrollView>

================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context="com.example.admin.klineview.MainActivity"
    android:background="#ffffff"
    android:overScrollMode="never">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.example.admin.klineview.kline.KLineView
            android:id="@+id/klv_main"
            android:layout_width="match_parent"
            android:layout_height="380dp"
            />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:background="#294058"
            />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_ma"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="MA"
                />

            <Button
                android:id="@+id/btn_ema"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="EMA"
                />

            <Button
                android:id="@+id/btn_boll"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="BOLL"
                />

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_deputy"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="显示副图"
                />

            <Button
                android:id="@+id/btn_macd"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="MACD"
                />

            <Button
                android:id="@+id/btn_kdj"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="KDJ"
                />

            <Button
                android:id="@+id/btn_rsi"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="RSI"
                />

        </LinearLayout>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btn_kline_reset"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="reset"
                />

            <Button
                android:id="@+id/btn_instant"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="分时图"
                />

        </LinearLayout>



        <Button
            android:id="@+id/btn_depth_activity"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="深度图"
            />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="#eeeeee"
            />


    </LinearLayout>

</android.support.v4.widget.NestedScrollView>


================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?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>

================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?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>

================================================
FILE: app/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!--K线控件-->
    <declare-styleable name="KLineView">
        <!--刻度线颜色-->
        <attr name="klTickMarkLineCol" format="color"/>
        <!--横坐标字体颜色-->
        <attr name="klAbscissaTextCol" format="color"/>
        <!--横坐标字体大小-->
        <attr name="klAbscissaTextSize" format="integer"/>
        <!--纵坐标字体颜色-->
        <attr name="klOrdinateTextCol" format="color"/>
        <!--纵坐标字体大小-->
        <attr name="klOrdinateTextSize" format="integer"/>
        <!--顶部MA字体大小-->
        <attr name="klTopMaTextSize" format="integer"/>
        <!--涨价方块颜色-->
        <attr name="klPriceIncreaseCol" format="color"/>
        <!--跌价方块颜色-->
        <attr name="klPriceFallCol" format="color"/>
        <!--价格MA5,MA10,MA30颜色-->
        <attr name="klPriceMa5LineCol" format="color"/>
        <attr name="klPriceMa10LineCol" format="color"/>
        <attr name="klPriceMa30LineCol" format="color"/>
        <!--最高价标签颜色-->
        <attr name="klPriceMaxLabelCol" format="color"/>
        <!--最高价标签字体颜色-->
        <attr name="klPriceMaxLabelTextCol" format="color"/>
        <!--最高价标签字体大小-->
        <attr name="klPriceMaxLabelTextSize" format="integer"/>
        <!--最低价标签颜色-->
        <attr name="klPriceMinLabelCol" format="color"/>
        <!--最低价标签字体颜色-->
        <attr name="klPriceMinLabelTextCol" format="color"/>
        <!--最低价标签字体大小-->
        <attr name="klPriceMinLabelTextSize" format="integer"/>
        <!--数量字体,MA5,MA10颜色-->
        <attr name="klVolumeTextCol" format="color"/>
        <!--数量字体大小-->
        <attr name="klVolumeTextSize" format="integer"/>
        <attr name="klVolumeMa5LineCol" format="color"/>
        <attr name="klVolumeMa10LineCol" format="color"/>
        <!--MACD字体颜色(同KDJ字体颜色)-->
        <attr name="klMacdTextCol" format="color"/>
        <!--MACD正值方块颜色-->
        <attr name="klMacdPositiveCol" format="color"/>
        <!--MACD负值方块颜色-->
        <attr name="klMacdNegativeCol" format="color"/>
        <!--DIF,DEA颜色-->
        <attr name="klDifLineCol" format="color"/>
        <attr name="klDeaLineCol" format="color"/>
        <!--K,D,J颜色-->
        <attr name="klKLineCol" format="color"/>
        <attr name="klDLineCol" format="color"/>
        <attr name="klJLineCol" format="color"/>
        <!--十字线-->
        <attr name="klCrossHairCol" format="color"/>
        <!--十字线右侧标签-->
        <attr name="klCrossHairRightLabelCol" format="color"/>
        <!--十字线右侧标签字体颜色-->
        <attr name="klCrossHairRightLabelTextCol" format="color"/>
        <!--十字线右侧标签字体大小-->
        <attr name="klCrossHairRightLabelTextSize" format="integer"/>
        <!--十字线底部标签-->
        <attr name="klCrossHairBottomLabelCol" format="color"/>
        <!--十字线底部标签字体颜色-->
        <attr name="klCrossHairBottomLabelTextCol" format="color"/>
        <!--十字线底部标签字体大小-->
        <attr name="klCrossHairBottomLabelTextSize" format="integer"/>
        <!--数据详情边框颜色-->
        <attr name="klDetailFrameCol" format="color"/>
        <!--数据详情字体颜色-->
        <attr name="klDetailTextCol" format="color"/>
        <!--数据详情字体大小-->
        <attr name="klDetailTextSize" format="integer"/>
        <!--数据详情背景颜色-->
        <attr name="klDetailBgCol" format="color"/>

    </declare-styleable>

    <declare-styleable name="DepthView">
        <!--买方线条颜色、线条宽度、背景颜色-->
        <attr name="dvBuyLineCol" format="color"/>
        <attr name="dvBuyLineStrokeWidth" format="integer"/>
        <attr name="dvBuyBgCol" format="color"/>
        <!--卖方线条颜色、背景颜色-->
        <attr name="dvSellLineCol" format="color"/>
        <attr name="dvSellLineStrokeWidth" format="integer"/>
        <attr name="dvSellBgCol" format="color"/>
        <!--纵坐标颜色、字体大小-->
        <attr name="dvOrdinateCol" format="color"/>
        <attr name="dvOrdinateTextSize" format="integer"/>
        <!--纵坐标个数-->
        <attr name="dvOrdinateNum" format="integer"/>
        <!--横坐标颜色、字体大小-->
        <attr name="dvAbscissaCol" format="color"/>
        <attr name="dvAbscissaTextSize" format="integer"/>
        <!--详情背景颜色、字体颜色、字体大小、游标线颜色-->
        <attr name="dvDetailBgCol" format="color"/>
        <attr name="dvDetailTextCol" format="color"/>
        <attr name="dvDetailTextSize" format="integer"/>
        <attr name="dvDetailLineCol" format="color"/>
        <!--游标线宽度、交界圆点半径、价格文字说明、数量文字说明-->
        <attr name="dvDetailLineWidth" format="float" />
        <attr name="dvDetailPointRadius" format="integer"/>
        <attr name="dvDetailPriceTitle" format="string"/>
        <attr name="dvDetailVolumeTitle" format="string"/>

    </declare-styleable>

</resources>

================================================
FILE: app/src/main/res/values/colors.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="red_main">#ef462d</color>
    <color name="red_pressed">#ce2b13</color>

    <color name="white">#ffffff</color>

    <color name="gray_f5">#f5f5f5</color>
    <color name="gray_66">#666666</color>
    <color name="gray_99">#999999</color>

    <color name="text_blue">#308BE9</color>
    <color name="child_line_gray_ee">#eeeeee</color>
    <color name="child_line_gray_dd">#dddddd</color>
    <color name="comment
Download .txt
gitextract_4lzolz3h/

├── .gitignore
├── .idea/
│   ├── codeStyles/
│   │   └── Project.xml
│   ├── gradle.xml
│   ├── misc.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── README.md
├── apk/
│   └── app-debug.apk
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── example/
│       │               └── admin/
│       │                   └── klineview/
│       │                       └── ExampleInstrumentedTest.java
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── example/
│       │   │           └── admin/
│       │   │               └── klineview/
│       │   │                   ├── DepthActivity.java
│       │   │                   ├── MainActivity.java
│       │   │                   ├── Print.java
│       │   │                   ├── depth/
│       │   │                   │   ├── Depth.java
│       │   │                   │   └── DepthView.java
│       │   │                   └── kline/
│       │   │                       ├── KData.java
│       │   │                       ├── KLineView.java
│       │   │                       ├── Pointer.java
│       │   │                       ├── QuotaThread.java
│       │   │                       └── QuotaUtil.java
│       │   └── res/
│       │       ├── drawable/
│       │       │   └── ic_launcher_background.xml
│       │       ├── drawable-v24/
│       │       │   └── ic_launcher_foreground.xml
│       │       ├── layout/
│       │       │   ├── activity_depth.xml
│       │       │   └── activity_main.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       └── values/
│       │           ├── attrs.xml
│       │           ├── colors.xml
│       │           ├── strings.xml
│       │           └── styles.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── example/
│                       └── admin/
│                           └── klineview/
│                               └── ExampleUnitTest.java
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Download .txt
SYMBOL INDEX (243 symbols across 12 files)

FILE: app/src/androidTest/java/com/example/admin/klineview/ExampleInstrumentedTest.java
  class ExampleInstrumentedTest (line 17) | @RunWith(AndroidJUnit4.class)
    method useAppContext (line 19) | @Test

FILE: app/src/main/java/com/example/admin/klineview/DepthActivity.java
  class DepthActivity (line 22) | public class DepthActivity extends AppCompatActivity {
    method onCreate (line 28) | @Override
    method initView (line 39) | private void initView(){
    method initData (line 44) | private void initData(){
    method setListener (line 74) | private void setListener(){
    method getBuyDepthList (line 85) | private List<Depth> getBuyDepthList(){
    method getSellDepthList (line 96) | private List<Depth> getSellDepthList(){
    method onDestroy (line 106) | @Override

FILE: app/src/main/java/com/example/admin/klineview/MainActivity.java
  class MainActivity (line 22) | public class MainActivity extends AppCompatActivity implements View.OnCl...
    method onCreate (line 31) | @Override
    method initView (line 52) | private void initView() {
    method initData (line 66) | private void initData() {
    method setListener (line 98) | private void setListener() {
    method onClick (line 122) | @Override
    method getKDataList (line 181) | private List<KData> getKDataList(double num) {
    method getAddRandomDouble (line 227) | private double getAddRandomDouble() {
    method getSubRandomDouble (line 232) | private double getSubRandomDouble() {
    method dp2px (line 237) | private int dp2px(float dpValue) {
    method onDestroy (line 242) | @Override

FILE: app/src/main/java/com/example/admin/klineview/Print.java
  class Print (line 9) | public class Print {
    method log (line 14) | public static void log(Object obj) {
    method log (line 20) | public static void log(String title, Object obj) {
    method getCurrentClassName (line 26) | private static String getCurrentClassName() {

FILE: app/src/main/java/com/example/admin/klineview/depth/Depth.java
  class Depth (line 10) | public class Depth implements Comparable<Depth>{
    method Depth (line 19) | public Depth() {
    method Depth (line 22) | public Depth(double price, double volume, int tradeType) {
    method getPrice (line 28) | public double getPrice() {
    method setPrice (line 32) | public void setPrice(double price) {
    method getVolume (line 36) | public double getVolume() {
    method setVolume (line 40) | public void setVolume(double volume) {
    method getTradeType (line 44) | public int getTradeType() {
    method setTradeType (line 48) | public void setTradeType(int tradeType) {
    method getX (line 52) | public float getX() {
    method setX (line 56) | public void setX(float x) {
    method getY (line 60) | public float getY() {
    method setY (line 64) | public void setY(float y) {
    method compareTo (line 68) | @Override
    method toString (line 80) | @Override

FILE: app/src/main/java/com/example/admin/klineview/depth/DepthView.java
  class DepthView (line 29) | public class DepthView extends View {
    method DepthView (line 67) | public DepthView(Context context) {
    method DepthView (line 71) | public DepthView(Context context, @Nullable AttributeSet attrs) {
    method DepthView (line 75) | public DepthView(Context context, @Nullable AttributeSet attrs, int de...
    method setBuyDataList (line 83) | public void setBuyDataList(List<Depth> depthList) {
    method setSellDataList (line 101) | public void setSellDataList(List<Depth> depthList) {
    method resetAllData (line 119) | public void resetAllData(List<Depth> buyDataList, List<Depth> sellData...
    method setAbscissaCenterPrice (line 130) | public void setAbscissaCenterPrice(double centerPrice) {
    method setShowDetailLine (line 137) | public void setShowDetailLine(boolean isShowLine) {
    method setShowDetailSingleClick (line 144) | public void setShowDetailSingleClick(boolean isShowDetailSingleClick) {
    method setShowDetailLongPress (line 151) | public void setShowDetailLongPress(boolean isShowDetailLongPress) {
    method setPricePrecision (line 158) | public void setPricePrecision(int pricePrecision) {
    method setDetailPriceTitle (line 165) | public void setDetailPriceTitle(String priceTitle) {
    method setDetailVolumeTitle (line 172) | public void setDetailVolumeTitle(String volumeTitle) {
    method cancelCallback (line 179) | public void cancelCallback() {
    method init (line 184) | private void init(Context context, AttributeSet attrs) {
    method onLayout (line 252) | @Override
    method onDraw (line 318) | @Override
    method dispatchTouchEvent (line 329) | @Override
    method onTouchEvent (line 381) | @Override
    method getClickDepth (line 408) | private void getClickDepth(float clickX) {
    method drawCoordinateValue (line 448) | private void drawCoordinateValue(Canvas canvas) {
    method drawLineAndBg (line 483) | private void drawLineAndBg(Canvas canvas) {
    method drawDetailData (line 548) | private void drawDetailData(Canvas canvas) {
    method setPrecision (line 622) | private String setPrecision(Double num, int scale) {
    method formatNum (line 630) | private String formatNum(double num) {
    method resetStrokePaint (line 644) | private void resetStrokePaint(int colorId, int textSize, float strokeW...
    method dp2px (line 650) | private int dp2px(float dpValue) {
    method sp2px (line 655) | private int sp2px(float spValue) {

FILE: app/src/main/java/com/example/admin/klineview/kline/KData.java
  class KData (line 8) | public class KData {
    method KData (line 46) | public KData() {
    method KData (line 49) | public KData(double openPrice, double closedPrice, double maxPrice, do...
    method KData (line 57) | public KData(long time, double openPrice, double closePrice, double ma...
    method getCenterX (line 66) | public double getCenterX() {
    method setCenterX (line 70) | public void setCenterX(double centerX) {
    method getEma (line 74) | public double getEma() {
    method setEma (line 78) | public void setEma(double ema) {
    method getOpenPrice (line 82) | public double getOpenPrice() {
    method setOpenPrice (line 86) | public void setOpenPrice(double openPrice) {
    method getClosePrice (line 90) | public double getClosePrice() {
    method setClosePrice (line 94) | public void setClosePrice(double closePrice) {
    method getMaxPrice (line 98) | public double getMaxPrice() {
    method setMaxPrice (line 102) | public void setMaxPrice(double maxPrice) {
    method getMinPrice (line 106) | public double getMinPrice() {
    method setMinPrice (line 110) | public void setMinPrice(double minPrice) {
    method getLeftX (line 114) | public double getLeftX() {
    method setLeftX (line 118) | public void setLeftX(double leftX) {
    method getRightX (line 122) | public double getRightX() {
    method setRightX (line 126) | public void setRightX(double rightX) {
    method getTime (line 130) | public long getTime() {
    method setTime (line 134) | public void setTime(long time) {
    method getVolume (line 138) | public double getVolume() {
    method setVolume (line 142) | public void setVolume(double volume) {
    method getPriceMa5 (line 146) | public double getPriceMa5() {
    method setPriceMa5 (line 150) | public void setPriceMa5(double priceMa5) {
    method getPriceMa10 (line 154) | public double getPriceMa10() {
    method setPriceMa10 (line 158) | public void setPriceMa10(double priceMa10) {
    method getPriceMa30 (line 162) | public double getPriceMa30() {
    method setPriceMa30 (line 166) | public void setPriceMa30(double priceMa30) {
    method getVolumeMa5 (line 170) | public double getVolumeMa5() {
    method setVolumeMa5 (line 174) | public void setVolumeMa5(double volumeMa5) {
    method getVolumeMa10 (line 178) | public double getVolumeMa10() {
    method setVolumeMa10 (line 182) | public void setVolumeMa10(double volumeMa10) {
    method getEma5 (line 186) | public double getEma5() {
    method setEma5 (line 190) | public void setEma5(double ema5) {
    method getEma10 (line 194) | public double getEma10() {
    method setEma10 (line 198) | public void setEma10(double ema10) {
    method getEma30 (line 202) | public double getEma30() {
    method setEma30 (line 206) | public void setEma30(double ema30) {
    method getBollMb (line 210) | public double getBollMb() {
    method setBollMb (line 214) | public void setBollMb(double bollMb) {
    method getBollUp (line 218) | public double getBollUp() {
    method setBollUp (line 222) | public void setBollUp(double bollUp) {
    method getBollDn (line 226) | public double getBollDn() {
    method setBollDn (line 230) | public void setBollDn(double bollDn) {
    method getMacd (line 234) | public double getMacd() {
    method setMacd (line 238) | public void setMacd(double macd) {
    method getDea (line 242) | public double getDea() {
    method setDea (line 246) | public void setDea(double dea) {
    method getDif (line 250) | public double getDif() {
    method setDif (line 254) | public void setDif(double dif) {
    method getK (line 258) | public double getK() {
    method setK (line 262) | public void setK(double k) {
    method getD (line 266) | public double getD() {
    method setD (line 270) | public void setD(double d) {
    method getJ (line 274) | public double getJ() {
    method setJ (line 278) | public void setJ(double j) {
    method getRs1 (line 282) | public double getRs1() {
    method setRs1 (line 286) | public void setRs1(double rs1) {
    method getRs2 (line 290) | public double getRs2() {
    method setRs2 (line 294) | public void setRs2(double rs2) {
    method getRs3 (line 298) | public double getRs3() {
    method setRs3 (line 302) | public void setRs3(double rs3) {
    method getUpDnAmount (line 306) | public double getUpDnAmount() {
    method setUpDnAmount (line 310) | public void setUpDnAmount(double upDnAmount) {
    method getUpDnRate (line 314) | public double getUpDnRate() {
    method setUpDnRate (line 318) | public void setUpDnRate(double upDnRate) {
    method getCloseY (line 322) | public double getCloseY() {
    method setCloseY (line 326) | public void setCloseY(double closeY) {
    method getOpenY (line 330) | public double getOpenY() {
    method setOpenY (line 334) | public void setOpenY(double openY) {
    method isInitFinish (line 338) | public boolean isInitFinish() {
    method setInitFinish (line 342) | public void setInitFinish(boolean initFinish) {
    method toString (line 346) | @Override

FILE: app/src/main/java/com/example/admin/klineview/kline/KLineView.java
  class KLineView (line 38) | public class KLineView extends View implements View.OnTouchListener, Han...
    method KLineView (line 160) | public KLineView(Context context) {
    method KLineView (line 164) | public KLineView(Context context, @Nullable AttributeSet attrs) {
    method KLineView (line 168) | public KLineView(Context context, @Nullable AttributeSet attrs, int de...
    type OnRequestDataListListener (line 174) | public interface OnRequestDataListListener {
      method requestData (line 175) | void requestData();
    method setOnRequestDataListListener (line 178) | public void setOnRequestDataListListener(OnRequestDataListListener req...
    method initKDataList (line 190) | public void initKDataList(List<KData> dataList) {
    method getTotalDataList (line 204) | public List<KData> getTotalDataList(){
    method getViewDataList (line 211) | public List<KData> getViewDataList(){
    method addSingleData (line 218) | public void addSingleData(KData data) {
    method addPreDataList (line 242) | public void addPreDataList(List<KData> dataList, boolean isNeedReqPre) {
    method addPreDataList (line 263) | public void addPreDataList(List<KData> dataList) {
    method resetDataList (line 281) | public void resetDataList(List<KData> dataList) {
    method resetDataList (line 295) | public void resetDataList(List<KData> dataList, boolean isNeedLocateCu...
    method setMainImgType (line 383) | public void setMainImgType(int type) {
    method setDeputyPicShow (line 407) | public void setDeputyPicShow(boolean showState) {
    method setDeputyImgType (line 421) | public void setDeputyImgType(int type) {
    method setCrossHairMoveMode (line 448) | public void setCrossHairMoveMode(int moveMode) {
    method getVicePicShow (line 455) | public boolean getVicePicShow() {
    method cancelQuotaThread (line 462) | public void cancelQuotaThread() {
    method setShowInstant (line 475) | public void setShowInstant(boolean state){
    method isShowInstant (line 483) | public boolean isShowInstant(){
    method initAttrs (line 487) | private void initAttrs(Context context, AttributeSet attrs) {
    method initData (line 534) | private void initData() {
    method initQuotaThread (line 574) | private void initQuotaThread() {
    method handleMessage (line 581) | @Override
    method onMeasure (line 607) | @Override
    method onDraw (line 616) | @Override
    method dispatchTouchEvent (line 643) | @Override
    method onTouchEvent (line 703) | @Override
    method onTouch (line 837) | @Override
    class CustomGestureListener (line 842) | private class CustomGestureListener extends GestureDetector.SimpleOnGe...
      method onScroll (line 844) | @Override
      method onFling (line 868) | @Override
    method stopDelay (line 885) | private void stopDelay() {
    method initStopDelay (line 889) | private void initStopDelay() {
    method moveData (line 936) | private void moveData(float distanceX) {
    method setSpeed (line 955) | private void setSpeed(float distanceX, double num) {
    method requestNewData (line 965) | private void requestNewData() {
    method resetViewData (line 974) | private void resetViewData() {
    method resetData (line 999) | private void resetData() {
    method drawTickMark (line 1170) | private void drawTickMark(Canvas canvas) {
    method drawMainDeputyRect (line 1208) | private void drawMainDeputyRect(Canvas canvas) {
    method drawVolume (line 1273) | private void drawVolume(Canvas canvas){
    method drawBezierCurve (line 1296) | private void drawBezierCurve(Canvas canvas) {
    method drawMainBezierCurve (line 1487) | private void drawMainBezierCurve(@NonNull Canvas canvas) {
    method drawVolumeBezierCurve (line 1502) | private void drawVolumeBezierCurve(@NonNull Canvas canvas) {
    method drawDeputyCurve (line 1513) | private void drawDeputyCurve(@NonNull Canvas canvas) {
    method getClickKData (line 1528) | private void getClickKData(float clickX) {
    method drawCrossHairLine (line 1562) | private void drawCrossHairLine(Canvas canvas) {
    method drawMaxMinPriceLabel (line 1655) | private void drawMaxMinPriceLabel(Canvas canvas) {
    method drawDetailData (line 1751) | private void drawDetailData(Canvas canvas) {
    method drawTopPriceMAData (line 1887) | private void drawTopPriceMAData(Canvas canvas) {
    method drawBotMAData (line 1918) | private void drawBotMAData(Canvas canvas) {
    method drawAbscissa (line 2001) | private void drawAbscissa(Canvas canvas) {
    method drawOrdinate (line 2033) | private void drawOrdinate(@NonNull Canvas canvas) {
    method drawInstant (line 2135) | private void drawInstant(Canvas canvas){
    method dp2px (line 2166) | private int dp2px(float dpValue) {
    method sp2px (line 2171) | private int sp2px(float spValue) {
    method formatDate (line 2176) | private String formatDate(long timeStamp) {
    method setPrecision (line 2189) | private String setPrecision(Double num, int scale) {
    method formatKDataNum (line 2197) | private String formatKDataNum(double num) {
    method formatVolNum (line 2216) | private String formatVolNum(double num) {
    method resetStrokePaint (line 2226) | private void resetStrokePaint(int colorId, int textSize) {

FILE: app/src/main/java/com/example/admin/klineview/kline/Pointer.java
  class Pointer (line 7) | public class Pointer {
    method Pointer (line 12) | public Pointer() {
    method Pointer (line 15) | public Pointer(float x, float y) {
    method getX (line 20) | public float getX() {
    method setX (line 24) | public void setX(float x) {
    method getY (line 28) | public float getY() {
    method setY (line 32) | public void setY(float y) {
    method toString (line 36) | @Override

FILE: app/src/main/java/com/example/admin/klineview/kline/QuotaThread.java
  class QuotaThread (line 15) | public class QuotaThread extends HandlerThread implements Handler.Callba...
    method QuotaThread (line 22) | public QuotaThread(String name, int priority) {
    method setUIHandler (line 26) | public void setUIHandler(Handler uiHandler) {
    method quotaListCalculate (line 30) | public void quotaListCalculate(List<KData> dataList) {
    method quotaSingleCalculate (line 39) | public void quotaSingleCalculate(List<KData> dataList){
    method calculateKDataQuota (line 48) | private void calculateKDataQuota(List<KData> dataList, boolean isEndDa...
    method onLooperPrepared (line 57) | @Override
    method handleMessage (line 63) | @Override
    method handleData (line 73) | private void handleData(Message msg, int whatId, boolean isEndData){

FILE: app/src/main/java/com/example/admin/klineview/kline/QuotaUtil.java
  class QuotaUtil (line 13) | public class QuotaUtil {
    method initMa (line 26) | public static void initMa(List<KData> dataList, boolean isEndData) {
    method getVolumeMa (line 58) | private static double getVolumeMa(List<KData> dataList) {
    method getPriceMa (line 69) | private static double getPriceMa(List<KData> dataList) {
    method initEma (line 83) | public static void initEma(List<KData> dataList, boolean isEndData) {
    method initBOLL (line 126) | public static void initBOLL(List<KData> dataList, int period, int k, b...
    method initBoll (line 180) | public static void initBoll(List<KData> dataList, boolean isEndData) {
    method initMACD (line 192) | public static void initMACD(List<KData> dataList, int fastPeriod, int ...
    method initMACD (line 227) | public static void initMACD(List<KData> dataList, boolean isEndData) {
    method initKDJ (line 238) | public static void initKDJ(List<KData> dataList, int n1, int n2, int n...
    method initKDJ (line 316) | public static void initKDJ(List<KData> dataList, boolean isEndData) {
    method initRSI (line 327) | public static void initRSI(List<KData> dataList, int period1, int peri...
    method initRSI (line 417) | public static void initRSI(List<KData> dataList, boolean isEndData) {
    method setBezierPath (line 427) | public static void setBezierPath(List<Pointer> pointList, Path path) {
    method setLinePath (line 480) | public static void setLinePath(List<Pointer> pointerList, Path path) {

FILE: app/src/test/java/com/example/admin/klineview/ExampleUnitTest.java
  class ExampleUnitTest (line 12) | public class ExampleUnitTest {
    method addition_isCorrect (line 13) | @Test
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (224K chars).
[
  {
    "path": ".gitignore",
    "chars": 211,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "chars": 3309,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <codeStyleSettings lan"
  },
  {
    "path": ".idea/gradle.xml",
    "chars": 748,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleMigrationSettings\" migrationVersio"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 3235,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"NullableNotNullManager\">\n    <option nam"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 564,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 180,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "README.md",
    "chars": 3907,
    "preview": "# KLineView\n股票走势图K线控件  \n[![](https://jitpack.io/v/xiesuichao/KLineView.svg)](https://jitpack.io/#xiesuichao/KLineView)  "
  },
  {
    "path": "app/.gitignore",
    "chars": 7,
    "preview": "/build\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 932,
    "preview": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion 27\n    defaultConfig {\n        applicationId \"c"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 751,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "app/src/androidTest/java/com/example/admin/klineview/ExampleInstrumentedTest.java",
    "chars": 755,
    "preview": "package com.example.admin.klineview;\n\nimport android.content.Context;\nimport android.support.test.InstrumentationRegistr"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 772,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/DepthActivity.java",
    "chars": 2898,
    "preview": "package com.example.admin.klineview;\n\nimport android.os.Bundle;\nimport android.os.Handler;\nimport android.support.annota"
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/MainActivity.java",
    "chars": 8644,
    "preview": "package com.example.admin.klineview;\n\nimport android.content.Intent;\nimport android.content.res.Configuration;\nimport an"
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/Print.java",
    "chars": 866,
    "preview": "package com.example.admin.klineview;\n\n\nimport android.util.Log;\n\n/**\n * Created by xiesuichao on 2016/10/18.\n */\npublic "
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/depth/Depth.java",
    "chars": 1723,
    "preview": "package com.example.admin.klineview.depth;\n\nimport android.support.annotation.NonNull;\n\n/**\n * 深度数据\n * Created by xiesui"
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/depth/DepthView.java",
    "chars": 25456,
    "preview": "package com.example.admin.klineview.depth;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimpor"
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/kline/KData.java",
    "chars": 8091,
    "preview": "package com.example.admin.klineview.kline;\n\n/**\n * K线数据\n * Created by xiesuichao on 2018/6/29.\n */\n\npublic class KData {"
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/kline/KLineView.java",
    "chars": 92805,
    "preview": "package com.example.admin.klineview.kline;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimpor"
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/kline/Pointer.java",
    "chars": 656,
    "preview": "package com.example.admin.klineview.kline;\n\n/**\n * Created by xiesuichao on 2018/7/3.\n */\n\npublic class Pointer {\n    \n "
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/kline/QuotaThread.java",
    "chars": 2561,
    "preview": "package com.example.admin.klineview.kline;\n\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.o"
  },
  {
    "path": "app/src/main/java/com/example/admin/klineview/kline/QuotaUtil.java",
    "chars": 18181,
    "preview": "package com.example.admin.klineview.kline;\n\nimport android.graphics.Path;\n\nimport java.util.ArrayList;\nimport java.util."
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 5606,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:wi"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "chars": 1880,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    "
  },
  {
    "path": "app/src/main/res/layout/activity_depth.xml",
    "chars": 1055,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v4.widget.NestedScrollView xmlns:android=\"http://schemas.android"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 3949,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<android.support.v4.widget.NestedScrollView xmlns:android=\"http://schemas.android"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 272,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 272,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "chars": 4595,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--K线控件-->\n    <declare-styleable name=\"KLineView\">\n        <!-"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 2731,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 72,
    "preview": "<resources>\n    <string name=\"app_name\">KLineView</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 381,
    "preview": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar\">"
  },
  {
    "path": "app/src/test/java/com/example/admin/klineview/ExampleUnitTest.java",
    "chars": 405,
    "preview": "package com.example.admin.klineview;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.*;\n\n/**\n * Example local un"
  },
  {
    "path": "build.gradle",
    "chars": 546,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    \n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 232,
    "preview": "#Fri Jun 29 11:21:17 CST 2018\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 730,
    "preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
  },
  {
    "path": "gradlew",
    "chars": 4971,
    "preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start "
  },
  {
    "path": "gradlew.bat",
    "chars": 2314,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "settings.gradle",
    "chars": 15,
    "preview": "include ':app'\n"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the xiesuichao/KLineView GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (202.4 KB), approximately 54.2k tokens, and a symbol index with 243 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!