This commit is contained in:
coco
2026-07-03 15:56:07 +08:00
commit caef23209c
5767 changed files with 1004268 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+1
View File
@@ -0,0 +1 @@
/build
+62
View File
@@ -0,0 +1,62 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode 1
versionName "1.0"
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api 'androidx.appcompat:appcompat:1.0.2'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "com.google.android.material:material:1.2.1"
api "androidx.constraintlayout:constraintlayout:1.1.3"
//Google AAC(Android Architecture Components)android架构组件
// implementation rootProject.ext.dependencies["lifecycle-extensions"]
def lifecycle_version = "2.2.0"
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
api "com.yanzhenjie.permission:x:2.0.1"
//Rx
api "com.uber.autodispose:autodispose-android-archcomponents:1.1.0"
api "io.reactivex.rxjava3:rxjava:3.0.0"
api "io.reactivex.rxjava3:rxandroid:3.0.0"
}
repositories {
mavenCentral()
}
// must be applied after your artifact generating plugin (eg. java / com.android.library)
//apply plugin: 'com.novoda.bintray-release'
//publish {
// userOrg = 'oxo1' //bintray.com 账户下某个组织id
// groupId = 'com.openxu.viewlib' //jcenter上的路径,一般为包名
// artifactId = 'OXViewLib' //项目名称
// publishVersion = '1.0.3' //版本号
// desc = 'custom chart for android' //项目介绍,可以不写
// website = '' //项目主页,可以不写
//}
+92
View File
@@ -0,0 +1,92 @@
/**③.发布到JCenter*/
apply plugin: 'com.jfrog.bintray'
apply plugin: 'com.github.dcendents.android-maven'
def siteUrl = 'https://github.com/openXu/OXChart' //项目主页
def gitUrl = 'https://github.com/openXu/OXChart.git'//git仓库
group = 'com.openxu.viewlib' //groupid 一般为包名
version = '1.0.0' //版本号
install {
repositories.mavenInstaller {
pom {
project {
packaging 'aar'
name 'OXViewLib'// 可选,项目名称
description 'custom chart for android'// 可选,项目描述
url siteUrl //项目地址
// 软件开源协议
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
//开发者信息
developers {
developer {
id 'OPEN-XU' //yourId
name 'OPEN-XU' //yourName
email '17373613414@yeah.net' //yourEmail
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
task javadoc(type: Javadoc) {
options {
encoding "UTF-8"
charSet 'UTF-8'
author true
version true
links "http://docs.oracle.com/javase/7/docs/api"
}
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
options.encoding = "UTF-8"
failOnError false // 忽略注释语法错误,如果用jdk1.8你的注释写的不规范就编译不过。
}
// 生成jar包的task
task sourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
// 生成javaDoc的jar,不需要修改。
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
// archives androidJavadocsJar
}
bintray {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
user = properties.getProperty("bintray.user")
key = properties.getProperty("bintray.apikey")
configurations = ['archives']
pkg {
repo = 'maven'
name = 'OXViewLib' //发布到JCenter上项目名
userOrg = 'oxo1'
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = ['Apache-2.0']
publish = true
}
}
+13
View File
@@ -0,0 +1,13 @@
/**①.发布到本地maven仓库*/
apply plugin: 'maven'
//添加下面配置后,在Gradle projects窗口中:OXViewLib->Tasks中会多出一个upload目录,里面就有一个名为uploadArchives的task
//uploadArchives就是用于发布到仓库的task
uploadArchives{
repositories.mavenDeployer{
// 配置本地仓库路径,项目根目录下的repository目录中
repository(url: uri('../repository'))
pom.groupId = "com.openxu.viewlib"// 唯一标识(通常为模块包名,也可以任意)
pom.artifactId = "OXViewLib" // 项目名称(通常为类库模块名称,也可以任意)
pom.version = "1.0.0" // 版本号
}
}
+37
View File
@@ -0,0 +1,37 @@
/**②.发布到私有服务器maven仓库*/
apply plugin: 'maven'
/*task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}*/
//打包java文档的 task
/*task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}*/
//打包main目录下文件的 task
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
//配置需要上传到maven仓库的文件
artifacts {
archives androidSourcesJar
// archives androidJavadocsJar
}
//上传到Maven仓库的task
uploadArchives {
repositories {
mavenDeployer {
//指定maven仓库url
repository(url: "http://localhost:8081/nexus/content/repositories/releases/") {
//nexus登录默认用户名和密码
authentication(userName: "admin", password: "admin123")
}
pom.groupId = "com.openxu.viewlib"// 唯一标识(通常为模块包名,也可以任意)
pom.artifactId = "OXViewLib" // 项目名称(通常为类库模块名称,也可以任意)
pom.version = "1.0.0" // 版本号
}
}
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.openxu.cview" />
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@@ -0,0 +1,38 @@
package com.openxu;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import java.util.Stack;
/**
* Author: openX
* Time: 2019/2/23 14:49
* class: BaseApplication
* Description: Application请继承该基类
*/
public class MyApplication extends Application {
private static Stack<Activity> activityStack = new Stack<>();
private static Context mcontext;
protected static MyApplication application;
@Override
public void onCreate() {
application = this;
mcontext = getApplicationContext();
super.onCreate();
}
public static MyApplication getApplication() {
return application;
}
public static Context getContext() {
return mcontext;
}
}
@@ -0,0 +1,609 @@
package com.openxu.cview;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.openxu.utils.DensityUtil;
/**
* autour: openXu
* date: 2017/3/10 13:32
* className: TitleLayout
* version:
* description: 通用的titlebar
* Sample:(属性详情参照attrs.xml-TitleView)
*/
public class TitleLayout extends RelativeLayout {
private String TAG = "TitleLayout";
private View view_tran_bar; //状态栏预留占位
/*总容器*/
private RelativeLayout titleLayout; //title总布局
/*左侧部分*/
private LinearLayout ll_left; //左侧容器
private LinearLayout ll_back;
// private View view_left;
private ImageView iv_left_back, iv_left_icon; //返回键、图标
private TextView tv_left_text; //左侧文字
/*中间*/
private RelativeLayout rl_center; //中间容器
private TextView tv_center_text; //中间文字
/*右侧*/
private LinearLayout ll_right; //右侧容器
private RelativeLayout rl_right_icon; //右侧图标(可带气泡)
private TextView tv_right_text, tv_right_pop; //右侧文字
private ImageView iv_right_icon; //菜单图标、气泡
private LinearLayout ll_right_icon_content; //其他菜单容器
/**
* 属性值
*/
private String textLeft, textcenter, textRight, numRightPop; //文字
private int iconBack, iconLeft, iconCenterRow, iconRight; //图标
private int[] rightIconArr; //其他菜单容器
private float titleHeight; //文字高度
private float textSize; //文字大小
private int textColor; //文字颜色
private float textIconSpace; //item之间的间距
private float leftSpace; //标题左侧的空隙
private float rightSpace; //标题右侧的空隙
private float centerRowSpace; //中间箭头和文字的距离
private int background;
private float leftUsed;
private float rightUsed;
public TitleLayout(Context context) {
this(context, null);
}
public TitleLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TitleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.title_layout, this, true);
view_tran_bar = findViewById(R.id.view_tran_bar);
//设置状态栏占位高度
setStatusBarHeight(getStatusBarHeight(getContext()));
//title总布局
titleLayout = (RelativeLayout) findViewById(R.id.titleLayout);
/*左侧部分*/
ll_left = (LinearLayout) findViewById(R.id.ll_left);
ll_back = (LinearLayout) findViewById(R.id.ll_back);
// view_left = findViewById(R.id.view_left);
iv_left_back = (ImageView) findViewById(R.id.iv_left_back);
iv_left_icon = (ImageView) findViewById(R.id.iv_left_icon);
tv_left_text = (TextView) findViewById(R.id.tv_left_text);
/*中间*/
rl_center = (RelativeLayout) findViewById(R.id.rl_center);
tv_center_text = (TextView) findViewById(R.id.tv_center_text);
/*右侧*/
ll_right = (LinearLayout) findViewById(R.id.ll_right);
rl_right_icon = (RelativeLayout) findViewById(R.id.rl_right_icon);
tv_right_text = (TextView) findViewById(R.id.tv_right_text);
iv_right_icon = (ImageView) findViewById(R.id.iv_right_icon);
tv_right_pop = (TextView) findViewById(R.id.tv_right_pop);
ll_right_icon_content = (LinearLayout) findViewById(R.id.ll_right_icon_content);
if(attrs!=null){
//获取自定义属性的值
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TitleView, defStyleAttr, 0);
textLeft = ta.getString(R.styleable.TitleView_textLeft);
textcenter = ta.getString(R.styleable.TitleView_textcenter);
textRight = ta.getString(R.styleable.TitleView_textRight);
iconBack = ta.getResourceId(R.styleable.TitleView_iconBack, 0);
iconLeft = ta.getResourceId(R.styleable.TitleView_iconLeft, 0);
iconCenterRow = ta.getResourceId(R.styleable.TitleView_iconCenterRow, 0);
iconRight = ta.getResourceId(R.styleable.TitleView_iconRight, 0);
textSize = ta.getDimension(R.styleable.TitleView_android_textSize, 1);
titleHeight = ta.getDimension(R.styleable.TitleView_titleHeight, 0);
textColor = ta.getColor(R.styleable.TitleView_android_textColor, Color.WHITE);
textIconSpace = ta.getDimension(R.styleable.TitleView_textIconSpace, 0);
leftSpace = ta.getDimension(R.styleable.TitleView_leftSpace, 0);
rightSpace = ta.getDimension(R.styleable.TitleView_rightSpace, 0);
centerRowSpace = ta.getDimension(R.styleable.TitleView_centerRowSpace, 0);
/*
Drawable backgroundDrawable = getBackground();
LogUtil.i(TAG, "backgroundDrawable="+backgroundDrawable);
int defTitleColor = getResources().getColor(R.color.title_bar_color);
//background包括color和Drawable,这里分开取值
if(null == backgroundDrawable){
}else if(backgroundDrawable instanceof ColorDrawable){
background = ta.getColor(R.styleable.TitleView_android_background, defTitleColor);
LogUtil.i(TAG, "设置title背景色"+background);
setBackgroundColor(background);
}else{
background = ta.getResourceId(R.styleable.TitleView_android_background, -1);
LogUtil.i(TAG, "设置title背景资源图片"+background);
setBackgroundResource(background);
}
LogUtil.i(TAG, "background = "+background);*/
// LogUtil.i(TAG, "textLeft = "+textLeft);
// LogUtil.i(TAG, "textcenter = "+textcenter);
// LogUtil.i(TAG, "textRight = "+textRight);
// LogUtil.i(TAG, "iconBack = " + iconBack);
// LogUtil.i(TAG, "iconLeft = "+iconLeft);
// LogUtil.i(TAG, "iconCenterRow = "+iconCenterRow);
// LogUtil.i(TAG, "iconRight = "+iconRight);
// LogUtil.i(TAG, "iconRight = "+iconRight);
// LogUtil.i(TAG, "textSize = "+textSize);
// LogUtil.i(TAG, "titleHeight = "+titleHeight);
// LogUtil.i(TAG, "textColor = "+textColor);
// LogUtil.i(TAG, "textIconSpace = "+textIconSpace);
// LogUtil.i(TAG, "leftSpace = " + leftSpace);
// LogUtil.i(TAG, "rightSpace = "+rightSpace);
// LogUtil.i(TAG, "centerRowSpace = "+centerRowSpace);
ta.recycle();
}
show();
}
/**
* 显示,当设置属性值后,调用此方法
*/
public void show() {
setParams();
setGone();
setVisibleAndLayout();
}
private void setParams() {
tv_left_text.setTextColor(textColor);
tv_left_text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
tv_center_text.setTextColor(textColor);
tv_center_text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
tv_right_text.setTextColor(textColor);
tv_right_text.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
tv_right_pop.setTextColor(textColor);
// LogUtil.d(TAG, "设置字体");
//设置标题高度
ViewGroup.LayoutParams params = titleLayout.getLayoutParams();
if (params == null)
return;
params.height = (int) titleHeight;
titleLayout.setLayoutParams(params);
/*设置左边距*/
/*设置左边距*/
/* RelativeLayout.LayoutParams rl_params = (RelativeLayout.LayoutParams) ll_left.getLayoutParams();
rl_params.leftMargin = (int)leftSpace;
ll_left.setLayoutParams(rl_params);*/
LinearLayout.LayoutParams ll_params = (LinearLayout.LayoutParams) iv_left_back.getLayoutParams();
ll_params.leftMargin = (int) leftSpace;
ll_params.rightMargin = (int) leftSpace;
iv_left_back.setLayoutParams(ll_params);
// iv_left_back.setPadding((int)leftSpace, 0, 0, 0);
// ll_back.setPadding((int)leftSpace, ll_back.getPaddingTop(), ll_back.getPaddingRight(), ll_back.get);
/*设置右边距*/
LayoutParams rl_params1 = (LayoutParams) ll_right.getLayoutParams();
rl_params1.rightMargin = (int) rightSpace;
ll_right.setLayoutParams(rl_params1);
leftUsed = leftSpace;
rightUsed = rightSpace;
}
public TitleLayout setStatusBarHeight(int height) {
if(view_tran_bar!=null){
LinearLayout.LayoutParams barparams = (LinearLayout.LayoutParams) view_tran_bar.getLayoutParams();
barparams.height = height;
view_tran_bar.setLayoutParams(barparams);
}
return this;
}
public static int getStatusBarHeight(Context context){
int barHeight = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
/**由于app的标题栏背景是图片而不是纯色,所以version>4.4的版本都显示在状态栏之上,需要添加一个状态栏高度的占位控件*/
// LogUtil.i(TAG, "版本:"+Build.VERSION.SDK_INT+" >= "+Build.VERSION_CODES.KITKAT+",需要显示状态栏占位");
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
barHeight = context.getResources().getDimensionPixelSize(resourceId);
}
}
return barHeight;
}
private void setGone() {
tv_left_text.setVisibility(View.GONE);
ll_back.setVisibility(View.GONE);
iv_left_icon.setVisibility(View.GONE);
ll_left.setVisibility(View.GONE);
tv_center_text.setVisibility(View.GONE);
rl_center.setVisibility(View.GONE);
tv_right_text.setVisibility(View.GONE);
iv_right_icon.setVisibility(View.GONE);
tv_right_pop.setVisibility(View.GONE);
rl_right_icon.setVisibility(View.GONE);
ll_right_icon_content.setVisibility(View.GONE);
ll_right.setVisibility(View.GONE);
}
public OnMenuClickListener getOnMenuClickListener() {
return listener;
}
/**
* 控制item显示
*/
private void setVisibleAndLayout() {
// LogUtil.d(TAG, "设置可见");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
if (!TextUtils.isEmpty(textLeft) || iconBack != 0 || iconLeft != 0) {
// LogUtil.d(TAG, "左侧可见");
ll_left.setVisibility(View.VISIBLE);
if (!TextUtils.isEmpty(textLeft)) {
tv_left_text.setVisibility(View.VISIBLE);
tv_left_text.setText(textLeft);
tv_left_text.measure(0, 0);
// LogUtil.d(TAG, "左侧文字可见"+ tv_left_text.getMeasuredWidth());
// LogUtil.d(TAG, "左侧文字可见"+ FontUtil.getFontlength(textPaint, textLeft));
leftUsed += tv_left_text.getMeasuredWidth();
tv_left_text.setOnClickListener(v -> {
if (listener != null) {
listener.onClick(MENU_NAME.MENU_LEFT_TEXT, tv_left_text);
}
});
}
if (iconBack != 0) {
ll_back.setVisibility(View.VISIBLE);
iv_left_back.setImageResource(iconBack);
// iv_left_back.setPadding(20, 20, 20, 20);
BitmapFactory.decodeResource(getResources(), iconBack, options);
int width = options.outWidth;
// LogUtil.d(TAG, "左侧返回可见"+width);
leftUsed += width;
ll_back.setOnClickListener(v -> {
((Activity) getContext()).onBackPressed();
});
}
if (iconLeft != 0) {
iv_left_icon.setVisibility(View.VISIBLE);
iv_left_icon.setImageResource(iconLeft);
BitmapFactory.decodeResource(getResources(), iconLeft, options);
int width = options.outWidth;
// LogUtil.d(TAG, "左侧图标可见"+width);
leftUsed += width;
/*iv_left_icon.setOnClickListener(v->{
if(listener!=null)
listener.onClick(MENU_NAME.MENU_LEFT_ICON);
});*/
}
if (!TextUtils.isEmpty(textLeft) && (iconLeft != 0 || iconBack != 0)) {
LinearLayout.LayoutParams ll_params = (LinearLayout.LayoutParams) tv_left_text.getLayoutParams();
ll_params.leftMargin = (int) textIconSpace;
leftUsed += textIconSpace;
tv_left_text.setLayoutParams(ll_params);
}
if (iconBack == 0 && (!TextUtils.isEmpty(textLeft) || iconLeft != 0)) {
//返回键不显示时,流出左边空隙
LayoutParams rl_params = (LayoutParams) ll_left.getLayoutParams();
rl_params.leftMargin = (int) leftSpace;
ll_left.setLayoutParams(rl_params);
}
}
if (!TextUtils.isEmpty(textcenter) || iconCenterRow != 0) {
rl_center.setVisibility(View.VISIBLE);
// LogUtil.d(TAG, "中间可见");
if (!TextUtils.isEmpty(textcenter)) {
// LogUtil.d(TAG, "中间文字可见:" + textcenter);
tv_center_text.setVisibility(View.VISIBLE);
tv_center_text.setText(textcenter);
if (iconCenterRow != 0) {
// LogUtil.d(TAG, "中间箭头可见");
Drawable iconCenterDraw = getResources().getDrawable(iconCenterRow);
iconCenterDraw.setBounds(0, 0, iconCenterDraw.getMinimumWidth(), iconCenterDraw.getMinimumHeight());
tv_center_text.setCompoundDrawables(null, null, iconCenterDraw, null);
tv_center_text.setCompoundDrawablePadding((int) centerRowSpace);
rl_center.setOnClickListener(v -> {
if (listener != null)
listener.onClick(MENU_NAME.MENU_CENTER, rl_center);
});
}
}
}
if (!TextUtils.isEmpty(textRight) || iconRight != 0 || !TextUtils.isEmpty(numRightPop)) {
ll_right.setVisibility(View.VISIBLE);
// LogUtil.d(TAG, "右侧可见");
if (!TextUtils.isEmpty(textRight)) {
tv_right_text.setVisibility(View.VISIBLE);
tv_right_text.setText(textRight);
tv_right_text.measure(0, 0);
// LogUtil.d(TAG, "右侧文字可见"+tv_left_text.getMeasuredWidth());
// LogUtil.d(TAG, "右侧文字可见"+FontUtil.getFontlength(textPaint, textRight));
rightUsed += tv_left_text.getMeasuredWidth();
tv_right_text.setOnClickListener(v -> {
if (listener != null) {
listener.onClick(MENU_NAME.MENU_RIGHT_TEXT, tv_right_text);
}
});
}
if (iconRight != 0) {
rl_right_icon.setVisibility(View.VISIBLE);
iv_right_icon.setVisibility(View.VISIBLE);
iv_right_icon.setImageResource(iconRight);
rl_right_icon.setOnClickListener(v -> {
if (listener != null) {
listener.onClick(MENU_NAME.MENU_RIGHT_ICON, rl_right_icon);
}
});
// LogUtil.d(TAG, "右侧图标可见");
if (!TextUtils.isEmpty(numRightPop)) {
tv_right_pop.setVisibility(View.VISIBLE);
tv_right_pop.setText(numRightPop + "");
}
}
/*设置右侧menu图标和气泡的间距*/
BitmapFactory.decodeResource(getResources(), iconRight, options);
int width = options.outWidth;
int height = options.outHeight;
// LogUtil.i(TAG, "右侧图标的宽高:"+width+" * "+height);
LinearLayout.LayoutParams ll_params = (LinearLayout.LayoutParams) rl_right_icon.getLayoutParams();
if (!TextUtils.isEmpty(numRightPop)) {
tv_right_pop.measure(0, 0);
// LogUtil.d(TAG, "气泡大小"+tv_right_pop.getMeasuredWidth());
int widthPop = tv_right_pop.getMeasuredWidth();
int heightPop = tv_right_pop.getMeasuredHeight();
// LogUtil.i(TAG, "气泡的宽高:"+widthPop+" * "+heightPop);
ll_params.width = width + widthPop;
ll_params.height = height + heightPop;
} else {
ll_params.width = width;
ll_params.height = height;
}
rl_right_icon.setLayoutParams(ll_params);
rightUsed += ll_params.width;
if (!TextUtils.isEmpty(textRight) && iconRight != 0) {
ll_params = (LinearLayout.LayoutParams) rl_right_icon.getLayoutParams();
ll_params.leftMargin = (int) textIconSpace;
rl_right_icon.setLayoutParams(ll_params);
rightUsed += textIconSpace;
}
if(null!=rightIconArr && rightIconArr.length>0){
ll_right_icon_content.setVisibility(View.VISIBLE);
for(int id : rightIconArr){
LinearLayout.LayoutParams content_params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
content_params.leftMargin = DensityUtil.dip2px(getContext(), 10);
ImageView iv = new ImageView(getContext());
iv.setImageResource(id);
ll_right_icon_content.addView(iv ,content_params);
iv.setOnClickListener(v -> {
if (contentIconListener != null) {
contentIconListener.onClick(id, v);
}
});
}
}
}
if (!TextUtils.isEmpty(textcenter)) {
/*避免中间的内容超出范围*/
// LogUtil.e(TAG, "左边占用:" + leftUsed + " 右边占用:" + rightUsed);
float max = leftUsed > rightUsed ? leftUsed : rightUsed;
int widthRow = 0;
if (iconCenterRow != 0) {
BitmapFactory.decodeResource(getResources(), iconCenterRow, options);
widthRow = options.outWidth;
}
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
int centerTextMaxWidth = wm.getDefaultDisplay().getWidth() -
(int) (2 * (max + textIconSpace)) - widthRow
- (int) (widthRow == 0 ? 0 : centerRowSpace + widthRow);
// LogUtil.e(TAG, " 剩余:" + centerTextMaxWidth);
tv_center_text.measure(0, 0);
// LogUtil.d(TAG, "中间标题长度:" + tv_center_text.getMeasuredWidth());
float centerTextWidth = tv_center_text.getMeasuredWidth() > centerTextMaxWidth ?
centerTextMaxWidth : tv_center_text.getMeasuredWidth();
LayoutParams rl_params = (LayoutParams) tv_center_text.getLayoutParams();
rl_params.width = (int) centerTextWidth;
tv_center_text.setLayoutParams(rl_params);
}
requestLayout();
}
/**
* 点击事件类型
*/
public enum MENU_NAME {
MENU_BACK,
// MENU_LEFT_ICON,
MENU_LEFT_TEXT,
MENU_CENTER,
MENU_RIGHT_ICON,
MENU_RIGHT_TEXT,
MENU_RIGHT_CONTENTICON
}
public TitleLayout setRightIconArr(int[] rightIconArr) {
this.rightIconArr = rightIconArr;
return this;
}
public void setLeftIconVisible(boolean showIcon){
if(showIcon){
ll_back.setVisibility(VISIBLE);
}else{
ll_back.setVisibility(GONE);
}
}
public interface OnMenuClickListener {
void onClick(MENU_NAME menu, View view);
}
public interface OnContentIconClickListener {
void onClick(int srcId, View view);
}
private OnMenuClickListener listener;
private OnContentIconClickListener contentIconListener;
public TitleLayout setOnMenuClickListener(OnMenuClickListener listener) {
this.listener = listener;
return this;
}
public TitleLayout setOnContentIconClickListener(OnContentIconClickListener listener) {
this.contentIconListener = listener;
return this;
}
public TitleLayout setBackgroundColor1(int Color) {
titleLayout.setBackgroundColor(Color);
view_tran_bar.setBackgroundColor(Color);
return this;
}
/**
* 设置内容
*/
public TitleLayout setTextLeft(String textLeft) {
this.textLeft = textLeft;
tv_left_text.setText(textLeft);
return this;
}
public TitleLayout setTextcenter(String textcenter) {
this.textcenter = textcenter;
tv_center_text.setText(textcenter);
return this;
}
public TitleLayout setTextRight(String textRight) {
this.textRight = textRight;
tv_right_text.setText(textRight);
return this;
}
public TitleLayout setIconBack(int iconBack) {
this.iconBack = iconBack;
iv_left_back.setImageResource(iconBack);
return this;
}
public TitleLayout setIconLeft(int iconLeft) {
this.iconLeft = iconLeft;
iv_left_icon.setImageResource(iconLeft);
return this;
}
public TitleLayout setIconCenterRow(int iconCenterRow) {
this.iconCenterRow = iconCenterRow;
return this;
}
public TitleLayout setIconRight(int iconRight) {
this.iconRight = iconRight;
iv_right_icon.setImageResource(iconRight);
return this;
}
public TitleLayout setNumRightPop(String numRightPop) {
this.numRightPop = numRightPop;
tv_right_pop.setText(numRightPop);
return this;
}
public TitleLayout setTitleHeight(int titleHeight) {
this.titleHeight = DensityUtil.dip2px(getContext(), titleHeight);
return this;
}
public TitleLayout setTextSize(int textSize) {
this.textSize = textSize;
return this;
}
public TitleLayout setTextColor(int textColor) {
this.textColor = textColor;
return this;
}
public TitleLayout setTextIconSpace(int textIconSpace) {
this.textIconSpace = DensityUtil.dip2px(getContext(), textIconSpace);
return this;
}
public TitleLayout setLeftSpace(int leftSpace) {
this.leftSpace = DensityUtil.dip2px(getContext(), leftSpace);
return this;
}
public TitleLayout setRightSpace(int rightSpace) {
this.rightSpace = DensityUtil.dip2px(getContext(), rightSpace);
return this;
}
public TitleLayout setCenterRowSpace(int centerRowSpace) {
this.centerRowSpace = DensityUtil.dip2px(getContext(), centerRowSpace);
return this;
}
public String getTextLeft() {
return textLeft;
}
public String getTextcenter() {
return textcenter;
}
public String getTextRight() {
return textRight;
}
}
@@ -0,0 +1,358 @@
package com.openxu.cview.chart;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import com.openxu.cview.BuildConfig;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.SharedData;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : BaseChart
* version : 1.0
* description : 图表基类
*/
public abstract class BaseChart extends View {
protected String TAG = "BaseChart";
protected int ScrWidth,ScrHeight; //屏幕宽高
protected RectF rectChart; //图表矩形
protected PointF centerPoint; //chart中心点坐标
protected int total; //总数量
/**可设置属性*/
protected int startAngle = -90; //开始的角度
protected int backColor = Color.WHITE;
protected int lineWidth = 1; //辅助线宽度
protected String loadingStr = "loading...";
protected int defColor = Color.rgb(220, 220, 220); //底色
protected Paint paint;
protected Paint paintEffect;
protected Paint paintLabel;
/**动画相关统一属性,也可以设置,需要写set方法*/
protected long animDuration = 1000;
protected ValueAnimator anim;
protected boolean startDraw = false;
/**正在加载*/
protected boolean isLoading = true;
protected boolean drawLine = true;
protected boolean drawBottomLine = true;
protected boolean debug = SharedData.getInstance().getSp().getBoolean(SharedData.KEY_DEBUG, false);
/**手指抬起后,动画*/
protected ValueAnimator touchAnim;
protected GestureDetector mGestureDetector;
public void setLoading(boolean loading) {
isLoading = loading;
invalidate();
}
public void setLineWidth(int lineWidth) {
this.lineWidth = lineWidth;
}
public BaseChart(Context context) {
this(context, null);
}
public BaseChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
init(context, attrs, defStyle);
}
public void setBackColor(int backColor) {
this.backColor = backColor;
}
public void init() {
TAG = getClass().getSimpleName();
DisplayMetrics dm = getResources().getDisplayMetrics();
ScrHeight = dm.heightPixels;
ScrWidth = dm.widthPixels;
//画笔初始化
paint = new Paint();
paint.setAntiAlias(true);
paintLabel = new Paint();
paintLabel.setAntiAlias(true);
paintEffect = new Paint();
paintEffect.setAntiAlias(true);
paintEffect.setStyle(Paint.Style.FILL);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(Color.RED);
mGestureDetector = new GestureDetector(getContext(), new MyOnGestureListener());
}
public abstract void init(Context context, AttributeSet attrs, int defStyleAttr);
public int getTotal() {
return total;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerPoint = new PointF(getMeasuredWidth()/2, getMeasuredHeight()/2);
rectChart = new RectF(getPaddingLeft(),getPaddingTop(),getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom());
}
protected TOUCH_EVENT_TYPE touchEventType = TOUCH_EVENT_TYPE.EVENT_NULL;
/**需要拦截的事件方向*/
public enum TOUCH_EVENT_TYPE{
EVENT_NULL, /*不处理事件*/
EVENT_X, /*拦截X轴方向的事件*/
EVENT_Y, /*拦截Y轴方向的事件*/
EVENT_XY /*拦截XY轴方向的事件*/
}
/**设置事件方向*/
public void setTouchEventType(TOUCH_EVENT_TYPE touchEventType) {
this.touchEventType = touchEventType;
}
protected boolean touchEnable = false; //是否超界,控件的大小是否足以显示内容,是否需要滑动来展示。子控件根据计算赋值
protected float mDownX, mDownY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
LogUtil.d(TAG, "dispatchTouchEvent "+touchEventType);
if(!touchEnable){
LogUtil.w(TAG, "没超界");
}else if(TOUCH_EVENT_TYPE.EVENT_NULL == touchEventType){
LogUtil.w(TAG, "不需要处理事件");
}else if(TOUCH_EVENT_TYPE.EVENT_XY == touchEventType){
LogUtil.w(TAG, "需要拦截XY方向的事件");
getParent().requestDisallowInterceptTouchEvent(true);
}else{
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
if(null!=touchAnim && touchAnim.isRunning())
touchAnim.cancel();
getParent().requestDisallowInterceptTouchEvent(true);//ACTION_DOWN的时候,赶紧把事件hold住
break;
case MotionEvent.ACTION_MOVE:
if(TOUCH_EVENT_TYPE.EVENT_X == touchEventType){
if(Math.abs(event.getY()-mDownY)> Math.abs(event.getX() - mDownX)) {
getParent().requestDisallowInterceptTouchEvent(false);
LogUtil.i(TAG, "竖直滑动的距离大于水平的时候,将事件还给父控件");
}else {
getParent().requestDisallowInterceptTouchEvent(true);
LogUtil.i(TAG, "正常请求事件");
dispatchTouchEvent1(event);
}
}else if(TOUCH_EVENT_TYPE.EVENT_Y == touchEventType){
if(Math.abs(event.getX() - mDownX)> Math.abs(event.getY()-mDownY)) {
getParent().requestDisallowInterceptTouchEvent(false);
LogUtil.i(TAG, "水平滑动的距离大于竖直的时候,将事件还给父控件");
}else {
getParent().requestDisallowInterceptTouchEvent(true);
dispatchTouchEvent1(event);
LogUtil.i(TAG, "正常请求事件");
}
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
break;
}
}
return super.dispatchTouchEvent(event);
}
protected void dispatchTouchEvent1(MotionEvent event){
}
protected PointF lastTouchPoint;
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!touchEnable)
return false;
boolean result = mGestureDetector.onTouchEvent(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
lastTouchPoint = new PointF(event.getX(), event.getY());
onTouchMoved(lastTouchPoint);
return true;
case MotionEvent.ACTION_MOVE:
int move = 0;
if(TOUCH_EVENT_TYPE.EVENT_X == touchEventType){
move = (int)(event.getX() - lastTouchPoint.x);
}else if(TOUCH_EVENT_TYPE.EVENT_Y == touchEventType){
move = (int)(event.getY() - lastTouchPoint.y);
}
LogUtil.i(TAG, "MotionEvent.ACTION_MOVE"+move);
lastTouchPoint.x = (int)event.getX();
lastTouchPoint.y = (int)event.getY();
onTouchMoved(lastTouchPoint);
evaluatorFling(move);
invalidate();
return true;
case MotionEvent.ACTION_UP:
lastTouchPoint.x = 0;
lastTouchPoint.y = 0;
onTouchMoved(null);
return true;
}
return result;
}
protected void onTouchMoved(PointF point){
}
public void onDraw(Canvas canvas){
//画布背景
// canvas.drawColor(backColor);
if(debug) {
drawDebug(canvas);
}
drawDefult(canvas);
if(isLoading){
drawLoading(canvas);
return;
}
// drawChart(canvas);
if(!startDraw){
startDraw = true;
startAnimation(canvas);
}else{
drawChart(canvas);
}
}
public void drawLoading(Canvas canvas) {
paintLabel.setTextSize(35);
float NullTextLead = FontUtil.getFontLeading(paintLabel);
float NullTextHeight = FontUtil.getFontHeight(paintLabel);
float textY = centerPoint.y-NullTextHeight/2+NullTextLead;
paintLabel.setColor(getContext().getResources().getColor(R.color.text_color_def));
canvas.drawText(loadingStr, centerPoint.x- FontUtil.getFontlength(paintLabel, loadingStr)/2, textY, paintLabel);
}
/**绘制图表基本框架*/
public abstract void drawDefult(Canvas canvas);
/**绘制debug辅助线*/
public void drawDebug(Canvas canvas){
paint.setStyle(Paint.Style.STROKE);//设置空心
paint.setStrokeWidth(lineWidth);
//绘制边界--chart区域
paint.setColor(Color.BLUE);
RectF r = new RectF(0,0,getMeasuredWidth(), getMeasuredHeight());
canvas.drawRect(r, paint);
paint.setColor(Color.RED);
canvas.drawRect(rectChart, paint);
}
/**绘制图表*/
public abstract void drawChart(Canvas canvas);
/**创建动画*/
protected abstract ValueAnimator initAnim();
/**动画值变化之后计算数据*/
protected abstract void evaluatorData(ValueAnimator animation);
public void setAnimDuration(long duration){
this.animDuration = duration;
}
protected void startAnimation(Canvas canvas) {
if(anim!=null){
anim.cancel();
}
LogUtil.w(TAG, "开始绘制动画");
anim = initAnim();
if(anim==null){
drawChart(canvas);
}else{
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addUpdateListener((ValueAnimator animation)->{
evaluatorData(animation);
invalidate();
});
anim.setDuration(animDuration);
anim.start();
}
}
/**动画值变化之后计算数据*/
protected void evaluatorFling(float velocityAnim){}
protected int mMoveLen = 0; //滚动距离
protected boolean moveOver = false; //是否滚动到头了
/**手指松开后滑动动画效果*/
protected void startFlingAnimation(float velocity) {
if(touchAnim!=null){
touchAnim.cancel();
}
touchAnim = ValueAnimator.ofObject(new AngleEvaluator(), 1f, 0f);
touchAnim.setInterpolator(new DecelerateInterpolator()); //越来越慢
touchAnim.addUpdateListener((ValueAnimator animation)->{
float velocityAnim = (float)animation.getAnimatedValue();
evaluatorFling(velocity /100 * velocityAnim);
invalidate();
});
touchAnim.setDuration(1000+(int)+Math.abs(velocity)/4);
touchAnim.start();
}
class MyOnGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
LogUtil.e(TAG,"onFling------------>velocityX="+velocityX+" velocityY="+velocityY);
if(TOUCH_EVENT_TYPE.EVENT_X == touchEventType){
startFlingAnimation(velocityX);
}else if(TOUCH_EVENT_TYPE.EVENT_Y == touchEventType){
startFlingAnimation(velocityY);
}
return false;
}
}
}
@@ -0,0 +1,164 @@
package com.openxu.cview.chart;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.chart.bean.ChartLable;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import java.util.List;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : ProgressPieChart
* version : 1.0
* description : 进度图表
*/
public class ProgressPieChart extends BaseChart {
private int total; //总数量
private int progress; //占比数量
private float animPro; //动画计算的占比数量
private List<ChartLable> lableList; //需要绘制的lable集合
private int textSpace = DensityUtil.dip2px(getContext(), 3); //文字间距
private int raidus; //圆环半径
private int proSize = DensityUtil.dip2px(getContext(), 8); //圈宽度
private int proColor = Color.rgb(0, 255, 0); //进度环颜色
public ProgressPieChart(Context context) {
super(context, null);
}
public ProgressPieChart(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public ProgressPieChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
startAngle = -90; //开始的角度
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if(width<height){
rectChart = new RectF(0,(height-width)/2,width, (height+width)/2);
}else{
rectChart = new RectF((width-height)/2, 0, (width+height)/2, height);
}
raidus = (width<height?width:height)/2;
}
public void setProSize(int proSize) {
this.proSize = proSize;
}
public void setDefColor(int defColor) {
this.defColor = defColor;
}
public void setProColor(int proColor) {
this.proColor = proColor;
}
public void setData(int total, int progress, List<ChartLable> lableList){
this.total = total;
this.progress = progress;
this.lableList = lableList;
startDraw = false;
invalidate();
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
paint.setStyle(Paint.Style.STROKE);//设置空心
paint.setStrokeWidth(proSize);
paint.setColor(defColor);
canvas.drawCircle(centerPoint.x,centerPoint.y,raidus-proSize/2, paint);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(progress>0) {
paint.setStrokeWidth(proSize);
paint.setColor(proColor);
//1绘制开始圆点
// paint.setStyle(Paint.Style.FILL);
// canvas.drawCircle(centerPoint.x, centerPoint.y - raidus + proSize / 2, proSize / 2, paint);
//2绘制进度圆弧
paint.setStyle(Paint.Style.STROKE);//设置空心
int r = raidus - proSize / 2;
float arcLeft = centerPoint.x - r;
float arcTop = centerPoint.y - r;
float arcRight = centerPoint.x + r;
float arcBottom = centerPoint.y + r;
canvas.drawArc(new RectF(arcLeft, arcTop, arcRight, arcBottom), startAngle, animPro, false, paint);
//3绘制结束圆点
// paint.setStyle(Paint.Style.FILL);
// float endX = centerPoint.x + (float) (r * Math.cos(Math.toRadians(startAngle + animPro)));
// float endY = centerPoint.y + (float) (r * Math.sin(Math.toRadians(startAngle + animPro)));
// canvas.drawCircle(endX, endY, proSize / 2, paint);
}
//4绘制文字
if(lableList!=null &&lableList.size()>0){
float textAllHeight = 0;
float textW, textH, textL;
for(ChartLable lable : lableList){
paintLabel.setTextSize(lable.getTextSize());
textH = FontUtil.getFontHeight(paintLabel);
textAllHeight += (textH+textSpace);
}
textAllHeight -= textSpace;
int top = (int)(centerPoint.y-textAllHeight/2);
for(ChartLable lable : lableList){
paintLabel.setColor(lable.getTextColor());
paintLabel.setTextSize(lable.getTextSize());
textW = FontUtil.getFontlength(paintLabel, lable.getText());
textH = FontUtil.getFontHeight(paintLabel);
textL = FontUtil.getFontLeading(paintLabel);
canvas.drawText(lable.getText(), centerPoint.x-textW/2, top + textL, paintLabel);
top += (textH+textSpace);
}
}
}
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(progress>0) {
float angle = (float) progress / (float) total * 360;
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, angle);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,22 @@
package com.openxu.cview.chart.anim;
import android.animation.TypeEvaluator;
/**
* autour : openXu
* date : 2017/7/28 9:12
* className : AngleEvaluator
* version : 1.0
* description : 角度计算器
*/
public class AngleEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
float start = (float)startValue;
float end = (float)endValue;
return start+fraction*(end-start);
}
}
@@ -0,0 +1,383 @@
package com.openxu.cview.chart.barchart;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.BaseChart;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.chart.bean.BarBean;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import java.util.ArrayList;
import java.util.List;
import static com.openxu.cview.chart.BaseChart.TOUCH_EVENT_TYPE.EVENT_Y;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : BarHorizontalChart
* version : 1.0
* description : 横向(柱子水平) 柱状图,支持多柱
*/
public class BarHorizontalChart extends BaseChart {
private List<List<BarBean>> dataList;
private List<String> strList;
/**可以设置的属性*/
private int barNum = 2; //柱子数量
private int XMARK_NUM = 5; //X轴刻度数量
private int barWidth = DensityUtil.dip2px(getContext(), 15); //柱宽度
private int barSpace = DensityUtil.dip2px(getContext(), 1); //双柱间的间距
private int barItemSpace = DensityUtil.dip2px(getContext(), 25);//一组柱之间的间距
private int[] barColor = new int[]{Color.BLUE, Color.YELLOW}; //柱颜色
private int textSizeCoordinate = (int)getResources().getDimension(R.dimen.text_size_level_small); //坐标文字大小
private int textColorCoordinate = getResources().getColor(R.color.text_color_light_gray);
private int textSizeTag = (int)getResources().getDimension(R.dimen.text_size_level_small); //数值字体
private int textColorTag = getResources().getColor(R.color.text_color_light_gray);
private int textSpace = DensityUtil.dip2px(getContext(), 3); //默认的字与其他的间距
/**需要计算相关值*/
private int oneBarW; //单个宽度,需要计算
private int YMARK_MAX_WIDTH = DensityUtil.dip2px(getContext(), 30); //Y轴刻度最大值的宽度
private int XMARK_H, XMARK_ALL_H; //Y轴刻度间距值
private int topStartPointY; //从左侧开始绘制的X坐标
private int minTopPointY; //滑动到最右侧时的X坐标
private PointF zeroPoint = new PointF(); //柱状图图体圆点坐标
/*字体绘制相关*/
private int XMARK = 1; //X刻度间隔
private int XMARK_MAX = 1; //X轴刻度最大值(根据设置的数据自动赋值)
private int heightCoordinate;
private int leadCoordinate;
private int heightTag;
private int leadTag;
public BarHorizontalChart(Context context) {
this(context, null);
}
public BarHorizontalChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BarHorizontalChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
touchEventType = EVENT_Y;
dataList = new ArrayList<>();
strList = new ArrayList<>();
}
/***********************************设置属性set方法**********************************/
public void setBarNum(int barNum) {
this.barNum = barNum;
}
public void setXMARK_NUM(int XMARK_NUM) {
this.XMARK_NUM = XMARK_NUM;
}
public void setBarWidth(int barWidth) {
this.barWidth = barWidth;
}
public void setBarSpace(int barSpace) {
this.barSpace = barSpace;
}
public void setBarItemSpace(int barItemSpace) {
this.barItemSpace = barItemSpace;
}
public void setBarColor(int[] barColor) {
this.barColor = barColor;
}
public void setTextSizeCoordinate(int textSizeCoordinate) {
this.textSizeCoordinate = textSizeCoordinate;
}
public void setTextColorCoordinate(int textColorCoordinate) {
this.textColorCoordinate = textColorCoordinate;
}
public void setTextSizeTag(int textSizeTag) {
this.textSizeTag = textSizeTag;
}
public void setTextColorTag(int textColorTag) {
this.textColorTag = textColorTag;
}
public void setTextSpace(int textSpace) {
this.textSpace = textSpace;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
evaluatorByData();
postInvalidate();
}
/***************************************************/
/**滑动*/
@Override
protected void evaluatorFling(float fling) {
LogUtil.i(TAG, "fling = "+fling);
//已经滚动到顶部或者底部
if(topStartPointY + mMoveLen + fling <= minTopPointY){
mMoveLen = minTopPointY-topStartPointY;
moveOver = true;
if(null!=touchAnim && touchAnim.isRunning())
touchAnim.cancel();
}else if(topStartPointY + mMoveLen+ fling>=topStartPointY){
mMoveLen = 0;
moveOver = true;
if(null!=touchAnim && touchAnim.isRunning())
touchAnim.cancel();
}else{
moveOver = false;
mMoveLen += fling;
}
}
/***************************************************/
/**
* 设置数据
* @param dataList 柱状图数据
* @param strYList Y轴坐标数据
*/
public void setData(List<List<BarBean>> dataList, List<String> strYList){
this.strList.clear();
this.dataList.clear();
if(dataList!=null)
this.dataList.addAll(dataList);
if(strYList!=null)
this.strList.addAll(strYList);
if(rectChart!=null) {
evaluatorByData();
startDraw = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
total = 0;
//三种字体计算
paintLabel.setTextSize(textSizeCoordinate);
heightCoordinate = (int) FontUtil.getFontHeight(paintLabel);
leadCoordinate = (int)FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(textSizeTag);
heightTag = (int)FontUtil.getFontHeight(paintLabel);
leadTag = (int)FontUtil.getFontLeading(paintLabel);
//zeroPoint坐标
if(strList!=null && strList.size()>0){
YMARK_MAX_WIDTH = 0;
paintLabel.setTextSize(textSizeCoordinate);
for(String xStr : strList){
int tw = (int)FontUtil.getFontlength(paintLabel, xStr);
YMARK_MAX_WIDTH = tw>YMARK_MAX_WIDTH?tw:YMARK_MAX_WIDTH;
}
}
zeroPoint.x = rectChart.left + YMARK_MAX_WIDTH + textSpace;
zeroPoint.y = heightCoordinate + textSpace;
/*计算X刻度最大值*/
XMARK_MAX = 1;
for(List<BarBean> list : dataList){
for(BarBean bean : list){
total += bean.getNum();
XMARK_MAX = (int)bean.getNum()>XMARK_MAX?(int)bean.getNum():XMARK_MAX;
}
}
LogUtil.i(TAG, "真实XMARK_MAX="+XMARK_MAX);
if (XMARK_MAX<=5)
XMARK_MAX = 5;
XMARK = XMARK_MAX/XMARK_NUM + 1;
int MARK = (Integer.parseInt((XMARK+"").substring(0,1))+1);
if((XMARK+"").length()==1){
//XMARK = 1、2、5、10
XMARK = (XMARK==3||XMARK==4||XMARK==6||XMARK==7||XMARK==8||XMARK==9)?((XMARK==3||XMARK==4)?5:10):XMARK;
}else if((XMARK+"").length()==2){
XMARK = MARK*10;
}else if((XMARK+"").length()==3){
XMARK = MARK*100;
}else if((XMARK+"").length()==4){
XMARK = MARK*1000;
}else if((XMARK+"").length()==5){
XMARK = MARK*10000;
}else if((XMARK+"").length()==6){
XMARK = MARK*100000;
}
XMARK_MAX = XMARK*XMARK_NUM;
LogUtil.i(TAG, "计算XMARK_MAX="+XMARK_MAX+" XMARK="+XMARK);
//Y轴刻度间距值
XMARK_ALL_H = (int)(rectChart.right-zeroPoint.x);
XMARK_H = (int)((float)XMARK_ALL_H/XMARK_NUM);
//单份柱子(包含多个及其间距)宽度
oneBarW = (barWidth*barNum)+(barSpace*(barNum-1));
topStartPointY = (int)zeroPoint.y+barItemSpace/2; //从左侧开始绘制的X坐标
int allW = (oneBarW+barItemSpace)*dataList.size(); //每份柱子宽度+item间距(其中包含第一个item左边和最后一个item右边的半份间距)
int contentW = (int)(rectChart.bottom-zeroPoint.y);
touchEnable = allW>contentW;
if(touchEnable){
//超出总宽度了
minTopPointY = -allW+(int)(rectChart.bottom-topStartPointY-barItemSpace);
}
LogUtil.w(TAG, "柱状图表宽高:"+getMeasuredWidth()+"*"+getMeasuredHeight()+
" 图表范围"+rectChart+" 圆点坐标zeroPoint="+zeroPoint);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
canvas.drawLine(zeroPoint.x, zeroPoint.y, rectChart.right, zeroPoint.y, paint);
if(touchEnable){
//超出高度了
canvas.drawLine(zeroPoint.x, zeroPoint.y,zeroPoint.x , rectChart.bottom, paint);
}else{
canvas.drawLine(zeroPoint.x, zeroPoint.y,zeroPoint.x , zeroPoint.y+(oneBarW+barItemSpace)*dataList.size(), paint);
}
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
int topStart = topStartPointY + mMoveLen;
// LogUtil.i(TAG, "leftStart:"+leftStart+" zezeroPoint"+zeroPoint+" barItemSpace="+barItemSpace+" barWidth="+barWidth +" oneBarW="+oneBarW);
paint.setStyle(Paint.Style.FILL);
LogUtil.w(TAG, "");
for(int i = 0; i<dataList.size(); i++){
String str = strList.get(i);
List<BarBean> list = dataList.get(i);
//柱子开始绘制的Y坐标
int topY = topStart +(oneBarW+barItemSpace)*i;
//绘制X刻度
paintLabel.setTextSize(textSizeCoordinate);
paintLabel.setColor(textColorCoordinate);
float tw = FontUtil.getFontlength(paintLabel, str);
canvas.drawText(str, zeroPoint.x-textSpace-tw, topY + oneBarW/2-heightCoordinate/2+leadCoordinate,paintLabel);
//绘制辅助线
paint.setStrokeWidth(0.5f);
paint.setColor(defColor);
canvas.drawLine(zeroPoint.x, topY-15, rectChart.right, topY-15, paint);
paintLabel.setTextSize(textSizeTag);
paintLabel.setColor(textColorTag);
for(int j = 0 ;j <list.size(); j++){
BarBean bean = list.get(j);
//绘制辅助线
paint.setStrokeWidth(0.5f);
paint.setColor(defColor);
canvas.drawLine(zeroPoint.x, topY+barWidth/2, rectChart.right, topY+barWidth/2, paint);
//绘制柱子
paint.setColor(barColor[j]);
float right = (zeroPoint.x+XMARK_ALL_H*(bean.getNum()/XMARK_MAX)*animPro);
RectF br = new RectF(zeroPoint.x , topY , right, topY+barWidth);
canvas.drawRect(br, paint);
//绘制柱子占比
str = (int)bean.getNum()+"";
canvas.drawText(str, right+textSpace, topY +barWidth/2 -heightTag/2+ leadTag,paintLabel);
// LogUtil.d(TAG, "绘制bar:"+(br)+" "+getMeasuredWidth()+"*"+getMeasuredHeight());
topY += (barWidth+barSpace);
}
//绘制辅助线
paint.setStrokeWidth(0.5f);
paint.setColor(defColor);
canvas.drawLine(zeroPoint.x, topY-barSpace+15, rectChart.right, topY-barSpace+15, paint);
}
drawYmark(canvas);
}
private void drawYmark(Canvas canvas) {
//遮盖超出的柱状
paint.setStyle(Paint.Style.FILL);
paint.setColor(backColor);
canvas.drawRect(new RectF(0,0,rectChart.right, zeroPoint.y-lineWidth), paint);
canvas.drawRect(new RectF(0,rectChart.bottom,rectChart.right, getMeasuredHeight()), paint);
paintLabel.setTextSize(textSizeCoordinate);
paintLabel.setColor(textColorCoordinate);
for(int i = 0; i<=XMARK_MAX/XMARK; i++){
//绘制X刻度
String textX = (XMARK*i)+"";
float tw = FontUtil.getFontlength(paintLabel, textX);
// canvas.drawText(textX, getPaddingLeft()+(YMARK_MAX_WIDTH-tw), zeroPoint.y-(i*XMARK_H) - heightCoordinate/2+leadCoordinate,paintLabel);
canvas.drawText(textX, (int)(zeroPoint.x + (i*XMARK_H) - tw/2), zeroPoint.y-textSpace-heightCoordinate+leadCoordinate,paintLabel);
}
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,540 @@
package com.openxu.cview.chart.barchart;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.cview.chart.BaseChart;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.chart.bean.BarBean;
import com.openxu.cview.R;
import java.util.ArrayList;
import java.util.List;
import static com.openxu.cview.chart.BaseChart.TOUCH_EVENT_TYPE.EVENT_X;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : BarVerticalChart
* version : 1.0
* description : 竖向(柱子竖直) 柱状图,支持多柱
*/
public class BarVerticalChart extends BaseChart {
private List<List<BarBean>> dataList;
private List<String> strList;
/**
* 可以设置的属性
*/
private boolean showLable = true; //是否显示下面的lable
private boolean showEnd = true; //当数据超出一屏宽度时,实现最后的数据
private int barNum = 2; //柱子数量
private int YMARK_NUM = 5; //Y轴刻度数量
private int barWidth = DensityUtil.dip2px(getContext(), 15); //柱宽度
private int barSpace = DensityUtil.dip2px(getContext(), 1); //双柱间的间距
private int barItemSpace = DensityUtil.dip2px(getContext(), 25);//一组柱之间的间距
private int[] barColor = new int[]{Color.BLUE, Color.YELLOW}; //柱颜色
private int rectW = DensityUtil.dip2px(getContext(), 10); //lable矩形宽高
private int rectH = DensityUtil.dip2px(getContext(), 10);
private int textSizeCoordinate = (int) getResources().getDimension(R.dimen.text_size_level_small); //坐标文字大小
private int textColorCoordinate = getResources().getColor(R.color.text_color_light_gray);
private int textSizeTag = (int) getResources().getDimension(R.dimen.text_size_level_small); //数值字体
private int textColorTag = getResources().getColor(R.color.text_color_light_gray);
private int textSizeLable = (int) getResources().getDimension(R.dimen.text_size_level_small); //lable字体
private int textColorLable = getResources().getColor(R.color.text_color_light_gray);
private int textSpace = DensityUtil.dip2px(getContext(), 3); //默认的字与其他的间距
private int textLableSpace = DensityUtil.dip2px(getContext(), 10); //默认的lable字与其他的间距
private int lableItemSpace = DensityUtil.dip2px(getContext(), 30); //默认的lable字与其他的间距
private int lableTopSpace = DensityUtil.dip2px(getContext(), 20); //默认的lable字与其他的间距
/**
* 需要计算相关值
*/
private int oneBarW; //单个宽度,需要计算
private int YMARK_MAX_WIDTH; //Y轴刻度最大值的宽度
private int YMARK_H, YMARK_ALL_H; //Y轴刻度间距值
private int leftStartPointX; //从左侧开始绘制的X坐标
private int minLeftPointX; //滑动到最右侧时的X坐标
private PointF zeroPoint = new PointF(); //柱状图图体圆点坐标
private RectF lableRect;
/*字体绘制相关*/
private int YMARK = 1; //Y轴刻度最大值(根据设置的数据自动赋值)
private int YMARK_MAX = 1; //Y轴刻度最大值(根据设置的数据自动赋值)
private int heightCoordinate;
private int leadCoordinate;
private int heightTag;
private int leadTag;
private int heightLable;
private int leadLable;
private float animPro; //动画计算的占比数量
public BarVerticalChart(Context context) {
this(context, null);
}
public BarVerticalChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BarVerticalChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
touchEventType = EVENT_X;
dataList = new ArrayList<>();
strList = new ArrayList<>();
}
public void setShowEnd(boolean showEnd) {
this.showEnd = showEnd;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
evaluatorByData();
postInvalidate();
}
/**
* 设置数据后,计算相关值
*/
private void evaluatorByData() {
total = 0;
//三种字体计算
paintLabel.setTextSize(textSizeCoordinate);
heightCoordinate = (int) FontUtil.getFontHeight(paintLabel);
leadCoordinate = (int) FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(textSizeTag);
heightTag = (int) FontUtil.getFontHeight(paintLabel);
leadTag = (int) FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(textSizeLable);
heightLable = (int) FontUtil.getFontHeight(paintLabel);
leadLable = (int) FontUtil.getFontLeading(paintLabel);
//下方lable范围计算
int lableH = 0;
if (showLable) {
lableH = (heightLable > rectH ? heightLable : rectH) + lableTopSpace;
lableRect = new RectF(rectChart.left, getMeasuredHeight() - getPaddingBottom() - lableH,
rectChart.right, rectChart.bottom);
}
//Y轴开始绘制的坐标 = 控件高度-padingB-lable高度-X轴刻度字高度-X刻度间距
zeroPoint.y = getMeasuredHeight() - getPaddingBottom() - lableH - heightCoordinate - textSpace;
LogUtil.w(TAG, "lableRect:" + lableRect + " lableH=" + lableH + " heightCoordinate=" + heightCoordinate + " textSpace=" + textSpace);
YMARK_MAX = 1;
/*计算Y刻度最大值*/
for (List<BarBean> list : dataList) {
for (BarBean bean : list) {
total += bean.getNum();
YMARK_MAX = (int) bean.getNum() > YMARK_MAX ? (int) bean.getNum() : YMARK_MAX;
}
}
LogUtil.i(TAG, "真实YMARK_MAX=" + YMARK_MAX);
if (YMARK_MAX <= 5)
YMARK_MAX = 5;
YMARK = YMARK_MAX / YMARK_NUM + 1;
int MARK = (Integer.parseInt((YMARK + "").substring(0, 1)) + 1);
if ((YMARK + "").length() == 1) {
//YMARK = 1、2、5、10
YMARK = (YMARK == 3 || YMARK == 4 || YMARK == 6 || YMARK == 7 || YMARK == 8 || YMARK == 9) ? ((YMARK == 3 || YMARK == 4) ? 5 : 10) : YMARK;
} else if ((YMARK + "").length() == 2) {
YMARK = MARK * 10;
} else if ((YMARK + "").length() == 3) {
YMARK = MARK * 100;
} else if ((YMARK + "").length() == 4) {
YMARK = MARK * 1000;
} else if ((YMARK + "").length() == 5) {
YMARK = MARK * 10000;
} else if ((YMARK + "").length() == 6) {
YMARK = MARK * 100000;
}
YMARK_MAX = YMARK * YMARK_NUM;
LogUtil.i(TAG, "计算YMARK_MAX=" + YMARK_MAX + " YMARK=" + YMARK);
//Y轴刻度间距值
YMARK_ALL_H = (int) (zeroPoint.y - getPaddingTop() - heightCoordinate);
YMARK_H = (int) ((float) YMARK_ALL_H / YMARK_NUM);
//单份柱子(包含多个及其间距)宽度
oneBarW = (barWidth * barNum) + (barSpace * (barNum - 1));
int allW = (oneBarW + barItemSpace) * dataList.size(); //每份柱子宽度+item间距(其中包含第一个item左边和最后一个item右边的半份间距)
paintLabel.setTextSize(textSizeCoordinate);
if(drawLine){
YMARK_MAX_WIDTH = (int) FontUtil.getFontlength(paintLabel, YMARK_MAX + "");
}else{
YMARK_MAX_WIDTH = 0;
}
int contentW = (int) (rectChart.right - rectChart.left - YMARK_MAX_WIDTH - textSpace);
touchEnable = allW > contentW;
zeroPoint.x = (int) rectChart.left + YMARK_MAX_WIDTH + textSpace;
leftStartPointX = (int) zeroPoint.x + barItemSpace / 2; //从左侧开始绘制的X坐标
if (touchEnable) {
//超出总宽度了
minLeftPointX = -allW + (int) rectChart.right;
mMoveLen = showEnd?(minLeftPointX - leftStartPointX):0;
} else {
minLeftPointX = 0;
mMoveLen = 0;
}
LogUtil.w(TAG, "柱状图表宽高:" + getMeasuredWidth() + "*" + getMeasuredHeight() +
" 图表范围" + rectChart + " 圆点坐标zeroPoint=" + zeroPoint);
LogUtil.w(TAG, "YMARK_MAX=" + YMARK_MAX + " YMARK=" + YMARK + " YMARK_H=" + YMARK_H + " YMARK_MAX_WIDTH=" + YMARK_MAX_WIDTH);
LogUtil.w(TAG, "minLeftPointX=" + minLeftPointX + " mMoveLen=" + mMoveLen + " leftStartPointX=" + leftStartPointX);
}
/**
* 绘制图表基本框架
*/
@Override
public void drawDefult(Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
//float startX, float startY, float stopX, float stopY
for (int i = 0; i <= YMARK_MAX / YMARK; i++) {
//绘制横向X轴刻度
float startX = zeroPoint.x;
float startY = zeroPoint.y - (i * YMARK_H);
float stopX = rectChart.right;
float stopY = startY;
if (drawLine) {
canvas.drawLine(startX, startY, stopX, stopY, paint);
}
// 绘制最下面的基线
if(drawBottomLine&&i==0){
canvas.drawLine(startX, startY, stopX, stopY, paint);
}
// LogUtil.w(TAG, "绘制直线start="+startX+"*"+startY+" stop="+stopX+"*"+stopY);
//绘制Y刻度
// paintLabel.setTextSize(textSizeCoordinate);
// String textY = (YMARK*i)+"";
// float tw = FontUtil.getFontlength(paintLabel, textY);
// canvas.drawText(textY, getPaddingLeft()+(YMARK_MAX_WIDTH-tw), startY - heightCoordinate/2+leadCoordinate,paintLabel);
}
//绘制左侧Y轴
if (drawLine) {
canvas.drawLine(zeroPoint.x + lineWidth / 2, zeroPoint.y, zeroPoint.x + lineWidth / 2, getPaddingTop(), paint);
}
if (showLable) {
//绘制lable
List<String> lableList = new ArrayList<>();
if (dataList.size() > 0) {
for (BarBean bean : dataList.get(0)) {
lableList.add(bean.getName());
}
}
paintLabel.setTextSize(textSizeLable);
paintLabel.setColor(textColorLable);
if (lableList != null && lableList.size() > 0) {
int lableAllW = 0;
for (int i = 0; i < lableList.size(); i++) {
lableAllW += (rectW + textLableSpace + FontUtil.getFontlength(paintLabel, lableList.get(i)));
}
lableAllW += (lableItemSpace * lableList.size() - 1);
int lableStart = 0;
if (lableAllW > ((int) (lableRect.right - lableRect.left))) {
//超过了,从left开始画
lableStart = (int) lableRect.left;
} else {
//没超过,话中间
lableStart = (int) (lableRect.left + ((lableRect.right - lableRect.left) / 2 - lableAllW / 2));
}
int left = lableStart;
for (int i = 0; i < lableList.size(); i++) {
String lable = lableList.get(i);
int tw = (int) FontUtil.getFontlength(paintLabel, lable);
paint.setColor(barColor[i]);
float rectTop = lableRect.bottom - rectH;
RectF rect = new RectF(left, rectTop, left + rectW, rectTop + rectH);
canvas.drawRect(rect, paint);
left += (rectW + textLableSpace);
rectTop = rectTop + rectH / 2 - heightLable / 2 + leadLable;
canvas.drawText(lable, left, rectTop, paintLabel);
left += (tw + lableItemSpace);
}
}
}
}
// 是否绘制最基础的线
public void setBaseLineAndText(boolean drawLine) {
this.drawLine = drawLine;
}
// 是否绘制最下面的基线
public void setBottomLine(boolean drawBottomLine) {
this.drawBottomLine = drawBottomLine;
}
/**
* 绘制debug辅助线
*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
paint.setStyle(Paint.Style.STROKE);//设置空心
paint.setStrokeWidth(lineWidth);
paint.setColor(Color.BLACK);
canvas.drawRect(lableRect, paint);
}
/**
* 绘制图表
*/
@Override
public void drawChart(Canvas canvas) {
int leftStart = leftStartPointX + mMoveLen;
// LogUtil.i(TAG, "leftStart:"+leftStart+" zezeroPoint"+zeroPoint+" barItemSpace="+barItemSpace+" barWidth="+barWidth +" oneBarW="+oneBarW);
paint.setStyle(Paint.Style.FILL);
LogUtil.w(TAG, "");
for (int i = 0; i < dataList.size(); i++) {
String str = strList.get(i);
List<BarBean> list = dataList.get(i);
//柱子开始绘制的X坐标
int leftX = leftStart + (oneBarW + barItemSpace) * i;
//绘制X刻度
paintLabel.setTextSize(textSizeCoordinate);
paintLabel.setColor(textColorCoordinate);
float tw = FontUtil.getFontlength(paintLabel, str);
canvas.drawText(str, leftX + oneBarW / 2 - tw / 2, zeroPoint.y + textSpace + leadCoordinate, paintLabel);
// LogUtil.i(TAG, "绘制X刻度:leftX="+leftX +" "+(leftX + oneBarW/2-tw/2)+"*"+(zeroPoint.y+textSpace + leadCoordinate));
// LogUtil.i(TAG, "leftStartPointX="+leftStartPointX+" leftStart="+leftStart+ " oneBarW="+oneBarW +" barWidth="+barWidth+" barSpace="+barSpace);
paintLabel.setTextSize(textSizeTag);
paintLabel.setColor(textColorTag);
for (int j = 0; j < list.size(); j++) {
BarBean bean = list.get(j);
paint.setColor(barColor[j]);
float top = (zeroPoint.y - YMARK_ALL_H * (bean.getNum() / YMARK_MAX) * animPro);
RectF br = new RectF(leftX, top, leftX + barWidth, zeroPoint.y);
canvas.drawRect(br, paint);
str = (int) bean.getNum() + "";
tw = FontUtil.getFontlength(paintLabel, str);
canvas.drawText(str, leftX + barWidth / 2 - tw / 2, top - textSpace - heightTag + leadTag, paintLabel);
// LogUtil.d(TAG, "绘制bar:"+(br)+" "+getMeasuredWidth()+"*"+getMeasuredHeight());
leftX += (barWidth + barSpace);
}
}
if (drawLine) {
drawYmark(canvas);
}
}
private void drawYmark(Canvas canvas) {
//遮盖超出的柱状
paint.setStyle(Paint.Style.FILL);
paint.setColor(backColor);
canvas.drawRect(new RectF(0, 0, zeroPoint.x, lableRect.top), paint);
canvas.drawRect(new RectF(rectChart.right, 0, getMeasuredWidth(), lableRect.top), paint);
// LogUtil.d(TAG, "遮盖超出的柱状:"+new RectF(0,0,zeroPoint.x, lableRect.top));
// LogUtil.d(TAG, "遮盖超出的柱状:"+new RectF(rectChart.right,0,getMeasuredWidth(), lableRect.top));
paintLabel.setTextSize(textSizeCoordinate);
paintLabel.setColor(textColorCoordinate);
for (int i = 0; i <= YMARK_MAX / YMARK; i++) {
//绘制Y刻度
String textY = (YMARK * i) + "";
float tw = FontUtil.getFontlength(paintLabel, textY);
canvas.drawText(textY, getPaddingLeft() + (YMARK_MAX_WIDTH - tw), zeroPoint.y - (i * YMARK_H) - heightCoordinate / 2 + leadCoordinate, paintLabel);
}
}
/**
* 创建动画
*/
@Override
protected ValueAnimator initAnim() {
if (dataList.size() > 0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**
* 动画值变化之后计算数据
*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float) animation.getAnimatedValue();
}
/***************************************************/
@Override
protected void evaluatorFling(float fling) {
LogUtil.i(TAG, "fling = " + fling);
if (leftStartPointX + mMoveLen + fling <= minLeftPointX) {
//最右侧
mMoveLen = minLeftPointX - leftStartPointX;
if (null != touchAnim && touchAnim.isRunning())
touchAnim.cancel();
} else if (leftStartPointX + mMoveLen + fling >= leftStartPointX) {
//最左侧
mMoveLen = 0;
if (null != touchAnim && touchAnim.isRunning())
touchAnim.cancel();
} else {
mMoveLen += fling;
}
}
/***********************************设置属性set方法**********************************/
public void setRectW(int rectW) {
this.rectW = rectW;
}
public void setRectH(int rectH) {
this.rectH = rectH;
}
public void setShowLable(boolean showLable) {
this.showLable = showLable;
}
public void setBarNum(int barNum) {
this.barNum = barNum;
}
public void setYMARK_NUM(int YMARK_NUM) {
this.YMARK_NUM = YMARK_NUM;
}
public void setBarWidth(int barWidth) {
this.barWidth = barWidth;
}
public void setBarSpace(int barSpace) {
this.barSpace = barSpace;
}
public void setBarItemSpace(int barItemSpace) {
this.barItemSpace = barItemSpace;
}
public void setBarColor(int[] barColor) {
this.barColor = barColor;
}
public void setTextSizeCoordinate(int textSizeCoordinate) {
this.textSizeCoordinate = textSizeCoordinate;
}
public void setTextColorCoordinate(int textColorCoordinate) {
this.textColorCoordinate = textColorCoordinate;
}
/***************************************************/
public void setTextSizeTag(int textSizeTag) {
this.textSizeTag = textSizeTag;
}
public void setTextColorTag(int textColorTag) {
this.textColorTag = textColorTag;
}
public void setTextSizeLable(int textSizeLable) {
this.textSizeLable = textSizeLable;
}
public void setTextColorLable(int textColorLable) {
this.textColorLable = textColorLable;
}
public void setTextSpace(int textSpace) {
this.textSpace = textSpace;
}
public void setTextLableSpace(int textLableSpace) {
this.textLableSpace = textLableSpace;
}
public void setLableItemSpace(int lableItemSpace) {
this.lableItemSpace = lableItemSpace;
}
public void setLableTopSpace(int lableTopSpace) {
this.lableTopSpace = lableTopSpace;
}
/**
* 设置数据
*
* @param dataList 柱状图数据
* @param strXList X轴坐标数据
*/
public void setData(List<List<BarBean>> dataList, List<String> strXList) {
LogUtil.w(TAG, "柱状图设置数据" + dataList);
this.strList.clear();
this.dataList.clear();
if (dataList != null)
this.dataList.addAll(dataList);
if (strXList != null)
this.strList.addAll(strXList);
if (rectChart!=null) {
evaluatorByData();
startDraw = false;
invalidate();
}
}
}
@@ -0,0 +1,69 @@
package com.openxu.cview.chart.bean;
import android.graphics.RectF;
import android.graphics.Region;
/**
* autour : openXu
* date : 2017/7/24 11:04
* className : PieChartBean
* version : 1.0
* description : 柱状图数据
*/
public class BarBean {
private float num;
private String name;
//bar绘制矩形
private RectF arcRect;
//触摸相关
private Region region; //扇形区域--用于判断手指触摸点是否在此范围
@Override
public String toString() {
return "BarBean{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
public BarBean() {
}
public BarBean(float num, String name) {
this.num = num;
this.name = name;
}
public float getNum() {
return num;
}
public void setNum(float num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public RectF getArcRect() {
return arcRect;
}
public void setArcRect(RectF arcRect) {
this.arcRect = arcRect;
}
public Region getRegion() {
return region;
}
public void setRegion(Region region) {
this.region = region;
}
}
@@ -0,0 +1,55 @@
package com.openxu.cview.chart.bean;
/**
* autour : openXu
* date : 2017/10/11 14:08
* className : ChartLable
* version : 1.0
* description : 图表lable文字对象,包含文字大小和颜色
*/
public class ChartLable {
private String text;
private int textSize;
private int textColor;
public ChartLable(String text, int textSize, int textColor) {
this.text = text;
this.textSize = textSize;
this.textColor = textColor;
}
public ChartLable() {
}
@Override
public String toString() {
return "ChartLable{" +
"text='" + text + '\'' +
", textSize=" + textSize +
", textColor=" + textColor +
'}';
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public int getTextSize() {
return textSize;
}
public void setTextSize(int textSize) {
this.textSize = textSize;
}
public int getTextColor() {
return textColor;
}
public void setTextColor(int textColor) {
this.textColor = textColor;
}
}
@@ -0,0 +1,133 @@
package com.openxu.cview.chart.bean;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import java.util.List;
/**
* autour : openXu
* date : 2017/7/24 11:04
* className : PieChartBean
* version : 1.0
* description : 饼状图数据
*/
public class PieChartBean {
private float num;
private String name;
//触摸相关
private Region region; //扇形区域--用于判断手指触摸点是否在此范围
private RectF rectLable; //矩形区域--用于判断手指触摸点是否在此范围
//扇形相关
private RectF arcRect;
private float startAngle;
private float sweepAngle;
//tag线段
private List<PointF> tagLinePoints;
private String tagStr;
private PointF tagTextPoint;
// //右侧lable
// private RectF colorRect;
// private PointF nameTextPoint;
// private PointF perTextPoint;
// private PointF dashPathPointStart;
// private PointF dashPathPointEnd;
@Override
public String toString() {
return "RoseChartBean{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
public PieChartBean(float num, String name) {
this.num = num;
this.name = name;
}
public String getTagStr() {
return tagStr;
}
public void setTagStr(String tagStr) {
this.tagStr = tagStr;
}
public PointF getTagTextPoint() {
return tagTextPoint;
}
public void setTagTextPoint(PointF tagTextPoint) {
this.tagTextPoint = tagTextPoint;
}
public List<PointF> getTagLinePoints() {
return tagLinePoints;
}
public void setTagLinePoints(List<PointF> tagLinePoints) {
this.tagLinePoints = tagLinePoints;
}
public RectF getArcRect() {
return arcRect;
}
public void setArcRect(RectF arcRect) {
this.arcRect = arcRect;
}
public float getStartAngle() {
return startAngle;
}
public void setStartAngle(float startAngle) {
this.startAngle = startAngle;
}
public float getSweepAngle() {
return sweepAngle;
}
public void setSweepAngle(float sweepAngle) {
this.sweepAngle = sweepAngle;
}
public RectF getRectLable() {
return rectLable;
}
public void setRectLable(RectF rectLable) {
this.rectLable = rectLable;
}
public Region getRegion() {
return region;
}
public void setRegion(Region region) {
this.region = region;
}
public float getNum() {
return num;
}
public void setNum(float num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,67 @@
package com.openxu.cview.chart.dashboard;
/**
* autour : openXu
* date : 2018/4/20 15:14
* className : DashBoardItem
* version : 1.0
* description : 请添加类说明
*/
public class DashBoardItem {
private int color;
private String lable;
private float num;
private float angle;
public DashBoardItem() {
}
public DashBoardItem(int color, String lable, float num) {
this.color = color;
this.lable = lable;
this.num = num;
}
@Override
public String toString() {
return "DashBoardItem{" +
"color=" + color +
", lable='" + lable + '\'' +
", num=" + num +
", angle=" + angle +
'}';
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public String getLable() {
return lable;
}
public void setLable(String lable) {
this.lable = lable;
}
public float getNum() {
return num;
}
public void setNum(float num) {
this.num = num;
}
}
@@ -0,0 +1,270 @@
package com.openxu.cview.chart.dashboard;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.BaseChart;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : openXu
* date : 2018/4/20 15:12
* className : DashBoardView
* version : 1.0
* description : 仪表
*/
public class DashBoardView extends BaseChart {
private List<DashBoardItem> dataList;
/**设置的属性*/
private int ringWidth = DensityUtil.dip2px(getContext(), 20);//圆环宽度
private int textSizeCoordinate = (int)getResources().getDimension(R.dimen.ts_barchart_x);
private int textSizeLable = (int)getResources().getDimension(R.dimen.ts_barchart_lable);
private int textColorCoordinate = getResources().getColor(R.color.text_color_light_gray);
private int textColorLable = getResources().getColor(R.color.text_color_def);
private int textXSpace = DensityUtil.dip2px(getContext(), 6);
private int textLableSpace = DensityUtil.dip2px(getContext(), 10);
//小圆圈半径
private int lableCircleSize = DensityUtil.dip2px(getContext(), 4);
//指针半径
private int centerPointSize = DensityUtil.dip2px(getContext(), 5);
/**常量*/
private final float startAngle = 180;
private final float endAngle = 360;
/**计算*/
private PointF arcCenter;
private float arcRaidus; //饼状图半径
private float tagMaxW;
public DashBoardView(Context context) {
super(context, null);
}
public DashBoardView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public DashBoardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
calculatData();
}
/***********************************设置属性set方法**********************************/
public void setData(List<DashBoardItem> dataList) {
this.dataList.clear();
if(dataList!=null)
this.dataList.addAll(dataList);
calculatData();
}
private float progress;
private float progressAnim;
public void setPro(float progress){
this.progress = progress;
invalidate();
}
/***********************************设置属性set方法over**********************************/
public void calculatData() {
total = 0;
float maxLableLength = 0; //lable最长的长度
paintLabel.setTextSize(textSizeLable);
for(DashBoardItem bean : dataList){
total += bean.getNum();
float l = FontUtil.getFontlength(paintLabel, bean.getLable());
maxLableLength = maxLableLength>l?maxLableLength:l;
}
//计算每个部分的角度
float allAngle = endAngle - startAngle;
for(DashBoardItem bean : dataList){
bean.setAngle(allAngle * (bean.getNum()/total*1.0f));
}
//lable所占用的宽度
maxLableLength = maxLableLength + textLableSpace + lableCircleSize * 2;
int widthSize = getMeasuredWidth();
int heightSize = getMeasuredHeight();
paintLabel.setTextSize(textSizeCoordinate);
float xLableSize = FontUtil.getFontHeight(paint);
arcCenter = new PointF(
getPaddingLeft()+(widthSize - getPaddingLeft() - maxLableLength -getPaddingRight())/2,
heightSize - xLableSize - textXSpace- getPaddingBottom()
);
float raidusX = arcCenter.x - getPaddingLeft();
float raidusY = arcCenter.y - getPaddingTop();
arcRaidus = raidusX<raidusY?raidusX:raidusY;
rectChart = new RectF(arcCenter.x - arcRaidus,arcCenter.y - arcRaidus,
arcCenter.x + arcRaidus,arcCenter.y + arcRaidus);
if(centerPoint!=null && centerPoint.x>0){
startDraw = false;
invalidate();
}
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
/* paint.setStyle(Paint.Style.FILL);
paint.setColor(defColor);
canvas.drawCircle(centerPoint.x,centerPoint.y,chartRaidus, paint);
if(ringWidth>0){ //绘制空心圆圈
paint.setColor(backColor);
canvas.drawCircle(centerPoint.x,centerPoint.y,chartRaidus-ringWidth, paint);
}*/
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
paint.setColor(Color.RED);
canvas.drawRect(rectChart, paint);
}
@Override
public void drawChart(Canvas canvas) {
// paint.setStrokeWidth(ringWidth);
if(null==dataList || dataList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);//设置空心
float angle = startAngle;
float lableTop = getPaddingTop();
paintLabel.setTextSize(textSizeLable);
paintLabel.setColor(textColorLable);
float lableLead = FontUtil.getFontLeading(paintLabel);
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableCrc = (lableHeight - lableCircleSize*2)/2;
// canvas.drawArc(rectChart, 180, 180, false, paint);
for(DashBoardItem bean : dataList){
paint.setColor(bean.getColor());
LogUtil.w(TAG, "绘制扇形"+bean);
canvas.drawArc(rectChart, angle, bean.getAngle(), true, paint);
angle += bean.getAngle();
//绘制lable
canvas.drawCircle(rectChart.right+lableCircleSize, lableTop+lableCrc+lableCircleSize, lableCircleSize, paint);
canvas.drawText(bean.getLable(), rectChart.right+lableCircleSize*2+textLableSpace, lableTop + lableLead, paintLabel);
lableTop += lableHeight+DensityUtil.dip2px(getContext(),5);
}
//覆盖
paint.setColor(Color.WHITE);
canvas.drawCircle(arcCenter.x, arcCenter.y, arcRaidus - ringWidth, paint);
//绘制x坐标
paintLabel.setTextSize(textSizeCoordinate);
paintLabel.setColor(textColorCoordinate);
float xLead = FontUtil.getFontLeading(paintLabel);
float xLength = FontUtil.getFontlength(paintLabel, "0");
canvas.drawText("0", rectChart.left+(ringWidth-xLength)/2,
arcCenter.y + textXSpace+xLead, paintLabel);
xLength = FontUtil.getFontlength(paintLabel, "0分钟");
canvas.drawText("0分钟",
rectChart.right-(Math.abs((ringWidth-xLength)/2))-(xLength<ringWidth?xLength:ringWidth),
arcCenter.y + textXSpace+xLead, paintLabel);
//绘制虚线
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(defColor);
PathEffect effects = new DashPathEffect(new float[]{15,8,15,8},0);
paintEffect.setPathEffect(effects);
Path path = new Path();
path.moveTo(rectChart.left, arcCenter.y);
path.lineTo(rectChart.right, arcCenter.y);
canvas.drawPath(path, paintEffect);
path = new Path();
path.moveTo(arcCenter.x, rectChart.top+ringWidth);
path.lineTo(arcCenter.x, arcCenter.y);
canvas.drawPath(path, paintEffect);
//绘制指针
paint.setColor(Color.BLACK);
canvas.drawCircle(arcCenter.x, arcCenter.y, centerPointSize, paint);
/*画一个实心三角形*/
float proAndle = (endAngle - startAngle) * (progressAnim/total*1.0f);
//以正东面为0度起点计算指定角度所对应的圆周上的点的坐标:
float centerArcX1 = arcCenter.x + (float) (centerPointSize* Math.cos(Math.toRadians(startAngle+proAndle - 90)));
float centerArcY1 = arcCenter.y + (float) (centerPointSize* Math.sin(Math.toRadians(startAngle+proAndle - 90)));
float endx2 = arcCenter.x + (float) (arcRaidus* Math.cos(Math.toRadians(startAngle+proAndle)));
float endy2 = arcCenter.y + (float) (arcRaidus* Math.sin(Math.toRadians(startAngle+proAndle)));
float centerArcX2 = arcCenter.x + (float) (centerPointSize* Math.cos(Math.toRadians(startAngle+proAndle + 90)));
float centerArcY2 = arcCenter.y + (float) (centerPointSize* Math.sin(Math.toRadians(startAngle+proAndle + 90)));
Path path2=new Path();
path2.moveTo(centerArcX1,centerArcY1);
path2.lineTo(endx2,endy2);
path2.lineTo(centerArcX2,centerArcY2);
path2.close();
canvas.drawPath(path2, paint);
}
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1.0f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
float percentage = (float) animation.getAnimatedValue();
evaluatorData(percentage);
}
/**计算各种绘制坐标*/
private void evaluatorData(float animPre){
progressAnim = animPre*progress;
}
}
@@ -0,0 +1,395 @@
package com.openxu.cview.chart.piechart;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.chart.BaseChart;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.chart.bean.ChartLable;
import com.openxu.cview.chart.bean.PieChartBean;
import com.openxu.utils.FontUtil;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import static com.openxu.cview.chart.piechart.PieChartLayout.TAG_MODUL.MODUL_CHART;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : ProgressPieChart
* version : 1.0
* description : 占比饼状图表
*/
public class PieChart extends BaseChart {
private List<PieChartBean> dataList;
private List<ChartLable> lableList; //需要绘制的lable集合
/**设置的属性*/
private boolean showZeroPart = false; //如果某部分占比为0, 是否显示
private int centerLableSpace;
//圆环宽度,如果值>0,则为空心圆环,内环为白色,可以在内环中绘制字
private int ringWidth;
private int lineLenth;
private int outSpace;
private int textSpace;
private PieChartLayout.TAG_TYPE tagType; //TAG展示类型
private PieChartLayout.TAG_MODUL tagModul; //TAG展示位置
private int tagTextSize; //tag文字大小
private int tagTextColor;
/**计算*/
private int chartSize; //饼状图大小
private int chartRaidus; //饼状图半径
private float tagMaxW;
private Paint paintSelected;
//RGB颜色数组
private int arrColorRgb[][];
public PieChart(Context context) {
super(context, null);
}
public PieChart(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public PieChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
startAngle = -90; //开始的角度
paintSelected = new Paint();
paintSelected.setColor(Color.LTGRAY);
paintSelected.setStyle(Paint.Style.STROKE);//设置空心
paintSelected.setStrokeWidth(lineWidth*5);
paintSelected.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
initSome();
setMeasuredDimension(widthSize, heightSize);
}
private void initSome(){
int widthSize = getMeasuredWidth();
int heightSize = getMeasuredHeight();
chartSize = widthSize>heightSize?heightSize:widthSize;
//外圈半径=size/2-最大部分lble线长度-边距
paintLabel.setTextSize(tagTextSize);
tagMaxW = 0;
if(MODUL_CHART == tagModul){
switch (tagType){
case TYPE_NUM:
tagMaxW = FontUtil.getFontlength(paintLabel, "0.0%");
break;
case TYPE_PERCENT:
tagMaxW = FontUtil.getFontlength(paintLabel, "000");
break;
}
}
chartRaidus = chartSize/2-lineLenth-(int)tagMaxW-textSpace-outSpace;
centerPoint = new PointF(widthSize/2, heightSize/2);
if(widthSize<heightSize){
rectChart = new RectF(0,(heightSize-widthSize)/2,widthSize, (heightSize+widthSize)/2);
}else{
rectChart = new RectF((widthSize-heightSize)/2, 0, (widthSize+heightSize)/2, heightSize);
}
}
/***********************************设置属性set方法**********************************/
public void setArrColorRgb(int[][] arrColorRgb) {
this.arrColorRgb = arrColorRgb;
}
public void setTagTextSize(int tagTextSize) {
this.tagTextSize = tagTextSize;
initSome();
}
public void setTagTextColor(int tagTextColor) {
this.tagTextColor = tagTextColor;
}
public void setTagType(PieChartLayout.TAG_TYPE tagType) {
this.tagType = tagType;
initSome();
}
public void setTagModul(PieChartLayout.TAG_MODUL tagModul) {
this.tagModul = tagModul;
initSome();
}
public void setShowZeroPart(boolean showZeroPart) {
this.showZeroPart = showZeroPart;
}
public void setCenterLableSpace(int centerLableSpace) {
this.centerLableSpace = centerLableSpace;
}
public void setRingWidth(int ringWidth) {
this.ringWidth = ringWidth;
}
public void setLineLenth(int lineLenth) {
this.lineLenth = lineLenth;
}
public void setOutSpace(int outSpace) {
this.outSpace = outSpace;
}
public void setTextSpace(int textSpace) {
this.textSpace = textSpace;
}
/**
* 设置数据
*/
public void setData(List<PieChartBean> dataList, List<ChartLable> lableList){
total = 0;
this.lableList = lableList;
this.dataList.clear();
if(dataList!=null)
this.dataList.addAll(dataList);
for(PieChartBean bean : this.dataList){
total += bean.getNum();
}
if(centerPoint!=null && centerPoint.x>0){
startDraw = false;
invalidate();
}
}
/***********************************设置属性set方法over**********************************/
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
paint.setColor(defColor);
canvas.drawCircle(centerPoint.x,centerPoint.y,chartRaidus, paint);
if(ringWidth>0){ //绘制空心圆圈
paint.setColor(backColor);
canvas.drawCircle(centerPoint.x,centerPoint.y,chartRaidus-ringWidth, paint);
}
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidus, paint);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidus+lineLenth, paint);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidus+lineLenth+textSpace, paint);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidus+lineLenth+textSpace+(int)tagMaxW, paint);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidus+lineLenth+textSpace+(int)tagMaxW+outSpace, paint);
}
private int selectedIndex = -1; //被选中的索引
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
int index = -1;
for(int i = 0; i < dataList.size(); i++){
PieChartBean bean = dataList.get(i);
if(bean.getNum() == 0){
if(showZeroPart)
index++;
continue;
}
index++;
paint.setARGB(255, arrColorRgb[index%arrColorRgb.length][0], arrColorRgb[index%arrColorRgb.length][1], arrColorRgb[index%arrColorRgb.length][2]);
paintLabel.setARGB(255, arrColorRgb[index%arrColorRgb.length][0], arrColorRgb[index%arrColorRgb.length][1], arrColorRgb[index%arrColorRgb.length][2]);
if(selectedIndex == i){
paint.setTypeface(Typeface.DEFAULT_BOLD);
paintLabel.setTypeface(Typeface.DEFAULT_BOLD);
}else{
paint.setTypeface(Typeface.DEFAULT);
paintLabel.setTypeface(Typeface.DEFAULT);
}
/**1、绘制扇形*/
paint.setStyle(Paint.Style.FILL);//设置实心
canvas.drawArc(bean.getArcRect(), bean.getStartAngle(), bean.getSweepAngle(), true, paint);
if(selectedIndex == i){
//被选中的,绘制边界
paintSelected.setStyle(Paint.Style.STROKE);//设置空心
canvas.drawArc(bean.getArcRect(), bean.getStartAngle(), bean.getSweepAngle(), true, paintSelected);
}
if(MODUL_CHART == tagModul){
/**2、绘制直线*/
List<PointF> tagLinePoints = bean.getTagLinePoints();
if (tagLinePoints != null && tagLinePoints.size() > 0) {
for (int p = 1; p < tagLinePoints.size(); p++) {
canvas.drawLine(tagLinePoints.get(p - 1).x, tagLinePoints.get(p - 1).y,
tagLinePoints.get(p).x, tagLinePoints.get(p).y, paint);
}
}
/**3、绘制指示标签*/
paintLabel.setTextSize(tagTextSize);
if (tagTextColor != 0) {
paintLabel.setColor(tagTextColor);
}
canvas.drawText(bean.getTagStr() + "", bean.getTagTextPoint().x, bean.getTagTextPoint().y, paintLabel);
}
}
//绘制中心内圆
if(ringWidth>0){
paint.setColor(backColor);
paint.setStyle(Paint.Style.FILL);//设置实心
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidus-ringWidth, paint);
}
/**4 绘制中间文字*/
if(ringWidth>0 && lableList!=null &&lableList.size()>0){
float textAllHeight = 0;
float textW, textH, textL;
for(ChartLable lable : lableList){
paintLabel.setTextSize(lable.getTextSize());
textH = FontUtil.getFontHeight(paintLabel);
textAllHeight += (textH+centerLableSpace);
}
textAllHeight -= centerLableSpace;
int top = (int)(centerPoint.y-textAllHeight/2);
for(ChartLable lable : lableList){
paintLabel.setColor(lable.getTextColor());
paintLabel.setTextSize(lable.getTextSize());
textW = FontUtil.getFontlength(paintLabel, lable.getText());
textH = FontUtil.getFontHeight(paintLabel);
textL = FontUtil.getFontLeading(paintLabel);
canvas.drawText(lable.getText(), centerPoint.x-textW/2, top + textL, paintLabel);
top += (textH+centerLableSpace);
}
}
}
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1.0f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
float percentage = (float) animation.getAnimatedValue();
evaluatorData(percentage);
}
/**计算各种绘制坐标*/
private void evaluatorData(float animPre){
paintLabel.setTextSize(tagTextSize);
float tagTextLead = FontUtil.getFontLeading(paintLabel);
float tagTextHeight = FontUtil.getFontHeight(paintLabel);
float oneStartAngle = startAngle;
for(int i = 0; i < dataList.size(); i++) {
PieChartBean bean = dataList.get(i);
/* if(bean.getNum() == 0 && !showZeroPart){
continue;
}*/
/**1、绘制扇形*/
float arcLeft = centerPoint.x - chartRaidus; //扇形半径
float arcTop = centerPoint.y - chartRaidus;
float arcRight = centerPoint.x + chartRaidus;
float arcBottom = centerPoint.y + chartRaidus;
// float percentage = 360.0f / total * bean.getNum();
float percentage = (bean.getNum()==0?0:(360.0f / total * bean.getNum())*animPre);
bean.setArcRect(new RectF(arcLeft, arcTop, arcRight, arcBottom));
bean.setStartAngle(oneStartAngle);
bean.setSweepAngle(percentage);
/**2、计算扇形区域*/
arcLeft = centerPoint.x - chartSize;
arcTop = centerPoint.y - chartSize;
arcRight = centerPoint.x + chartSize;
arcBottom = centerPoint.y + chartSize;
Path allPath = new Path();
allPath.moveTo(centerPoint.x, centerPoint.y);//添加原始点
float ovalX = centerPoint.x + (float) (chartRaidus * Math.cos(Math.toRadians(oneStartAngle)));
float ovalY = centerPoint.y + (float) (chartRaidus * Math.sin(Math.toRadians(oneStartAngle)));
allPath.lineTo(ovalX, ovalY);
RectF touchOval = new RectF(arcLeft, arcTop, arcRight, arcBottom);
allPath.addArc(touchOval, oneStartAngle, percentage);
allPath.lineTo(centerPoint.x, centerPoint.y);
allPath.close();
RectF r = new RectF();
allPath.computeBounds(r, true);
Region region = new Region();
region.setPath(allPath, new Region((int) r.left, (int) r.top, (int) r.right, (int) r.bottom));
bean.setRegion(region);
if(MODUL_CHART == tagModul) {
/**3、绘制直线*/
//确定直线的起始和结束的点的位置
float startX = centerPoint.x + (float) (chartRaidus * Math.cos(Math.toRadians(oneStartAngle + percentage / 2)));
float startY = centerPoint.y + (float) (chartRaidus * Math.sin(Math.toRadians(oneStartAngle + percentage / 2)));
float endX = centerPoint.x + (float) ((chartRaidus + lineLenth - 20) * Math.cos(Math.toRadians(oneStartAngle + percentage / 2)));
float endY = centerPoint.y + (float) ((chartRaidus + lineLenth - 20) * Math.sin(Math.toRadians(oneStartAngle + percentage / 2)));
boolean isRight = true;
float lineAngle = oneStartAngle + percentage / 2;
if (lineAngle > 90 && lineAngle < 270) {
isRight = false;
}
// LogUtil.i(TAG, "直线坐标:start=("+startX+","+startY +") end=("+endX+","+endY+")"+" lineAngle="+lineAngle+" isRight="+isRight);
List<PointF> tagLinePoints = new ArrayList<>();
tagLinePoints.add(new PointF(startX, startY));
tagLinePoints.add(new PointF(endX, endY));
float textX = isRight ? (endX + 20) : (endX - 20);
tagLinePoints.add(new PointF(textX, endY));
bean.setTagLinePoints(tagLinePoints);
/**3、绘制指示标签*/
String tagText = "";
paintLabel.setTextSize(tagTextSize);
if (tagType == PieChartLayout.TAG_TYPE.TYPE_NUM) {
tagText = bean.getNum() + "";
} else if (tagType == PieChartLayout.TAG_TYPE.TYPE_PERCENT) {
DecimalFormat decimalFormat = new DecimalFormat("0.0%");
tagText = (total==0?"/":decimalFormat.format(((float) bean.getNum() / (float) total)));
}
float textW = FontUtil.getFontlength(paintLabel, tagText);
textX = isRight ? textX + textSpace : (textX - textW - textSpace);
float textY = endY - tagTextHeight / 2 + tagTextLead;
bean.setTagStr(tagText);
bean.setTagTextPoint(new PointF(textX, textY));
}
/*开始角度累加*/
oneStartAngle += percentage;
}
}
}
@@ -0,0 +1,285 @@
package com.openxu.cview.chart.piechart;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.openxu.cview.chart.BaseChart;
import com.openxu.cview.chart.bean.PieChartBean;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import static com.openxu.cview.chart.BaseChart.TOUCH_EVENT_TYPE.EVENT_Y;
import static com.openxu.cview.chart.piechart.PieChartLayout.TAG_MODUL.MODUL_LABLE;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : PieChartLableView
* version : 1.0
* description : 占比饼状图表
*/
public class PieChartLableView extends BaseChart {
private List<PieChartBean> dataList;
private boolean showZeroPart = false; //如果某部分占比为0, 是否显示
protected RectF rectLable; //图表矩形
private int minTopPointY; //滑动到最上面时的y坐标
private int top; //绘制的y坐标
private int oneLableHeight; //右侧标签单个高度,需要计算
/**设置的属性*/
private int rectW;
private int rectH;
private int rectRaidus; //矩形圆角
private int rectSpace; //右侧标签上下间距
private int leftSpace;
private int textSize = 90; //文字大小
private int textColor;
private int arrColorRgb[][];
private PieChartLayout.TAG_TYPE tagType; //TAG展示类型
private PieChartLayout.TAG_MODUL tagModul; //TAG展示位置
public PieChartLableView(Context context) {
super(context, null);
}
public PieChartLableView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public PieChartLableView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
touchEventType = EVENT_Y;
dataList = new ArrayList<>();
setClickable(true);
}
/***********************************设置属性set方法**********************************/
public void setArrColorRgb(int[][] arrColorRgb) {
this.arrColorRgb = arrColorRgb;
}
public void setTextSize(int textSize) {
this.textSize = textSize;
}
public void setTextColor(int textColor) {
this.textColor = textColor;
}
public void setShowZeroPart(boolean showZeroPart) {
this.showZeroPart = showZeroPart;
}
public void setRectW(int rectW) {
this.rectW = rectW;
}
public void setRectH(int rectH) {
this.rectH = rectH;
}
public void setRectRaidus(int rectRaidus) {
this.rectRaidus = rectRaidus;
}
public void setTagType(PieChartLayout.TAG_TYPE tagType) {
this.tagType = tagType;
}
public void setTagModul(PieChartLayout.TAG_MODUL tagModul) {
this.tagModul = tagModul;
}
public void setRectSpace(int rectSpace) {
this.rectSpace = rectSpace;
}
public void setLeftSpace(int leftSpace) {
this.leftSpace = leftSpace;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
centerPoint = new PointF(widthSize/2, heightSize/2);
rectLable = new RectF(0,0,widthSize, heightSize);
setMeasuredDimension(widthSize, heightSize);
evaluatorLable();
}
@Override
public void dispatchTouchEvent1(MotionEvent event) {
int move = (int)(event.getY() - lastTouchPoint.y);
if(top + mMoveLen <= minTopPointY){
if(move<0){
LogUtil.e(TAG, "已经到最下面了,还往上滑,不行");
getParent().requestDisallowInterceptTouchEvent(false);
}else{
getParent().requestDisallowInterceptTouchEvent(true);
}
}else if(top + mMoveLen>=rectSpace){
if(move>0){
LogUtil.w(TAG, "已经划到头了,还往下滑,不行");
getParent().requestDisallowInterceptTouchEvent(false);
}else{
getParent().requestDisallowInterceptTouchEvent(true);
}
}else{
//请求所有父控件及祖宗控件不要拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
LogUtil.i(TAG, "正常请求事件");
}
}
@Override
protected void evaluatorFling(float fling) {
if(top + mMoveLen + fling <= minTopPointY){
mMoveLen = minTopPointY-rectSpace;
}else if(top + mMoveLen+ fling>=rectSpace){
mMoveLen = 0;
} else{
mMoveLen += fling;
}
}
/**
* 设置数据
*/
public void setData(List<PieChartBean> dataList){
this.dataList.clear();
if(dataList!=null)
this.dataList.addAll(dataList);
evaluatorLable();
startDraw = false;
invalidate();
}
/**计算右侧标签相关坐标值*/
private void evaluatorLable(){
paintLabel.setTextSize(textSize);
oneLableHeight = (int) FontUtil.getFontHeight(paintLabel);
//字和矩形中高度的较大值
oneLableHeight = oneLableHeight>rectH?oneLableHeight:rectH;
int allHeight = 0 ;
//总高度
for(PieChartBean bean : dataList){
if(bean.getNum() == 0 && !showZeroPart){
continue;
}
allHeight += oneLableHeight+rectSpace;
}
if(allHeight>0)
allHeight -= rectSpace;
// int allHeight = (oneLableHeight+rectSpace)*dataList.size() - rectSpace;
int contentH = getMeasuredHeight() - rectSpace*2;
touchEnable = allHeight>contentH;
if(touchEnable){
//超出总高度了
top = rectSpace;
minTopPointY = -allHeight - rectSpace + getMeasuredHeight();
if(debug)
LogUtil.i(TAG, "边界"+minTopPointY+" "+getMeasuredHeight());
}else{
top = (getMeasuredHeight()-allHeight)/2;
}
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
int topY = top + mMoveLen;
paintLabel.setTextSize(textSize);
int lableH =(int) FontUtil.getFontHeight(paintLabel);
int lableL =(int) FontUtil.getFontLeading(paintLabel);
paint.setStyle(Paint.Style.FILL);
int index = -1;
for(int i = 0; i < dataList.size(); i++){
PieChartBean bean = dataList.get(i);
if(bean.getNum() == 0 && !showZeroPart){
continue;
}
index++;
paint.setARGB(255, arrColorRgb[index%arrColorRgb.length][0], arrColorRgb[index%arrColorRgb.length][1], arrColorRgb[index%arrColorRgb.length][2]);
paintLabel.setARGB(255, arrColorRgb[index%arrColorRgb.length][0], arrColorRgb[index%arrColorRgb.length][1], arrColorRgb[index%arrColorRgb.length][2]);
int rectTop = topY+(oneLableHeight-rectH)/2;
RectF rect = new RectF(leftSpace, rectTop, leftSpace+rectW, rectTop+rectH);
canvas.drawRoundRect(rect, rectRaidus, rectRaidus, paint);
if(textColor!=0) {
paintLabel.setColor(textColor);
}
rectTop = topY+(oneLableHeight-lableH)/2;
canvas.drawText(bean.getName(), leftSpace+rectW+leftSpace , rectTop+lableL , paintLabel);
float textW = FontUtil.getFontlength(paintLabel, bean.getName());
/**5、绘制指示标签*/
if(MODUL_LABLE == tagModul){
String tagText = "";
if (tagType == PieChartLayout.TAG_TYPE.TYPE_NUM) {
tagText = bean.getNum() + "";
} else if (tagType == PieChartLayout.TAG_TYPE.TYPE_PERCENT) {
DecimalFormat decimalFormat = new DecimalFormat("0.0%");
tagText = decimalFormat.format(((float) bean.getNum() / (float) total));
}
float tagW = FontUtil.getFontlength(paintLabel, tagText);
canvas.drawText(tagText, getMeasuredWidth()-tagW- DensityUtil.dip2px(getContext(),3), rectTop+lableL , paintLabel);
/* DashPathEffect dashPathEffect = new DashPathEffect(new float[]{8,10,8,10}, 0);
paint.setColor(Color.RED);
// paint.setPathEffect(dashPathEffect);
paint.setStrokeWidth(4);
Path path = new Path();
path.moveTo(leftSpace+rectW+leftSpace+textW, rectTop);
path.lineTo(getMeasuredWidth()-tagW- DensityUtil.dip2px(getContext(),3), rectTop);
LogUtil.i(TAG, "绘制虚线"+path);
canvas.drawPath(path,paint);*/
}
topY += oneLableHeight + rectSpace;
}
}
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
}
}
@@ -0,0 +1,293 @@
package com.openxu.cview.chart.piechart;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import com.openxu.cview.R;
import com.openxu.cview.chart.bean.ChartLable;
import com.openxu.cview.chart.bean.PieChartBean;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.SharedData;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : PieChartLayout
* version : 1.0
* description : 占比饼状图表
*/
public class PieChartLayout extends LinearLayout {
private String TAG = "PieChartLayout";
protected boolean isLoading = true;
protected boolean debug = SharedData.getInstance().getSp().getBoolean(SharedData.KEY_DEBUG, false);
protected List<PieChartBean> dataList;
protected List<ChartLable> lableList;
private PieChart chartView;
/**饼状图设置属性*/
private boolean showZeroPart = false; //如果某部分占比为0, 是否显示
private int centerLableSpace = DensityUtil.dip2px(getContext(), 1); //中间文字行距
//圆环宽度,如果值>0,则为空心圆环,内环为白色,可以在内环中绘制字
private int ringWidth = DensityUtil.dip2px(getContext(), 20); //圆环宽度
private int lineLenth = DensityUtil.dip2px(getContext(), 20); //指示线长度
private int outSpace = DensityUtil.dip2px(getContext(), 5);
private int textSpace = DensityUtil.dip2px(getContext(), 3); //tag指示文字与线的距离
private int tagTextSize = (int)getResources().getDimension(R.dimen.text_size_level_small); //饼状图占比指示文字大小
private int tagTextColor = getResources().getColor(R.color.text_color_light_gray); //文字颜色,如果为0,则根据扇形颜色一样;
private TAG_MODUL tagModul = TAG_MODUL.MODUL_LABLE; //TAG展示位置
private TAG_TYPE tagType = TAG_TYPE.TYPE_NUM; //TAG展示类型
private PieChartLableView lableView;
/**右侧lable设置属性*/
private int rectW = DensityUtil.dip2px(getContext(), 10); //lable矩形宽高
private int rectH = DensityUtil.dip2px(getContext(), 10);
private int rectRaidus = 0; //矩形圆角
private int rectSpace = DensityUtil.dip2px(getContext(), 8); //右侧标签上下间距
private int leftSpace = DensityUtil.dip2px(getContext(), 5); //右侧标签左右间距
private int lableTextSize = (int)getResources().getDimension(R.dimen.text_size_level_small); //饼状图占比指示文字大小
private int lableTextColor = getResources().getColor(R.color.text_color_light_gray);
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = super.dispatchTouchEvent(ev);
LogUtil.w(TAG, "dispatchTouchEvent分发事件 "+result);
return result;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean result = super.onInterceptTouchEvent(ev);
LogUtil.e(TAG, "onInterceptTouchEvent拦截事件 "+result);
return result;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean result = super.onTouchEvent(ev);
LogUtil.i(TAG, "onTouchEvent处理事件 "+result);
return result;
}
//RGB颜色数组
private int arrColorRgb[][] = {
{113, 137, 230}, // UIColorFromRGB(0xD95F5B),
{217, 95, 91}, // UIColorFromRGB(0x7189E6),
{90, 185, 199}, // UIColorFromRGB(0x5AB9C7),
{170, 150, 213}, // UIColorFromRGB(0xB096D5),
{107, 186, 151}, // UIColorFromRGB(0x6BBA97),
{91, 164, 231}, // UIColorFromRGB(0x5BA4E7),
{220, 170, 97},// UIColorFromRGB(0xDCAA61),
{125, 171, 88},// UIColorFromRGB(0x7DAB58),
{233, 200, 88},// UIColorFromRGB(0xE9C858),
{213, 150, 196},// UIColorFromRGB(0xd596c4)
{220, 127, 104},// UIColorFromRGB(0xDC7F68),
// {86, 138, 220}, // UIColorFromRGB(0x568ADC),
// {112, 173, 71}, //UIColorFromRGB(0x70AD47)
// {219, 69, 40}, // UIColorFromRGB(0xDB4528)
// {255, 193, 2}, // UIColorFromRGB(0xFFC102)
// {242, 141, 2}, // UIColorFromRGB(0xF28D02)
// {41, 182, 180}, // UIColorFromRGB(0x29B6B4)
// {187, 107, 201}, // UIColorFromRGB(0xbb6bc9)
// {124, 117, 214}, // UIColorFromRGB(0x7c75d6)
};
/**tag类型*/
public enum TAG_TYPE{
TYPE_NUM, //数量
TYPE_PERCENT, //百分比
}
public enum TAG_MODUL{
MODEUL_NULL, //不展示
MODUL_CHART, //在扇形图上显示tag
MODUL_LABLE, //在lable后面显示tag
}
public PieChartLayout(Context context) {
this(context, null);
}
public PieChartLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PieChartLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
this.dataList = new ArrayList<>();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
for(int i = 0; i<getChildCount(); i++){
View child = getChildAt(i);
if(child instanceof PieChart){
chartView = (PieChart)child;
}else if(child instanceof PieChartLableView){
lableView = (PieChartLableView)child;
}
}
LogUtil.i(TAG, "init"+ chartView+ lableView);
setConfig();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void setConfig(){
if(chartView!=null) {
chartView.setLoading(isLoading);
chartView.setArrColorRgb(arrColorRgb);
chartView.setTagType(tagType);
chartView.setTagModul(tagModul);
chartView.setTagTextColor(tagTextColor);
chartView.setTagTextSize(tagTextSize);
chartView.setShowZeroPart(showZeroPart);
chartView.setCenterLableSpace(centerLableSpace);
chartView.setRingWidth(ringWidth);
chartView.setLineLenth(lineLenth);
chartView.setOutSpace(outSpace);
chartView.setTextSpace(textSpace);
chartView.setData(dataList, lableList);
}
if(lableView!=null) {
lableView.setLoading(isLoading);
lableView.setTagType(tagType);
lableView.setTagModul(tagModul);
lableView.setShowZeroPart(showZeroPart);
lableView.setArrColorRgb(arrColorRgb);
lableView.setTextColor(lableTextColor);
lableView.setTextSize(lableTextSize);
lableView.setRectW(rectW);
lableView.setRectH(rectH);
lableView.setRectRaidus(rectRaidus);
lableView.setRectSpace(rectSpace);
lableView.setLeftSpace(leftSpace);
lableView.setData(dataList);
}
}
public int getTotal() {
return chartView.getTotal();
}
public void setLoading(boolean loading) {
this.isLoading = loading;
setConfig();
}
public void setShowZeroPart(boolean showZeroPart) {
this.showZeroPart = showZeroPart;
}
public void setCenterLableSpace(int centerLableSpace) {
this.centerLableSpace = centerLableSpace;
}
public void setTagType(TAG_TYPE tagType) {
this.tagType = tagType;
}
public void setTagModul(TAG_MODUL tagModul) {
this.tagModul = tagModul;
}
public void setRingWidth(int ringWidth) {
this.ringWidth = ringWidth;
}
public void setLineLenth(int lineLenth) {
this.lineLenth = lineLenth;
}
public void setOutSpace(int outSpace) {
this.outSpace = outSpace;
}
public void setTextSpace(int textSpace) {
this.textSpace = textSpace;
}
public void setRectW(int rectW) {
this.rectW = rectW;
}
public void setRectH(int rectH) {
this.rectH = rectH;
}
public void setRectRaidus(int rectRaidus) {
this.rectRaidus = rectRaidus;
}
public void setRectSpace(int rectSpace) {
this.rectSpace = rectSpace;
}
public void setLeftSpace(int leftSpace) {
this.leftSpace = leftSpace;
}
public void setTagTextSize(int tagTextSize) {
this.tagTextSize = tagTextSize;
}
public void setTagTextColor(int tagTextColor) {
this.tagTextColor = tagTextColor;
}
public void setLableTextSize(int lableTextSize) {
this.lableTextSize = lableTextSize;
}
public void setLableTextColor(int lableTextColor) {
this.lableTextColor = lableTextColor;
}
public void setArrColorRgb(int[][] arrColorRgb) {
this.arrColorRgb = arrColorRgb;
}
/**
* 设置数据
*/
public void setChartData(Class clazz, String per, String name, List<? extends Object> dataList, List<ChartLable> lableList){
this.lableList = lableList;
this.dataList.clear();
if(dataList!=null){
try{
Field filedPer = clazz.getDeclaredField(per);
Field filedName = clazz.getDeclaredField(name);
filedPer.setAccessible(true);
filedName.setAccessible(true);
for(Object obj : dataList){
String perStr = filedPer.get(obj).toString();
PieChartBean bean = new PieChartBean(Float.parseFloat(perStr), (String)filedName.get(obj));
this.dataList.add(bean);
}
}catch (Exception e){
e.printStackTrace();
}
}
setConfig();
}
}
@@ -0,0 +1,638 @@
package com.openxu.cview.chart.rosechart;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* autour : openXu
* date : 2017/7/24 10:46
* className : NightingaleRoseChart
* version : 1.0
* description : 南丁格尔玫瑰图
*/
public class NightingaleRoseChart extends View {
private String TAG = "NightingaleRoseChart";
private int ScrWidth,ScrHeight;
private int backColor = Color.WHITE;
private PointF centerPoint; //chart中心点坐标
private int startAngle = -90; //开始的角度
private int chartRaidusInner = DensityUtil.dip2px(getContext(), 15); //内圈半径
private int chartRaidusOuter; //最大部分扇形区域半径
private int outSpace = DensityUtil.dip2px(getContext(), 30); //图表与边界距离
private int lineLenth = DensityUtil.dip2px(getContext(), 8);
private int lineWidth = 1; //线宽度
private RectF rectChart, rectLable;
private int rightLableTop;
private int oneLableHeight; //右侧标签单个高度
private int rightRectItemW = DensityUtil.dip2px(getContext(), 16);
private int rightRectItemH = DensityUtil.dip2px(getContext(), 8);
private int rightRectSpace = DensityUtil.dip2px(getContext(), 8); //右侧标签上下间距
private int textSpace = DensityUtil.dip2px(getContext(), 5);
private int lableTextSize = (int)getResources().getDimension(R.dimen.text_size_level_mid); //右侧标注字体大小
private int tagTextSize = (int)getResources().getDimension(R.dimen.text_size_level_small);
private String nullStr = "暂无数据";
private List<RoseChartBean> dataList;
private float max;
private Paint paintArc , paintSelected;
private Paint paintLabel;
private Paint mLinePaint, mlableLinePaint;
private int selectedIndex = -1; //被选中的索引
/**正在加载*/
boolean isLoading = true;
/**是否在图表上显示指示lable*/
boolean showChartLable = false;
/**是否在图表上显示指示num*/
boolean showChartNum = true;
/**点击显示数量*/
boolean showNumTouched = false;
/**右侧显示数量*/
boolean showRightNum = false;
//RGB颜色数组
//#D95F5B红 #7189E6蓝 #5AB9C7蓝1 #B096D5紫 #6BBA97绿1 #DCAA61黄 #7DAB58绿2 #DC7F68橙
private final int arrColorRgb[][] = {
{113, 137, 230}, // UIColorFromRGB(0xD95F5B),
{217, 95, 91}, // UIColorFromRGB(0x7189E6),
{90, 185, 199}, // UIColorFromRGB(0x5AB9C7),
{170, 150, 213}, // UIColorFromRGB(0xB096D5),
{107, 186, 151}, // UIColorFromRGB(0x6BBA97),
{91, 164, 231}, // UIColorFromRGB(0x5BA4E7),
{220, 170, 97},// UIColorFromRGB(0xDCAA61),
{125, 171, 88},// UIColorFromRGB(0x7DAB58),
{233, 200, 88},// UIColorFromRGB(0xE9C858),
{213, 150, 196},// UIColorFromRGB(0xd596c4)
{220, 127, 104}// UIColorFromRGB(0xDC7F68),
};
public void setLoading(boolean loading) {
isLoading = loading;
}
public void setShowNumTouched(boolean showNumTouched) {
this.showNumTouched = showNumTouched;
}
public void setShowRightNum(boolean showRightNum) {
this.showRightNum = showRightNum;
}
public void setChartRaidusInner(int chartRaidusInner) {
this.chartRaidusInner = DensityUtil.dip2px(getContext(), chartRaidusInner); //内圈半径
}
public void setChartRaidusOuter(int chartRaidusOuter) {
this.chartRaidusOuter = DensityUtil.dip2px(getContext(), chartRaidusOuter); //图表与边界距离
}
public NightingaleRoseChart(Context context) {
this(context, null);
}
public NightingaleRoseChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NightingaleRoseChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
DisplayMetrics dm = getResources().getDisplayMetrics();
ScrHeight = dm.heightPixels;
ScrWidth = dm.widthPixels;
//画笔初始化
paintArc = new Paint();
paintArc.setAntiAlias(true);
paintLabel = new Paint();
paintLabel.setAntiAlias(true);
paintSelected = new Paint();
paintSelected.setColor(Color.LTGRAY);
paintSelected.setStyle(Paint.Style.STROKE);//设置空心
paintSelected.setStrokeWidth(lineWidth*5);
paintSelected.setAntiAlias(true);
mLinePaint = new Paint();
mLinePaint.setStyle(Paint.Style.FILL);
mLinePaint.setStrokeWidth(lineWidth);
mLinePaint.setAntiAlias(true);
mlableLinePaint = new Paint();
mlableLinePaint.setStyle(Paint.Style.STROKE);
mlableLinePaint.setColor(Color.DKGRAY);
mlableLinePaint.setStrokeWidth(3);
// PathEffect是用来控制绘制轮廓(线条)的方式
// 代码中的float数组,必须是偶数长度,且>=2,指定了多少长度的实线之后再画多少长度的空白
// .如本代码中,绘制长度5的实线,再绘制长度5的空白,再绘制长度5的实线,再绘制长度5的空白,依次重复.1是偏移量,可以不用理会.
PathEffect effects = new DashPathEffect(new float[]{5,5,5,5},1);
mlableLinePaint.setPathEffect(effects);
}
public void setData(Class clazz, String per, String name, List<Object> dataList){
this.dataList.clear();
max = 0;
LogUtil.i(TAG, "玫瑰图设置数据"+dataList);
if(dataList!=null){
try{
Field filedPer = clazz.getDeclaredField(per);
Field filedName = clazz.getDeclaredField(name);
filedPer.setAccessible(true);
filedName.setAccessible(true);
for(Object obj : dataList){
String perStr = filedPer.get(obj).toString();
this.dataList.add(new RoseChartBean(Float.parseFloat(perStr), (String)filedName.get(obj) ));
}
}catch (Exception e){
e.printStackTrace();
}
for(RoseChartBean bean : this.dataList){
max = max>bean.getPer()?max:bean.getPer();
}
if(getMeasuredHeight()>0){
evaluatorLable();
}
}
Log.i(TAG, "玫瑰图设置数据dataList"+this.dataList);
startDraw = false;
invalidate();
}
boolean startDraw = false;
public void setData(List<RoseChartBean> dataList){
this.dataList.clear();
max = 0;
if(dataList!=null){
this.dataList.addAll(dataList);
for(RoseChartBean bean : dataList){
max = max>bean.getPer()?max:bean.getPer();
}
evaluatorLable();
}
startDraw = false;
invalidate();
}
/**计算右侧标签相关坐标值*/
private void evaluatorLable(){
paintLabel.setTextSize(lableTextSize);
oneLableHeight = (int) FontUtil.getFontHeight(paintLabel);
//字和矩形中高度的较大值
oneLableHeight = oneLableHeight>rightRectItemH?oneLableHeight:rightRectItemH;
int allHeight = (oneLableHeight+rightRectSpace)*dataList.size() +rightRectSpace;
LogUtil.e(TAG, "测量高度"+getMeasuredHeight()+" allHeight="+allHeight);
if(allHeight>getMeasuredHeight()){
//超出总高度了
oneLableHeight = getMeasuredHeight() / ((dataList==null || dataList.size()<=0)?1:dataList.size());
rightLableTop = -1;
}else{
rightLableTop = (getMeasuredHeight()-allHeight)/2;
}
}
public void setShowChartLable(boolean showChartLable) {
this.showChartLable = showChartLable;
}
public void setShowChartNum(boolean showChartNum) {
this.showChartNum = showChartNum;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int height;
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = ScrWidth/3 * 2; //3分之2的位置画图,3分1的位置画标注
}
int chartWidth = width/3*2;
int chartSize = chartWidth>height?height:chartWidth;
//外圈半径=size/2-最大部分lable线长度-边距
chartRaidusOuter = chartSize/2-lineLenth-outSpace;
centerPoint = new PointF(chartWidth/2, height/2);
rectChart = new RectF(0,0,chartWidth, height);
rectLable = new RectF(chartWidth,0,width, height);
setMeasuredDimension(width, height);
if(dataList.size()>0){
evaluatorLable();
}
LogUtil.i(TAG, "图表总宽高="+width+"*"+height);
LogUtil.i(TAG, "chart宽高="+chartWidth+"*"+height);
LogUtil.i(TAG, "chart直径="+chartSize);
LogUtil.i(TAG, "lineLenth="+lineLenth+" outSpace="+outSpace);
LogUtil.i(TAG, "chartRaidusOuter="+chartRaidusOuter);
LogUtil.i(TAG, "centerPoint="+centerPoint.x+"*"+ centerPoint.y);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
PointF point = new PointF(event.getX(),event.getY());
sureSelectedIndex(point);
break;
case MotionEvent.ACTION_MOVE:
// LogUtil.w(TAG, "长按后的移动时间");
point = new PointF(event.getX(),event.getY());
sureSelectedIndex(point);
break;
case MotionEvent.ACTION_UP:
// selectedIndex = -1;
invalidate();
break;
}
return super.onTouchEvent(event);
}
private boolean sureSelectedIndex(PointF point){
for(int i = 0; i < dataList.size(); i++) {
RoseChartBean bean = dataList.get(i);
if(bean.getRegion()!=null && bean.getRegion().contains((int)point.x, (int)point.y)){
// LogUtil.e(TAG, "在第"+i+"个扇形区域");
selectedIndex = i;
invalidate();
return true;
}
if(bean.getRectLable()!=null && bean.getRectLable().contains((int)point.x, (int)point.y)){
// LogUtil.e(TAG, "在第"+i+"个lable区域");
selectedIndex = i;
invalidate();
return true;
}
}
return false;
}
public void onDraw(Canvas canvas){
//画布背景
canvas.drawColor(backColor);
if(isLoading){
paintLabel.setTextSize(lableTextSize);
float NullTextLead = FontUtil.getFontLeading(paintLabel);
float NullTextHeight = FontUtil.getFontHeight(paintLabel);
float textY = centerPoint.y-NullTextHeight/2+NullTextLead;
paintLabel.setColor(getContext().getResources().getColor(R.color.text_color_def));
canvas.drawText("loading...", centerPoint.x- FontUtil.getFontlength(paintLabel, "loading...")/2, textY, paintLabel);
return;
}
if(dataList==null || dataList.size()<=0) {
// paintArc.setStyle(Paint.Style.STROKE);//设置空心
// canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidusOuter, paintArc);
paintLabel.setTextSize(lableTextSize);
float NullTextLead = FontUtil.getFontLeading(paintLabel);
float NullTextHeight = FontUtil.getFontHeight(paintLabel);
float textY = centerPoint.y-NullTextHeight/2+NullTextLead;
canvas.drawText(nullStr, centerPoint.x- FontUtil.getFontlength(paintLabel, nullStr)/2, textY, paintLabel);
return;
}
// drawDebug(canvas);
if(!startDraw){
startDraw = true;
startAnimation();
}else{
drawChart(canvas);
}
}
private void drawDebug(Canvas canvas){
paintArc.setStyle(Paint.Style.STROKE);//设置空心
//绘制边界--chart区域
paintArc.setColor(Color.YELLOW);
canvas.drawRect(rectChart, paintArc);
//绘制边界--标签区域
paintArc.setColor(Color.RED);
canvas.drawRect(rectLable, paintArc);
//绘制边界--chart圆边界
paintArc.setColor(Color.BLUE);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidusOuter+outSpace+lineLenth, paintArc);
//绘制边界--chart lable边缘
paintArc.setColor(Color.GRAY);
//绘制边界--chart 扇形边缘
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidusOuter+lineLenth, paintArc);
paintArc.setColor(Color.LTGRAY);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidusOuter, paintArc);
}
public int STYLE_1 = 1;
public int STYLE_2 = 2;
private int STYLE = STYLE_2;
public void setAnimStyle(int style){
STYLE = style;
}
private long duration = 1000;
public void setAnimDuration(long duration){
this.duration = duration;
}
ValueAnimator anim;
private void startAnimation() {
if(anim!=null){
anim.cancel();
}
Log.w(TAG, "开始动画");
//将百分比转换为扇形半径长度
float percentage = 360.0f / dataList.size();
anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, percentage);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float percentage = (float) animation.getAnimatedValue();
evaluatorData(percentage);
invalidate();
}
});
anim.setDuration(duration);
anim.start();
}
/**
* 计算各种绘制坐标
* @param percentage 当前扇形度数
*/
private void evaluatorData(float percentage){
paintLabel.setTextSize(tagTextSize);
float chartTextLead = FontUtil.getFontLeading(paintLabel);
float chartTextHeight = FontUtil.getFontHeight(paintLabel);
paintLabel.setTextSize(lableTextSize);
float lableTextLead = FontUtil.getFontLeading(paintLabel);
float lableTextHeight = FontUtil.getFontHeight(paintLabel);
float radius; //每部分扇形半径
for(int i = 0; i < dataList.size(); i++){
RoseChartBean bean = dataList.get(i);
/**1、绘制扇形*/
//将百分比转换为扇区的半径
radius = chartRaidusInner+(chartRaidusOuter-chartRaidusInner)* (bean.getPer()*1.0f / max*1.0f);
float arcLeft = centerPoint.x - radius;
float arcTop = centerPoint.y - radius ;
float arcRight = centerPoint.x + radius ;
float arcBottom = centerPoint.y + radius ;
float oneStartAngle = startAngle + i * percentage;
if(STYLE == STYLE_1){
oneStartAngle = startAngle + i * percentage;
}else{
//将百分比转换为扇形半径长度
float partPercentage = 360.0f / dataList.size();
oneStartAngle = startAngle + i * partPercentage;
}
bean.setArcRect(new RectF(arcLeft ,arcTop, arcRight, arcBottom));
bean.setStartAngle(oneStartAngle);
bean.setSweepAngle(percentage);
/**计算扇形区域*/
float Allradius = chartRaidusOuter+ lineLenth + outSpace;
arcLeft = centerPoint.x - Allradius;
arcTop = centerPoint.y - Allradius;
arcRight = centerPoint.x + Allradius;
arcBottom = centerPoint.y + Allradius;
Path allPath = new Path();
allPath.moveTo(centerPoint.x, centerPoint.y);//添加原始点
float ovalX = centerPoint.x+(float) (radius* Math.cos(Math.toRadians(oneStartAngle)));
float ovalY = centerPoint.y+(float) (radius* Math.sin(Math.toRadians(oneStartAngle)));
allPath.lineTo(ovalX, ovalY);
RectF touchOval = new RectF(arcLeft, arcTop, arcRight, arcBottom);
allPath.addArc(touchOval, oneStartAngle, percentage);
allPath.lineTo(centerPoint.x, centerPoint.y);
allPath.close();
RectF r = new RectF();
allPath.computeBounds(r, true);
Region region = new Region();
region.setPath(allPath, new Region((int)r.left, (int) r.top, (int) r.right,(int)r.bottom));
bean.setRegion(region);
/**2、绘制直线*/
//确定直线的起始和结束的点的位置
float startX = centerPoint.x + (float) (radius * Math.cos(Math.toRadians(oneStartAngle + percentage / 2)));
float startY = centerPoint.y + (float) (radius * Math.sin(Math.toRadians(oneStartAngle + percentage / 2)));
float endX = centerPoint.x + (float) ((chartRaidusOuter + lineLenth) * Math.cos(Math.toRadians(oneStartAngle + percentage / 2)));
float endY = centerPoint.y + (float) ((chartRaidusOuter + lineLenth) * Math.sin(Math.toRadians(oneStartAngle + percentage / 2)));
boolean isRight = true;
float lineAngle = startAngle + i * percentage + percentage / 2;
if (lineAngle > 90 && lineAngle < 270) {
isRight = false;
}
// LogUtil.i(TAG, "直线坐标:start=("+startX+","+startY +") end=("+endX+","+endY+")"+" lineAngle="+lineAngle+" isRight="+isRight);
List<PointF> tagLinePoints = new ArrayList<>();
tagLinePoints.add(new PointF(startX, startY));
tagLinePoints.add(new PointF(endX, endY));
float textX = isRight ? (endX + 20) : (endX - 20);
tagLinePoints.add(new PointF(textX, endY));
bean.setTagLinePoints(tagLinePoints);
/**3、绘制指示标签*/
paintLabel.setTextSize(tagTextSize);
String lableText = "";
if(showChartLable){
lableText = bean.getName();
}else if(showChartNum){
lableText = bean.getPer()+"";
}
if(showNumTouched){
lableText = bean.getPer()+"";
}
//标签字体长度
float textW = FontUtil.getFontlength(paintLabel, lableText);
//字体绘制X坐标
textX = isRight ? textX : (textX - textW);
float textY = endY - chartTextHeight / 2 + chartTextLead;
bean.setTagTextPoint(new PointF(textX, textY));
/**4、绘制右侧item矩形*/
float rectL, rectT, rectR, centerY, DashPathL;
if(rightLableTop<0){
//超出总高度了
bean.setRectLable(new RectF(rectLable.left, i * oneLableHeight, rectLable.right, (i+1) * oneLableHeight));
rectL = rectLable.left+textSpace;
rectT = i * oneLableHeight + (oneLableHeight-rightRectItemH)/2;
rectR = rectL + rightRectItemW;
centerY = i * oneLableHeight + oneLableHeight/2.0f;
RectF colorRect = new RectF(rectL, rectT, rectR, rectT+ rightRectItemH);
bean.setColorRect(colorRect);
}else{
rectT = rightLableTop +rightRectSpace+ i * (oneLableHeight+rightRectSpace);
bean.setRectLable(new RectF(rectLable.left, rectT, rectLable.right, rectT+oneLableHeight));
centerY = rectT + oneLableHeight/2.0f;
rectL = rectLable.left+textSpace;
rectT = rectT + (oneLableHeight-rightRectItemH)/2;
rectR = rectL + rightRectItemW;
RectF colorRect = new RectF(rectL, rectT, rectR, rectT+ rightRectItemH);
bean.setColorRect(colorRect);
}
/**5、绘制指示标签*/
paintLabel.setTextSize(lableTextSize);
//标签字体长度
textW = FontUtil.getFontlength(paintLabel, bean.getName());
//字体绘制X坐标
rectL = rectR+textSpace;
textY = centerY -lableTextHeight/2 +lableTextLead;
bean.setNameTextPoint(new PointF(rectL, textY));
DashPathL = rectL + textW + textSpace;
/**6.绘制占比*/
textW = FontUtil.getFontlength(paintLabel, bean.getPer()+"");
rectL = rectLable.right - textW - textSpace;
bean.setPerTextPoint(new PointF(rectL, textY));
/**7、绘制虚线*/
if(DashPathL<(rectL-textSpace)){
bean.setDashPathPointStart(new PointF(DashPathL, centerY));
bean.setDashPathPointEnd(new PointF(rectL-textSpace, centerY));
}
}
}
private void drawChart(Canvas canvas){
paintArc.setStyle(Paint.Style.FILL);//设置实心
for(int i = 0; i < dataList.size(); i++){
paintArc.setARGB(255, arrColorRgb[i%arrColorRgb.length][0], arrColorRgb[i%arrColorRgb.length][1], arrColorRgb[i%arrColorRgb.length][2]);
mLinePaint.setARGB(255, arrColorRgb[i%arrColorRgb.length][0],
arrColorRgb[i%arrColorRgb.length][1], arrColorRgb[i%arrColorRgb.length][2]);
paintLabel.setARGB(255, arrColorRgb[i%arrColorRgb.length][0],
arrColorRgb[i%arrColorRgb.length][1], arrColorRgb[i%arrColorRgb.length][2]);
if(selectedIndex == i){
paintLabel.setTypeface(Typeface.DEFAULT_BOLD);
mLinePaint.setTypeface(Typeface.DEFAULT_BOLD);
}else{
paintLabel.setTypeface(Typeface.DEFAULT);
mLinePaint.setTypeface(Typeface.DEFAULT);
}
paintLabel.setTextSize(tagTextSize);
RoseChartBean bean = dataList.get(i);
/**1、绘制扇形*/
canvas.drawArc(bean.getArcRect(), bean.getStartAngle(), bean.getSweepAngle(), true, paintArc);
// canvas.drawPath(allPath,mLinePaint);
if(selectedIndex == i){
//被选中的,绘制边界
paintSelected.setStyle(Paint.Style.STROKE);//设置空心
canvas.drawArc(bean.getArcRect(), bean.getStartAngle(), bean.getSweepAngle(), true, paintSelected);
}
if(showChartLable || showChartNum){
/**2、绘制直线*/
List<PointF> tagLinePoints = bean.getTagLinePoints();
if (tagLinePoints != null && tagLinePoints.size() > 0) {
for (int p = 1; p < tagLinePoints.size(); p++) {
canvas.drawLine(tagLinePoints.get(p - 1).x, tagLinePoints.get(p - 1).y,
tagLinePoints.get(p).x, tagLinePoints.get(p).y, mLinePaint);
}
}
if(showChartLable){
/**3、绘制指示标签*/
canvas.drawText(bean.getName(), bean.getTagTextPoint().x, bean.getTagTextPoint().y, paintLabel);
}else if(showChartNum){
/**3、绘制指示标签*/
canvas.drawText(bean.getPer()+"", bean.getTagTextPoint().x, bean.getTagTextPoint().y, paintLabel);
}
}
if(showNumTouched && selectedIndex == i) {
/**2、绘制直线*/
List<PointF> tagLinePoints = bean.getTagLinePoints();
if (tagLinePoints != null && tagLinePoints.size() > 0) {
for (int p = 1; p < tagLinePoints.size(); p++) {
canvas.drawLine(tagLinePoints.get(p - 1).x, tagLinePoints.get(p - 1).y,
tagLinePoints.get(p).x, tagLinePoints.get(p).y, mLinePaint);
}
}
/**3、绘制数量*/
canvas.drawText(bean.getPer()+"", bean.getTagTextPoint().x, bean.getTagTextPoint().y, paintLabel);
LogUtil.i(TAG, "绘制bean="+bean.getName()+" "+bean.getPer());
}
/**4、绘制右侧item矩形*/
canvas.drawRoundRect(bean.getColorRect(), 8, 8, paintArc);//第二个参数是x半径,第三个参数是y半径
// LogUtil.i(TAG, "绘制lable矩形"+bean.getColorRect());
if(selectedIndex == i){
canvas.drawRoundRect(bean.getColorRect(), 8, 8, paintSelected);
}
/**5、绘制指示标签*/
paintLabel.setTextSize(lableTextSize);
paintLabel.setColor(getContext().getResources().getColor(R.color.text_color_def));
canvas.drawText(bean.getName(), bean.getNameTextPoint().x, bean.getNameTextPoint().y , paintLabel);
if(showRightNum){
/**6.绘制占比*/
canvas.drawText(bean.getPer()+"", bean.getPerTextPoint().x, bean.getPerTextPoint().y , paintLabel);
/**7、绘制虚线*/
if(bean.getDashPathPointStart()!=null){
Path path = new Path();
path.moveTo(bean.getDashPathPointStart().x,bean.getDashPathPointStart().y);
path.lineTo( bean.getDashPathPointEnd().x,bean.getDashPathPointEnd().y);
canvas.drawPath(path, mlableLinePaint);
}
}
}
//绘制中心内圆
paintArc.setColor(backColor);
canvas.drawCircle(centerPoint.x, centerPoint.y, chartRaidusInner, paintArc);
}
}
@@ -0,0 +1,162 @@
package com.openxu.cview.chart.rosechart;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import java.util.List;
/**
* autour : openXu
* date : 2017/7/24 11:04
* className : RoseChartBean
* version : 1.0
* description : 南丁格尔玫瑰图数据
*/
public class RoseChartBean {
private float per;
private String name;
//触摸相关
private Region region; //扇形区域--用于判断手指触摸点是否在此范围
private RectF rectLable; //矩形区域--用于判断手指触摸点是否在此范围
//扇形相关
private RectF arcRect;
private float startAngle;
private float sweepAngle;
//tag线段
private List<PointF> tagLinePoints;
private PointF tagTextPoint;
//右侧lable
private RectF colorRect;
private PointF nameTextPoint;
private PointF perTextPoint;
private PointF dashPathPointStart;
private PointF dashPathPointEnd;
@Override
public String toString() {
return "RoseChartBean{" +
"per=" + per +
", name='" + name + '\'' +
'}';
}
public RoseChartBean(float per, String name) {
this.per = per;
this.name = name;
}
public PointF getDashPathPointStart() {
return dashPathPointStart;
}
public void setDashPathPointStart(PointF dashPathPointStart) {
this.dashPathPointStart = dashPathPointStart;
}
public PointF getDashPathPointEnd() {
return dashPathPointEnd;
}
public void setDashPathPointEnd(PointF dashPathPointEnd) {
this.dashPathPointEnd = dashPathPointEnd;
}
public PointF getNameTextPoint() {
return nameTextPoint;
}
public void setNameTextPoint(PointF nameTextPoint) {
this.nameTextPoint = nameTextPoint;
}
public PointF getPerTextPoint() {
return perTextPoint;
}
public void setPerTextPoint(PointF perTextPoint) {
this.perTextPoint = perTextPoint;
}
public RectF getColorRect() {
return colorRect;
}
public void setColorRect(RectF colorRect) {
this.colorRect = colorRect;
}
public PointF getTagTextPoint() {
return tagTextPoint;
}
public void setTagTextPoint(PointF tagTextPoint) {
this.tagTextPoint = tagTextPoint;
}
public List<PointF> getTagLinePoints() {
return tagLinePoints;
}
public void setTagLinePoints(List<PointF> tagLinePoints) {
this.tagLinePoints = tagLinePoints;
}
public RectF getArcRect() {
return arcRect;
}
public void setArcRect(RectF arcRect) {
this.arcRect = arcRect;
}
public float getStartAngle() {
return startAngle;
}
public void setStartAngle(float startAngle) {
this.startAngle = startAngle;
}
public float getSweepAngle() {
return sweepAngle;
}
public void setSweepAngle(float sweepAngle) {
this.sweepAngle = sweepAngle;
}
public RectF getRectLable() {
return rectLable;
}
public void setRectLable(RectF rectLable) {
this.rectLable = rectLable;
}
public Region getRegion() {
return region;
}
public void setRegion(Region region) {
this.region = region;
}
public float getPer() {
return per;
}
public void setPer(float per) {
this.per = per;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,8 @@
package com.openxu.cview;
/**
* Created by Admin on 2018/6/7.
*/
public class sss {
}
@@ -0,0 +1,312 @@
package com.openxu.cview.xmstock;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.cview.xmstock.bean.FenbuInfo;
import com.openxu.cview.xmstock.bean.FenbuNum;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:26
* className : BarChart
* version : 1.0
* description : 股票涨跌分布图
*/
public class BarChart extends BaseChart {
//设置数据
private FenbuInfo fenbuData;
//计算后的数据
private List<DataPoint> itemCenterPointList;
/**可以设置的属性*/
//设置颜色(绿、黄、红)
private int[] barColor = new int[]{
Color.parseColor("#278d3a"),
Color.parseColor("#ec9343"),
Color.parseColor("#d03231")};
//设置柱子宽度
private int barSize = DensityUtil.dip2px(getContext(), 20);
//设置文字大小
private int textSize_X = (int)getResources().getDimension(R.dimen.ts_barchart_x); //X坐标文字大小
private int textSize_Y = (int)getResources().getDimension(R.dimen.ts_barchart_y); //Y坐标文字大小
private int lableTextSize = (int)getResources().getDimension(R.dimen.ts_barchart_lable);
//设置字体距离
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);
private int textSpaceY = DensityUtil.dip2px(getContext(), 6);
private int textSpaceLable = DensityUtil.dip2px(getContext(), 8);
private int textSpaceLableNum = DensityUtil.dip2px(getContext(), 12); //涨停文字与数字之间的间隙(涨停 58家)
//设置坐标文字颜色
private int textColorLable = getResources().getColor(R.color.tc_chart_lable);
private int textColorX = getResources().getColor(R.color.tc_chart_xy);
private float ONE_BAR_WEDTH; //单个柱状占的宽度(包含space)
private int YMARK_MAX = Integer.MIN_VALUE; //Y轴刻度最大值
public BarChart(Context context) {
this(context, null);
}
public BarChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BarChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
touchEnable = true;
}
/***********************************设置属性set方法**********************************/
public void setBarColor(int[] barColor) {
this.barColor = barColor;
}
public void setBarSize(int barSize) {
this.barSize = barSize;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
evaluatorByData();
invalidate();
}
/***************************************************/
/***************************************************/
/**设置数据*/
public void setData(FenbuInfo fenbuData){
this.fenbuData = fenbuData;
if(null==fenbuData)
return;
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(null==fenbuData)
return;
/**①、计算表体矩形区域*/
paintLabel.setTextSize(lableTextSize);
float lableHeight = FontUtil.getFontHeight(paintLabel);
paintLabel.setTextSize(textSize_Y);
float yMarkHeight = FontUtil.getFontHeight(paintLabel);
paintLabel.setTextSize(textSize_X);
float xLableHeight = FontUtil.getFontHeight(paintLabel);
//图表主体矩形
rectChart = new RectF(getPaddingLeft(),
getPaddingTop() + yMarkHeight + textSpaceY,
getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight() - getPaddingBottom() - lableHeight*2 - textSpaceLable*3
- xLableHeight - textSpaceX);
LogUtil.w(TAG, "表体矩形区域"+rectChart);
/**②、计算item的中点坐标(X轴)*/
List<List<String>> fenbu_line = fenbuData.getFenbu_line();
ONE_BAR_WEDTH = (rectChart.right - rectChart.left)/fenbu_line.size();
itemCenterPointList = new ArrayList<>();
for(int i = 0; i < fenbu_line.size() ; i++){
List<String> group = fenbu_line.get(i);
String valueX = group.get(0);
int valueY = Integer.parseInt(group.get(1));
if(YMARK_MAX<valueY) //计算Y刻度最大值
YMARK_MAX = valueY;
PointF point = new PointF(rectChart.left+ONE_BAR_WEDTH*i+ONE_BAR_WEDTH/2, rectChart.bottom);
LogUtil.w(TAG, valueX+" "+valueY+" point="+point);
itemCenterPointList.add(new DataPoint(valueX, valueY, point));
}
LogUtil.i(TAG, "Y轴YMARK_MAX="+YMARK_MAX +" ONE_BAR_WEDTH="+ONE_BAR_WEDTH);
}
private boolean onFocus = false; //是否正处于触摸焦点状态
private int focusIndex;
@Override
protected void onTouchMoved(PointF point) {
if(null==fenbuData)
return;
onFocus = (null != point);
List<List<String>> fenbu_line = fenbuData.getFenbu_line();
if(null != point && null!=fenbuData && fenbu_line.size()>0) {
//获取焦点对应的数据的索引
focusIndex = (int) ((point.x - rectChart.left) *
fenbu_line.size() / (rectChart.right - rectChart.left));
focusIndex = Math.max(0, Math.min(focusIndex, fenbu_line.size() - 1));
}
invalidate();
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==fenbuData)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
canvas.drawLine(rectChart.left, rectChart.bottom,
rectChart.right, rectChart.bottom, paint);
//绘制下面的文字
FenbuNum numData = fenbuData.getFenbu_num();
paintLabel.setTextSize(lableTextSize);
float doubleTextLength = FontUtil.getFontlength(paintLabel, "上涨");
float downTextLength = FontUtil.getFontlength(paintLabel, numData.getDown()+"家");
float downStopTextLength = FontUtil.getFontlength(paintLabel, numData.getDown_stop()+"家");
float equalTextLength = FontUtil.getFontlength(paintLabel, numData.getEqual()+"家");
float stopTextLength = FontUtil.getFontlength(paintLabel, numData.getStop()+"家");
float centerStart =rectChart.left + (rectChart.right-rectChart.left -
(doubleTextLength+textSpaceLableNum+(downTextLength>downStopTextLength?downTextLength:downStopTextLength)))/2;
float rightStart = rectChart.right-doubleTextLength-textSpaceLableNum-
(equalTextLength>stopTextLength?equalTextLength:stopTextLength);
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableLead = FontUtil.getFontHeight(paintLabel);
//第一行
float top = getMeasuredHeight() - getPaddingBottom() - (lableHeight+textSpaceLable)*2;
paintLabel.setColor(textColorLable);
canvas.drawText("上涨", rectChart.left,top + lableLead, paintLabel);
paintLabel.setColor(barColor[2]);
canvas.drawText(numData.getUp()+"家",
rectChart.left+doubleTextLength+textSpaceLableNum,top + lableLead, paintLabel);
paintLabel.setColor(textColorLable);
canvas.drawText("下跌", centerStart,top + lableLead, paintLabel);
paintLabel.setColor(barColor[0]);
canvas.drawText(numData.getDown()+"家",
centerStart+doubleTextLength+textSpaceLableNum,top + lableLead, paintLabel);
paintLabel.setColor(textColorLable);
canvas.drawText("平家", rightStart,top + lableLead, paintLabel);
paintLabel.setColor(barColor[1]);
canvas.drawText(numData.getEqual()+"家",
rightStart+doubleTextLength+textSpaceLableNum,top + lableLead, paintLabel);
//第二行
top = getMeasuredHeight() - getPaddingBottom() - (lableHeight+textSpaceLable);
paintLabel.setColor(textColorLable);
canvas.drawText("涨停", rectChart.left,top + lableLead, paintLabel);
paintLabel.setColor(barColor[2]);
canvas.drawText(numData.getUp_stop()+"家",
rectChart.left+doubleTextLength+textSpaceLableNum,top + lableLead, paintLabel);
paintLabel.setColor(textColorLable);
canvas.drawText("跌停", centerStart,top + lableLead, paintLabel);
paintLabel.setColor(barColor[0]);
canvas.drawText(numData.getDown_stop()+"家",
centerStart+doubleTextLength+textSpaceLableNum,top + lableLead, paintLabel);
paintLabel.setColor(textColorLable);
canvas.drawText("停牌", rightStart,top + lableLead, paintLabel);
paintLabel.setColor(Color.BLACK);
canvas.drawText(numData.getStop()+"家",
rightStart+doubleTextLength+textSpaceLableNum,top + lableLead, paintLabel);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==fenbuData)
return;
paintLabel.setTextSize(textSize_X);
float xLableLead = FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(textSize_Y);
float yLableHeight = FontUtil.getFontHeight(paintLabel);
float yLableLead = FontUtil.getFontLeading(paintLabel);
int barColorIndex = 0;
for(int i = 0 ;i<itemCenterPointList.size(); i++){
DataPoint dataPoint = itemCenterPointList.get(i);
if(onFocus && i == focusIndex){
//触摸焦点加粗
Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);//粗体
paintLabel.setTypeface(font);
}
//①、绘制X轴文字
paintLabel.setTextSize(textSize_X);
paintLabel.setColor(textColorX);
float xLableLength = FontUtil.getFontlength(paintLabel, dataPoint.getValueX());
canvas.drawText(dataPoint.getValueX(), dataPoint.getPoint().x-xLableLength/2,
dataPoint.getPoint().y + textSpaceX + xLableLead, paintLabel);
//②、绘制柱子
barColorIndex = (i==(itemCenterPointList.size()-1)/2)?1:(barColorIndex == 1 || barColorIndex == 2)?2:0;
paint.setColor(barColor[barColorIndex]);
int left = (int)(dataPoint.getPoint().x-barSize/2);
int top = (int)((rectChart.top+
(rectChart.bottom-rectChart.top)-
(rectChart.bottom-rectChart.top)/YMARK_MAX*dataPoint.getValueY()*animPro
));
canvas.drawRect(new Rect(left, top,left+barSize, (int)rectChart.bottom), paint);
//③、绘制柱子上的数字
paintLabel.setTextSize(textSize_Y);
paintLabel.setColor(barColor[barColorIndex]);
xLableLength = FontUtil.getFontlength(paintLabel, (int)dataPoint.getValueY()+"");
canvas.drawText((int) dataPoint.getValueY() + "", dataPoint.getPoint().x - xLableLength / 2,
top - textSpaceY - yLableHeight + yLableLead, paintLabel);
Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);//常规
paintLabel.setTypeface(font);
}
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(null!=fenbuData) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,288 @@
package com.openxu.cview.xmstock;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.xmstock.bean.Constacts;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.SharedData;
import static android.content.Context.VIBRATOR_SERVICE;
/**
* autour : xiami
* date : 2018/3/11 9:13
* className : BaseChart
* version : 1.0
* description : 图表基类
*
*
*
*/
public abstract class BaseChart extends View {
protected String TAG;
protected int ScrWidth,ScrHeight; //屏幕宽高
protected RectF rectChart; //图表矩形
protected PointF centerPoint; //chart中心点坐标
protected Paint paint;
protected Paint paintEffect;
protected Paint paintLabel;
protected boolean debug = SharedData.getInstance().getSp().getBoolean(SharedData.KEY_DEBUG, false);
/**可设置属性*/
protected int backColor = Color.WHITE;
//辅助线宽度
protected int lineWidth = DensityUtil.dip2px(getContext(), 0.7f);
//正在加载
protected boolean isLoading = true;
protected String loadingStr = "loading...";
protected int defColor = Color.rgb(220, 220, 220); //底色
protected boolean touchEnable = true; //是否能获取事件
protected boolean onFocus = false; //是否正处于触摸焦点状态
protected int focusPreTime = 500; //获取焦点长按时间
/**动画相关统一属性,也可以设置,需要写set方法*/
protected long animDuration = 1000;
protected ValueAnimator anim;
protected boolean startDraw = false;
public void setLoading(boolean loading) {
isLoading = loading;
invalidate();
}
public void setLineWidth(int lineWidth) {
this.lineWidth = lineWidth;
}
protected Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 100) {
onFocus = true;
onTouchMoved(lastTouchPoint);
Vibrator vibrator = (Vibrator) getContext().getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(50);
}
}
};
public BaseChart(Context context) {
this(context, null);
}
public BaseChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
init(context, attrs, defStyle);
}
public void setBackColor(int backColor) {
this.backColor = backColor;
}
public void init() {
TAG = getClass().getSimpleName();
DisplayMetrics dm = getResources().getDisplayMetrics();
ScrHeight = dm.heightPixels;
ScrWidth = dm.widthPixels;
paint = new Paint();
paint.setAntiAlias(true);
paintEffect = new Paint();
paintEffect.setAntiAlias(true);
paintEffect.setStyle(Paint.Style.FILL);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(Color.RED);
paintLabel = new Paint();
paintLabel.setAntiAlias(true);
}
public abstract void init(Context context, AttributeSet attrs, int defStyleAttr);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerPoint = new PointF(getMeasuredWidth()/2, getMeasuredHeight()/2);
rectChart = new RectF(getPaddingLeft(),getPaddingTop(),getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom());
}
protected PointF downPoint = new PointF();
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downPoint.x = event.getX();
downPoint.y = event.getY();
if(touchEnable) {
getParent().requestDisallowInterceptTouchEvent(true);
handler.sendEmptyMessageDelayed(100, focusPreTime);
}
break;
case MotionEvent.ACTION_MOVE:
if(!onFocus){
if(Math.abs(event.getY()-downPoint.y)>20 &&
Math.abs(event.getY()-downPoint.y)> Math.abs(event.getX() - downPoint.x)) {
getParent().requestDisallowInterceptTouchEvent(false);
handler.removeMessages(100);
onFocus = false;
} else {
getParent().requestDisallowInterceptTouchEvent(true);
}
}else
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
handler.removeMessages(100);
onFocus = false;
LogUtil.e(TAG, "事件结束");
break;
}
return super.dispatchTouchEvent(event);
}
protected PointF lastTouchPoint;
@Override
public boolean onTouchEvent(MotionEvent event) {
if(!touchEnable)
return false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
lastTouchPoint = new PointF(event.getX(), event.getY());
if(onFocus)
onTouchMoved(lastTouchPoint);
return true;
case MotionEvent.ACTION_MOVE:
int move = (int)(event.getX() - lastTouchPoint.x);
LogUtil.i(TAG, "MotionEvent.ACTION_MOVE"+move);
lastTouchPoint.x = (int)event.getX();
lastTouchPoint.y = (int)event.getY();
if(onFocus)
onTouchMoved(lastTouchPoint);
return true;
case MotionEvent.ACTION_UP:
lastTouchPoint.x = 0;
lastTouchPoint.y = 0;
onTouchMoved(null);
return true;
}
return true;
}
protected void onTouchMoved(PointF point){
}
public void onDraw(Canvas canvas){
try {
//画布背景
// canvas.drawColor(backColor);
if (debug) {
drawDebug(canvas);
}
drawDefult(canvas);
if (isLoading) {
drawLoading(canvas);
return;
}
if (!startDraw) {
startDraw = true;
startAnimation(canvas);
} else {
drawChart(canvas);
}
}catch (Exception e){
e.printStackTrace();
}
}
public void drawLoading(Canvas canvas) {
paintLabel.setTextSize(35);
float NullTextLead = FontUtil.getFontLeading(paintLabel);
float NullTextHeight = FontUtil.getFontHeight(paintLabel);
float textY = centerPoint.y-NullTextHeight/2+NullTextLead;
paintLabel.setColor(getContext().getResources().getColor(R.color.tc_chart_xy));
canvas.drawText(loadingStr, centerPoint.x- FontUtil.getFontlength(paintLabel, loadingStr)/2, textY, paintLabel);
}
/**绘制图表基本框架*/
public abstract void drawDefult(Canvas canvas);
/**绘制debug辅助线*/
public void drawDebug(Canvas canvas){
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
//绘制边界--chart区域
paint.setColor(Color.BLUE);
RectF r = new RectF(0,0,getMeasuredWidth(), getMeasuredHeight());
canvas.drawRect(r, paint);
paint.setColor(Color.RED);
canvas.drawRect(rectChart, paint);
}
/**绘制图表*/
public abstract void drawChart(Canvas canvas);
/**创建动画*/
protected abstract ValueAnimator initAnim();
/**动画值变化之后计算数据*/
protected abstract void evaluatorData(ValueAnimator animation);
public void setAnimDuration(long duration){
this.animDuration = duration;
}
protected void startAnimation(Canvas canvas) {
if(anim!=null){
anim.cancel();
}
anim = initAnim();
if(anim==null){
drawChart(canvas);
}else{
anim.setInterpolator(new AccelerateDecelerateInterpolator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
evaluatorData(animation);
invalidate();
}
});
anim.setDuration(animDuration);
anim.start();
}
}
}
@@ -0,0 +1,537 @@
package com.openxu.cview.xmstock;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.cview.xmstock.bean.FocusInfo;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.NumberFormatUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:26
* className : LinesChart
* version : 1.0
* description : 折线图表
*/
public class LinesChart extends BaseChart {
//设置数据
private List<List<String>> dataList;
private String[] lableXList = new String[]{"09:30", "10:30", "11:30/13:00", "14:00", "15:00"};
private int dataNumCount = 60 * 4; //60*4 一分钟一个,四小时
//计算后的数据
private List<DataPoint> lableXPointList;
private List<ArrayList<DataPoint>> linePointList;
/**可以设置的属性*/
//设置Y轴刻度数量
private int YMARK_NUM = 5;
private YMARK_TYPE yMarkType = YMARK_TYPE.INTEGER;
//设置线条颜色
private int[] lineColor = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};
//设置曲线粗细
private int lineSize = DensityUtil.dip2px(getContext(), 1.5f);
//曲线数量
private int LINE_NUM = 0;
//设置坐标文字大小
private int textSize = (int)getResources().getDimension(R.dimen.ts_chart_xy);
//设置坐标文字颜色
private int textColor = getResources().getColor(R.color.tc_chart_xy);
//设置X坐标字体与横轴的距离
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);
//设置Y坐标字体与横轴的距离
private int textSpaceY = DensityUtil.dip2px(getContext(), 3);
//设置动画类型
private AnimType animType = AnimType.SLOW_DRAW;
//设置焦点线颜色 及 粗细
private int focusLineColor = getResources().getColor(R.color.tc_chart_focus_line);
private int focusLineSize = DensityUtil.dip2px(getContext(), 0.8f);
public enum YMARK_TYPE{
PERCENTAGE, /*百分比*/
INTEGER
}
public enum AnimType{
LEFT_TO_RIGHT, //动画从左往右
BOTTOM_TO_TOP, //动画从下网上上升
SLOW_DRAW //动画缓慢绘制
}
/**需要计算相关值*/
private float lableLead, lableHeight;
/*字体绘制相关*/
private float YMARK = 1; //Y轴刻度间隔
private float YMARK_MAX = Float.MIN_VALUE; //Y轴刻度最大值
private float YMARK_MIN = Float.MAX_VALUE; //Y轴刻度最小值
public LinesChart(Context context) {
this(context, null);
}
public LinesChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LinesChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
touchEnable = true;
}
public void setYMARK_NUM(int YMARK_NUM) {
this.YMARK_NUM = YMARK_NUM;
}
public void setyMarkType(YMARK_TYPE yMarkType) {
this.yMarkType = yMarkType;
}
/***********************************设置属性set方法**********************************/
/**设置数据的数量,默认是60*4*/
public void setDataNumCount(int dataNumCount) {
this.dataNumCount = dataNumCount;
}
public void setLineColor(int[] lineColor) {
this.lineColor = lineColor;
}
public void setAnimType(AnimType animType) {
this.animType = animType;
}
public void setLINE_NUM(int LINE_NUM) {
this.LINE_NUM = LINE_NUM;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
evaluatorByData();
invalidate();
}
/***************************************************/
/***************************************************/
/**
* 设置数据
* @param dataList 折线数据
* @param lableXList X轴显示的刻度,默认是9:30-15:00(传null)
* 如果需要显示其他的数据,需要重新设置
*/
public void setData(List<List<String>> dataList, String[] lableXList){
if(null==dataList)
return;
this.dataList.clear();
this.dataList.addAll(dataList);
if(null!=lableXList && lableXList.length>0)
this.lableXList = lableXList;
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(dataList.size()<=0)
return;
dataNumCount = dataList.size();
LogUtil.w(TAG, "总共"+dataNumCount+"条数据");
/**①、计算字体相关以及图表原点坐标*/
paintLabel.setTextSize(textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
//图表主体矩形
rectChart = new RectF(getPaddingLeft(),getPaddingTop() + lableHeight + textSpaceY,
getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom() - lableHeight - textSpaceX);
/**②、计算X标签绘制坐标*/
float lableXSpace = 0;
for(String lableX : lableXList){
lableXSpace += FontUtil.getFontlength(paintLabel, lableX);
}
lableXSpace = (rectChart.right - rectChart.left - lableXSpace)/(lableXList.length -1);
lableXPointList = new ArrayList<>();
if(lableXSpace>0){
float left = rectChart.left;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
left,rectChart.bottom + textSpaceX + lableLead)));
left += (FontUtil.getFontlength(paintLabel, lableXList[i])+lableXSpace);
}
}else{
//如果X轴标签字体过长的情况需要特殊处理
float oneWidth = (rectChart.right - rectChart.left)/lableXList.length;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
float xLen = FontUtil.getFontlength(paintLabel, lableX);
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
rectChart.left+i*oneWidth+(oneWidth-xLen)/2,rectChart.bottom + textSpaceX + lableLead)));
}
}
/**③、计算Y刻度最大值和最小值以及幅度*/
int lineNum = LINE_NUM==0?(dataList.get(0).size()-1):LINE_NUM;
YMARK_MAX = Float.MIN_VALUE; //Y轴刻度最大值
YMARK_MIN = Float.MAX_VALUE; //Y轴刻度最小值
for(List<String> list : dataList){
for(int i = 1; i< lineNum+1 ; i++){
String str = list.get(i);
try {
if(str.contains("%")){
str = str.substring(0,str.indexOf("%"));//百分数不能直接强转
YMARK = Float.parseFloat(str) /100.0f;
}else{
YMARK = Float.parseFloat(str);
}
// LogUtil.d(TAG, str+" Y轴 被识别为Float = "+YMARK);
if(YMARK>YMARK_MAX)
YMARK_MAX = YMARK;
if(YMARK<YMARK_MIN)
YMARK_MIN = YMARK;
}catch (Exception e){
}
}
}
LogUtil.w(TAG, "Y轴真实YMARK_MIN="+YMARK_MIN+" YMARK_MAX="+YMARK_MAX);
if(YMARK_MAX>0)
YMARK_MAX *= 1.1f;
else
YMARK_MAX /= 1.1f;
if(YMARK_MIN>0)
YMARK_MIN /= 1.1f;
else
YMARK_MIN *= 1.1f;
if(YMARK_MIN>0)
YMARK_MIN = 0;
YMARK = (YMARK_MAX-YMARK_MIN)/(YMARK_NUM - 1);
if(yMarkType == YMARK_TYPE.INTEGER){
YMARK = (int)YMARK + 1;
YMARK_MIN = (int)YMARK_MIN;
YMARK_MAX = YMARK_MIN+YMARK*(YMARK_NUM-1);
}else if(yMarkType == YMARK_TYPE.PERCENTAGE){
}
LogUtil.i(TAG, "Y轴YMARK_MIN="+YMARK_MIN+" YMARK_MAX="+YMARK_MAX+" YMARK="+YMARK);
/**④、计算点的坐标,如果有动画的情况下,边绘制边计算会耗费性能,所以先计算*/
//创建集合,用于存放每条线上每个点的坐标数据
List<String> group = dataList.get(0);
linePointList = new ArrayList<>();
for(int i = 0; i<group.size()-1; i++)
linePointList.add(new ArrayList<DataPoint>());
float oneSpace = (rectChart.right - rectChart.left) / (dataNumCount-1);
List<DataPoint> lastDataGroup = null; //最后一组数据
for(int i = 0; i < dataList.size(); i++){
//一组数据,包含每条线的一个item数据
group = dataList.get(i);
for(int j = 1; j < group.size(); j++){
try {
float valueY;
if(group.get(j).contains("%"))
valueY = Float.parseFloat(group.get(j).substring(0,group.get(j).indexOf("%"))) /100.0f;
else
valueY = Float.parseFloat(group.get(j));
PointF point = new PointF();
//只有需要绘制的线才计算坐标
if(j<lineNum+1){
point.x = rectChart.left + i * oneSpace;
//根据最高价和最低价,计算当前数据在图表上Y轴的坐标
point.y = rectChart.bottom -
(rectChart.bottom-rectChart.top)/(YMARK_MAX - YMARK_MIN) * (valueY-YMARK_MIN);
}
linePointList.get(j-1).add(new DataPoint(group.get(0), valueY, point));
// LogUtil.w(TAG, "绘制"+j+"曲线"+(int)point.x+", "+(int)point.y);
/**默认最后一组数据为显示的lable数据*/
if(null!=onFocusChangeListener && i==dataList.size()-1){
if(null==focusInfo) {
focusInfo = new FocusInfo();
lastDataGroup = new ArrayList<>();
focusInfo.setPoint(point);
focusInfo.setFocusData(lastDataGroup);
}
lastDataGroup.add(new DataPoint(group.get(0), valueY, point));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusInfo);
}
private FocusInfo focusInfo;
@Override
protected void onTouchMoved(PointF point) {
if(null==dataList)
return;
onFocus = (null != point);
if(null != point && null!=dataList && dataList.size()>0) {
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * dataNumCount / (rectChart.right - rectChart.left));
//避免滑出
List<DataPoint> dataPoints = linePointList.get(0);
if(point.x > dataPoints.get(dataPoints.size()-1).getPoint().x)
point.x = dataPoints.get(dataPoints.size()-1).getPoint().x;
if(point.x < dataPoints.get(0).getPoint().x)
point.x = dataPoints.get(0).getPoint().x;
index = Math.max(0, Math.min(index, dataPoints.size() - 1));
focusInfo = new FocusInfo();
focusInfo.setPoint(point);
List<DataPoint> objs = new ArrayList<>();
for(List<DataPoint> points : linePointList){
objs.add(points.get(index));
}
focusInfo.setFocusData(objs);
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusInfo);
}
invalidate();
}
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
public void onfocus(FocusInfo focusInfo);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
drawGrid(canvas);
drawXLable(canvas);
drawYLable(canvas);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
drawDataPath(canvas);
drawFocus(canvas);
}
/**绘制X轴方向辅助网格*/
private void drawGrid(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(YMARK_NUM-1);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(defColor);
canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
canvas.drawLine(rectChart.right, rectChart.top, rectChart.right, rectChart.bottom, paint);
for (int i = 0; i < YMARK_NUM; i++) {
if(i==0||i ==YMARK_NUM-1) { //实线
canvas.drawLine(rectChart.left, rectChart.bottom-yMarkSpace*i,
rectChart.right, rectChart.bottom-yMarkSpace*i, paint);
} else { //虚线
Path path = new Path();
path.moveTo(rectChart.left, rectChart.bottom-yMarkSpace*i);
path.lineTo(rectChart.right,rectChart.bottom-yMarkSpace*i);
PathEffect effects = new DashPathEffect(new float[]{15,8,15,8},0);
paintEffect.setPathEffect(effects);
canvas.drawPath(path, paintEffect);
}
}
}
/**绘制X轴刻度*/
private void drawXLable(Canvas canvas){
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for(DataPoint lable : lableXPointList){
canvas.drawText(lable.getValueX(), lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制Y轴刻度*/
private void drawYLable(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(YMARK_NUM-1);
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for (int i = 0; i < YMARK_NUM; i++) {
if(yMarkType == YMARK_TYPE.INTEGER) {
canvas.drawText((int) (YMARK_MIN + i * YMARK) + "", rectChart.left,
rectChart.bottom - yMarkSpace * i - lableHeight - textSpaceY + lableLead, paintLabel);
}else if(yMarkType == YMARK_TYPE.PERCENTAGE){
canvas.drawText(NumberFormatUtil.formattedDecimalToPercentage((YMARK_MIN + i * YMARK), 2), rectChart.left,
rectChart.bottom - yMarkSpace * i - lableHeight - textSpaceY + lableLead, paintLabel);
}
}
}
/**绘制曲线*/
private void drawDataPath(Canvas canvas) {
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(lineSize);
long startTime =System.currentTimeMillis();
int lineNum = LINE_NUM==0?(dataList.get(0).size()-1):LINE_NUM;
Log.w(TAG, "总共:"+lineNum+ " 条线,每条线"+linePointList.get(0).size()+"个数据");
for(int j = 0; j < lineNum; j++){
// for(int j = 0; j < linePointList.size(); j++){
List<DataPoint> lineList = linePointList.get(j);
paint.setColor(lineColor[j]);
//一条一条的绘制
Path path = new Path();
PointF lastPoint = null;
for(int i = 0 ; i<lineList.size(); i++){
DataPoint dataPoint = lineList.get(i);
if(i == 0){
if(animType == AnimType.LEFT_TO_RIGHT){
path.moveTo(rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro, dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.moveTo(dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else{
path.moveTo(dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}else{
//quadTo:二阶贝塞尔曲线连接前后两点,这样使得曲线更加平滑
if(animType == AnimType.LEFT_TO_RIGHT){
path.quadTo(rectChart.left+(lastPoint.x-rectChart.left)*animPro, lastPoint.y,
rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro , dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.quadTo(lastPoint.x, rectChart.bottom-(rectChart.bottom-lastPoint.y)* animPro,
dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else if(animType == AnimType.SLOW_DRAW){
if(i>lineList.size()*animPro)
break;
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}else{
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}
lastPoint = dataPoint.getPoint();
}
canvas.drawPath(path, paint);
}
Log.w(TAG, "绘制一次需要:"+(System.currentTimeMillis() - startTime)+ " ms");
}
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusInfo)
return;
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(focusLineSize);
paint.setColor(focusLineColor);
//竖直线
canvas.drawLine(focusInfo.getPoint().x, rectChart.bottom, focusInfo.getPoint().x, rectChart.top, paint);
/*
paintLabel.setTextSize(focusTextSize);
paintLabel.setColor(focusTextColor);
float focusTextLead = FontUtil.getFontLeading(paintLabel);
float focusTextHeight = FontUtil.getFontHeight(paintLabel);
try {
for (Object obj : focusInfo.getObjs()) {
DataPoint dataPoint = (DataPoint)obj;
//水平参考线
canvas.drawLine(rectChart.left, dataPoint.getPoint().y, rectChart.right, dataPoint.getPoint().y, paint);
//中心点
canvas.drawCircle(dataPoint.getPoint().x, dataPoint.getPoint().y, 2.0f, paint);
//绘制刻度值
String str = "("+ dataPoint.getValueX()+",";
if(yMarkType == YMARK_TYPE.INTEGER) {
str += dataPoint.getValueY()+")";
}else if(yMarkType == YMARK_TYPE.PERCENTAGE){
str += formattedDecimalToPercentage(dataPoint.getValueY())+")";
}
float textLength = FontUtil.getFontlength(paintLabel,str);
if((rectChart.right- dataPoint.getPoint().x)>textLength){
canvas.drawText(str, dataPoint.getPoint().x + 10,
dataPoint.getPoint().y-focusTextHeight+focusTextLead - 10, paintLabel);
}else{
canvas.drawText(str, dataPoint.getPoint().x - 10 - textLength,
dataPoint.getPoint().y-focusTextHeight+focusTextLead - 10, paintLabel);
}
}
}catch (Exception e){
e.printStackTrace();
}
*/
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,223 @@
package com.openxu.cview.xmstock;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import com.openxu.cview.R;
import com.openxu.cview.chart.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* autour : xiami
* date : 2018/3/13 14:26
* className : UpDownChart
* version : 1.0
* description : 股票涨跌图
*/
public class LinesLableChart extends BaseChart {
//设置数据
private String timeStr; //时间
private String[] lableArr; //标签
private int columnNum = 1; //展示的列数
//设置线条颜色
private int[] lineColor = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};
//设置坐标文字大小
private int timeTextSize = (int)getResources().getDimension(R.dimen.ts_chart_time);
private int lableTextSize = (int)getResources().getDimension(R.dimen.ts_chart_lable);
//设置lable文字颜色
private int textColorLable = getResources().getColor(R.color.tc_chart_lable);
private int textColorTime = getResources().getColor(R.color.tc_chart_time);
//设置字体与小圆点的距离
private int textSpace = DensityUtil.dip2px(getContext(), 3); //圆圈 space 上证指数
private int itemSpace = DensityUtil.dip2px(getContext(), 12); //圆圈 上证指数 space 圆圈 上证指数
private int rowsSpace = DensityUtil.dip2px(getContext(), 12); //行距
private int radius = DensityUtil.dip2px(getContext(), 3); //小圆圈半径
/**计算*/
private float lableTextHeight,lableTextLead;
private float timeTextHeight,timeTextLead;
//数据分组
private List<List<DataPoint>> linePointList;
private Map<String, Float> columnMaxLenMap;
public LinesLableChart(Context context) {
this(context, null);
}
public LinesLableChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LinesLableChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
}
/***********************************设置属性set方法**********************************/
public void setColumnNum(int columnNum) {
this.columnNum = columnNum;
}
public void setLineColor(int[] lineColor) {
this.lineColor = lineColor;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
paintLabel.setTextSize(timeTextSize);
timeTextHeight = FontUtil.getFontHeight(paintLabel);
timeTextLead = FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(lableTextSize);
lableTextHeight = FontUtil.getFontHeight(paintLabel);
lableTextLead = FontUtil.getFontLeading(paintLabel);
int rowsNum;
if(null==lableArr || lableArr.length<=0){
rowsNum = TextUtils.isEmpty(timeStr)?0:1;
}else
rowsNum = lableArr.length<=columnNum?1:(lableArr.length/columnNum+(lableArr.length%columnNum>0?1:0));
int height = rowsNum==0?
(int)timeTextHeight:
(int)(lableTextHeight>timeTextHeight?lableTextHeight:timeTextHeight)*rowsNum + rowsSpace*(rowsNum-1);
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
// LogUtil.w(TAG, "时间文字高度"+timeTextHeight+" lable文字高度"+lableTextHeight);
// LogUtil.w(TAG, "lable总数"+lableArr.length+" 列数"+columnNum+" 行数"+rowsNum);
// LogUtil.w(TAG, "高度"+height);
setMeasuredDimension(widthSize, height+getPaddingTop()+getPaddingBottom());
evaluatorByData();
}
/***************************************************/
/**设置数据*/
public void setData(String[] lableArr, String timeStr){
this.lableArr = lableArr;
this.timeStr = timeStr;
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
requestLayout();
}
}
private void evaluatorByData() {
if(null==lableArr || lableArr.length<=0)
return;
//①、分行
linePointList = new ArrayList<>();
List<DataPoint> rowData = null;
int column = 0;
int rowNum = 1;
for(int i = 0; i<lableArr.length; i++){
// LogUtil.i(TAG, lableArr[i]+ " 第"+i+"个lable分组,到第"+rowNum+"行 "+column+"列");
if(column == 0)
rowData = new ArrayList<>();
rowData.add(new DataPoint(lableArr[i], 0, null));
if(column == columnNum-1) {
rowNum ++;
column = 0;
linePointList.add(rowData);
}else
column++;
}
columnMaxLenMap = new HashMap<>();
for(int i = 0; i<columnNum;i++){
columnMaxLenMap.put("c"+i, 0.0f);
}
//②、获取每列的最长字的长度
paintLabel.setTextSize(lableTextSize);
for(int i = 0 ; i< linePointList.size(); i++){ //遍历行
rowData = linePointList.get(i);
for(int j = 0; j< rowData.size(); j++){ //遍历列
float textLength = FontUtil.getFontlength(paintLabel, rowData.get(j).getValueX());
columnMaxLenMap.put("c"+j, textLength>columnMaxLenMap.get("c"+j)?textLength:columnMaxLenMap.get("c"+j));
// LogUtil.w(TAG, "第"+j+"列:"+rowData.get(j).getValueX()+" 文字长度"+textLength);
}
}
}
@Override
public void drawDefult(Canvas canvas) {
}
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==lableArr || lableArr.length<=0)
return;
float rowHeight = lableTextHeight>timeTextHeight?lableTextHeight:timeTextHeight;
paintLabel.setColor(textColorTime);
//绘制时间
paintLabel.setTextSize(timeTextSize);
canvas.drawText(timeStr, getPaddingRight(),
getPaddingTop() + (rowHeight- timeTextHeight)/2 + timeTextLead, paintLabel);
paintLabel.setTextSize(lableTextSize);
paintLabel.setColor(textColorLable);
float rowTextMaxLen = 0;
for(Float f : columnMaxLenMap.values()){
rowTextMaxLen += f;
}
float left = getMeasuredWidth()-getPaddingRight()-rowTextMaxLen
-(radius*2+textSpace)*columnNum-itemSpace*(columnNum-1);
paint.setStyle(Paint.Style.FILL);
for(int i = 0 ; i< linePointList.size(); i++){ //遍历行
float startX = left;
List<DataPoint> rowData = linePointList.get(i);
for(int j = 0; j< rowData.size(); j++){ //遍历列
//点
paint.setColor(lineColor[i*columnNum+j]);
canvas.drawCircle(startX+radius, getPaddingTop() +
i*(rowHeight+rowsSpace)+rowHeight/2, radius, paint);
startX += (radius*2+textSpace);
//文字
canvas.drawText(rowData.get(j).getValueX(), startX,getPaddingTop() +
i*(rowHeight+rowsSpace)+(rowHeight-lableTextHeight)/2+lableTextLead, paintLabel);
startX += (columnMaxLenMap.get("c"+j)+itemSpace);
}
}
}
@Override
protected ValueAnimator initAnim() {
return null;
}
@Override
protected void evaluatorData(ValueAnimator animation) {
}
}
@@ -0,0 +1,102 @@
package com.openxu.cview.xmstock.bean;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/14 10:54
* className : ChartData
* version : 1.0
* description : 股票数据解析封装
*/
public class ChartData {
private FenbuInfo fenbu; //分布
private TrendInfo trend; //涨跌趋势
private CompareInfo compare;
private ZhabanInfo zhaban;
private List<ZhabanStock> zhaban_stock_list;
private ZhangfuInfo zhangfu; //涨幅
private WeightInfo weight; //权重
private List<ZhangtingStock> zhangting_stock_list;
private List<ZhangtingStock> yesterday_zhangting_stock;
public FenbuInfo getFenbu() {
return fenbu;
}
public void setFenbu(FenbuInfo fenbu) {
this.fenbu = fenbu;
}
public TrendInfo getTrend() {
return trend;
}
public void setTrend(TrendInfo trend) {
this.trend = trend;
}
public CompareInfo getCompare() {
return compare;
}
public void setCompare(CompareInfo compare) {
this.compare = compare;
}
public ZhabanInfo getZhaban() {
return zhaban;
}
public void setZhaban(ZhabanInfo zhaban) {
this.zhaban = zhaban;
}
public List<ZhabanStock> getZhaban_stock_list() {
return zhaban_stock_list;
}
public void setZhaban_stock_list(List<ZhabanStock> zhaban_stock_list) {
this.zhaban_stock_list = zhaban_stock_list;
}
public ZhangfuInfo getZhangfu() {
return zhangfu;
}
public void setZhangfu(ZhangfuInfo zhangfu) {
this.zhangfu = zhangfu;
}
public WeightInfo getWeight() {
return weight;
}
public void setWeight(WeightInfo weight) {
this.weight = weight;
}
public List<ZhangtingStock> getZhangting_stock_list() {
return zhangting_stock_list;
}
public void setZhangting_stock_list(List<ZhangtingStock> zhangting_stock_list) {
this.zhangting_stock_list = zhangting_stock_list;
}
public List<ZhangtingStock> getYesterday_zhangting_stock() {
return yesterday_zhangting_stock;
}
public void setYesterday_zhangting_stock(List<ZhangtingStock> yesterday_zhangting_stock) {
this.yesterday_zhangting_stock = yesterday_zhangting_stock;
}
}
@@ -0,0 +1,30 @@
package com.openxu.cview.xmstock.bean;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : CompareInfo
* version : 1.0
* description : 涨跌对比
*/
public class CompareInfo {
private CompareLine compare_line;
private String compare_tag;
public CompareLine getCompare_line() {
return compare_line;
}
public void setCompare_line(CompareLine compare_line) {
this.compare_line = compare_line;
}
public String getCompare_tag() {
return compare_tag;
}
public void setCompare_tag(String compare_tag) {
this.compare_tag = compare_tag;
}
}
@@ -0,0 +1,59 @@
package com.openxu.cview.xmstock.bean;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : CompareLine
* version : 1.0
* description : 涨跌对比
*/
public class CompareLine {
private List<List<String>> stock_updown_all; //全部
private List<List<String>> stock_updown_sz; //深市
private List<List<String>> stock_updown_sh; //深沪
private List<List<String>> stock_updown_small; //中小
private List<List<String>> stock_updown_gem; //创业
public List<List<String>> getStock_updown_all() {
return stock_updown_all;
}
public void setStock_updown_all(List<List<String>> stock_updown_all) {
this.stock_updown_all = stock_updown_all;
}
public List<List<String>> getStock_updown_sz() {
return stock_updown_sz;
}
public void setStock_updown_sz(List<List<String>> stock_updown_sz) {
this.stock_updown_sz = stock_updown_sz;
}
public List<List<String>> getStock_updown_sh() {
return stock_updown_sh;
}
public void setStock_updown_sh(List<List<String>> stock_updown_sh) {
this.stock_updown_sh = stock_updown_sh;
}
public List<List<String>> getStock_updown_small() {
return stock_updown_small;
}
public void setStock_updown_small(List<List<String>> stock_updown_small) {
this.stock_updown_small = stock_updown_small;
}
public List<List<String>> getStock_updown_gem() {
return stock_updown_gem;
}
public void setStock_updown_gem(List<List<String>> stock_updown_gem) {
this.stock_updown_gem = stock_updown_gem;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
package com.openxu.cview.xmstock.bean;
import android.graphics.PointF;
/**
* autour : xiami
* date : 2018/3/13 16:44
* className : DataPoint
* version : 1.0
* description : 图表连线上单个点
*/
public class DataPoint {
private String valueX; //x轴 时间
private float valueY; //对应y轴上的值
private PointF point; //绘制的点的坐标
public DataPoint() {
}
public DataPoint(String valueX, float valueY, PointF point) {
this.valueX = valueX;
this.valueY = valueY;
this.point = point;
}
public String getValueX() {
return valueX;
}
public void setValueX(String valueX) {
this.valueX = valueX;
}
public float getValueY() {
return valueY;
}
public void setValueY(float valueY) {
this.valueY = valueY;
}
public PointF getPoint() {
return point;
}
public void setPoint(PointF point) {
this.point = point;
}
}
@@ -0,0 +1,41 @@
package com.openxu.cview.xmstock.bean;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : FenbuInfo
* version : 1.0
* description : 涨跌分布
*/
public class FenbuInfo {
private List<List<String>> fenbu_line;
private FenbuNum fenbu_num;
private String fenbu_tag;
public List<List<String>> getFenbu_line() {
return fenbu_line;
}
public void setFenbu_line(List<List<String>> fenbu_line) {
this.fenbu_line = fenbu_line;
}
public FenbuNum getFenbu_num() {
return fenbu_num;
}
public void setFenbu_num(FenbuNum fenbu_num) {
this.fenbu_num = fenbu_num;
}
public String getFenbu_tag() {
return fenbu_tag;
}
public void setFenbu_tag(String fenbu_tag) {
this.fenbu_tag = fenbu_tag;
}
}
@@ -0,0 +1,104 @@
package com.openxu.cview.xmstock.bean;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : FenbuInfo
* version : 1.0
* description : 涨跌分布
*/
public class FenbuNum {
private String time;
private String up;
private String up_stop;
private String down;
private String down_stop;
private String equal;
private String stop;
private String up_no_one;
private String up_one;
/*
"time": "14:51",
"up": 962,
"up_stop": 23,
"down": 2268,
"down_stop": 2,
"equal": 361,
"stop": 137,
"up_no_one": 0,
"up_one": 3
*/
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getUp() {
return up;
}
public void setUp(String up) {
this.up = up;
}
public String getUp_stop() {
return up_stop;
}
public void setUp_stop(String up_stop) {
this.up_stop = up_stop;
}
public String getDown() {
return down;
}
public void setDown(String down) {
this.down = down;
}
public String getDown_stop() {
return down_stop;
}
public void setDown_stop(String down_stop) {
this.down_stop = down_stop;
}
public String getEqual() {
return equal;
}
public void setEqual(String equal) {
this.equal = equal;
}
public String getStop() {
return stop;
}
public void setStop(String stop) {
this.stop = stop;
}
public String getUp_no_one() {
return up_no_one;
}
public void setUp_no_one(String up_no_one) {
this.up_no_one = up_no_one;
}
public String getUp_one() {
return up_one;
}
public void setUp_one(String up_one) {
this.up_one = up_one;
}
}
@@ -0,0 +1,35 @@
package com.openxu.cview.xmstock.bean;
import android.graphics.PointF;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/14 10:23
* className : FocusInfo
* version : 1.0
* description : 触摸焦点封装
*/
public class FocusInfo {
private PointF point;
private List<DataPoint> focusData;
public PointF getPoint() {
return point;
}
public void setPoint(PointF point) {
this.point = point;
}
public List<DataPoint> getFocusData() {
return focusData;
}
public void setFocusData(List<DataPoint> focusData) {
this.focusData = focusData;
}
}
@@ -0,0 +1,41 @@
package com.openxu.cview.xmstock.bean;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : TrendInfo
* version : 1.0
* description : 涨跌趋势
*/
public class TrendInfo {
private List<List<String>> day;
private List<List<String>> month;
private String trend_tag;
public List<List<String>> getDay() {
return day;
}
public void setDay(List<List<String>> day) {
this.day = day;
}
public List<List<String>> getMonth() {
return month;
}
public void setMonth(List<List<String>> month) {
this.month = month;
}
public String getTrend_tag() {
return trend_tag;
}
public void setTrend_tag(String trend_tag) {
this.trend_tag = trend_tag;
}
}
@@ -0,0 +1,32 @@
package com.openxu.cview.xmstock.bean;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : WeightInfo
* version : 1.0
* description : 权重
*/
public class WeightInfo {
private List<List<String>> weight_line;
private String weight_tag;
public List<List<String>> getWeight_line() {
return weight_line;
}
public void setWeight_line(List<List<String>> weight_line) {
this.weight_line = weight_line;
}
public String getWeight_tag() {
return weight_tag;
}
public void setWeight_tag(String weight_tag) {
this.weight_tag = weight_tag;
}
}
@@ -0,0 +1,32 @@
package com.openxu.cview.xmstock.bean;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : ZhabanInfo
* version : 1.0
* description : 炸板
*/
public class ZhabanInfo {
private List<List<String>> zhaban_line;
private String zhaban_tag;
public List<List<String>> getZhaban_line() {
return zhaban_line;
}
public void setZhaban_line(List<List<String>> zhaban_line) {
this.zhaban_line = zhaban_line;
}
public String getZhaban_tag() {
return zhaban_tag;
}
public void setZhaban_tag(String zhaban_tag) {
this.zhaban_tag = zhaban_tag;
}
}
@@ -0,0 +1,84 @@
package com.openxu.cview.xmstock.bean;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : ZhabanStock
* version : 1.0
* description : 炸板股票
*/
public class ZhabanStock {
private String symbol;
private String name;
private String code;
private String trade;
private String hsl;
private String amount;
private String updownrate;
private String zhenfu;
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTrade() {
return trade;
}
public void setTrade(String trade) {
this.trade = trade;
}
public String getHsl() {
return hsl;
}
public void setHsl(String hsl) {
this.hsl = hsl;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
public String getUpdownrate() {
return updownrate;
}
public void setUpdownrate(String updownrate) {
this.updownrate = updownrate;
}
public String getZhenfu() {
return zhenfu;
}
public void setZhenfu(String zhenfu) {
this.zhenfu = zhenfu;
}
}
@@ -0,0 +1,32 @@
package com.openxu.cview.xmstock.bean;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : ZhangfuInfo
* version : 1.0
* description : 涨幅
*/
public class ZhangfuInfo {
private List<List<String>> zhangfu_line;
private String zhangfu_tag;
public List<List<String>> getZhangfu_line() {
return zhangfu_line;
}
public void setZhangfu_line(List<List<String>> zhangfu_line) {
this.zhangfu_line = zhangfu_line;
}
public String getZhangfu_tag() {
return zhangfu_tag;
}
public void setZhangfu_tag(String zhangfu_tag) {
this.zhangfu_tag = zhangfu_tag;
}
}
@@ -0,0 +1,71 @@
package com.openxu.cview.xmstock.bean;
/**
* autour : xiami
* date : 2018/3/13 14:59
* className : ZhangtingStock
* version : 1.0
* description : 涨停股票信息
*/
public class ZhangtingStock {
//"symbol": "sh603083",
// "name": "剑桥科技",
// "code": "603083",
// "trade": "43.320",
// "updownrate": "-8.95%",
// "hsl": "6.05%"
private String symbol;
private String name;
private String code;
private String trade;
private String hsl;
private String updownrate;
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTrade() {
return trade;
}
public void setTrade(String trade) {
this.trade = trade;
}
public String getHsl() {
return hsl;
}
public void setHsl(String hsl) {
this.hsl = hsl;
}
public String getUpdownrate() {
return updownrate;
}
public void setUpdownrate(String updownrate) {
this.updownrate = updownrate;
}
}
@@ -0,0 +1,282 @@
package com.openxu.cview.xmstock20191205;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
/**
* 仪表盘图表
*/
public class DashboardView extends BaseChart {
private int total; //总数量
private int progress; //占比数量
private float animPro; //动画计算的占比数量
//开始角度 (正常情况正下方为90度,这里的30是指正下方向左偏30度,也就是120度)
private int startAngle = 30;
private int dashesSize = DensityUtil.dip2px(getContext(), 20); //虚线宽度
private int solidSize = DensityUtil.dip2px(getContext(), 9); //实线宽度
private int rSpace1 = DensityUtil.dip2px(getContext(), 25); //中心白圈与 实线间隙
private int rSpace2 = DensityUtil.dip2px(getContext(), 4); //虚线 实线间隙
//指针颜色和长度(长度应该比rSpace1小)
private int pointerColor = Color.parseColor("#d55942");
private int pointerRaidus = DensityUtil.dip2px(getContext(), 20);
private int pointerSize = DensityUtil.dip2px(getContext(), 10);
/**文字大小颜色*/
//中间文字 (5000亿)
private int centerTextSize = DensityUtil.sp2px(getContext(), 18);
private int centerTextColor = Color.parseColor("#d55942");
//仪表名称 (量能)
private String nameText = "能量";
private int nameTextSize = DensityUtil.sp2px(getContext(), 20);
private int nameTextColor = Color.parseColor("#dc3929");
//仪表刻度 (0 一万亿)
private int lableTextSize = DensityUtil.sp2px(getContext(), 18);
private int lableTextColor = Color.parseColor("#e2978a");
private String lableLeft = "0";
private String lableRight = "一万亿";
//0 一万亿 文字与扇形间距
private int lableSpace = DensityUtil.dip2px(getContext(), 4);
//渐变色
int[] proColors = new int[]{
Color.parseColor("#e7ac6a"), //初始颜色
Color.parseColor("#e47f3f"), //中间颜色
Color.parseColor("#df422c")}; //最终颜色
float[] proPositions = new float[]{0.3f, .5f, .7f};
SweepGradient proShader;
//中间灰色边缘渐变色
RadialGradient sideShader;
int[] sideColors = new int[]{
Color.parseColor("#b2c2dd"), //灰色
Color.parseColor("#00b2c2dd")}; //全透明灰色
//虚线间隔
PathEffect effects = new DashPathEffect(new float[]{9,11,9,11},0);
/**计算*/
private int centerRaidus; //中间白色圆圈半径
private int chartRaidus; //半径
protected RectF rectDashes; //虚线扇形矩形框(位于扇形中心)
protected RectF rectSolid; //实线扇形矩形框(位于扇形中心)
private PointF startPointLeft, startPointRight; //0 一万亿 lable绘制点
private PointF startPointName; //量能 绘制点
public DashboardView(Context context) {
super(context, null);
}
public DashboardView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public DashboardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(widthMeasureSpec);
LogUtil.i(TAG, "建议宽高:"+widthSize + " * "+heightSize);
//根据中间文字大小计算白色圆圈半径
paintLabel.setTextSize(centerTextSize);
int textWidth = (int)FontUtil.getFontlength(paintLabel, "100000");
// int textHeight = (int)FontUtil.getFontHeight(paintLabel);
centerRaidus = textWidth/2 + DensityUtil.dip2px(getContext(), 5);
//最大半径
chartRaidus = centerRaidus + rSpace1 + solidSize + rSpace2 + dashesSize;
paintLabel.setTextSize(lableTextSize);
LogUtil.d(TAG, "centerRaidus:"+centerRaidus);
LogUtil.d(TAG, "chartRaidus:"+chartRaidus);
//计算控件宽高
int raidus = chartRaidus;
int sectionRaidus = (int)(Math.cos(Math.toRadians(startAngle)) * raidus);//下切面高度 Math.toRadians将角度转换成弧度值
widthSize = getPaddingLeft() + getPaddingRight() + chartRaidus*2;
heightSize = getPaddingTop()+getPaddingBottom() + chartRaidus + sectionRaidus + lableSpace + (int)FontUtil.getFontHeight(paintLabel);
setMeasuredDimension(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
LogUtil.i(TAG, "宽高:"+getMeasuredWidth() + " * "+getMeasuredHeight());
centerPoint = new PointF(getPaddingLeft()+chartRaidus, getPaddingTop()+chartRaidus);
rectChart = new RectF(getPaddingLeft(),getPaddingTop(),
getMeasuredWidth()-getPaddingRight(), getPaddingTop()+chartRaidus*2);
//计算实线扇形矩形区域-----------------
raidus = centerRaidus+rSpace1+solidSize/2;
rectSolid = new RectF(centerPoint.x-raidus,centerPoint.y-raidus,
centerPoint.x+raidus, centerPoint.y+raidus);
//量能 绘制点
startPointName = new PointF(centerPoint.x, rectSolid.bottom);
//虚线部分--------------------------
raidus = centerRaidus+rSpace1+solidSize +rSpace2+dashesSize/2;
rectDashes = new RectF(centerPoint.x-raidus,centerPoint.y-raidus,
centerPoint.x+raidus, centerPoint.y+raidus);
//lable绘制开始和结束点 ----------------
raidus = centerRaidus+rSpace1+solidSize +rSpace2+dashesSize;
sectionRaidus = (int)(Math.cos(Math.toRadians(startAngle)) * raidus);
startPointLeft = new PointF((int)(centerPoint.x-Math.sin(Math.toRadians(startAngle))*raidus), centerPoint.y+sectionRaidus);
startPointRight = new PointF((int)(centerPoint.x+Math.sin(Math.toRadians(startAngle))*raidus), centerPoint.y+sectionRaidus);
//渐变色
proShader = new SweepGradient(centerPoint.x, centerPoint.y,proColors,proPositions);
sideShader = new RadialGradient(centerPoint.x, centerPoint.y, centerRaidus+rSpace1, sideColors,
new float[]{0.2f,.75f}, Shader.TileMode.CLAMP);
LogUtil.d(TAG, "centerPoint:"+centerPoint);
LogUtil.d(TAG, "rectChart:"+rectChart);
invalidate();
}
public void setData(int total, int progress){
if(progress>total)
progress = total;
this.total = total;
this.progress = progress;
startDraw = false;
invalidate();
}
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.GREEN);
canvas.drawRect(rectChart.left, rectChart.top, rectChart.right, rectChart.bottom, paint);
paint.setColor(Color.BLACK);
canvas.drawRect(rectDashes.left, rectDashes.top, rectDashes.right, rectDashes.bottom, paint);
paint.setColor(Color.GRAY);
canvas.drawRect(rectSolid.left, rectSolid.top, rectSolid.right, rectSolid.bottom, paint);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
//量能
paintLabel.setTextSize(nameTextSize);
paintLabel.setColor(nameTextColor);
canvas.drawText(nameText, startPointName.x - FontUtil.getFontlength(paintLabel, nameText)/2,
startPointName.y-FontUtil.getFontHeight(paintLabel)/3*2+FontUtil.getFontLeading(paintLabel), paintLabel);
//0 一万亿
paintLabel.setTextSize(lableTextSize);
paintLabel.setColor(lableTextColor);
canvas.drawText(lableLeft, startPointLeft.x - FontUtil.getFontlength(paintLabel, lableLeft)/2,
startPointLeft.y+lableSpace+FontUtil.getFontLeading(paintLabel), paintLabel);
canvas.drawText(lableRight, startPointRight.x - FontUtil.getFontlength(paintLabel, lableRight)/2,
startPointRight.y+lableSpace+FontUtil.getFontLeading(paintLabel), paintLabel);
//实线扇形
//由于渐变色shader是从正东方开始绘制,导致颜色排版错误,这里将画布旋转90度,正下方为0度
canvas.rotate(90, centerPoint.x, centerPoint.y);
paint.setShader(proShader);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(solidSize);
canvas.drawArc(rectSolid, startAngle, 360.0f - startAngle*2, false, paint);
paint.setShader(null);
//绘制虚线
paintEffect.setShader(null);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setPathEffect(effects);
paintEffect.setColor(defColor);
paintEffect.setStrokeWidth(dashesSize);
canvas.drawArc(rectDashes, startAngle, 360.0f - startAngle*2, false, paintEffect);
//绘制中间灰色边缘
paint.setShader(sideShader);
// paint.setColor(Color.RED);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(rSpace1);
canvas.drawCircle(centerPoint.x, centerPoint.y, centerRaidus+rSpace1/2, paint);
paint.setShader(null);
//中间白色圆圈
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
canvas.drawCircle(centerPoint.x, centerPoint.y, centerRaidus, paint);
//反转90度还原
canvas.rotate(-90, centerPoint.x, centerPoint.y);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(progress>0) {
//数量文字
paintLabel.setTextSize(centerTextSize);
paintLabel.setColor(centerTextColor);
String text = ((int)animPro)+"";
canvas.drawText(text, centerPoint.x - FontUtil.getFontlength(paintLabel, text)/2,
centerPoint.y-FontUtil.getFontHeight(paintLabel)+FontUtil.getFontLeading(paintLabel), paintLabel);
text = "亿";
canvas.drawText(text, centerPoint.x - FontUtil.getFontlength(paintLabel, text)/2,
centerPoint.y+DensityUtil.dip2px(getContext(), 2)+FontUtil.getFontLeading(paintLabel), paintLabel);
//绘制彩色虚线
//再次寻转90度
canvas.rotate(90, centerPoint.x, centerPoint.y);
float proAngle = (360.0f - startAngle*2) * (animPro/total*1.0f);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setPathEffect(effects);
paintEffect.setShader(proShader);
paintEffect.setStrokeWidth(dashesSize);
//消除由于虚线间隔导致视觉上仪表进度不够的情况,默认多绘制2.5度
canvas.drawArc(rectDashes, startAngle, proAngle<(360.0f - startAngle*2-5)? proAngle+2.5f:proAngle, false, paintEffect);
paintEffect.setShader(null);
//绘制指针
/*画一个实心三角形*/
//以正东面为0度起点计算指定角度所对应的圆周上的点的坐标:
double asin = Math.toDegrees(Math.asin(pointerSize/2f/centerRaidus));
float centerArcX1 = centerPoint.x + (float) (centerRaidus* Math.cos(Math.toRadians(startAngle-asin+proAngle)));
float centerArcY1 = centerPoint.y + (float) (centerRaidus* Math.sin(Math.toRadians(startAngle-asin+proAngle)));
// LogUtil.i(TAG, "角度 "+(startAngle-asin+proAngle)+" 弧度:"+Math.toRadians(startAngle-asin+proAngle));
// LogUtil.i(TAG, "cos "+Math.cos(Math.toRadians(startAngle-asin+proAngle)));
// LogUtil.i(TAG, "sin "+Math.sin(Math.toRadians(startAngle-asin+proAngle)));
float endx2 = centerPoint.x + (float) ((pointerRaidus+centerRaidus)* Math.cos(Math.toRadians(startAngle+proAngle)));
float endy2 = centerPoint.y + (float) ((pointerRaidus+centerRaidus)* Math.sin(Math.toRadians(startAngle+proAngle)));
float centerArcX2 = centerPoint.x + (float) (centerRaidus* Math.cos(Math.toRadians(startAngle+asin+proAngle)));
float centerArcY2 = centerPoint.y + (float) (centerRaidus* Math.sin(Math.toRadians(startAngle+asin+proAngle)));
// LogUtil.d(TAG, "角度 "+(startAngle+asin+proAngle)+" 弧度:"+Math.toRadians(startAngle+asin+proAngle));
// LogUtil.d(TAG, "cos "+Math.cos(Math.toRadians(startAngle+asin+proAngle)));
// LogUtil.d(TAG, "sin "+Math.sin(Math.toRadians(startAngle+asin+proAngle)));
Path path2=new Path();
path2.moveTo(centerArcX1,centerArcY1);
path2.lineTo(endx2,endy2);
path2.lineTo(centerArcX2,centerArcY2);
path2.close();
paint.setColor(pointerColor);
canvas.drawPath(path2, paint);
}
}
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1.0f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue()*progress;
}
}
@@ -0,0 +1,162 @@
package com.openxu.cview.xmstock20191205;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
public class LevelProgressView extends BaseChart {
private int total; //总数量
private int progress; //占比数量
private float animPro; //动画计算的占比数量
private String lvStr = "Lv";
private String lable;
protected PointF startPoint;
//文字大小
private int lableTextSize = (int)getResources().getDimension(R.dimen.ts_chart_lable);
private int lvTextSize = (int)getResources().getDimension(R.dimen.ts_chart_xy);
private int textSpace = DensityUtil.dip2px(getContext(), 3); //文字间距
private int raidus = DensityUtil.dip2px(getContext(), 5); //圆环半径
private int barSize = DensityUtil.dip2px(getContext(), 5); //宽度
//默认Lv字体颜色(白色)
private int textColor = Color.WHITE;
//进度颜色(黄色)
private int proColor = Color.parseColor("#fda33c");
public LevelProgressView(Context context) {
super(context, null);
}
public LevelProgressView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
public LevelProgressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
float height = getMeasuredHeight();
paintLabel.setTextSize(lvTextSize);
int centerY = (int)(getPaddingBottom()+FontUtil.getFontHeight(paintLabel) + textSpace + raidus);
height = getPaddingBottom()+FontUtil.getFontHeight(paintLabel) + textSpace*2 + raidus*2+getPaddingTop();
paintLabel.setTextSize(lvTextSize);
height += FontUtil.getFontHeight(paintLabel);
float lv0 = FontUtil.getFontlength(paintLabel, lvStr+"0");
float lv10 = FontUtil.getFontlength(paintLabel, lvStr+"10");
startPoint = new PointF(getPaddingLeft() + lv0/2, centerY);
rectChart = new RectF(startPoint.x, getPaddingTop(), width - getPaddingRight() - lv10/2, height-getPaddingBottom());
setMeasuredDimension(width, (int)height);
centerPoint = new PointF(width/2, height/2);
}
public void setLable(String lable) {
this.lable = lable;
}
public void setData(int total, int progress){
this.total = total;
this.progress = progress;
startDraw = false;
invalidate();
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
paint.setStyle(Paint.Style.FILL);
paint.setColor(defColor);
canvas.drawRect(rectChart.left, startPoint.y-barSize/2,
rectChart.right, startPoint.y+barSize/2, paint);
float itemWidth = rectChart.width()/(total-1);
float x;
paintLabel.setTextSize(lvTextSize);
paintLabel.setColor(textColor);
float lvHeight = FontUtil.getFontHeight(paintLabel);
float lvLead = FontUtil.getFontLeading(paintLabel);
for(int i = 0; i < total; i++){
x = rectChart.left + i*itemWidth;
canvas.drawCircle(x, startPoint.y, raidus, paint);
//绘制文字
canvas.drawText(lvStr+i, x - FontUtil.getFontlength(paintLabel, lvStr+i)/2,
rectChart.bottom-lvHeight+lvLead, paintLabel);
}
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(progress>0) {
paint.setStyle(Paint.Style.FILL);
paint.setColor(proColor);
float itemWidth = rectChart.width()/(total-1);
Log.w(TAG, "动画进度:"+animPro);
canvas.drawRect(rectChart.left, startPoint.y-barSize/2,
rectChart.left+animPro*itemWidth, startPoint.y+barSize/2, paint);
for(int i = 0; i <= (int)animPro; i++){
canvas.drawCircle(rectChart.left + i*itemWidth, startPoint.y, raidus, paint);
if(i!=0 && i==animPro){
//绘制颜色Lv文字
paintLabel.setTextSize(lvTextSize);
paintLabel.setColor(proColor);
float textHeight = FontUtil.getFontHeight(paintLabel);
float textLead = FontUtil.getFontLeading(paintLabel);
canvas.drawText(lvStr+i, rectChart.left + i*itemWidth
- FontUtil.getFontlength(paintLabel, lvStr+i)/2,
rectChart.bottom-textHeight+textLead, paintLabel);
//绘制上方文字
if(null!=lable){
paintLabel.setTextSize(lableTextSize);
textHeight = FontUtil.getFontHeight(paintLabel);
textLead = FontUtil.getFontLeading(paintLabel);
float textLength = FontUtil.getFontlength(paintLabel, lable);
canvas.drawText(lable,
(rectChart.left + i*itemWidth + textLength/2)<=getWidth() - getPaddingRight()?
rectChart.left + i*itemWidth-textLength/2 :
getWidth() - getPaddingRight() - textLength,
startPoint.y -raidus - textSpace - textHeight+textLead, paintLabel);
}
}
}
}
}
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(progress>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 1f, Float.valueOf(progress+""));
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue() - 1;
}
}
@@ -0,0 +1,673 @@
package com.openxu.cview.xmstock20191205;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.cview.xmstock.bean.FocusInfo;
import com.openxu.cview.xmstock20191205.bean.NSFocusInfo;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.NumberFormatUtil;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class NorthSouthChart extends BaseChart {
//计算后的数据
private int dataNumCount = 60*4;// 今日数据(60分钟*4小时) ,历史日周数据按照接口返回数量
private List<DataPoint> lableXPointList = new ArrayList<>(); //计算X轴坐标点容器
private List<DataPoint> lableYLPointList = new ArrayList<>(); //计算左侧Y轴坐标点容器
private List<DataPoint> lableYRPointList = new ArrayList<>(); //计算右侧Y轴坐标点容器
private List<ArrayList<DataPoint>> linePointList;
private float YMARK_MAX_L = Float.MIN_VALUE; //Y轴刻度最大值
private float YMARK_MIN_L = Float.MAX_VALUE; //Y轴刻度最小值
private float YMARK_MAX_R = Float.MIN_VALUE; //Y轴刻度最大值
private float YMARK_MIN_R = Float.MAX_VALUE; //Y轴刻度最小值
private float lableLead, lableHeight;
public enum ChartType{
TYPE_T_NORTH, //北向资金今日流向
TYPE_T_SOUTH, //南向资金今日流向
TYPE_DW_NORTH, //北向资金 历史每日/周流向
TYPE_DW_SOUTH, //南向资金 历史每日/周流向
}
/**可以设置的属性 ★表示必设置*/
//设置数据
private List<List<String>> dataList; //★图表数据
private String[] lableXArray; // X坐标刻度文字设置 new String[]{"09:30", "10:30", "11:30/13:00", "14:00", "15:00"};
private String[] lableArray; //★指标文字
private ChartType chartType; //★图表类型
private int XMARK_NUM = 5; //X轴刻度数量 和lableXArray互斥(必须设置其中一个)
private int YMARK_NUM = 5; //Y轴刻度数量
private int[] lableColor;//★设置lable对应折线颜色
private int[] upDownColor;//★设置涨跌颜色(注意顺序,第一个为涨(红色), 第二个为跌(绿色))
private int lineSize = DensityUtil.dip2px(getContext(), 1.2f);//设置曲线粗细
private int barSize = DensityUtil.dip2px(getContext(), 7); //设置柱子宽度
private float ONE_BAR_WEDTH; //不需要设置 (计算)单个柱状占的宽度(包含space)
private int lableTextSize = (int)getResources().getDimension(R.dimen.ts_chart_lable); //指标文字大小
private int textSpaceLable = DensityUtil.dip2px(getContext(), 12); //指标文字间距
private int textSize = (int)getResources().getDimension(R.dimen.ts_chart_xy);//坐标刻度文字大小
private int textColor = getResources().getColor(R.color.tc_chart_xy);//设置坐标文字颜色
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);//设置X坐标字体与横轴的距离
private int textSpaceY = DensityUtil.dip2px(getContext(), 1); //设置Y坐标字体与左右的距离
//设置焦点相关
private int focusLineColor = getResources().getColor(R.color.tc_chart_focus_line);//焦点交互线颜色
private int focusLineSize = DensityUtil.dip2px(getContext(), 0.8f); //焦点交互线宽度
private int focusPanelColor = Color.parseColor("#5f93e7"); //焦点面板背景色
private int focusTextColor = Color.parseColor("#ffffff"); //焦点面板上文字颜色
private int focusTextSize = DensityUtil.sp2px(getContext(), 8); //焦点面板上文字大小
private String[] focusLableArray; //★焦点面板文字
public NorthSouthChart(Context context) {
this(context, null);
}
public NorthSouthChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NorthSouthChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
touchEnable = true;
}
/***********************************设置属性set方法**********************************/
public void setChartType(ChartType chartType) {
this.chartType = chartType;
}
public void setYMARK_NUM(int YMARK_NUM) {
this.YMARK_NUM = YMARK_NUM;
}
public void setXMARK_NUM(int XMARK_NUM) {
this.XMARK_NUM = XMARK_NUM;
}
/**
* 设置x刻度
* @param lableXArray X轴显示的刻度,如果不设置,会自动配置,但是需要设置x刻度显示数量
*/
public void setLableX(String[] lableXArray){
if(null!=lableXArray && lableXArray.length>0)
this.lableXArray = lableXArray;
}
public void setlableArray(String[] lableArray) {
this.lableArray = lableArray;
}
public void setFocusLableArray(String[] focusLableArray) {
this.focusLableArray = focusLableArray;
}
public void setlableColor(int[] lableColor) {
this.lableColor = lableColor;
}
public void setUpDownColor(int[] upDownColor) {
this.upDownColor = upDownColor;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
evaluatorByData();
invalidate();
}
/**设置数据*/
public void setData(List<List<String>> dataList){
if(null==dataList)
return;
this.dataList.clear();
this.dataList.addAll(dataList);
}
/**重绘制*/
public void refresh(){
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(dataList.size()<=0)
return;
if(chartType == ChartType.TYPE_T_SOUTH){//南向资金今日流向
dataNumCount = (int)(60*5.5f)+2; //9:30 - 12:00 13:00 - 16:00 2.5+3=5.5小时
}else if(chartType == ChartType.TYPE_T_NORTH){//北向资金今日流向
dataNumCount = 60*4+2; //9:30 - 11:30 13:00 - 15:00 2+2=4小时
}else{
dataNumCount = dataList.size();
lableXArray = null;
}
/**①、计算字体相关以及图表原点坐标*/
paintLabel.setTextSize(textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(lableTextSize);
//图表主体矩形
rectChart = new RectF(getPaddingLeft(),
getPaddingTop() + FontUtil.getFontHeight(paintLabel) +
textSpaceLable +lableHeight/2,
getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom() - lableHeight - textSpaceX - lableHeight/2);
centerPoint = new PointF(rectChart.left+(rectChart.right-rectChart.left)/2,
rectChart.top+(rectChart.bottom-rectChart.top)/2);
/**②、计算X标签绘制坐标*/
float lableXSpace = 0;
if(lableXArray==null){
//从数据中抽取x刻度
if(dataNumCount<=XMARK_NUM)
XMARK_NUM = dataNumCount;
lableXArray = new String[XMARK_NUM];
if(dataNumCount==XMARK_NUM){
for(int i = 0 ; i < XMARK_NUM; i++)
lableXArray[i] = dataList.get(i).get(0);
}else{
lableXArray[0] = dataList.get(0).get(0); //取第一条数据的x坐标
lableXArray[XMARK_NUM-1] = dataList.get(dataNumCount-1).get(0);
float space = dataNumCount/XMARK_NUM;
boolean m = dataNumCount%XMARK_NUM>0;
float indexCount = 0;
for(int i = 1; i < XMARK_NUM-1; i++){
indexCount += space;
//四舍五入,均匀分布
lableXArray[i] = dataList.get(
((indexCount - (int)indexCount)>0.5)?
(int)indexCount + 1 : (int)indexCount
).get(0);
}
}
}
paintLabel.setTextSize(textSize);
//计算x刻度坐标
for(String lableX : lableXArray)
lableXSpace += FontUtil.getFontlength(paintLabel, lableX);
lableXSpace = (rectChart.right - rectChart.left - lableXSpace)/(lableXArray.length -1);
lableXPointList = new ArrayList<>();
if(lableXSpace>0){
float left = rectChart.left;
for(int i = 0; i<lableXArray.length; i++){
String lableX = lableXArray[i];
//借用valueY字段表示 x轴刻度点x坐标, point表示字体绘制开始点坐标
lableXPointList.add(new DataPoint(lableX, i==0?rectChart.left:(i==lableXArray.length-1?rectChart.right:(left+FontUtil.getFontlength(paintLabel, lableXArray[i])/2)),
new PointF(left,rectChart.bottom + textSpaceX + lableLead)));
left += (FontUtil.getFontlength(paintLabel, lableXArray[i])+lableXSpace);
}
}else{
//如果X轴标签字体过长的情况需要特殊处理
float oneWidth = (rectChart.right - rectChart.left)/lableXArray.length;
for(int i = 0; i<lableXArray.length; i++){
String lableX = lableXArray[i];
float xLen = FontUtil.getFontlength(paintLabel, lableX);
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
rectChart.left+i*oneWidth+(oneWidth-xLen)/2,rectChart.bottom + textSpaceX + lableLead)));
}
}
/**③、计算Y刻度最大值和最小值以及幅度*/
evaluatorYMark(true);
evaluatorYMark(false);
/**④、计算点的坐标,如果有动画的情况下,边绘制边计算会耗费性能,所以先计算*/
linePointList = new ArrayList<>();
linePointList.add(new ArrayList<DataPoint>());
linePointList.add(new ArrayList<DataPoint>());
if(chartType==ChartType.TYPE_T_SOUTH||chartType==ChartType.TYPE_T_NORTH){
float oneSpace = (rectChart.right - rectChart.left) / dataNumCount; //分时图
//今日 ["1558","27.3亿元","26208.240","+0.56%"]
for(int i=0; i<dataList.size(); i++){
List<String> itemList = dataList.get(i);
//右Y刻度线
float valueY = Float.valueOf(itemList.get(1).replace("亿元", ""));
PointF point = new PointF(rectChart.left + i * oneSpace, rectChart.bottom -
(rectChart.bottom-rectChart.top)/(YMARK_MAX_R - YMARK_MIN_R) * (valueY-YMARK_MIN_R));
linePointList.get(0).add(new DataPoint(itemList.get(0), valueY, point));
//左Y刻度线
valueY = Float.valueOf(itemList.get(2));
point = new PointF(rectChart.left + i * oneSpace,
rectChart.bottom -
(rectChart.bottom-rectChart.top)/(YMARK_MAX_L - YMARK_MIN_L) * (valueY-YMARK_MIN_L));
linePointList.get(1).add(new DataPoint(itemList.get(0), valueY, point));
}
}else{
//历史 ["2019-07-05","37.60亿元","28774.83","+0.81%"]
ONE_BAR_WEDTH = (rectChart.right - rectChart.left)/dataList.size();
for(int i=0; i<dataList.size(); i++){
List<String> itemList = dataList.get(i);
//右Y刻度线
float valueY = Float.valueOf(itemList.get(1).replace("亿元", ""));
PointF point = new PointF(rectChart.left+ONE_BAR_WEDTH*i+ONE_BAR_WEDTH/2,
valueY>0? centerPoint.y - (centerPoint.y-rectChart.top)/YMARK_MAX_R * valueY :
centerPoint.y + (rectChart.bottom-centerPoint.y)/YMARK_MIN_R * valueY);
linePointList.get(0).add(new DataPoint(itemList.get(0), valueY, point));
//左Y刻度线
valueY = Float.valueOf(itemList.get(2));
point = new PointF(rectChart.left+ONE_BAR_WEDTH*i+ONE_BAR_WEDTH/2, rectChart.bottom -
(rectChart.bottom-rectChart.top)/(YMARK_MAX_L - YMARK_MIN_L) * (valueY-YMARK_MIN_L));
linePointList.get(1).add(new DataPoint(itemList.get(0), valueY, point));
}
}
// if(null!=onFocusChangeListener)
// onFocusChangeListener.onfocus(focusInfo);
}
private void evaluatorYMark(boolean left){
float YMARK;
float YMARK_MAX = Float.MIN_VALUE;
float YMARK_MIN = Float.MAX_VALUE;
for(List<String> list : dataList){
String str = left?list.get(2):list.get(1);
if(str.contains("亿元"))
str = str.replaceAll("亿元", "");
YMARK = Float.parseFloat(str);
if(YMARK>YMARK_MAX)
YMARK_MAX = YMARK;
if(YMARK<YMARK_MIN)
YMARK_MIN = YMARK;
}
LogUtil.w(TAG, "Y轴真实YMARK_MIN="+YMARK_MIN+" YMARK_MAX="+YMARK_MAX);
float ce = (YMARK_MAX-YMARK_MIN)/10;
YMARK_MAX += ce;
YMARK_MIN -= ce;
if((chartType == ChartType.TYPE_DW_NORTH || chartType == ChartType.TYPE_DW_SOUTH) && !left){
//历史日、周,计算净流入资金中点值0
float YMARK_ABS = Math.abs(YMARK_MAX) > Math.abs(YMARK_MIN)?Math.abs(YMARK_MAX):Math.abs(YMARK_MIN);
YMARK_MAX = YMARK_ABS;
YMARK_MIN = -YMARK_ABS;
YMARK = (YMARK_MAX-YMARK_MIN)/(YMARK_NUM - 1);
}else{
YMARK = (YMARK_MAX-YMARK_MIN)/(YMARK_NUM - 1);
}
if(left){
YMARK_MAX_L = YMARK_MAX;
YMARK_MIN_L = YMARK_MIN;
}else{
YMARK_MAX_R = YMARK_MAX;
YMARK_MIN_R = YMARK_MIN;
}
List<DataPoint> lableYPointList = left?lableYLPointList:lableYRPointList;
lableYPointList.clear();
float yMarkSpace = (rectChart.bottom - rectChart.top)/(YMARK_NUM-1);
DecimalFormat df = new DecimalFormat("0.0");
for (int i = 0; i < YMARK_NUM; i++) {
float mark = Float.parseFloat(df.format(YMARK_MIN + i * YMARK));
lableYPointList.add(new DataPoint("", mark,
new PointF(left?rectChart.left+textSpaceY:rectChart.right-textSpaceY-
FontUtil.getFontlength(paintLabel, mark+""),
rectChart.bottom - yMarkSpace * i - lableHeight/2 + lableLead)));
}
}
private NSFocusInfo focusInfo;
@Override
protected void onTouchMoved(PointF point) {
if(null==dataList)
return;
onFocus = (null != point);
if(null != point && null!=dataList && dataList.size()>0) {
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * dataNumCount / (rectChart.right - rectChart.left));
//避免滑出
List<DataPoint> dataPoints = linePointList.get(0);
if(point.x > dataPoints.get(dataPoints.size()-1).getPoint().x)
point.x = dataPoints.get(dataPoints.size()-1).getPoint().x;
if(point.x < dataPoints.get(0).getPoint().x)
point.x = dataPoints.get(0).getPoint().x;
index = Math.max(0, Math.min(index, dataPoints.size() - 1));
focusInfo = new NSFocusInfo();
focusInfo.setPoint(point);
List<DataPoint> objs = new ArrayList<>();
for(List<DataPoint> points : linePointList)
objs.add(points.get(index));
//交点处坐标数据
focusInfo.setDataPoints(objs);
//焦点处后台原始数据
focusInfo.setFocusData(dataList.get(index));
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusInfo);
}
invalidate();
}
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
void onfocus(NSFocusInfo focusInfo);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
drawTopLable(canvas);
drawGrid(canvas);
drawXLable(canvas);
drawYLable(canvas);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
drawDataPath(canvas);
drawFocus(canvas);
}
/*** 绘制上方lable*/
private void drawTopLable(Canvas canvas) {
if(null==lableArray)
return;
String lableL, lableR;
if(chartType==ChartType.TYPE_T_SOUTH||chartType==ChartType.TYPE_T_NORTH){
lableL = "指数价格";
lableR = "金额 (亿)";
}else{
lableL = "指数价格";
lableR = "金额 (亿)";
}
paintLabel.setTextSize(lableTextSize);
int itemSpace = DensityUtil.dip2px(getContext(), 12); //指标lable间距
int lableSpace = DensityUtil.dip2px(getContext(), 2); //指标y与圆圈的距离
int radis = DensityUtil.dip2px(getContext(), 3); //圆圈半径
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableLead = FontUtil.getFontLeading(paintLabel);
int lableLengthCount = (lableSpace + radis*2) * lableArray.length + itemSpace * (lableArray.length-1);
for(String lable : lableArray){
lableLengthCount += FontUtil.getFontlength(paintLabel, lable);
}
float lableL_len = FontUtil.getFontlength(paintLabel, lableL);
float lableR_len = FontUtil.getFontlength(paintLabel, lableR);
//指标开始绘制x坐标
float startX = rectChart.left + lableL_len +
(rectChart.right - rectChart.left - lableLengthCount - lableL_len - lableR_len)/2;
paintLabel.setColor(Color.parseColor("#5E5E5E"));
paintLabel.setFakeBoldText(true); //加粗
canvas.drawText(lableL, rectChart.left, getPaddingTop()+lableLead, paintLabel);
canvas.drawText(lableR, rectChart.right-lableR_len, getPaddingTop()+lableLead, paintLabel);
paintLabel.setColor(Color.parseColor("#939393"));
paintLabel.setFakeBoldText(false);
for(int i = 0; i<lableArray.length; i++){
paint.setColor(lableColor[i]);
canvas.drawCircle(startX+radis, getPaddingTop()+lableHeight/2, radis, paint);
startX += radis*2+lableSpace;
canvas.drawText(lableArray[i],startX,getPaddingTop()+lableLead, paintLabel);
startX += FontUtil.getFontlength(paintLabel, lableArray[i]) + itemSpace;
}
}
/**绘制X轴方向辅助网格*/
private void drawGrid(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(YMARK_NUM-1);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(defColor);
canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
canvas.drawLine(rectChart.right, rectChart.top, rectChart.right, rectChart.bottom, paint);
if(chartType==ChartType.TYPE_T_SOUTH||chartType==ChartType.TYPE_T_NORTH){
//今日图表,需要绘制x刻度线
for(DataPoint lable : lableXPointList){
canvas.drawLine(lable.getValueY(), rectChart.bottom, lable.getValueY(),
rectChart.bottom-DensityUtil.dip2px(getContext(), 3), paint);
}
}
for (int i = 0; i < YMARK_NUM; i++) {
if(i==0||i ==YMARK_NUM-1) { //实线
canvas.drawLine(rectChart.left, rectChart.bottom-yMarkSpace*i,
rectChart.right, rectChart.bottom-yMarkSpace*i, paint);
} else { //虚线
Path path = new Path();
path.moveTo(rectChart.left, rectChart.bottom-yMarkSpace*i);
path.lineTo(rectChart.right,rectChart.bottom-yMarkSpace*i);
PathEffect effects = new DashPathEffect(new float[]{15,8,15,8},0);
paintEffect.setPathEffect(effects);
canvas.drawPath(path, paintEffect);
}
}
}
/**绘制X轴刻度*/
private void drawXLable(Canvas canvas){
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for(DataPoint lable : lableXPointList){
canvas.drawText(lable.getValueX(), lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制Y轴刻度*/
private void drawYLable(Canvas canvas){
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for(DataPoint lable : lableYLPointList){
canvas.drawText(lable.getValueY()+"", lable.getPoint().x, lable.getPoint().y, paintLabel);
}
for(DataPoint lable : lableYRPointList){
canvas.drawText(lable.getValueY()+"", lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制曲线*/
private void drawDataPath(Canvas canvas) {
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(lineSize);
if(chartType==ChartType.TYPE_T_SOUTH||chartType==ChartType.TYPE_T_NORTH){
//今日 ["1558","27.3亿元","26208.240","+0.56%"]
for(int j = 0; j < linePointList.size(); j++){ //先画右边刻度线,再画左边
List<DataPoint> lineList = linePointList.get(j);
paint.setColor(lableColor[j]);
Path path = new Path();
PointF lastPoint = null;
for(int i = 0 ; i<lineList.size(); i++){
if(i == 0){
path.moveTo(lineList.get(i).getPoint().x, lineList.get(i).getPoint().y);
}else{
//quadTo:二阶贝塞尔曲线连接前后两点,这样使得曲线更加平滑
path.quadTo(lastPoint.x, lastPoint.y, lineList.get(i).getPoint().x, lineList.get(i).getPoint().y);
}
lastPoint = lineList.get(i).getPoint();
if(i==lineList.size()-1){
//绘制今日图 最后一个点的值,显示在折线上
paintLabel.setColor(lableColor[j]);
paintLabel.setTextSize(textSize);
float length = FontUtil.getFontlength(paintLabel, lineList.get(i).getValueY()+"");
canvas.drawText(lineList.get(i).getValueY()+"",
(lastPoint.x + length/2)>rectChart.right? rectChart.right : (lastPoint.x - length/2),
lastPoint.y - DensityUtil.dip2px(getContext(), 6) - lableHeight + lableLead,
paintLabel);
}
}
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.FILL);
paint.setAlpha(100); //设置alpha不透明度,范围为0~255
canvas.drawCircle(lastPoint.x, lastPoint.y, DensityUtil.dip2px(getContext(), 5), paint);
paint.setAlpha(255); //设置alpha不透明度,范围为0~255
canvas.drawCircle(lastPoint.x, lastPoint.y, DensityUtil.dip2px(getContext(), 2), paint);
paint.setStyle(Paint.Style.STROKE);
}
}else{
//历史 ["2019-07-05","37.60亿元","28774.83","+0.81%"]
//绘制柱子
List<DataPoint> barList = linePointList.get(0);
paint.setStyle(Paint.Style.FILL);
for(int i = 0 ;i<barList.size(); i++) {
DataPoint dataPoint = barList.get(i);
//"37.60亿元" 净流入、流出颜色选择
//以中心点0为分割线,红色向上,绿色向下
if(dataPoint.getValueY()>0){
paint.setColor(upDownColor[0]); //红色
canvas.drawRect(new Rect((int)(dataPoint.getPoint().x-barSize/2),
(int)dataPoint.getPoint().y,
(int)(dataPoint.getPoint().x+barSize/2),
(int)centerPoint.y), paint);
}else{
paint.setColor(upDownColor[1]); //绿色
canvas.drawRect(new Rect((int)(dataPoint.getPoint().x-barSize/2),
(int)centerPoint.y,
(int)(dataPoint.getPoint().x+barSize/2),
(int)dataPoint.getPoint().y), paint);
}
}
//绘制指数线
List<DataPoint> lineList = linePointList.get(1);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(lableColor[2]);
Path path = new Path();
PointF lastPoint = null;
for(int i = 0 ; i<lineList.size(); i++){
if(i == 0){
path.moveTo(lineList.get(i).getPoint().x, lineList.get(i).getPoint().y);
}else{
//quadTo:二阶贝塞尔曲线连接前后两点,这样使得曲线更加平滑
path.quadTo(lastPoint.x, lastPoint.y, lineList.get(i).getPoint().x, lineList.get(i).getPoint().y);
}
lastPoint = lineList.get(i).getPoint();
}
canvas.drawPath(path, paint);
}
}
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusInfo)
return;
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(focusLineSize);
paint.setColor(focusLineColor);
//垂直方向焦点线
canvas.drawLine(focusInfo.getDataPoints().get(0).getPoint().x, rectChart.bottom,
focusInfo.getDataPoints().get(0).getPoint().x, rectChart.top, paint);
int radis = DensityUtil.dip2px(getContext(), 2);
if(chartType==ChartType.TYPE_T_SOUTH||chartType==ChartType.TYPE_T_NORTH){
//横向焦点线
canvas.drawLine(rectChart.left, focusInfo.getDataPoints().get(0).getPoint().y,
rectChart.right, focusInfo.getDataPoints().get(0).getPoint().y, paint);
//折线上面焦点圆圈
for(int i = 0; i< focusInfo.getDataPoints().size(); i++){
DataPoint dataPoint = focusInfo.getDataPoints().get(i);
paint.setColor(lableColor[i]);
canvas.drawCircle(dataPoint.getPoint().x, dataPoint.getPoint().y, radis, paint);
}
}else{
//历史图,指数焦点
DataPoint dataPoint = focusInfo.getDataPoints().get(1); //指数
canvas.drawLine(rectChart.left, dataPoint.getPoint().y,
rectChart.right, dataPoint.getPoint().y, paint);
paint.setColor(lableColor[2]);
canvas.drawCircle(dataPoint.getPoint().x, dataPoint.getPoint().y, radis, paint);
}
//绘制焦点数据显示面板
paintLabel.setTextSize(focusTextSize);
String maxStr = "恒生指数跌涨浮 00.00亿元";
float maxStrLength = FontUtil.getFontlength(paintLabel, maxStr);
float txSpace = DensityUtil.dip2px(getContext(), 2);
float txHeight = FontUtil.getFontHeight(paintLabel);
float txLead = FontUtil.getFontLeading(paintLabel);
float panelWidth = maxStrLength+txSpace*4;
float panelHeight = FontUtil.getFontHeight(paintLabel)*4+txSpace*3+txSpace*4;//四行字,3个字行距,2个2倍上下间距
paint.setColor(focusPanelColor);
RectF rectF;
if(focusInfo.getPoint().x>=centerPoint.x) //绘制在左上角
rectF = new RectF(rectChart.left, rectChart.top, rectChart.left+panelWidth, rectChart.top+panelHeight);
else
rectF = new RectF(rectChart.right-panelWidth, rectChart.top, rectChart.right, rectChart.top+panelHeight);
//绘制面板背景
paint.setColor(focusPanelColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(rectF, paint);
//绘制文字
if(chartType==ChartType.TYPE_T_SOUTH||chartType==ChartType.TYPE_T_NORTH){
//["0930","1.0亿元","26300.510","+0.91%"]
// new String[]{"时间", "净流入金额", "恒生指数价格", "恒生指数跌涨幅"}
drawFocusText(canvas, focusLableArray[0], focusInfo.getFocusData().get(0), focusTextColor, focusTextColor,1, rectF, txSpace, txHeight, txLead);
drawFocusText(canvas, focusLableArray[1], focusInfo.getFocusData().get(1), focusTextColor, focusTextColor,2, rectF, txSpace, txHeight, txLead);
int udColor = Float.parseFloat(focusInfo.getFocusData().get(3).replace("%", ""))>0?upDownColor[0]:upDownColor[1];
drawFocusText(canvas, focusLableArray[2], focusInfo.getFocusData().get(2), focusTextColor, udColor,3, rectF, txSpace, txHeight, txLead);
drawFocusText(canvas, focusLableArray[3], focusInfo.getFocusData().get(3), focusTextColor, udColor,4, rectF, txSpace, txHeight, txLead);
}else{
//["2019-11-14","22.85亿元","2909.87","+0.16%"]
drawFocusText(canvas, focusLableArray[0], focusInfo.getFocusData().get(0), focusTextColor, focusTextColor,1, rectF, txSpace, txHeight, txLead);
drawFocusText(canvas, focusLableArray[1], focusInfo.getFocusData().get(1), focusTextColor, focusTextColor,2, rectF, txSpace, txHeight, txLead);
int udColor = Float.parseFloat(focusInfo.getFocusData().get(3).replace("%", ""))>0?upDownColor[0]:upDownColor[1];
drawFocusText(canvas, focusLableArray[2], focusInfo.getFocusData().get(2), focusTextColor, udColor,3, rectF, txSpace, txHeight, txLead);
drawFocusText(canvas, focusLableArray[3], focusInfo.getFocusData().get(3), focusTextColor, udColor,4, rectF, txSpace, txHeight, txLead);
}
}
private void drawFocusText(Canvas canvas, String text, String value, int txColor1, int txColor2,
int line, RectF rectF, float txSpace, float txHeight, float txLead){
float y = rectF.top + txSpace*2 + (line-1)*(txSpace+txHeight) + txLead;
paintLabel.setColor(txColor1);
canvas.drawText(text, rectF.left+txSpace*2, y, paintLabel);
paintLabel.setColor(txColor2);
canvas.drawText(value, rectF.right - txSpace*2 - FontUtil.getFontlength(paintLabel, value), y, paintLabel);
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,114 @@
package com.openxu.cview.xmstock20191205;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;
/**
* Author: openXu
* Time: 2020/4/9 14:13
* class: OilTableLine
* Description:
*/
public class OilTableLine extends View {
public static final int[] SWEEP_GRADIENT_COLORS = new int[]{Color.GREEN, Color.GREEN, Color.BLUE, Color.RED, Color.RED};
private int tableWidth = 50;
private Paint mPaint;
private Path mPath;
private RectF mTableRectF;
//把路径分成虚线段的
private DashPathEffect dashPathEffect;
//给路径上色
private SweepGradient mColorShader;
//指针的路径
private Path mPointerPath;
private float mCurrentDegree = 60;
public OilTableLine(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(Color.BLACK);
mPath = new Path();
mPointerPath = new Path();
startAnimator();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
float size = Math.min(w, h) - tableWidth * 2;
//油表的位置方框
mTableRectF = new RectF(0, 0, size, size);
mPath.reset();
//在油表路径中增加一个从起始弧度
mPath.addArc(mTableRectF, 60, 240);
//计算路径的长度
PathMeasure pathMeasure = new PathMeasure(mPath, false);
float length = pathMeasure.getLength();
float step = length / 60;
dashPathEffect = new DashPathEffect(new float[]{step / 3, step * 2 / 3}, 0);
float radius = size / 2;
mColorShader = new SweepGradient(radius, radius,SWEEP_GRADIENT_COLORS,null);
//设置指针的路径位置
mPointerPath.reset();
mPointerPath.moveTo(radius, radius - 20);
mPointerPath.lineTo(radius, radius + 20);
mPointerPath.lineTo(radius * 2 - tableWidth, radius);
mPointerPath.close();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float dx = (getWidth() - mTableRectF.width()) / 2;
float dy = (getHeight() - mTableRectF.height()) / 2;
//把油表的方框平移到正中间
canvas.translate(dx, dy);
canvas.save();
//旋转画布
canvas.rotate(90, mTableRectF.width() / 2, mTableRectF.height() / 2);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(tableWidth);
mPaint.setPathEffect(dashPathEffect);
mPaint.setShader(mColorShader);
canvas.drawPath(mPath, mPaint);
canvas.restore();
//还原画笔
mPaint.setPathEffect(null);
mPaint.setShader(null);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(tableWidth / 10);
canvas.save();
canvas.rotate(150 + mCurrentDegree, mTableRectF.width() / 2, mTableRectF.height() / 2);
canvas.drawPath(mPointerPath, mPaint);
canvas.restore();
}
public void startAnimator() {
ValueAnimator animator = ValueAnimator.ofFloat(0, 240);
animator.setDuration(40000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentDegree = (int) (0 + (Float) animation.getAnimatedValue());
invalidate();
}
});
animator.start();
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,46 @@
package com.openxu.cview.xmstock20191205.bean;
import android.graphics.PointF;
import com.openxu.cview.xmstock.bean.DataPoint;
import java.util.List;
/**
* autour : xiami
* date : 2018/3/14 10:23
* className : FocusInfo
* version : 1.0
* description : 触摸焦点封装
*/
public class NSFocusInfo {
private PointF point;
private List<DataPoint> dataPoints;
private List<String> focusData;
public PointF getPoint() {
return point;
}
public void setPoint(PointF point) {
this.point = point;
}
public List<DataPoint> getDataPoints() {
return dataPoints;
}
public void setDataPoints(List<DataPoint> dataPoints) {
this.dataPoints = dataPoints;
}
public List<String> getFocusData() {
return focusData;
}
public void setFocusData(List<String> focusData) {
this.focusData = focusData;
}
}
@@ -0,0 +1,22 @@
package com.openxu.cview.xmstock20191205.bean;
import java.util.List;
/**
* Author: openXu
* Time: 2019/12/5 17:22
* class: NorthSouth
* Description:
*/
public class NorthSouth {
private List<List<String>> data;
public List<List<String>> getData() {
return data;
}
public void setData(List<List<String>> data) {
this.data = data;
}
}
@@ -0,0 +1,609 @@
package com.openxu.cview.xmstock20201030;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.openxu.cview.R;
import com.openxu.cview.xmstock20201030.bean.DayFinish;
import com.openxu.utils.FontUtil;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* author : openXu
* create at : 2017/1/3 9:56
* blog : http://blog.csdn.net/xmxkf
* gitHub : https://github.com/openXu
* project : CustomCalendar
* class name : CustomCalendar
* version : 1.0
* class describe:自定义日历控件
*/
public class CustomCalendar extends View {
private String TAG = "CustomCalendar";
/** 各部分背景*/
private int mBgMonth, mBgWeek, mBgDay, mBgPre;
/** 标题的颜色、大小*/
private int mTextColorMonth;
private float mTextSizeMonth;
private int mMonthRowL, mMonthRowR;
private float mMonthRowSpac;
private float mMonthSpac;
/** 星期的颜色、大小*/
private int mTextColorWeek, mSelectWeekTextColor;
private float mTextSizeWeek;
/** 日期文本的颜色、大小*/
private int mTextColorDay;
private float mTextSizeDay;
/** 任务次数文本的颜色、大小*/
private int mTextColorPreFinish, mTextColorPreUnFinish, mTextColorPreNull;
private float mTextSizePre;
/** 选中的文本的颜色*/
private int mSelectTextColor;
/** 选中背景*/
private int mSelectBg, mCurrentBg;
private float mSelectRadius, mCurrentBgStrokeWidth;
private float[] mCurrentBgDashPath;
/** 行间距*/
private float mLineSpac;
/** 字体上下间距*/
private float mTextSpac;
private Paint mPaint;
private Paint bgPaint;
private float titleHeight, weekHeight, dayHeight, preHeight, oneHeight;
private int columnWidth; //每列宽度
private Date month; //当前的月份
private boolean isCurrentMonth; //展示的月份是否是当前月
private int currentDay, selectDay, lastSelectDay; //当前日期 、 选中的日期 、上一次选中的日期(避免造成重复回调请求)
private int dayOfMonth; //月份天数
private int firstIndex; //当月第一天位置索引
private int todayWeekIndex;//今天是星期几
private int firstLineNum, lastLineNum; //第一行、最后一行能展示多少日期
private int lineNum; //日期行数
private String[] WEEK_STR = new String[]{"Sun", "Mon", "Tues", "Wed", "Thur", "Fri", "Sat", };
public CustomCalendar(Context context) {
this(context, null);
}
public CustomCalendar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomCalendar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性的值
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomCalendar, defStyleAttr, 0);
mBgMonth = a.getColor(R.styleable.CustomCalendar_mBgMonth, Color.TRANSPARENT);
mBgWeek = a.getColor(R.styleable.CustomCalendar_mBgWeek, Color.TRANSPARENT);
mBgDay = a.getColor(R.styleable.CustomCalendar_mBgDay, Color.TRANSPARENT);
mBgPre = a.getColor(R.styleable.CustomCalendar_mBgPre, Color.TRANSPARENT);
mMonthRowL = a.getResourceId(R.styleable.CustomCalendar_mMonthRowL, R.mipmap.custom_calendar_row_left);
mMonthRowR = a.getResourceId(R.styleable.CustomCalendar_mMonthRowR, R.mipmap.custom_calendar_row_right);
mMonthRowSpac = a.getDimension(R.styleable.CustomCalendar_mMonthRowSpac, 20);
mTextColorMonth = a.getColor(R.styleable.CustomCalendar_mTextColorMonth, Color.BLACK);
mTextSizeMonth = a.getDimension(R.styleable.CustomCalendar_mTextSizeMonth, 100);
mMonthSpac = a.getDimension(R.styleable.CustomCalendar_mMonthSpac, 20);
mTextColorWeek = a.getColor(R.styleable.CustomCalendar_mTextColorWeek, Color.BLACK);
mSelectWeekTextColor = a.getColor(R.styleable.CustomCalendar_mSelectWeekTextColor, Color.BLACK);
mTextSizeWeek = a.getDimension(R.styleable.CustomCalendar_mTextSizeWeek, 70);
mTextColorDay = a.getColor(R.styleable.CustomCalendar_mTextColorDay, Color.GRAY);
mTextSizeDay = a.getDimension(R.styleable.CustomCalendar_mTextSizeDay, 70);
mTextColorPreFinish = a.getColor(R.styleable.CustomCalendar_mTextColorPreFinish, Color.BLUE);
mTextColorPreUnFinish = a.getColor(R.styleable.CustomCalendar_mTextColorPreUnFinish, Color.BLUE);
mTextColorPreNull = a.getColor(R.styleable.CustomCalendar_mTextColorPreNull, Color.BLUE);
mTextSizePre = a.getDimension(R.styleable.CustomCalendar_mTextSizePre, 40);
mSelectTextColor = a.getColor(R.styleable.CustomCalendar_mSelectTextColor, Color.YELLOW);
mCurrentBg = a.getColor(R.styleable.CustomCalendar_mCurrentBg, Color.GRAY);
try {
int dashPathId = a.getResourceId(R.styleable.CustomCalendar_mCurrentBgDashPath, R.array.customCalendar_currentDay_bg_DashPath);
int[] array = getResources().getIntArray(dashPathId);
mCurrentBgDashPath = new float[array.length];
for(int i=0;i<array.length;i++){
mCurrentBgDashPath[i]=array[i];
}
}catch (Exception e){
e.printStackTrace();
mCurrentBgDashPath = new float[]{2, 3, 2, 3};
}
mSelectBg = a.getColor(R.styleable.CustomCalendar_mSelectBg, Color.YELLOW);
mSelectRadius = a.getDimension(R.styleable.CustomCalendar_mSelectRadius, 20);
mCurrentBgStrokeWidth = a.getDimension(R.styleable.CustomCalendar_mCurrentBgStrokeWidth, 5);
mLineSpac = a.getDimension(R.styleable.CustomCalendar_mLineSpac, 20);
mTextSpac = a.getDimension(R.styleable.CustomCalendar_mTextSpac, 20);
a.recycle(); //注意回收
initCompute();
}
/**计算相关常量,构造方法中调用*/
private void initCompute(){
mPaint = new Paint();
bgPaint = new Paint();
mPaint.setAntiAlias(true); //抗锯齿
bgPaint.setAntiAlias(true); //抗锯齿
map = new HashMap<>();
//标题高度
mPaint.setTextSize(mTextSizeMonth);
titleHeight = FontUtil.getFontHeight(mPaint) + 2 * mMonthSpac;
//星期高度
mPaint.setTextSize(mTextSizeWeek);
weekHeight = FontUtil.getFontHeight(mPaint);
//日期高度
mPaint.setTextSize(mTextSizeDay);
dayHeight = FontUtil.getFontHeight(mPaint);
//次数字体高度
mPaint.setTextSize(mTextSizePre);
preHeight = FontUtil.getFontHeight(mPaint);
//每行高度 = 行间距 + 日期字体高度 + 字间距 + 次数字体高度
oneHeight = mLineSpac + dayHeight + mTextSpac + preHeight;
//默认当前月份
String cDateStr = getMonthStr(new Date());
// cDateStr = "2015年08月";
setMonth(cDateStr);
}
/**设置月份*/
private void setMonth(String Month){
//设置的月份(2017年01月)
month = str2Date(Month);
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
//获取今天是多少号
currentDay = calendar.get(Calendar.DAY_OF_MONTH);
todayWeekIndex = calendar.get(Calendar.DAY_OF_WEEK)-1;
Date cM = str2Date(getMonthStr(new Date()));
//判断是否为当月
if(cM.getTime() == month.getTime()){
isCurrentMonth = true;
selectDay = currentDay;//当月默认选中当前日
}else{
isCurrentMonth = false;
selectDay = 0;
}
Log.d(TAG, "设置月份:"+month+" 今天"+currentDay+"号, 是否为当前月:"+isCurrentMonth);
calendar.setTime(month);
dayOfMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
//第一行1号显示在什么位置(星期几)
firstIndex = calendar.get(Calendar.DAY_OF_WEEK)-1;
lineNum = 1;
//第一行能展示的天数
firstLineNum = 7-firstIndex;
lastLineNum = 0;
int shengyu = dayOfMonth - firstLineNum;
while (shengyu>7){
lineNum ++;
shengyu-=7;
}
if(shengyu>0){
lineNum ++;
lastLineNum = shengyu;
}
Log.i(TAG, getMonthStr(month)+"一共有"+dayOfMonth+"天,第一天的索引是:"+firstIndex+" 有"+lineNum+
"行,第一行"+firstLineNum+"个,最后一行"+lastLineNum+"个");
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//宽度 = 填充父窗体
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //获取宽的尺寸
columnWidth = widthSize / 7;
//高度 = 标题高度 + 星期高度 + 日期行数*每行高度
float height = titleHeight + weekHeight + (lineNum * oneHeight);
Log.v(TAG, "标题高度:"+titleHeight+" 星期高度:"+weekHeight+" 每行高度:"+oneHeight+
" 行数:"+ lineNum + " \n控件高度:"+height);
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
(int)height);
}
@Override
protected void onDraw(Canvas canvas) {
drawMonth(canvas);
drawWeek(canvas);
drawDayAndPre(canvas);
}
/**绘制月份*/
private int rowLStart, rowRStart, rowWidth;
private void drawMonth(Canvas canvas){
//背景
bgPaint.setColor(mBgMonth);
RectF rect = new RectF(0, 0, getWidth(), titleHeight);
canvas.drawRect(rect, bgPaint);
//绘制月份
mPaint.setTextSize(mTextSizeMonth);
mPaint.setColor(mTextColorMonth);
float textLen = FontUtil.getFontlength(mPaint, getMonthStr(month));
float textStart = (getWidth() - textLen)/ 2;
canvas.drawText(getMonthStr(month), textStart,
mMonthSpac+FontUtil.getFontLeading(mPaint), mPaint);
/*绘制左右箭头*/
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mMonthRowL);
int h = bitmap.getHeight();
rowWidth = bitmap.getWidth();
//float left, float top
rowLStart = (int)(textStart-2*mMonthRowSpac-rowWidth);
canvas.drawBitmap(bitmap, rowLStart+mMonthRowSpac , (titleHeight - h)/2, new Paint());
bitmap = BitmapFactory.decodeResource(getResources(), mMonthRowR);
rowRStart = (int)(textStart+textLen);
canvas.drawBitmap(bitmap, rowRStart+mMonthRowSpac, (titleHeight - h)/2, new Paint());
}
/**绘制绘制星期*/
private void drawWeek(Canvas canvas){
//背景
bgPaint.setColor(mBgWeek);
RectF rect = new RectF(0, titleHeight, getWidth(), titleHeight + weekHeight);
canvas.drawRect(rect, bgPaint);
//绘制星期:七天
mPaint.setTextSize(mTextSizeWeek);
for(int i = 0; i < WEEK_STR.length; i++){
if(todayWeekIndex == i && isCurrentMonth){
mPaint.setColor(mSelectWeekTextColor);
}else{
mPaint.setColor(mTextColorWeek);
}
int len = (int)FontUtil.getFontlength(mPaint, WEEK_STR[i]);
int x = i * columnWidth + (columnWidth - len)/2;
canvas.drawText(WEEK_STR[i], x, titleHeight + FontUtil.getFontLeading(mPaint), mPaint);
}
}
/**绘制日期和次数*/
private void drawDayAndPre(Canvas canvas){
//某行开始绘制的Y坐标,第一行开始的坐标为标题高度+星期部分高度
float top = titleHeight+weekHeight;
//行
for(int line = 0; line < lineNum; line++){
if(line == 0){
//第一行
drawDayAndPre(canvas, top, firstLineNum, 0, firstIndex);
}else if(line == lineNum-1){
//最后一行
top += oneHeight;
drawDayAndPre(canvas, top, lastLineNum, firstLineNum+(line-1)*7, 0);
}else{
//满行
top += oneHeight;
drawDayAndPre(canvas, top, 7, firstLineNum+(line-1)*7, 0);
}
}
}
/**
* 绘制某一行的日期
* @param canvas
* @param top 顶部坐标
* @param count 此行需要绘制的日期数量(不一定都是7天)
* @param overDay 已经绘制过的日期,从overDay+1开始绘制
* @param startIndex 此行第一个日期的星期索引
*/
private void drawDayAndPre(Canvas canvas, float top,
int count, int overDay, int startIndex){
// Log.e(TAG, "总共"+dayOfMonth+"天 有"+lineNum+"行"+ " 已经画了"+overDay+"天,下面绘制:"+count+"天");
//背景
float topPre = top + mLineSpac + dayHeight;
bgPaint.setColor(mBgDay);
RectF rect = new RectF(0, top, getWidth(), topPre);
canvas.drawRect(rect, bgPaint);
bgPaint.setColor(mBgPre);
rect = new RectF(0, topPre, getWidth(), topPre + mTextSpac + dayHeight);
canvas.drawRect(rect, bgPaint);
mPaint.setTextSize(mTextSizeDay);
float dayTextLeading = FontUtil.getFontLeading(mPaint);
mPaint.setTextSize(mTextSizePre);
float preTextLeading = FontUtil.getFontLeading(mPaint);
// Log.v(TAG, "当前日期:"+currentDay+" 选择日期:"+selectDay+" 是否为当前月:"+isCurrentMonth);
for(int i = 0; i<count; i++){
int left = (startIndex + i)*columnWidth;
int day = (overDay+i+1);
mPaint.setTextSize(mTextSizeDay);
//如果是当前月,当天日期需要做处理
if(isCurrentMonth && currentDay == day){
mPaint.setColor(mTextColorDay);
bgPaint.setColor(mCurrentBg);
bgPaint.setStyle(Paint.Style.STROKE); //空心
PathEffect effect = new DashPathEffect(mCurrentBgDashPath, 1);
bgPaint.setPathEffect(effect); //设置画笔曲线间隔
bgPaint.setStrokeWidth(mCurrentBgStrokeWidth); //画笔宽度
//绘制空心圆背景
canvas.drawCircle(left+columnWidth/2, top + mLineSpac +dayHeight/2,
mSelectRadius-mCurrentBgStrokeWidth, bgPaint);
}
//绘制完后将画笔还原,避免脏笔
bgPaint.setPathEffect(null);
bgPaint.setStrokeWidth(0);
bgPaint.setStyle(Paint.Style.FILL);
//选中的日期,如果是本月,选中日期正好是当天日期,下面的背景会覆盖上面绘制的虚线背景
if(selectDay == day){
//选中的日期字体白色,橙色背景
mPaint.setColor(mSelectTextColor);
bgPaint.setColor(mSelectBg);
//绘制橙色圆背景,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径,参数四是paint对象;
canvas.drawCircle(left+columnWidth/2, top + mLineSpac +dayHeight/2, mSelectRadius, bgPaint);
}else{
mPaint.setColor(mTextColorDay);
}
int len = (int)FontUtil.getFontlength(mPaint, day+"");
int x = left + (columnWidth - len)/2;
canvas.drawText(day+"", x, top + mLineSpac + dayTextLeading, mPaint);
//绘制次数
mPaint.setTextSize(mTextSizePre);
DayFinish finish = map.get(day);
String preStr = "0/0";
if(isCurrentMonth){
if(day>currentDay){
mPaint.setColor(mTextColorPreNull);
}else if(finish!=null){
//区分完成未完成
if(finish.finish >= finish.all) {
mPaint.setColor(mTextColorPreFinish);
}else{
mPaint.setColor(mTextColorPreUnFinish);
}
preStr = finish.finish+"/"+finish.all;
}else{
mPaint.setColor(mTextColorPreNull);
}
}else{
if(finish!=null){
//区分完成未完成
if(finish.finish >= finish.all) {
mPaint.setColor(mTextColorPreFinish);
}else{
mPaint.setColor(mTextColorPreUnFinish);
}
preStr = finish.finish+"/"+finish.all;
}else{
mPaint.setColor(mTextColorPreNull);
}
}
len = (int)FontUtil.getFontlength(mPaint, preStr);
x = left + (columnWidth - len)/2;
canvas.drawText(preStr, x, topPre + mTextSpac + preTextLeading, mPaint);
}
}
/**获取月份标题*/
private String getMonthStr(Date month){
SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月");
return df.format(month);
}
private Date str2Date(String str){
try {
SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月");
return df.parse(str);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/****************************事件处理↓↓↓↓↓↓↓****************************/
//焦点坐标
private PointF focusPoint = new PointF();
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
focusPoint.set(event.getX(), event.getY());
touchFocusMove(focusPoint, false);
break;
case MotionEvent.ACTION_MOVE:
focusPoint.set(event.getX(), event.getY());
touchFocusMove(focusPoint, false);
break;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
focusPoint.set(event.getX(), event.getY());
touchFocusMove(focusPoint, true);
break;
}
return true;
}
/**焦点滑动*/
public void touchFocusMove(final PointF point, boolean eventEnd) {
Log.e(TAG, "点击坐标:("+point.x+" ,"+point.y+"),事件是否结束:"+eventEnd);
/**标题和星期只有在事件结束后才响应*/
if(point.y<=titleHeight){
//事件在标题上
if(eventEnd && listener!=null){
if(point.x>=rowLStart && point.x<(rowLStart+2*mMonthRowSpac+rowWidth)){
Log.w(TAG, "点击左箭头");
listener.onLeftRowClick();
}else if(point.x>rowRStart && point.x<(rowRStart + 2*mMonthRowSpac+rowWidth)){
Log.w(TAG, "点击右箭头");
listener.onRightRowClick();
}else if(point.x>rowLStart && point.x <rowRStart){
listener.onTitleClick(getMonthStr(month), month);
}
}
}else if(point.y<=(titleHeight+weekHeight)){
//事件在星期部分
if(eventEnd && listener!=null){
//根据X坐标找到具体的焦点日期
int xIndex = (int)point.x / columnWidth;
Log.e(TAG, "列宽:"+columnWidth+" x坐标余数:"+(point.x / columnWidth));
if((point.x / columnWidth-xIndex)>0){
xIndex += 1;
}
if(listener!=null){
listener.onWeekClick(xIndex-1, WEEK_STR[xIndex-1]);
}
}
}else{
/**日期部分按下和滑动时重绘,只有在事件结束后才响应*/
touchDay(point, eventEnd);
}
}
//控制事件是否响应
private boolean responseWhenEnd = false;
/**事件点在 日期区域 范围内*/
private void touchDay(final PointF point, boolean eventEnd){
//根据Y坐标找到焦点行
boolean availability = false; //事件是否有效
//日期部分
float top = titleHeight+weekHeight+oneHeight;
int foucsLine = 1;
while(foucsLine<=lineNum){
if(top>=point.y){
availability = true;
break;
}
top += oneHeight;
foucsLine ++;
}
if(availability){
//根据X坐标找到具体的焦点日期
int xIndex = (int)point.x / columnWidth;
if((point.x / columnWidth-xIndex)>0){
xIndex += 1;
}
// Log.e(TAG, "列宽:"+columnWidth+" x坐标余数:"+(point.x / columnWidth));
if(xIndex<=0)
xIndex = 1; //避免调到上一行最后一个日期
if(xIndex>7)
xIndex = 7; //避免调到下一行第一个日期
// Log.e(TAG, "事件在日期部分,第"+foucsLine+"/"+lineNum+"行, "+xIndex+"列");
if(foucsLine == 1){
//第一行
if(xIndex<=firstIndex){
Log.e(TAG, "点到开始空位了");
setSelectedDay(selectDay, true);
}else{
setSelectedDay(xIndex-firstIndex, eventEnd);
}
}else if(foucsLine == lineNum){
//最后一行
if(xIndex>lastLineNum){
Log.e(TAG, "点到结尾空位了");
setSelectedDay(selectDay, true);
}else{
setSelectedDay(firstLineNum + (foucsLine-2)*7+ xIndex, eventEnd);
}
}else{
setSelectedDay(firstLineNum + (foucsLine-2)*7+ xIndex, eventEnd);
}
}else{
//超出日期区域后,视为事件结束,响应最后一个选择日期的回调
setSelectedDay(selectDay, true);
}
}
/**设置选中的日期*/
private void setSelectedDay(int day, boolean eventEnd){
Log.w(TAG, "选中:"+day+" 事件是否结束"+eventEnd);
selectDay = day;
invalidate();
if(listener!=null && eventEnd && responseWhenEnd && lastSelectDay!=selectDay) {
lastSelectDay = selectDay;
listener.onDayClick(selectDay, getMonthStr(month) + selectDay + "日", map.get(selectDay));
}
responseWhenEnd = !eventEnd;
}
/****************************事件处理↑↑↑↑↑↑↑****************************/
@Override
public void invalidate() {
requestLayout();
super.invalidate();
}
/***********************接口API↓↓↓↓↓↓↓**************************/
private Map<Integer, DayFinish> map;
public void setRenwu(String month, List<DayFinish> list){
setMonth(month);
if(list!=null && list.size()>0){
map.clear();
for(DayFinish finish : list){
map.put(finish.day, finish);
}
}
invalidate();
}
public void setRenwu(List<DayFinish> list){
if(list!=null && list.size()>0){
map.clear();
for(DayFinish finish : list){
map.put(finish.day, finish);
}
}
invalidate();
}
/**月份增减*/
public void monthChange(int change){
Calendar calendar = Calendar.getInstance();
calendar.setTime(month);
calendar.add(Calendar.MONTH, change);
setMonth(getMonthStr(calendar.getTime()));
map.clear();
invalidate();
}
private onClickListener listener;
public void setOnClickListener(onClickListener listener){
this.listener = listener;
}
public interface onClickListener{
public abstract void onLeftRowClick();
public abstract void onRightRowClick();
public abstract void onTitleClick(String monthStr, Date month);
public abstract void onWeekClick(int weekIndex, String weekStr);
public abstract void onDayClick(int day, String dayStr, DayFinish finish);
}
/***********************接口API↑↑↑↑↑↑↑**************************/
}
@@ -0,0 +1,578 @@
package com.openxu.cview.xmstock20201030;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.hkchart.config.YAxisMark;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.NumberFormatUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:26
* className : GlzsLinesChart
* version : 1.0
* description : 2. 概念走势图
*/
public class GlzsLinesChart extends BaseChart {
//设置数据 概念走势图, 每个元素表示:时间、概念、热度 [20200803,"1582.08","30.00"],
private List<List<Object>> dataList;
private String[] lableXList;
//计算后的数据
private List<DataPoint> lableXPointList;
private List<List<DataPoint>> linePointList;
//y轴刻度设置
private YAxisMark yLeft, yRight;
/**可以设置的属性*/
//设置线条颜色(蓝色,红色)
private int[] lineColor = new int[]{Color.parseColor("#3d7cc9"),
Color.parseColor("#d74b3c")};
//设置曲线粗细
private int lineSize = DensityUtil.dip2px(getContext(), 1.5f);
//设置坐标文字大小
private int textSizeX = (int)getResources().getDimension(R.dimen.ts_chart_xy);
//设置坐标文字颜色
private int textColorX = getResources().getColor(R.color.tc_chart_xy);
//设置X坐标字体与横轴的距离
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);
//设置动画类型
private AnimType animType = AnimType.SLOW_DRAW;
//设置焦点线颜色 及 粗细
private int focusLineColor = Color.parseColor("#5E5E5E");
private int focusLineSize = DensityUtil.dip2px(getContext(), 1f);
//焦点面板矩形,只记录矩形宽高
private int foucsRectTextSpace = DensityUtil.dip2px(getContext(), 3);
private int foucsRectSpace = DensityUtil.dip2px(getContext(), 6);
private float foucsRectWidth;
private float foucsRectHeight;
public enum AnimType{
LEFT_TO_RIGHT, //动画从左往右
BOTTOM_TO_TOP, //动画从下网上上升
SLOW_DRAW //动画缓慢绘制
}
/**需要计算相关值*/
// private float lableLead, lableHeight;
public GlzsLinesChart(Context context) {
this(context, null);
}
public GlzsLinesChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GlzsLinesChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
touchEnable = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
//20201028 --> 10-28
private String formatDate(String date){
String str = date.substring(4, 6)+"-"+date.substring(6);
Log.v(TAG, date+ "---->"+str);
return str;
}
/**
* 设置数据
*/
public void setData(List<List<Object>> dataList){
if(null==dataList)
return;
this.dataList.clear();
this.dataList.addAll(dataList);
//[20200803,"1582.08","30.00"],
Log.v(TAG, "---->"+dataList.get(0));
//重组X轴刻度, 取其中5个日期
lableXList = new String[5];
int part = dataList.size()/4;
lableXList[0] = dataList.get(0).get(0).toString(); //第一天
// Log.v(TAG, 0+ "---->"+Integer.parseInt(dataList.get(0).get(0).toString()));
lableXList[1] = dataList.get(part).get(0).toString(); //
lableXList[2] = dataList.get(dataList.size()/2).get(0).toString(); //中间一天
lableXList[3] = dataList.get(part*3).get(0).toString(); //
lableXList[4] = dataList.get(dataList.size()-1).get(0).toString(); //最后一天
for(int i = 0; i<lableXList.length; i++){
lableXList[i] = formatDate(lableXList[i]);
}
yLeft = new YAxisMark.Builder(getContext())
.lableNum(4)
.textSize(textSizeX)
.textColor(lineColor[0])
.build();
yRight = new YAxisMark.Builder(getContext())
.lableNum(4)
.textSize(textSizeX)
.textColor(lineColor[1])
.build();
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(dataList.size()<=0)
return;
/**③、计算Y刻度最大值和最小值以及幅度*/
//时间、概念、热度 [20200803,"1582.08","30.00"],
//概念
calYLable(yLeft, 1);
//热度
calYLable(yRight, 2);
/**①、计算字体相关以及图表原点坐标*/
paintLabel.setTextSize(textSizeX);
float xlableHeight = FontUtil.getFontHeight(paintLabel);
float xlableLead = FontUtil.getFontLeading(paintLabel);
float xFoucsLenght = FontUtil.getFontlength(paintLabel, lableXList[0]);
paintLabel.setTextSize(yLeft.textSize);
float yLeftlableHeight = FontUtil.getFontHeight(paintLabel);
float yLeftlableMaxLength = FontUtil.getFontlength(paintLabel, (int)yLeft.cal_mark_max+"");
float yLeftFoucsLenght = FontUtil.getFontlength(paintLabel, "概念:"+ NumberFormatUtil.formattedDecimal(yLeft.cal_mark_max));
Log.w(TAG, "计算面板最长字符串:"+"概念:"+yLeft.cal_mark_max);
paintLabel.setTextSize(yRight.textSize);
float yRightlableHeight = FontUtil.getFontHeight(paintLabel);
float yRightlableMaxLength = FontUtil.getFontlength(paintLabel, (int)yRight.cal_mark_max+"");
float yRightFoucsLenght = FontUtil.getFontlength(paintLabel, "热度:"+NumberFormatUtil.formattedDecimal(yRight.cal_mark_max));
Log.w(TAG, "计算面板最长字符串:"+"热度:"+yRight.cal_mark_max);
//图表主体矩形
rectChart = new RectF(getPaddingLeft() + yLeft.textSpace + yLeftlableMaxLength,
getPaddingTop() + Math.max(yLeftlableHeight, yRightlableHeight)/2,
getMeasuredWidth()-getPaddingRight()-yRight.textSpace - yRightlableMaxLength,
getMeasuredHeight()-getPaddingBottom() - xlableHeight - textSpaceX);
//焦点面板
//10-16
//概念:1234.02
//热度:45.00
foucsRectWidth = Math.max(xFoucsLenght, Math.max(yLeftFoucsLenght, yRightFoucsLenght)) + foucsRectSpace*2;
foucsRectHeight = xlableHeight + yLeftlableHeight + yRightlableHeight + foucsRectTextSpace*2 + foucsRectSpace*2;
/**②、计算X标签绘制坐标*/
float lableXSpace = 0;
for(String lableX : lableXList){
lableXSpace += FontUtil.getFontlength(paintLabel, lableX);
}
lableXSpace = (rectChart.right - rectChart.left - lableXSpace)/(lableXList.length -1);
lableXPointList = new ArrayList<>();
if(lableXSpace>0){
float left = rectChart.left;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
left,rectChart.bottom + textSpaceX + xlableLead)));
left += (FontUtil.getFontlength(paintLabel, lableXList[i])+lableXSpace);
}
}else{
//如果X轴标签字体过长的情况需要特殊处理
float oneWidth = (rectChart.right - rectChart.left)/lableXList.length;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
float xLen = FontUtil.getFontlength(paintLabel, lableX);
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
rectChart.left+i*oneWidth+(oneWidth-xLen)/2,rectChart.bottom + textSpaceX + xlableLead)));
}
}
/**④、计算点的坐标,如果有动画的情况下,边绘制边计算会耗费性能,所以先计算*/
linePointList = new ArrayList<>();
//两条线
linePointList.add(new ArrayList<>());
linePointList.add(new ArrayList<>());
float oneSpace = (rectChart.right - rectChart.left) / (dataList.size()-1);
for(int i = 0; i < dataList.size(); i++){
//每个元素表示:时间、概念、热度 [20200803,"1582.08","30.00"],
List<Object> onePart = dataList.get(i);
try {
//第一条线的数据
float valueY = Float.parseFloat(onePart.get(1).toString());
PointF point = new PointF(rectChart.left + i * oneSpace,
rectChart.bottom -
(rectChart.bottom-rectChart.top)/(yLeft.cal_mark_max - yLeft.cal_mark_min) * (valueY-yLeft.cal_mark_min));
linePointList.get(0).add(new DataPoint(onePart.get(0).toString(), valueY, point));
//第二条线的数据
valueY = Float.parseFloat(onePart.get(2).toString());
// Log.w(TAG, i+"计算右侧y值"+valueY);
point = new PointF(rectChart.left + i * oneSpace,
rectChart.bottom - (rectChart.bottom-rectChart.top)/(yRight.cal_mark_max - yRight.cal_mark_min) * (valueY-yRight.cal_mark_min));
linePointList.get(1).add(new DataPoint(onePart.get(0).toString(), valueY, point));
}catch (Exception e){
e.printStackTrace();
}
}
}
private FocusData focusData;
@Override
protected void onTouchMoved(PointF point) {
if(null==dataList)
return;
onFocus = (null != point);
if(null != point && null!=dataList && dataList.size()>0) {
//避免滑出
if(point.x > linePointList.get(0).get(dataList.size()-1).getPoint().x)
point.x = linePointList.get(0).get(dataList.size()-1).getPoint().x;
if(point.x < linePointList.get(0).get(0).getPoint().x)
point.x = linePointList.get(0).get(0).getPoint().x;
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * dataList.size() / (rectChart.right - rectChart.left));
LogUtil.e(getClass().getSimpleName(), "========焦点索引:"+index+" 数据总数:"+dataList.size()+" 线条数量:"+linePointList.size());
index = Math.max(0, Math.min(index, dataList.size() - 1));
focusData = new FocusData();
focusData.setData(dataList.get(index));
focusData.setPoints(new ArrayList<>());
focusData.getPoints().add(linePointList.get(0).get(index));
focusData.getPoints().add(linePointList.get(1).get(index));
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusData);
}
invalidate();
}
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
public void onfocus(FocusData focusData);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
drawGrid(canvas);
drawXLable(canvas);
drawYLable(canvas);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
drawDataPath(canvas);
drawFocus(canvas);
}
/**绘制X轴方向辅助网格*/
private void drawGrid(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(yLeft.lableNum-1);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(defColor);
// canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
// canvas.drawLine(rectChart.right, rectChart.top, rectChart.right, rectChart.bottom, paint);
PathEffect effects = new DashPathEffect(new float[]{15,6,15,6},0);
Path path = new Path();
for (int i = 0; i < yLeft.lableNum; i++) {
path.reset();
path.moveTo(rectChart.left, rectChart.bottom-yMarkSpace*i);
path.lineTo(rectChart.right,rectChart.bottom-yMarkSpace*i);
paintEffect.setPathEffect(effects);
canvas.drawPath(path, paintEffect);
}
}
/**绘制X轴刻度*/
private void drawXLable(Canvas canvas){
paintLabel.setTextSize(textSizeX);
paintLabel.setColor(textColorX);
for(DataPoint lable : lableXPointList){
canvas.drawText(lable.getValueX(), lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制Y轴刻度*/
private void drawYLable(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(yLeft.lableNum-1);
paintLabel.setTextSize(yLeft.textSize);
paintLabel.setColor(yLeft.textColor);
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableLead = FontUtil.getFontLeading(paintLabel);
for (int i = 0; i < yLeft.lableNum; i++) {
String text = (int) (yLeft.cal_mark_min + i * yLeft.cal_mark) + "";
canvas.drawText(text,
rectChart.left - yLeft.textSpace - FontUtil.getFontlength(paintLabel, text),
rectChart.bottom - yMarkSpace * i - lableHeight/2 + lableLead, paintLabel);
}
paintLabel.setTextSize(yRight.textSize);
paintLabel.setColor(yRight.textColor);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
for (int i = 0; i < yRight.lableNum; i++) {
String text = (int) (yRight.cal_mark_min + i * yRight.cal_mark) + "";
canvas.drawText(text,
rectChart.right + yRight.textSpace,
rectChart.bottom - yMarkSpace * i - lableHeight/2 + lableLead, paintLabel);
}
}
/**绘制曲线*/
private void drawDataPath(Canvas canvas) {
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(lineSize);
for(int j = 0; j < linePointList.size(); j++){
List<DataPoint> lineList = linePointList.get(j);
paint.setColor(lineColor[j]);
//一条一条的绘制
Path path = new Path();
PointF lastPoint = null;
for(int i = 0 ; i<lineList.size(); i++){
DataPoint dataPoint = lineList.get(i);
if(i == 0){
if(animType == AnimType.LEFT_TO_RIGHT){
path.moveTo(rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro, dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.moveTo(dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else{
path.moveTo(dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}else{
//quadTo:二阶贝塞尔曲线连接前后两点,这样使得曲线更加平滑
if(animType == AnimType.LEFT_TO_RIGHT){
path.quadTo(rectChart.left+(lastPoint.x-rectChart.left)*animPro, lastPoint.y,
rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro , dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.quadTo(lastPoint.x, rectChart.bottom-(rectChart.bottom-lastPoint.y)* animPro,
dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else if(animType == AnimType.SLOW_DRAW){
if(i>lineList.size()*animPro)
break;
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}else{
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}
lastPoint = dataPoint.getPoint();
}
canvas.drawPath(path, paint);
}
}
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusData)
return;
PointF point1 = focusData.getPoints().get(0).getPoint();
PointF point2 = focusData.getPoints().get(1).getPoint();
//绘制竖直虚线
PathEffect effects = new DashPathEffect(new float[]{15,10,15,10},0);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(focusLineSize);
paintEffect.setColor(focusLineColor);
paintEffect.setPathEffect(effects);
Path path = new Path();
path.moveTo(point1.x, rectChart.bottom);
path.lineTo(point1.x, rectChart.top);
canvas.drawPath(path , paintEffect);
// canvas.drawLine(focusData.getPoints().get(0).getPoint().x, rectChart.bottom, focusData.getPoints().get(0).getPoint().x, rectChart.top, paint);
//绘制水平线
// canvas.drawLine(rectChart.left, focusData.getPoints().get(0).getPoint().y, rectChart.right, focusData.getPoints().get(0).getPoint().y, paint);
// canvas.drawLine(rectChart.left, focusData.getPoints().get(1).getPoint().y, rectChart.right, focusData.getPoints().get(1).getPoint().y, paint);
//绘制焦点
paint.setAntiAlias(true);
//半透明圆圈
int radius = DensityUtil.dip2px(getContext(), 8);
int strokeWidth = DensityUtil.dip2px(getContext(), 3);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(lineColor[0]);
paint.setAlpha(80); //透明度
canvas.drawCircle(point1.x, point1.y, radius,paint);
paint.setColor(lineColor[1]);
paint.setAlpha(80);
canvas.drawCircle(point2.x, point2.y, radius,paint);
//里面
int radius1 = radius-strokeWidth/2 - 4;
paint.setStyle(Paint.Style.FILL);
paint.setColor(lineColor[0]);
paint.setAlpha(255);
canvas.drawCircle(point1.x, point1.y, radius1, paint);
paint.setColor(lineColor[1]);
paint.setAlpha(255);
// canvas.rotate(-90);
// canvas.drawRect(new Rect((int), (int)point2.y - radius1, (int)point2.x+radius1, (int)point2.y+radius1), paint);
radius1 = radius-strokeWidth/2;
path.reset();
path.moveTo(point2.x - radius1, point2.y);
path.lineTo(point2.x, point2.y - radius1);
path.lineTo(point2.x + radius1, point2.y);
path.lineTo(point2.x, point2.y + radius1);
canvas.drawPath(path , paint);
//面板
radius1 = radius*2;
boolean showLeft = point1.x-rectChart.left > (rectChart.right - rectChart.left)/2;
RectF rect = new RectF(
showLeft?point1.x - foucsRectWidth - radius1:point1.x + radius1,
rectChart.top + (rectChart.bottom - rectChart.top)/2 - foucsRectHeight/2,
showLeft? point1.x - radius1 : point1.x + foucsRectWidth + radius1,
rectChart.top + (rectChart.bottom - rectChart.top)/2 + foucsRectHeight/2
);
paint.setColor(lineColor[0]);
strokeWidth = DensityUtil.dip2px(getContext(), 0.8f);
paint.setStrokeWidth(strokeWidth);
paint.setStyle(Paint.Style.STROKE);
path.reset();
path.moveTo(rect.left, rect.top);
path.lineTo(rect.right, rect.top);
path.lineTo(rect.right, rect.bottom);
path.lineTo(rect.left, rect.bottom);
path.close();
canvas.drawPath(path , paint);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setAlpha(210);
canvas.drawRect(rect , paint);
//面板中的文字
paintLabel.setTextSize(textSizeX);
paintLabel.setColor(textColorX);
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableLead = FontUtil.getFontLeading(paintLabel);
float top = rect.top + foucsRectSpace;
float left = showLeft?rect.left + foucsRectSpace:rect.left + foucsRectSpace;
canvas.drawText(formatDate(focusData.getPoints().get(0).getValueX()),
left, top + lableLead, paintLabel);
top += (lableHeight+foucsRectTextSpace);
paintLabel.setTextSize(yLeft.textSize);
paintLabel.setColor(yLeft.textColor);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
canvas.drawText("概念:"+NumberFormatUtil.formattedDecimal(focusData.getPoints().get(0).getValueY()),
left, top + lableLead, paintLabel);
top += (lableHeight+foucsRectTextSpace);
paintLabel.setTextSize(yRight.textSize);
paintLabel.setColor(yRight.textColor);
lableLead = FontUtil.getFontLeading(paintLabel);
canvas.drawText("热度:"+NumberFormatUtil.formattedDecimal(focusData.getPoints().get(1).getValueY()),
left, top + lableLead, paintLabel);
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
/**焦点数据*/
public static class FocusData {
private List<DataPoint> points;
private List<Object> data;
public List<DataPoint> getPoints() {
return points;
}
public void setPoints(List<DataPoint> points) {
this.points = points;
}
public List<Object> getData() {
return data;
}
public void setData(List<Object> data) {
this.data = data;
}
}
/**
* 根据传入的Y轴信息,从数据中获取最小最大值,并获取到Y刻度点值
*/
private void calYLable(YAxisMark axisMark, int index){
float redundance = 1.01f; //y轴最大和最小值冗余
axisMark.cal_mark_max = Float.MIN_VALUE; //Y轴刻度最大值
axisMark.cal_mark_min = Float.MAX_VALUE; //Y轴刻度最小值
//设置数据 概念走势图, 每个元素表示:时间、概念、热度 [20200803,"1582.08","30.00"],
for(List<Object> data : dataList){
try {
axisMark.cal_mark = Float.parseFloat(data.get(index).toString());
if(axisMark.cal_mark>axisMark.cal_mark_max)
axisMark.cal_mark_max = axisMark.cal_mark;
if(axisMark.cal_mark<axisMark.cal_mark_min)
axisMark.cal_mark_min = axisMark.cal_mark;
}catch (Exception e){
}
}
LogUtil.i(TAG, "Y轴真实axisMark.cal_mark_min="+axisMark.cal_mark_min+" axisMark.cal_mark_max="+axisMark.cal_mark_max);
if(axisMark.cal_mark_max>0)
axisMark.cal_mark_max *= redundance;
else
axisMark.cal_mark_max /= redundance;
if(axisMark.cal_mark_min>0)
axisMark.cal_mark_min /= redundance;
else
axisMark.cal_mark_min *= redundance;
if(index == 2)
axisMark.cal_mark_min = 0;
axisMark.cal_mark = (axisMark.cal_mark_max-axisMark.cal_mark_min)/(axisMark.lableNum - 1);
LogUtil.i(TAG, "Y轴axisMark.cal_mark_min="+axisMark.cal_mark_min+" axisMark.cal_mark_max="+axisMark.cal_mark_max+" axisMark.cal_mark="+axisMark.cal_mark);
}
}
@@ -0,0 +1,586 @@
package com.openxu.cview.xmstock20201030;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import com.openxu.cview.R;
import com.openxu.cview.xmstock20201030.bean.CalendarData;
import com.openxu.cview.xmstock20201030.bean.CalendarDataStock;
import com.openxu.cview.xmstock20201030.bean.CalendarDetail;
import com.openxu.cview.xmstock20201030.bean.Constacts;
import com.openxu.cview.xmstock20201030.bean.DayFinish;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* author : xm
* create at : 2020/11/3 9:56
* class name : MyCalendar
* version : 1.0
* class describe:自定义日历控件
*/
public class MyCalendar extends View {
private String TAG = "MyCalendar";
private boolean debug = false;
/** 字体颜色、大小*/
private int textColorMonth = Color.parseColor("#868686");
private int textSizeMonth = DensityUtil.sp2px(getContext(), 10);
private int textColorLable = Color.parseColor("#212025");
private int textSizeLable = DensityUtil.sp2px(getContext(), 14);
private int textColorWeek = Color.parseColor("#afafaf");
private int textSizeWeek = DensityUtil.sp2px(getContext(), 14);
private int textColorDate = Color.parseColor("#3d3d3d");
private int textColorDateNoStock = Color.parseColor("#dcdcdc"); //没有股票的日期字体颜色
private int textSizeDate = DensityUtil.sp2px(getContext(), 14);
private int textColorStock = Color.parseColor("#484848");
private int textSizeStock = DensityUtil.sp2px(getContext(), 10);
/**颜色*/
private int colorDot = Color.parseColor("#5f90c8");
private int colorSelected = Color.parseColor("#d36647");
/**间隙*/
private int lableTextSpace = DensityUtil.dip2px(getContext(), 10); //lable与上方的间距
private int dateLineSpace = DensityUtil.dip2px(getContext(), 20); //日期每行间距
private int stockTextSpace = DensityUtil.dip2px(getContext(), 4); //股票字之间的间距
private int dateTopSpace = DensityUtil.dip2px(getContext(), 5); //日子与月份、星期的间距
private int dotSpace = DensityUtil.dip2px(getContext(), 6); //圆点与字的间距
private float selectRadius = DensityUtil.dip2px(getContext(), 13); //选中日期的圆圈半径
private float dotRadius = DensityUtil.dip2px(getContext(), 3); //小圆点半径
private float bitmapSpace = DensityUtil.dip2px(getContext(), 8);
/**常量*/
private Bitmap bitmapDown, bitmapUp;
private String[] weekStrs = new String[]{"一", "二", "三", "四", "五", "六", "日", };
private Paint paint, paintText;
/**计算*/
private float textMonthHeight, textMonthLead;
private float textLableHeight, textLableLead;
private float textWeekHeight, textWeekLead;
private float textDateHeight, textDateLead;
private float textStockHeight, textStockLead;
private float topHeight; //上方简要日期高度
private float lableHeight; //lable高度
private float weekHeight;
private float dateLineHeight; //每行日期高度,包括下间隙
private RectF rectTop, rectLable, rectWeek, rectDate, rectRow;
private int lineNum; //日期行数
private int lastLineDataNum;
private int startIndex; //第一行开始绘制索引0-6
private boolean showCalendar = false;
public MyCalendar(Context context) {
this(context, null);
}
public MyCalendar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCalendar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// setBackgroundColor(Color.parseColor("#aa000000"));
bitmapDown = BitmapFactory.decodeResource(getResources(), R.mipmap.calendar_down);
bitmapUp = BitmapFactory.decodeResource(getResources(), R.mipmap.calendar_up);
paint = new Paint();
paintText = new Paint();
paint.setAntiAlias(true);
paintText.setAntiAlias(true);
paintText.setTextSize(textSizeMonth);
textMonthHeight = FontUtil.getFontHeight(paintText);
textMonthLead = FontUtil.getFontLeading(paintText);
paintText.setTextSize(textSizeLable);
textLableHeight = FontUtil.getFontHeight(paintText);
textLableLead = FontUtil.getFontLeading(paintText);
paintText.setTextSize(textSizeWeek);
textWeekHeight = FontUtil.getFontHeight(paintText);
textWeekLead = FontUtil.getFontLeading(paintText);
paintText.setTextSize(textSizeDate);
textDateHeight = FontUtil.getFontHeight(paintText);
textDateLead = FontUtil.getFontLeading(paintText);
paintText.setTextSize(textSizeStock);
textStockHeight = FontUtil.getFontHeight(paintText);
textStockLead = FontUtil.getFontLeading(paintText);
//上方简要日期高度
topHeight = textMonthHeight + dateTopSpace + textDateHeight + dotSpace + dotRadius*2;
//lable高度
lableHeight = lableTextSpace + textLableHeight;
weekHeight = dateLineSpace + textWeekHeight + dateTopSpace;
//每行日期高度
dateLineHeight = textDateHeight + dotSpace+dotRadius*2+ (stockTextSpace+textStockHeight)*2+dateLineSpace;
}
/***********************接口API↓↓↓↓↓↓↓**************************/
List<CalendarData> calendar_data = new ArrayList<>();
List<CalendarDetail> calendar_detail = new ArrayList<>();
//上方显示的7条数据
List<CalendarData> calendar_data_top = new ArrayList<>();
//焦点数据
CalendarData focusData;
public void setData(List<CalendarData> calendar, List<CalendarDetail> detail) {
Log.w(TAG, "设置数据:"+calendar.size());
calendar_data.clear();
calendar_data.addAll(calendar);
calendar_detail.clear();
calendar_detail.addAll(detail);
calendar_data_top.clear();
for(int i = 0; i<7; i++){
if(i<calendar_data.size())
calendar_data_top.add(calendar_data.get(i));
}
if(calendar_data.size()>0)
focusData = calendar_data.get(0);
requestLayout();
invalidate();
}
private ItemClickListener listener;
public void setItemClickListener(ItemClickListener listener){
this.listener = listener;
}
public interface ItemClickListener{
void onDayClick(CalendarData calendar, List<CalendarDetail> detail);
}
/***********************接口API↑↑↑↑↑↑↑**************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//宽度 = 填充父窗体
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //获取宽的尺寸
float rowHeight = bitmapDown.getHeight() + bitmapSpace * 2;
float weekHeight1 = 0;
float dateHeight1 = 0;
if(showCalendar && calendar_data!=null && calendar_data.size()>0){
weekHeight1 = weekHeight;
//"date":"2020-10-20","week":"2","stock_list"
startIndex = Integer.parseInt(calendar_data.get(0).getWeek())-1;
lineNum = 1 + ((calendar_data.size()-(7-startIndex))/7) +
((calendar_data.size()-(7-startIndex))%7>0?1:0);
//最后一行不足7个
if((calendar_data.size()-(7-startIndex))%7>0){
lineNum = 1 + ((calendar_data.size()-(7-startIndex))/7) + 1;
lastLineDataNum = calendar_data.size() - (7-startIndex) - ((lineNum-2)*7);
}else{
lineNum = 1 + ((calendar_data.size()-(7-startIndex))/7);
lastLineDataNum = 7;
}
Log.w(TAG, "行数:"+(calendar_data.size()-(7-startIndex))/7);
Log.w(TAG, "行数:"+((calendar_data.size()-(7-startIndex))%7>0?1:0));
//计算日历高度
dateHeight1 = dateLineHeight * lineNum;
// Log.w(TAG, "总共"+calendar_data.size()+"个数据,第一个数据显示索引"+startIndex+" 总行数:"+lineNum+" 最后一行有"+lastLineDataNum+"个数据 日历数据部分高度:"+dateHeight1);
}
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
(int)(getPaddingTop() + getPaddingBottom() +
topHeight +
lableHeight +
weekHeight1 +
dateHeight1 +
rowHeight));
Log.w(TAG, "测量:"+getMeasuredWidth()+"*"+getMeasuredHeight());
rectTop = new RectF(getPaddingLeft(), getPaddingTop(), getMeasuredWidth()-getPaddingRight(),getPaddingTop()+topHeight);
rectLable = new RectF(rectTop.left, rectTop.bottom, rectTop.right, rectTop.bottom+lableHeight);
rectWeek = new RectF(rectTop.left, rectLable.bottom, rectTop.right, rectLable.bottom+weekHeight1);
rectDate = new RectF(rectTop.left, rectWeek.bottom, rectTop.right, rectWeek.bottom+dateHeight1);
rectRow = new RectF(rectTop.left, rectDate.bottom, rectTop.right, rectDate.bottom+rowHeight);
}
@Override
protected void onDraw(Canvas canvas) {
Log.w(TAG, "绘制"+calendar_data);
if(calendar_data==null || calendar_data.size()<=0)
return;
if(debug)
drawRect(canvas);
drawTop(canvas);
drawLable(canvas);
if(showCalendar){
drawWeek(canvas);
drawDayAndPre(canvas);
}
drawRow(canvas);
}
private void drawRect(Canvas canvas){
paint.setStyle(Paint.Style.FILL);
// paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.YELLOW);
canvas.drawRect(rectTop, paint);
paint.setColor(Color.BLUE);
canvas.drawRect(rectLable, paint);
paint.setColor(Color.GREEN);
canvas.drawRect(rectWeek, paint);
paint.setColor(Color.WHITE);
canvas.drawRect(rectDate, paint);
paint.setColor(Color.BLACK);
canvas.drawRect(rectRow, paint);
}
private void drawRow(Canvas canvas) {
canvas.drawBitmap(showCalendar?bitmapUp:bitmapDown,
rectRow.left+(rectRow.right-rectRow.left)/2-bitmapUp.getWidth()/2,
rectRow.top + bitmapSpace ,paint);
}
private void drawTop(Canvas canvas) {
float top;
float left = rectTop.left;
float oneWidth = (rectTop.right-rectTop.left)/7;
float textWidth;
String text;
paint.setStyle(Paint.Style.FILL);
for(int i = 0; i<calendar_data_top.size(); i++){
top = rectTop.top;
CalendarData data = calendar_data_top.get(i);
//月
paintText.setTextSize(textSizeMonth);
paintText.setColor(textColorMonth);
text = getMonthStr(data.getDate());
textWidth = FontUtil.getFontlength(paintText, text);
canvas.drawText(text, left + oneWidth/2 - textWidth/2, top + textMonthLead , paintText);
//日
top += textMonthHeight+dateTopSpace;
paintText.setTextSize(textSizeDate);
paintText.setColor(textColorDate);
text = getDateStr(data.getDate());
textWidth = FontUtil.getFontlength(paintText, text);
if(focusData!=null && focusData.getDate().equals(data.getDate())){
//被选中的日子需要标红圈
paint.setColor(colorSelected);
canvas.drawCircle(left + oneWidth/2, top+textDateHeight/2, selectRadius, paint);
paintText.setColor(Color.WHITE);
}
canvas.drawText(text, left + oneWidth/2 - textWidth/2, top + textDateLead , paintText);
//点
top += textDateHeight+dotSpace;
paint.setColor(colorDot);
canvas.drawCircle(left + oneWidth/2, top+dotRadius, dotRadius, paint);
left+=oneWidth;
//分割线
if(debug)
canvas.drawLine(left, rectTop.top, left, rectTop.bottom,paint);
}
}
private void drawLable(Canvas canvas) {
int dotLableSpace = DensityUtil.dip2px(getContext(), 4);
paintText.setTextSize(textSizeLable);
paintText.setColor(textColorLable);
String str = "日历效应";
float textWidth = FontUtil.getFontlength(paintText, str);
float left = rectLable.right - lableTextSpace - textWidth;
canvas.drawText(str, left, rectLable.bottom - textLableHeight + textLableLead ,paintText);
if(debug)
canvas.drawLine(left, rectLable.top, left, rectLable.bottom,paint);
//点
left -= (dotLableSpace+dotRadius);
if(debug)
canvas.drawLine(left, rectLable.top, left, rectLable.bottom,paint);
paint.setStyle(Paint.Style.FILL);
paint.setColor(colorSelected);
canvas.drawCircle(left, rectLable.bottom - textLableHeight/2, dotRadius, paint);
str = "重大事件";
textWidth = FontUtil.getFontlength(paintText, str);
left -= (dotRadius+lableTextSpace+textWidth);
canvas.drawText(str, left, rectLable.bottom - textLableHeight + textLableLead ,paintText);
if(debug)
canvas.drawLine(left, rectLable.top, left, rectLable.bottom,paint);
left -= (dotLableSpace+dotRadius);
paint.setStyle(Paint.Style.FILL);
paint.setColor(colorDot);
canvas.drawCircle(left, rectLable.bottom - textLableHeight/2, dotRadius, paint);
}
private void drawWeek(Canvas canvas){
float left = rectWeek.left;
float oneWidth = (rectWeek.right-rectWeek.left)/7;
float textWidth;
for(int i = 0 ;i<weekStrs.length; i++){
paintText.setTextSize(textSizeWeek);
paintText.setColor(textColorWeek);
textWidth = FontUtil.getFontlength(paintText, weekStrs[i]);
canvas.drawText(weekStrs[i], left + oneWidth/2 - textWidth/2,
rectWeek.top + dateLineSpace + textWeekLead ,paintText);
left += oneWidth;
}
}
/**绘制日历主体*/
private void drawDayAndPre(Canvas canvas){
if(!showCalendar || calendar_data==null || calendar_data.size()<=0)
return;
for(int i = 0 ; i < lineNum; i++){
//一行行绘制
drawDayAndPre(canvas, i, i==0?startIndex+1:1);
}
}
/**
* 绘制某一行的日期
* @param canvas
* @param startWeek 此行第一个日期的星期值
*/
private void drawDayAndPre(Canvas canvas, int line, int startWeek){
//该行绘制的第一个数据索引
int firstDataIndex = line == 0 ? 0 : (7-startIndex) + (line-1)*7;
float top;
float oneWidth = (rectDate.right-rectDate.left)/7;
float left = rectDate.left + (startWeek-1)*oneWidth;
float textWidth;
// Log.w(TAG, "绘制日期,当前第"+line+"行,第一条数据为星期"+startWeek+", 数据开始索引:"+firstDataIndex+", 当前行绘制数量:"+(7 - startWeek+1));
for(int i = 0; i<=7 - startWeek; i++){
if(firstDataIndex+i>=calendar_data.size())
return;
paint.setStyle(Paint.Style.FILL);
top = rectDate.top + line * dateLineHeight;
CalendarData data = calendar_data.get(firstDataIndex+i);
paintText.setTextSize(textSizeDate);
paintText.setColor((data.getStock_list()==null || data.getStock_list().size()<=0)?textColorDateNoStock:textColorDate);
textWidth = FontUtil.getFontlength(paintText, getDateStr(data.getDate()));
//日期
if(focusData!=null && focusData.getDate().equals(data.getDate())){
//被选中的日子需要标红圈
paint.setColor(colorSelected);
canvas.drawCircle(left + oneWidth/2, top+textDateHeight/2, selectRadius, paint);
paintText.setColor(Color.WHITE);
}
canvas.drawText(getDateStr(data.getDate()), left + oneWidth/2 - textWidth/2, top +textDateLead ,paintText);
if(data.getStock_list()!=null && data.getStock_list().size()>0){
top += textDateHeight+dotSpace+dotRadius;
//点
paint.setColor(colorDot);
canvas.drawCircle(left + oneWidth/2, top, dotRadius, paint);
top += dotRadius+stockTextSpace;
//股票
paintText.setTextSize(textSizeStock);
paintText.setColor(textColorStock);
for(CalendarDataStock stock : data.getStock_list()){
textWidth = FontUtil.getFontlength(paintText, stock.getName());
canvas.drawText(stock.getName(), left + oneWidth/2 - textWidth/2, top+textStockLead,paintText);
top += textStockHeight+stockTextSpace;
}
}
left += oneWidth;
if(debug)
canvas.drawLine(left, rectDate.top + line * dateLineHeight, left, top,paint);
}
}
/****************************事件处理↓↓↓↓↓↓↓****************************/
/* @Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(showCalendar){
getParent().requestDisallowInterceptTouchEvent(true);
}
return super.dispatchTouchEvent(event);
}*/
//焦点坐标
private PointF downPoint = new PointF();
private PointF upPoint = new PointF();
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "========onTouchEvent按下");
downPoint.set(event.getX(), event.getY());
return true;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "========onTouchEvent滑动");
break;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
Log.i(TAG, "========onTouchEvent抬起");
upPoint.set(event.getX(), event.getY());
if(Math.abs(upPoint.x - downPoint.x)<20 && Math.abs(upPoint.y - downPoint.y)<20 )
touchFocus(upPoint);
break;
}
return false;
}
public void touchFocus(final PointF point) {
Log.e(TAG, "点击坐标:("+point.x+" ,"+point.y);
/**标题和星期只有在事件结束后才响应*/
if(point.y<=rectTop.bottom){
// Log.i(TAG, "事件在标题上,处理");
//计算点落在哪个日期上
float oneWidth = (rectTop.right-rectTop.left)/7;
int xIndex = (int)((point.x - rectTop.left) / oneWidth);
if(((point.x - rectTop.left) % oneWidth)>0)
xIndex += 1;
if(xIndex<=0 || xIndex>calendar_data_top.size())
return;
Log.e(TAG, "第"+xIndex+"列, 列宽:"+oneWidth+" x坐标余数:"+((point.x - rectTop.left) % oneWidth));
focusData = calendar_data_top.get(xIndex-1);
showCalendar = false;
requestLayout();
if(listener!=null) {
List<CalendarDetail> details = getDetail(focusData);
if(details!=null)
listener.onDayClick(focusData, details);
}
invalidate();
}else if(point.y<=rectLable.bottom){
// Log.i(TAG, "事件在lable栏,不处理");
}else if(point.y<=rectWeek.bottom){
// Log.i(TAG, "事件在星期栏,不处理");
}else if(point.y<=rectDate.bottom){
// Log.w(TAG, "=============事件在日期上,处理");
//计算点落在哪个日期上
float oneWidth = (rectDate.right-rectDate.left)/7;
int xIndex = (int)((point.x - rectTop.left) / oneWidth);
int foucsLine = (int)((point.y - rectDate.top)/dateLineHeight);
if(((point.y - rectDate.top) % dateLineHeight)>0)
foucsLine += 1;
Log.e(TAG, "点击到第"+foucsLine+"行 "+xIndex+" 列");
int dataIndex = 0;
if(foucsLine == 1){ //第一行
if(xIndex<startIndex){
Log.e(TAG, "点到开始空位了");
return;
}else{
dataIndex = xIndex-startIndex;
}
}else if(foucsLine == lineNum){
//最后一行
if(xIndex>=lastLineDataNum){
Log.e(TAG, "点到结尾空位了");
return;
}else{
dataIndex = calendar_data.size() - (lastLineDataNum - xIndex);
}
}else{
dataIndex = (7-startIndex) + (foucsLine -1)*7 - (7-xIndex);
}
CalendarData data = calendar_data.get(dataIndex);
if(data.getStock_list()==null||data.getStock_list().size()<=0){
return;
}
focusData = data;
// Log.e(TAG, "点击的数据索引为"+dataIndex+" 数据为:"+focusData);
selectCanendar(dataIndex);
showCalendar = false;
requestLayout();
if(listener!=null) {
List<CalendarDetail> details = getDetail(focusData);
if(details!=null)
listener.onDayClick(focusData, details);
}
}else if(point.y<=rectRow.bottom){
Log.i(TAG, "事件在箭头栏,处理");
float center = rectRow.left + (rectRow.right-rectRow.left)/2;
if(point.x < center - bitmapDown.getWidth()||
point.x>center + bitmapDown.getWidth())
return;
showCalendar = !showCalendar;
requestLayout();
}
}
//根据选中的日期,选出7个数据展示在顶部
private void selectCanendar(int selectIndex){
calendar_data_top.clear();
int count = 0;
int preCount = 0;
int lastPreIndex = selectIndex;
int houCount = 0;
//从开始位置从前取4个
for(int i = selectIndex; i >=0 ; i--){
CalendarData data = calendar_data.get(i);
if(data.getStock_list()!=null && data.getStock_list().size()>0){
lastPreIndex = i;
calendar_data_top.add(0, data);
count ++;
preCount ++;
if(preCount >= 4)
break;
}
}
//从开始位置向后取3个
for(int i = selectIndex+1; i < calendar_data.size() ; i++){
CalendarData data = calendar_data.get(i);
if(data.getStock_list()!=null && data.getStock_list().size()>0){
calendar_data_top.add(data);
count ++;
houCount ++;
if(count>=7)
break;
if(preCount == 4){
if(houCount >= 3) //前面取4个后面3个
break;
}else{ //前面没取到4个,继续从后面取,直到取7个,或者遍历到最后
continue;
}
}
}
if(houCount<3){ //后面没取到3个,从前面取
//从开始位置从前取4个
for(int i = lastPreIndex-1; i >=0 ; i--){
CalendarData data = calendar_data.get(i);
if(data.getStock_list()!=null && data.getStock_list().size()>0){
calendar_data_top.add(0, data);
count ++;
preCount ++;
if(count>=7)
break;
}
}
}
Log.w(TAG, "取7个数据:======="+selectIndex+"前面取"+preCount+"个,后面取"+houCount+"个, 总共"+calendar_data_top.size());
}
private List<CalendarDetail> getDetail(CalendarData focusData) {
List<CalendarDetail> details = null;
for(CalendarDetail detail : calendar_detail){
if(focusData.getDate().equals(detail.getDate())){
if(details==null)
details = new ArrayList<>();
details.add(detail);
return details;
}
}
return details;
}
/****************************事件处理↑↑↑↑↑↑↑****************************/
@Override
public void invalidate() {
requestLayout();
super.invalidate();
}
//2020-10-20
private String getMonthStr(String date){
return date.substring(5, 7)+"月";
}
private String getDateStr(String date){
return date.substring(8, date.length());
}
}
@@ -0,0 +1,174 @@
package com.openxu.cview.xmstock20201030;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.NumberFormatUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:26
* className : ProgressBar
* version : 1.0
* description : 不规则进度条
*/
public class ProgressBar extends BaseChart {
/**可以设置的属性*/
private int[] barColor = new int[]{Color.parseColor("#e9403b"),
Color.parseColor("#f98683")};
private float total = 100;
private float progress = 0;
//左边图
private int bitmapID = R.mipmap.ic_concept_hot_more_fire;
//进度条高度
private int barHeight = DensityUtil.dip2px(getContext(), 8f);
private int chonghe = DensityUtil.dip2px(getContext(), 3f);//重合度
private float up = 0.2f; //预留图片下方白色区域
private Bitmap bitmap;
public ProgressBar(Context context) {
this(context, null);
}
public ProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
touchEnable = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if(bitmap==null)
bitmap = BitmapFactory.decodeResource(getResources(), bitmapID);
float uph = up*bitmap.getHeight();
float bottom = bitmap.getHeight() - uph - barHeight;
heightSize = bitmap.getHeight() + (int)bottom; //让进度条居中
setMeasuredDimension(widthSize, heightSize);
rectChart = new RectF(getPaddingLeft(),getPaddingTop(),getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom());
centerPoint = new PointF(getMeasuredWidth()/2, getMeasuredHeight()/2);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
public void setData(float total, float progress){
this.total = total;
this.progress = progress;
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
}
private void evaluatorByData(){
}
@Override
public void drawDefult(Canvas canvas) {
}
@Override
public void drawChart(Canvas canvas) {
float left = bitmap.getWidth()-chonghe;
float proSize = (rectChart.right-left)/total*progress * animPro;
//添加路径信息
Path pathDef = new Path();
Path pathPro = new Path();
pathDef.moveTo(left , centerPoint.y-barHeight/2); //左上
pathPro.moveTo(left , centerPoint.y-barHeight/2);
pathDef.lineTo(rectChart.right-barHeight/2, centerPoint.y-barHeight/2); //右上
pathPro.lineTo(left + proSize, centerPoint.y-barHeight/2);
RectF rectFDef = new RectF(rectChart.right-barHeight, centerPoint.y-barHeight/2, rectChart.right, centerPoint.y+barHeight/2);
RectF rectFPro = new RectF(left + proSize -barHeight, centerPoint.y-barHeight/2, left + proSize , centerPoint.y+barHeight/2);
pathDef.arcTo(rectFDef, -90, 180);
pathPro.arcTo(rectFPro, -90, 180);
// canvas.drawArc(rectFDef, -90, 180, false ,paint);
pathDef.lineTo(rectChart.right - barHeight/2, centerPoint.y+barHeight/2); //右下
pathPro.lineTo(rectChart.right - proSize - barHeight/2, centerPoint.y+barHeight/2);
pathDef.lineTo(left, centerPoint.y+barHeight/2); //左下
pathPro.lineTo(left, centerPoint.y+barHeight/2);
//1.绘制默认灰色
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(defColor);
canvas.drawPath(pathDef, paint);
//2.绘制进度
//线性渐变:前两个参数是渐变开始的点坐标,第三四个参数是渐变结束的点的坐标;渐变的颜色,渐变颜色的分布,模式
Shader mShader = new LinearGradient(left, centerPoint.y ,rectChart.right, centerPoint.y,
new int[] {barColor[0],barColor[1]},null,Shader.TileMode.CLAMP);
// Log.w(TAG, "从"+left+"*"+centerPoint.y+"->"+rectChart.right+"*"+centerPoint.y+"渐变");
paintEffect.setStyle(Paint.Style.FILL);
paintEffect.setColor(defColor);
paintEffect.setShader(mShader);
canvas.drawPath(pathPro, paintEffect);
paintEffect.setShader(null);
//圆角矩形
// canvas.drawRoundRect(new RectF(bitmap.getWidth()-chonghe, centerPoint.y+barHeight/2,
// rectChart.right, centerPoint.y-barHeight/2),
// barHeight/2, barHeight/2, paint);
canvas.drawBitmap(bitmap, rectChart.left, rectChart.top, paint);
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(progress>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,447 @@
package com.openxu.cview.xmstock20201030;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.cview.xmstock20201030.bean.TopDetailNew;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:26
* className : QsrdLinesChart
* version : 1.0
* description : 1. 券商热点走势对比图
*/
public class QsrdLinesChart extends BaseChart {
/**可以设置的属性*/
//设置Y轴刻度数量
private int YMARK_NUM = 3;
//设置线条颜色 粗细
private int lineColor = Color.parseColor("#358ce5"); //蓝色
private int lineSize = DensityUtil.dip2px(getContext(), 1.5f);
//新闻圆点颜色
private int newCircleColor = Color.parseColor("#e7413d");
//设置坐标文字大小
private int textSize = (int)getResources().getDimension(R.dimen.ts_chart_xy);
//设置坐标文字颜色
private int textColor = Color.parseColor("#5E5E5E");
//设置X坐标字体与横轴的距离
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);
//设置Y坐标字体与横轴的距离
private int textSpaceY = DensityUtil.dip2px(getContext(), 3);
//设置动画类型
private AnimType animType = AnimType.SLOW_DRAW;
//设置焦点线颜色 及 粗细
private int focusLineColor = Color.parseColor("#5E5E5E");
private int focusLineSize = DensityUtil.dip2px(getContext(), 1f);
/**设置数据*/
private List<List<Object>> dataList = new ArrayList<>();
//需要标点的新闻日期
private List<TopDetailNew> newsList = new ArrayList<>();
private List<String> newsDate = new ArrayList<>();
//计算后的数据
private String[] lableXList;
private List<DataPoint> lableXPointList;
private List<DataPoint> linePointList;
public enum AnimType{
LEFT_TO_RIGHT, //动画从左往右
BOTTOM_TO_TOP, //动画从下网上上升
SLOW_DRAW //动画缓慢绘制
}
/**需要计算相关值*/
private float lableLead, lableHeight;
/*字体绘制相关*/
private float YMARK = 1; //Y轴刻度间隔
private float YMARK_MAX = Float.MIN_VALUE; //Y轴刻度最大值
private float YMARK_MIN = Float.MAX_VALUE; //Y轴刻度最小值
public QsrdLinesChart(Context context) {
this(context, null);
}
public QsrdLinesChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QsrdLinesChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
touchEnable = true;
}
public void setYMARK_NUM(int YMARK_NUM) {
this.YMARK_NUM = YMARK_NUM;
}
/***********************************设置属性set方法**********************************/
public void setAnimType(AnimType animType) {
this.animType = animType;
}
/**
* 设置数据
*/
public void setData(List<List<Object>> dataList, List<TopDetailNew> newsList){
if(null==dataList)
return;
this.dataList.clear();
this.dataList.addAll(dataList);
this.newsList.clear();
this.newsList.addAll(newsList);
this.newsDate.clear();
for(TopDetailNew news : newsList){
//新闻日期:[2020-11-03 08:00:00, 2020-10-21 08:00:00, 2020-08-03 07:54:00, 2020-10-14 08:00:00]
newsDate.add(news.getPublishdate().contains(" ")?
news.getPublishdate().substring(0, news.getPublishdate().indexOf(" "))
: news.getPublishdate());
}
LogUtil.v(TAG, "新闻日期:"+newsDate);
//获取X轴刻度
lableXList = new String[]{dataList.get(0).get(0).toString(), dataList.get(dataList.size()-1).get(0).toString()};
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
evaluatorByData();
startDraw = false;
invalidate();
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(dataList.size()<=0)
return;
/**③、计算Y刻度最大值和最小值以及幅度*/
YMARK_MAX = Float.MIN_VALUE; //Y轴刻度最大值
YMARK_MIN = Float.MAX_VALUE; //Y轴刻度最小值
float redundance = 1.01f; //y轴最大和最小值冗余
for(List<Object> list : dataList){
//["2020-07-10", 2491.7 ]
try {
YMARK = Float.parseFloat(list.get(1).toString());
// LogUtil.d(TAG, list.get(1)+" Y轴 被识别为Float = "+YMARK);
if(YMARK>YMARK_MAX)
YMARK_MAX = YMARK;
if(YMARK<YMARK_MIN)
YMARK_MIN = YMARK;
}catch (Exception e){
}
}
LogUtil.w(TAG, "Y轴真实YMARK_MIN="+YMARK_MIN+" YMARK_MAX="+YMARK_MAX);
if(YMARK_MAX>0)
YMARK_MAX *= redundance;
else
YMARK_MAX /= redundance;
if(YMARK_MIN>0)
YMARK_MIN /= redundance;
else
YMARK_MIN *= redundance;
YMARK = (YMARK_MAX-YMARK_MIN)/(YMARK_NUM - 1);
YMARK = (int)YMARK + 1;
YMARK_MIN = (int)YMARK_MIN;
YMARK_MAX = (int)(YMARK_MIN+YMARK*(YMARK_NUM-1));
LogUtil.i(TAG, "Y轴YMARK_MIN="+YMARK_MIN+" YMARK_MAX="+YMARK_MAX+" YMARK="+YMARK);
/**①、计算字体相关以及图表原点坐标*/
paintLabel.setTextSize(textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
//图表主体矩形
rectChart = new RectF(getPaddingLeft() + textSpaceY + FontUtil.getFontlength(paintLabel, (int)YMARK_MAX+""),
getPaddingTop(),
getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom() - lableHeight - textSpaceX);
/**②、计算X标签绘制坐标*/
float lableXSpace = 0;
for(String lableX : lableXList){
lableXSpace += FontUtil.getFontlength(paintLabel, lableX);
}
lableXSpace = (rectChart.right - rectChart.left - lableXSpace)/(lableXList.length -1);
lableXPointList = new ArrayList<>();
float left = rectChart.left;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
left,rectChart.bottom + textSpaceX + lableLead)));
left += (FontUtil.getFontlength(paintLabel, lableXList[i])+lableXSpace);
}
/**④、计算点的坐标,如果有动画的情况下,边绘制边计算会耗费性能,所以先计算*/
linePointList = new ArrayList<>();
float oneSpace = (rectChart.right - rectChart.left) / (dataList.size()-1);
List<DataPoint> lastDataGroup = null; //最后一组数据
for(int i = 0; i < dataList.size(); i++){
// List<String> oneData = dataList.get(0);
try {
////["2020-07-10", 2491.7 ]
float valueY = Float.parseFloat(dataList.get(i).get(1).toString());
PointF point = new PointF();
point.x = rectChart.left + i * oneSpace;
point.y = rectChart.bottom -
(rectChart.bottom-rectChart.top)/(YMARK_MAX - YMARK_MIN) * (valueY-YMARK_MIN);
linePointList.add(new DataPoint(dataList.get(i).get(0).toString(), valueY, point));
}catch (Exception e){
e.printStackTrace();
}
}
}
@Override
protected void onTouchMoved(PointF point) {
if(null==dataList)
return;
onFocus = (null != point);
if(null != point && null!=dataList && dataList.size()>0) {
//避免滑出
if(point.x > linePointList.get(linePointList.size()-1).getPoint().x)
point.x = linePointList.get(linePointList.size()-1).getPoint().x;
if(point.x < linePointList.get(0).getPoint().x)
point.x = linePointList.get(0).getPoint().x;
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * dataList.size() / (rectChart.right - rectChart.left));
// LogUtil.e(getClass().getSimpleName(), "========焦点索引:"+index+" 数据总数:"+dataList.size()+" 绘制点总数:"+linePointList.size());
index = Math.max(0, Math.min(index, dataList.size() - 1));
focusData = new FocusData();
focusData.setPoint(linePointList.get(index));
focusData.setData(dataList.get(index));
//获取该日期对应的新闻数据
if(newsDate.contains(dataList.get(index).get(0))) {
for(TopDetailNew news : newsList)
if(news.getPublishdate().contains(dataList.get(index).get(0).toString()))
focusData.setNews(news);
}
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusData);
}
invalidate();
}
private FocusData focusData;
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
void onfocus(FocusData focusData);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
drawGrid(canvas);
drawXLable(canvas);
drawYLable(canvas);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
drawDataPath(canvas);
drawFocus(canvas);
}
/**绘制X轴方向辅助网格*/
private void drawGrid(Canvas canvas){
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
canvas.drawLine(rectChart.left, rectChart.bottom, rectChart.right, rectChart.bottom, paint);
}
/**绘制X轴刻度*/
private void drawXLable(Canvas canvas){
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for(DataPoint lable : lableXPointList){
canvas.drawText(lable.getValueX(), lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制Y轴刻度*/
private void drawYLable(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(YMARK_NUM-1);
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for (int i = 0; i < YMARK_NUM; i++) {
canvas.drawText((int) (YMARK_MIN + i * YMARK) + "",
rectChart.left - textSpaceY - FontUtil.getFontlength(paintLabel, (int) (YMARK_MIN + i * YMARK) + ""),
rectChart.bottom - yMarkSpace * i - lableHeight/2 + lableLead, paintLabel);
}
}
/**绘制曲线*/
private void drawDataPath(Canvas canvas) {
Path path = new Path();
PointF lastPoint = null;
List<DataPoint> newDatePoint = new ArrayList<>();
for(int i = 0 ; i<linePointList.size(); i++){
DataPoint dataPoint = linePointList.get(i);
if(i == 0){
if(animType == AnimType.LEFT_TO_RIGHT){
path.moveTo(rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro, dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.moveTo(dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else{
path.moveTo(dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}else{
//quadTo:二阶贝塞尔曲线连接前后两点,这样使得曲线更加平滑
if(animType == AnimType.LEFT_TO_RIGHT){
path.quadTo(rectChart.left+(lastPoint.x-rectChart.left)*animPro, lastPoint.y,
rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro , dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.quadTo(lastPoint.x, rectChart.bottom-(rectChart.bottom-lastPoint.y)* animPro,
dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else if(animType == AnimType.SLOW_DRAW){
if(i>linePointList.size()*animPro)
break;
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}else{
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}
lastPoint = dataPoint.getPoint();
//绘制新闻点
//["2020-09-03",2359.21]
if(newsDate.contains(dataList.get(i).get(0))){
newDatePoint.add(dataPoint);
}
}
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(lineSize);
paint.setColor(lineColor);
canvas.drawPath(path, paint);
//绘制新闻点
int radius = DensityUtil.dip2px(getContext(), 4);
for(DataPoint point : newDatePoint){
// LogUtil.v(TAG, "绘制新闻点:"+point.getPoint());
paint.setStyle(Paint.Style.STROKE);
//新闻点圆圈宽度
paint.setStrokeWidth(DensityUtil.dip2px(getContext(), 3));
paint.setColor(newCircleColor);
canvas.drawCircle(point.getPoint().x, point.getPoint().y, radius,paint);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(DensityUtil.dip2px(getContext(), 3));
paint.setColor(Color.WHITE);
canvas.drawCircle(point.getPoint().x, point.getPoint().y, radius/2,paint);
}
}
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusData)
return;
//竖直线
PathEffect effects = new DashPathEffect(new float[]{15,10,15,10},0);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(focusLineSize);
paintEffect.setColor(focusLineColor);
paintEffect.setPathEffect(effects);
Path path = new Path();
path.moveTo(focusData.getPoint().getPoint().x, rectChart.bottom);
path.lineTo(focusData.getPoint().getPoint().x, rectChart.top);
canvas.drawPath(path , paintEffect);
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
/**焦点数据*/
public static class FocusData {
private DataPoint point;
private List<Object> data;
private TopDetailNew news;
public DataPoint getPoint() {
return point;
}
public void setPoint(DataPoint point) {
this.point = point;
}
public List<Object> getData() {
return data;
}
public void setData(List<Object> data) {
this.data = data;
}
public TopDetailNew getNews() {
return news;
}
public void setNews(TopDetailNew news) {
this.news = news;
}
}
}
@@ -0,0 +1,460 @@
package com.openxu.cview.xmstock20201030;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.hkchart.config.YAxisMark;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.NumberFormatUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:26
* className : SshqLinesChart
* version : 1.0
* description : 3. 实时行情图
*/
public class SshqLinesChart extends BaseChart {
//实时行情走势图,每个元素字段分别是:时间、现价、均价 、跌涨浮["0930",1639.83,1625.58,"0.10"]
private List<List<Object>> dataList;
private int dataNumCount = 60*4+2; //9:30 - 11:30 13:00 - 15:00 2+2=4小时
private String[] lableXList = new String[]{"09:30", "11:30/13:00", "15:00"};
//计算后的数据
private List<DataPoint> lableXPointList;
private List<DataPoint> linePointList;
//y轴刻度设置
private YAxisMark yLeft, yRight;
/**可以设置的属性*/
//设置线条颜色(蓝色,渐变色开始)
private int[] lineColor = new int[]{Color.parseColor("#3d7cc9"),
Color.parseColor("#3d7cc9")};
//设置曲线粗细
private int lineSize = DensityUtil.dip2px(getContext(), 1.5f);
//设置坐标文字大小
private int textSizeX = (int)getResources().getDimension(R.dimen.ts_chart_xy);
//设置坐标文字颜色
private int textColorX = getResources().getColor(R.color.tc_chart_xy);
//设置X坐标字体与横轴的距离
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);
//设置焦点线颜色 及 粗细
private int focusLineColor = getResources().getColor(R.color.tc_chart_focus_line);
private int focusLineSize = DensityUtil.dip2px(getContext(), 0.8f);
public SshqLinesChart(Context context) {
this(context, null);
}
public SshqLinesChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SshqLinesChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
touchEnable = false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
//20201028 --> 10-28
private String formatDate(String date){
String str = date.substring(4, 6)+"-"+date.substring(6);
Log.v(TAG, date+ "---->"+str);
return str;
}
/**
* 设置数据
*/
public void setData(List<List<Object>> dataList){
if(null==dataList)
return;
this.dataList.clear();
this.dataList.addAll(dataList);
//实时行情走势图,每个元素字段分别是:时间、现价、均价 、跌涨浮["0930",1639.83,1625.58,"0.10"]
yLeft = new YAxisMark.Builder(getContext())
.lableNum(3)
.textSize(textSizeX)
.textColor(lineColor[0])
.build();
yRight = new YAxisMark.Builder(getContext())
.lableNum(3)
.textSize(textSizeX)
.textColor(lineColor[1])
.build();
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(dataList.size()<=0)
return;
/**③、计算Y刻度最大值和最小值以及幅度*/
//实时行情走势图,每个元素字段分别是:时间、现价、均价 、跌涨浮["0930",1639.83,1625.58,"0.10"]
calYLable();
/**①、计算字体相关以及图表原点坐标*/
paintLabel.setTextSize(textSizeX);
float xlableHeight = FontUtil.getFontHeight(paintLabel);
float xlableLead = FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(yLeft.textSize);
float yLeftlableHeight = FontUtil.getFontHeight(paintLabel);
float yLeftlableMaxLength = FontUtil.getFontlength(paintLabel, (int)yLeft.cal_mark_max+"");
paintLabel.setTextSize(yRight.textSize);
float yRightlableHeight = FontUtil.getFontHeight(paintLabel);
String text = NumberFormatUtil.formattedDecimalToPercentage(yRight.cal_mark_min, 2);
float yRightlableMaxLength = FontUtil.getFontlength(paintLabel, text);
//图表主体矩形
rectChart = new RectF(getPaddingLeft() + yLeft.textSpace + yLeftlableMaxLength,
getPaddingTop() + Math.max(yLeftlableHeight, yRightlableHeight)/2,
getMeasuredWidth()-getPaddingRight()-yRight.textSpace - yRightlableMaxLength,
getMeasuredHeight()-getPaddingBottom() - xlableHeight - textSpaceX);
Log.w(TAG, "计算图表矩形:"+rectChart);
/**②、计算X标签绘制坐标*/
float lableXSpace = 0;
for(String lableX : lableXList){
lableXSpace += FontUtil.getFontlength(paintLabel, lableX);
}
lableXSpace = (rectChart.right - rectChart.left - lableXSpace)/(lableXList.length -1);
lableXPointList = new ArrayList<>();
if(lableXSpace>0){
float left = rectChart.left;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
left,rectChart.bottom + textSpaceX + xlableLead)));
left += (FontUtil.getFontlength(paintLabel, lableXList[i])+lableXSpace);
}
}else{
//如果X轴标签字体过长的情况需要特殊处理
float oneWidth = (rectChart.right - rectChart.left)/lableXList.length;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
float xLen = FontUtil.getFontlength(paintLabel, lableX);
lableXPointList.add(new DataPoint(lableX, 0,
new PointF(rectChart.left+i*oneWidth+(oneWidth-xLen)/2,
rectChart.bottom + textSpaceX + xlableLead)));
}
}
/**④、计算点的坐标,如果有动画的情况下,边绘制边计算会耗费性能,所以先计算*/
linePointList = new ArrayList<>();
float oneSpace = (rectChart.right - rectChart.left) / (dataNumCount-1);
for(int i = 0; i < dataList.size(); i++){
// //实时行情走势图,每个元素字段分别是:时间、现价、均价 、跌涨浮["0930",1639.83,1625.58,"0.10"]
List<Object> onePart = dataList.get(i);
DataPoint dataPoint = new DataPoint(onePart.get(0).toString(), 0, new PointF(rectChart.left + i * oneSpace,
rectChart.bottom));
try {
//第一条线的数据
float valueY = Float.parseFloat(onePart.get(1).toString());
dataPoint.setValueY(valueY);
dataPoint.getPoint().y = rectChart.bottom - (rectChart.bottom-rectChart.top)/(yLeft.cal_mark_max - yLeft.cal_mark_min) * (valueY-yLeft.cal_mark_min);
}catch (Exception e){
e.printStackTrace();
}
linePointList.add(dataPoint);
}
}
private FocusData focusData;
@Override
protected void onTouchMoved(PointF point) {
if(null==dataList)
return;
/* onFocus = (null != point);
if(null != point && null!=dataList && dataList.size()>0) {
//避免滑出
if(point.x > linePointList.get(0).get(dataList.size()-1).getPoint().x)
point.x = linePointList.get(0).get(dataList.size()-1).getPoint().x;
if(point.x < linePointList.get(0).get(0).getPoint().x)
point.x = linePointList.get(0).get(0).getPoint().x;
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * dataList.size() / (rectChart.right - rectChart.left));
LogUtil.e(getClass().getSimpleName(), "========焦点索引:"+index+" 数据总数:"+dataList.size()+" 线条数量:"+linePointList.size());
index = Math.max(0, Math.min(index, dataList.size() - 1));
focusData = new FocusData();
focusData.setData(dataList.get(index));
focusData.setPoints(new ArrayList<>());
focusData.getPoints().add(linePointList.get(0).get(index));
focusData.getPoints().add(linePointList.get(1).get(index));
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusData);
}
invalidate();*/
}
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
public void onfocus(FocusData focusData);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
drawGrid(canvas);
drawXLable(canvas);
drawYLable(canvas);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
drawDataPath(canvas);
drawFocus(canvas);
}
/**绘制X轴方向辅助网格*/
private void drawGrid(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(yLeft.lableNum-1);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(defColor);
// canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
// canvas.drawLine(rectChart.right, rectChart.top, rectChart.right, rectChart.bottom, paint);
PathEffect effects = new DashPathEffect(new float[]{15,6,15,6},0);
Path path = new Path();
for (int i = 0; i < yLeft.lableNum; i++) {
path.reset();
path.moveTo(rectChart.left, rectChart.bottom-yMarkSpace*i);
path.lineTo(rectChart.right,rectChart.bottom-yMarkSpace*i);
paintEffect.setPathEffect(effects);
canvas.drawPath(path, paintEffect);
}
}
/**绘制X轴刻度*/
private void drawXLable(Canvas canvas){
paintLabel.setTextSize(textSizeX);
paintLabel.setColor(textColorX);
for(DataPoint lable : lableXPointList){
canvas.drawText(lable.getValueX(), lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制Y轴刻度*/
private void drawYLable(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(yLeft.lableNum-1);
paintLabel.setTextSize(yLeft.textSize);
paintLabel.setColor(yLeft.textColor);
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableLead = FontUtil.getFontLeading(paintLabel);
for (int i = 0; i < yLeft.lableNum; i++) {
String text = (int) (yLeft.cal_mark_min + i * yLeft.cal_mark) + "";
canvas.drawText(text,
rectChart.left - yLeft.textSpace - FontUtil.getFontlength(paintLabel, text),
rectChart.bottom - yMarkSpace * i - lableHeight/2 + lableLead, paintLabel);
}
paintLabel.setTextSize(yRight.textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
for (int i = 0; i < yRight.lableNum; i++) {
//最后一个涨幅值刻度为红色
paintLabel.setColor(i==yRight.lableNum-1?Color.RED: yRight.textColor);
String text = NumberFormatUtil.formattedDecimal(yRight.cal_mark_min + i * yRight.cal_mark)+"%";
canvas.drawText(text,
rectChart.right + yRight.textSpace,
rectChart.bottom - yMarkSpace * i - lableHeight/2 + lableLead, paintLabel);
}
}
/**绘制曲线*/
private void drawDataPath(Canvas canvas) {
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(lineSize);
paint.setColor(lineColor[0]);
//曲线路径
Path path = new Path();
//渐变色路径
Path jianBianPath = new Path();
PointF lastPoint = null;
PointF lastDrawPoint = new PointF();
float minYValue = 0;
for(int i = 0 ; i<linePointList.size(); i++){
DataPoint dataPoint = linePointList.get(i);
if(i == 0){
jianBianPath.moveTo(rectChart.left, rectChart.bottom);
lastDrawPoint.x = dataPoint.getPoint().x;
lastDrawPoint.y = dataPoint.getPoint().y;
minYValue = Math.min(lastDrawPoint.y,minYValue);
path.moveTo(lastDrawPoint.x, lastDrawPoint.y);
jianBianPath.lineTo(lastDrawPoint.x, lastDrawPoint.y);
}else{
if(i>linePointList.size()*animPro)
break;
lastDrawPoint.x = dataPoint.getPoint().x;
lastDrawPoint.y = dataPoint.getPoint().y;
path.quadTo(lastPoint.x, lastPoint.y, lastDrawPoint.x, lastDrawPoint.y);
minYValue = Math.min(lastDrawPoint.y,minYValue);
// path.moveTo(lastDrawPoint.x, lastDrawPoint.y);
jianBianPath.lineTo(lastDrawPoint.x, lastDrawPoint.y);
}
lastPoint = dataPoint.getPoint();
}
canvas.drawPath(path, paint);
//https://blog.csdn.net/s297165331/article/details/52875624
jianBianPath.lineTo(lastDrawPoint.x, rectChart.bottom);
jianBianPath.close();
//线性渐变:前两个参数是渐变开始的点坐标,第三四个参数是渐变结束的点的坐标;渐变的颜色,渐变颜色的分布,模式
Shader mShader = new LinearGradient(rectChart.left, minYValue ,rectChart.left, rectChart.bottom,
new int[] {lineColor[1],Color.TRANSPARENT},null,Shader.TileMode.CLAMP);
// Log.w(TAG, "从"+rectChart.left+"*"+minYValue+"->"+rectChart.left+"*"+rectChart.bottom+"渐变");
paintEffect.setStyle(Paint.Style.FILL);
paintEffect.setColor(defColor);
paintEffect.setShader(mShader);
canvas.drawPath(jianBianPath, paintEffect);
paintEffect.setShader(null);
}
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusData)
return;
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(focusLineSize);
paint.setColor(focusLineColor);
canvas.drawLine(focusData.getPoints().get(0).getPoint().x, rectChart.bottom, focusData.getPoints().get(0).getPoint().x, rectChart.top, paint);
canvas.drawLine(rectChart.left, focusData.getPoints().get(0).getPoint().y, rectChart.right, focusData.getPoints().get(0).getPoint().y, paint);
canvas.drawLine(rectChart.left, focusData.getPoints().get(1).getPoint().y, rectChart.right, focusData.getPoints().get(1).getPoint().y, paint);
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
/**焦点数据*/
public static class FocusData {
private List<DataPoint> points;
private List<Object> data;
public List<DataPoint> getPoints() {
return points;
}
public void setPoints(List<DataPoint> points) {
this.points = points;
}
public List<Object> getData() {
return data;
}
public void setData(List<Object> data) {
this.data = data;
}
}
/**
* 根据传入的Y轴信息,从数据中获取最小最大值,并获取到Y刻度点值
*/
private void calYLable(){
float redundance = 1.01f; //y轴最大和最小值冗余
yLeft.cal_mark_max = Float.MIN_VALUE; //Y轴刻度最大值
yLeft.cal_mark_min = Float.MAX_VALUE; //Y轴刻度最小值
yRight.cal_mark_max = Float.MIN_VALUE; //记录最大涨幅
yRight.cal_mark_min = Float.MAX_VALUE; //记录最大跌幅
//实时行情走势图,每个元素字段分别是:时间、现价、均价 、跌涨浮["0930",1639.83,1625.58,"0.10"]
float price, downUp;
for(List<Object> data : dataList){
try {
price = Float.parseFloat(data.get(1).toString());
downUp = Float.parseFloat(data.get(3).toString());
yLeft.cal_mark_max = Math.max(yLeft.cal_mark_max, price);
yLeft.cal_mark_min = Math.min(yLeft.cal_mark_min, price);
yRight.cal_mark_max = Math.max(yRight.cal_mark_max, downUp);
yRight.cal_mark_min = Math.min(yRight.cal_mark_min, downUp);
}catch (Exception e){
e.printStackTrace();
}
}
Log.i(TAG, "Y轴真实yLeft.cal_mark_min="+yLeft.cal_mark_min+" yLeft.cal_mark_max="+yLeft.cal_mark_max);
Log.i(TAG, "Y轴真实yRight.cal_mark_min="+yRight.cal_mark_min+" yRight.cal_mark_max="+yRight.cal_mark_max);
yLeft.cal_mark_max *= redundance;
yLeft.cal_mark_min /= redundance;
yLeft.cal_mark = (yLeft.cal_mark_max-yLeft.cal_mark_min)/(yLeft.lableNum - 1);
//获取正负跌涨浮绝对值最大值
downUp = Math.max(Math.abs(yRight.cal_mark_max), Math.abs(yRight.cal_mark_min));
downUp *= redundance;
yRight.cal_mark_max = downUp;
yRight.cal_mark_min = -downUp;
yRight.cal_mark = downUp;
Log.i(TAG, "yLeft.cal_mark_min="+yLeft.cal_mark_min+" yLeft.cal_mark_max="+yLeft.cal_mark_max+" yLeft.cal_mark="+yLeft.cal_mark);
Log.i(TAG, "yRight.cal_mark_min="+yRight.cal_mark_min+" yRight.cal_mark_max="+yRight.cal_mark_max+" yRight.cal_mark="+yRight.cal_mark);
}
}
@@ -0,0 +1,618 @@
package com.openxu.cview.xmstock20201030;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock20201030.build.AnimType;
import com.openxu.cview.xmstock20201030.build.AxisLine;
import com.openxu.cview.xmstock20201030.build.AxisLineType;
import com.openxu.cview.xmstock20201030.build.AxisMark;
import com.openxu.cview.xmstock20201030.build.ChartFocus;
import com.openxu.cview.xmstock20201030.build.DataPoint;
import com.openxu.cview.xmstock20201030.build.Line;
import com.openxu.cview.xmstock20201030.build.Orientation;
import com.openxu.utils.ChartCalUtil;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.ReflectUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : openXu
* date : 2020/10/31 14:26
* className : StandLinesChart
* version : 1.0
* description : 通用的折线图表
*/
public class StandLinesChart extends BaseChart {
//折线数据, 多少元素就有多少条线, 需要计算
private List<Line> lineList;
//坐标轴刻度
private AxisMark xAxisMark;
private AxisMark yLeftAxisMark;
private AxisMark yRightAxisMark;
//坐标轴线
private AxisLine[] xAxisLines;
private AxisLine[] yAxisLines;
private Builder builder;
public Builder builder(){
if (builder==null)
this.builder = new Builder();
return builder;
}
public class Builder{
/**
* 设置一条曲线
* @param line
* @return
*/
public Builder line(Line line){
if(lineList == null)
lineList = new ArrayList<>();
lineList.add(line) ;
return this;
}
/**
* 设置X轴刻度
* @param xAxisMark
* @return
*/
public Builder xAxisMark(AxisMark xAxisMark){
if(xAxisMark.lableOrientation==null)
xAxisMark.lableOrientation = Orientation.BOTTOM;
//计算x轴刻度值
if(xAxisMark.lables==null)
ChartCalUtil.calXLable(xAxisMark);
StandLinesChart.this.xAxisMark = xAxisMark;
//上面计算了X轴刻度,说明确定了竖直方向有几根轴,下面给予默认的竖直方向轴样式
yAxisLines = new AxisLine[xAxisMark.lableNum];
for(int i = 0; i<xAxisMark.lableNum; i++){
//第一条和最后一条线为实线,其他竖直方向不划线
yAxisLines[i] = new AxisLine.Builder(getContext())
.lineType((i ==0 || i == xAxisMark.lableNum-1)?AxisLineType.SOLID:AxisLineType.NONE)
.build();
}
return this;
}
/**
* 设置Y轴左侧刻度
* @param yLeftAxisMark
* @return
*/
public Builder yLeftAxisMark(AxisMark yLeftAxisMark){
if(yLeftAxisMark.lableOrientation==null)
yLeftAxisMark.lableOrientation = Orientation.LEFT;
//计算Y刻度
if(yLeftAxisMark.lables==null)
ChartCalUtil.calYLable(yLeftAxisMark);
StandLinesChart.this.yLeftAxisMark = yLeftAxisMark;
setXAxisLines(yLeftAxisMark);
return this;
}
/**
* 设置Y轴右侧刻度
* @param yRightAxisMark
* @return
*/
public Builder yRightAxisMark(AxisMark yRightAxisMark){
if(yRightAxisMark.lableOrientation==null)
yRightAxisMark.lableOrientation = Orientation.RIGHT;
if(yRightAxisMark.showLable && yRightAxisMark.lables==null)
ChartCalUtil.calYLable(yRightAxisMark);
StandLinesChart.this.yRightAxisMark = yRightAxisMark;
setXAxisLines(yRightAxisMark);
return this;
}
/**
* 设置竖直方向第index条网格线,0 <= index<= xAxisMark.lableNum-1
* @param index
* @param axisLine
* @return
*/
public Builder verticalAxisLine(int index, AxisLine axisLine){
if(xAxisMark==null) {
//设置竖直方向坐标轴网格线样式时,必须线调用xAxisMark()方法设置x轴刻度
throw new RuntimeException("please call xAxisMark() to determine the vertical line before this");
}
if(index>=yAxisLines.length){
throw new RuntimeException("index over size");
}
yAxisLines[index] = axisLine;
return this;
}
/**
* 设置水平方向第index条网格线,0 <= index<= yAxisMark.lableNum-1
* @param index
* @param axisLine
* @return
*/
public Builder horizontalAxisLine(int index, AxisLine axisLine){
if(yLeftAxisMark==null && yRightAxisMark==null) {
//设置水平方向坐标轴网格线样式时,必须线调用yLeftAxisMark()或者yRightAxisMark()设置y轴刻度
throw new RuntimeException("please call yLeftAxisMark() or yRightAxisMark() to determine the horizontal line before this");
}
if(index>=xAxisLines.length){
throw new RuntimeException("index over size");
}
xAxisLines[index] = axisLine;
return this;
}
private void setXAxisLines(AxisMark yAxisMark){
if(xAxisLines!=null)
return;
xAxisLines = new AxisLine[yAxisMark.lableNum];
for(int i = 0; i<yAxisMark.lableNum; i++){
//第一条和最后一条线为实线,其他竖直方向不划线
xAxisLines[i] = new AxisLine.Builder(getContext())
.lineType((i ==0 || i == xAxisMark.lableNum-1)?AxisLineType.SOLID:AxisLineType.DASHE)
.build();
}
}
public void build(){
isLoading = false;
evaluatorByData();
startDraw = false;
invalidate();
}
}
public StandLinesChart(Context context) {
this(context, null);
}
public StandLinesChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public StandLinesChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
touchEnable = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
Log.w(TAG, "======onMeasure:"+widthSize+" * "+heightSize);
}
/**设置数据后,计算相关值*/
private void evaluatorByData() {
float lableHeight;
float lableLead;
/**1. 确定图表绘制矩形*/
if (xAxisMark != null) {
paintLabel.setTextSize(xAxisMark.textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
//确定图表最下放绘制位置
if (xAxisMark.showLable && xAxisMark.lableOrientation == Orientation.BOTTOM) {
rectChart.bottom = getMeasuredHeight() - getPaddingBottom() - lableHeight - xAxisMark.textSpace;
}
}
//计算Y刻度
if (yLeftAxisMark != null) {
if (yLeftAxisMark.showLable && yLeftAxisMark.lableOrientation == Orientation.LEFT) {
paintLabel.setTextSize(yLeftAxisMark.textSize);
String maxLable = "";
for (String lable : yLeftAxisMark.lables) {
if (maxLable.length() < lable.length())
maxLable = lable;
}
rectChart.left = getPaddingLeft() + yLeftAxisMark.textSpace +
FontUtil.getFontlength(paintLabel, maxLable);
}
}
if (yRightAxisMark != null) {
if (yRightAxisMark.showLable && yRightAxisMark.lableOrientation == Orientation.RIGHT) {
paintLabel.setTextSize(yRightAxisMark.textSize);
String maxLable = "";
for (String lable : yRightAxisMark.lables) {
if (maxLable.length() < lable.length())
maxLable = lable;
}
rectChart.right = getMeasuredWidth() - getPaddingRight() - yRightAxisMark.textSpace -
FontUtil.getFontlength(paintLabel, maxLable);
}
}
Log.v(TAG, "计算图表矩形框:" + rectChart);
/**2. 计算X轴绘制点坐标*/
float markSpace;
if (xAxisMark != null) {
markSpace = (rectChart.right - rectChart.left) * 1.0f / (xAxisMark.lableNum - 1);
xAxisMark.markPointList.clear();
paintLabel.setTextSize(xAxisMark.textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontHeight(paintLabel);
float left = rectChart.left;
for (int i = 0; i < xAxisMark.lableNum; i++) {
if (i >= 1 && i < xAxisMark.lableNum - 1) {
left = rectChart.left + i * markSpace - FontUtil.getFontlength(paintLabel, xAxisMark.lables[i]) / 2;
} else if (i == xAxisMark.lableNum - 1) {
left = rectChart.right - FontUtil.getFontlength(paintLabel, xAxisMark.lables[i]);
}
/**x轴刻度位置确定竖直方向的轴线*/
yAxisLines[i].pointStart = new PointF(rectChart.left + i * markSpace, rectChart.bottom);
yAxisLines[i].pointEnd = new PointF(rectChart.left + i * markSpace, rectChart.top);
xAxisMark.markPointList.add(new AxisMark.MarkPoint(xAxisMark.lables[i],
new PointF(
left,
rectChart.bottom + xAxisMark.textSpace + lableLead
)));
}
}
/**3. 计算Y轴绘制点坐标*/
if (yLeftAxisMark != null) {
markSpace = (rectChart.bottom - rectChart.top) / (yLeftAxisMark.lableNum - 1);
yLeftAxisMark.markPointList.clear();
paintLabel.setTextSize(yLeftAxisMark.textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontHeight(paintLabel);
for (int i = 0; i < yLeftAxisMark.lableNum; i++) {
/**y轴刻度位置确定水平方向的轴线*/
xAxisLines[i].pointStart = new PointF(rectChart.left, rectChart.bottom - i * markSpace);
xAxisLines[i].pointEnd = new PointF(rectChart.right, rectChart.bottom - i * markSpace);
switch (yLeftAxisMark.lableOrientation) {
case LEFT: //左外居中
yLeftAxisMark.markPointList.add(new AxisMark.MarkPoint(yLeftAxisMark.lables[i],
new PointF(
rectChart.left - FontUtil.getFontlength(paintLabel, yLeftAxisMark.lables[i]) - yLeftAxisMark.textSpace,
rectChart.bottom - i * markSpace - lableHeight / 2 + lableLead
)));
break;
case TOP: //左内靠上
yLeftAxisMark.markPointList.add(new AxisMark.MarkPoint(yLeftAxisMark.lables[i],
new PointF(
rectChart.left + yLeftAxisMark.textSpace,
rectChart.bottom - i * markSpace - lableHeight - yLeftAxisMark.textSpace + lableLead
)));
break;
}
}
}
if (yRightAxisMark != null) {
markSpace = (rectChart.bottom - rectChart.top) / (yLeftAxisMark.lableNum - 1);
yRightAxisMark.markPointList.clear();
paintLabel.setTextSize(yRightAxisMark.textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontHeight(paintLabel);
for (int i = 0; i < yRightAxisMark.lableNum; i++) {
/**y轴刻度位置确定水平方向的轴线*/
xAxisLines[i].pointStart = new PointF(rectChart.left, rectChart.bottom - i * markSpace);
xAxisLines[i].pointEnd = new PointF(rectChart.right, rectChart.bottom - i * markSpace);
switch (yRightAxisMark.lableOrientation) {
case RIGHT: //右外居中
yRightAxisMark.markPointList.add(new AxisMark.MarkPoint(yRightAxisMark.lables[i],
new PointF(
rectChart.right + yRightAxisMark.textSpace,
rectChart.bottom - i * markSpace - lableHeight / 2 + lableLead
)));
break;
case TOP: //右内靠上
yRightAxisMark.markPointList.add(new AxisMark.MarkPoint(yRightAxisMark.lables[i],
new PointF(
rectChart.right + yRightAxisMark.textSpace,
rectChart.bottom - i * markSpace - lableHeight - yRightAxisMark.textSpace + lableLead
)));
break;
}
}
}
/**4. 计算曲线*/
if (lineList != null && lineList.size() > 0) {
for (Line line : lineList) {
line.linePointList.clear();
AxisMark axis = line.orientation == Orientation.LEFT ? yLeftAxisMark : yRightAxisMark;
//该折线上点的总数,不能直接取datas.size(),有些图表可能规定有数量,比如股票分时图,如果datas.size()<line.dataNumCount,则这个折线图不能绘制到终点
line.dataNumCount = line.dataNumCount == 0 ? line.datas.size() : line.dataNumCount;
float oneSpace = (rectChart.right - rectChart.left) / (line.dataNumCount - 1);
for (int i = 0; i < line.datas.size(); i++) {
String str_y = ReflectUtil.getField(line.datas.get(i), line.field_y).toString();
String str_x = ReflectUtil.getField(line.datas.get(i), line.field_x).toString();
float valueY;
if (str_y.contains("%"))
valueY = Float.parseFloat(str_y.substring(0, str_y.indexOf("%"))) / 100.0f;
else
valueY = Float.parseFloat(str_y);
line.linePointList.add(new DataPoint(str_x, str_y, new PointF(
rectChart.left + i * oneSpace,
//根据最大值和最小值,计算当前数据在图表上Y轴的坐标
rectChart.bottom -
(rectChart.bottom - rectChart.top) / (axis.cal_mark_max - axis.cal_mark_min)
* (valueY - axis.cal_mark_min)
)));
}
}
}
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
drawGrid(canvas);
drawXYLable(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
drawDataPath(canvas);
drawFocus(canvas);
}
/**1. 绘制坐标轴网格*/
private void drawGrid(Canvas canvas){
//绘制水平轴线
if(xAxisLines==null || xAxisLines.length==0)
return;
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paintEffect.setStyle(Paint.Style.STROKE);
for (AxisLine axisLine : xAxisLines) {
if(axisLine.type == AxisLineType.NONE)
continue;
drawAxisLine(canvas, axisLine);
}
//绘制Y轴方向辅助网格 上下
for (AxisLine axisLine : yAxisLines) {
if(axisLine.type == AxisLineType.NONE)
continue;
drawAxisLine(canvas, axisLine);
}
}
private void drawAxisLine(Canvas canvas, AxisLine axisLine){
if(axisLine.type == AxisLineType.SOLID){ //实线
paint.setStrokeWidth(axisLine.lineWidth);
paint.setColor(axisLine.lineColor);
canvas.drawLine(axisLine.pointStart.x, axisLine.pointStart.y,
axisLine.pointEnd.x, axisLine.pointEnd.y, paint);
}
if(axisLine.type == AxisLineType.DASHE){ //虚线
paintEffect.setStrokeWidth(axisLine.lineWidth);
paintEffect.setColor(axisLine.lineColor);
Path path = new Path();
path.moveTo(axisLine.pointStart.x, axisLine.pointStart.y);
path.lineTo(axisLine.pointEnd.x,axisLine.pointEnd.y);
PathEffect effects = new DashPathEffect(new float[]{15,8,15,8},0);
paintEffect.setPathEffect(effects);
canvas.drawPath(path, paintEffect);
}
}
/**绘制X轴刻度*/
private void drawXYLable(Canvas canvas){
drawAxisLable(canvas, xAxisMark);
drawAxisLable(canvas, yLeftAxisMark);
drawAxisLable(canvas, yRightAxisMark);
}
private void drawAxisLable(Canvas canvas, AxisMark axisMark){
if(axisMark==null)
return;
if(axisMark.showLable){
paintLabel.setTextSize(axisMark.textSize);
paintLabel.setColor(axisMark.textColor);
for(AxisMark.MarkPoint point : axisMark.markPointList){
canvas.drawText(point.value, point.point.x, point.point.y, paintLabel);
}
}
}
/**绘制曲线*/
private float smoothness=0.15f;
private void drawDataPath(Canvas canvas) {
if(lineList==null || lineList.size()==0)
return;
// animPro = .5f;
paint.setAntiAlias(true);
// paint.setStyle(Paint.Style.STROKE);
// paint.setStrokeCap(Paint.Cap.ROUND);
// paint.setStrokeJoin(Paint.Join.ROUND);
if(null==lineList || lineList.size()<=0)
return;
PointF prePoint = null;
int diffx, diffy;
for(int lineNum = 0; lineNum < lineList.size(); lineNum ++){
Line line = lineList.get(lineNum);
//一条一条的绘制
paint.setStrokeWidth(line.lineWidth);
paint.setColor(line.lineColor);
Path path = new Path();
for(int i = 0; i <line.dataNumCount; i++){
PointF point = ((DataPoint)line.linePointList.get(i)).point;
//下一个点
// nextPoint = i+1<line.dataNumCount?((DataPoint)line.linePointList.get(i+1)).point:point;
if(i == 0){
if(line.animType == AnimType.LEFT_TO_RIGHT){
path.moveTo(rectChart.left+(point.x-rectChart.left)*animPro, point.y);
}else if(line.animType == AnimType.BOTTOM_TO_TOP){
path.moveTo(point.x, rectChart.bottom-(rectChart.bottom - point.y)* animPro);
}else{
path.moveTo(point.x, point.y);
}
// canvas.drawCircle(point.x, point.y, 5.0f, paint);
// path.moveTo(point.x, point.y);
// Log.w(TAG, lineNum+"起点:"+point);
}else{
if(line.lineType == Line.LineType.CURVE) {
//quadTo:二阶贝塞尔曲线连接前后两点,这样使得曲线更加平滑
// * @param x1 The x-coordinate of the control point on a quadratic curve
// * @param y1 The y-coordinate of the control point on a quadratic curve
// * @param x2 The x-coordinate of the end point on a quadratic curve
// * @param y2 The y-coordinate of the end point on a quadratic curve
//cubicTo : 三阶
diffx = (int) ((point.x - prePoint.x) * smoothness);
if (line.animType == AnimType.LEFT_TO_RIGHT) {
path.cubicTo(rectChart.left + (prePoint.x + diffx - rectChart.left) * animPro,
prePoint.y,
rectChart.left + (point.x - diffx - rectChart.left) * animPro,
point.y,
rectChart.left + (point.x - rectChart.left) * animPro,
point.y);
} else if (line.animType == AnimType.BOTTOM_TO_TOP) {
path.cubicTo(prePoint.x + diffx, rectChart.bottom - (rectChart.bottom - prePoint.y) * animPro,
point.x - diffx, rectChart.bottom - (rectChart.bottom - point.y) * animPro,
point.x, rectChart.bottom - (rectChart.bottom - point.y) * animPro);
} else if (line.animType == AnimType.SLOW_DRAW) {
if (i > lineList.size() * animPro)
break;
path.cubicTo(prePoint.x + diffx, prePoint.y,
point.x - diffx, point.y,
point.x, point.y);
} else if (line.animType == AnimType.NONE) {
path.cubicTo(prePoint.x + diffx, prePoint.y,
point.x - diffx, point.y,
point.x, point.y);
}
// path.cubicTo(prePoint.x+diffx, prePoint.y,
// point.x-diffx, point.y,
// point.x, point.y);
}else if(line.lineType == Line.LineType.BROKEN){
if (line.animType == AnimType.LEFT_TO_RIGHT) {
path.lineTo(rectChart.left + (point.x - rectChart.left) * animPro,
point.y);
} else if (line.animType == AnimType.BOTTOM_TO_TOP) {
path.lineTo(point.x, rectChart.bottom - (rectChart.bottom - point.y) * animPro);
} else if (line.animType == AnimType.SLOW_DRAW) {
if (i > lineList.size() * animPro)
break;
path.lineTo(point.x, point.y);
} else if (line.animType == AnimType.NONE) {
path.lineTo(point.x, point.y);
}
}
// canvas.drawCircle(prePoint.x+diffx, prePoint.y, 10.0f, paint);
// canvas.drawCircle(point.x-diffx, point.y, 10.0f, paint);
// canvas.drawCircle(point.x, point.y, 5.0f, paint);
}
prePoint = point;
}
canvas.drawPath(path, paint);
}
}
private ChartFocus focusInfo;
@Override
protected void onTouchMoved(PointF point) {
if(null==lineList || lineList.size()<=0)
return;
onFocus = (null != point);
if(null != point) {
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * lineList.get(0).dataNumCount
/ (rectChart.right - rectChart.left));
//避免滑出
List<DataPoint> dataPoints = lineList.get(0).linePointList;
if(point.x > dataPoints.get(dataPoints.size()-1).point.x)
point.x = dataPoints.get(dataPoints.size()-1).point.x;
if(point.x < dataPoints.get(0).point.x)
point.x = dataPoints.get(0).point.x;
index = Math.max(0, Math.min(index, dataPoints.size() - 1));
focusInfo = new ChartFocus();
List<Object> objs = new ArrayList<>();
List<PointF> points = new ArrayList<>();
for(Line line : lineList){
objs.add(line.datas.get(index));
points.add(((DataPoint)line.linePointList.get(index)).point);
}
focusInfo.setFocusData(objs);
focusInfo.setPoints(points);
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusInfo);
}
invalidate();
}
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
public void onfocus(ChartFocus focusInfo);
}
//设置焦点线颜色 及 粗细
private int focusLineColor = getResources().getColor(R.color.tc_chart_focus_line);
private int focusLineSize = DensityUtil.dip2px(getContext(), 0.8f);
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusInfo)
return;
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(focusLineSize);
paint.setColor(focusLineColor);
//竖直线
canvas.drawLine(focusInfo.getPoints().get(0).x, rectChart.bottom,
focusInfo.getPoints().get(0).x, rectChart.top, paint);
// paintLabel.setTextSize(focusTextSize);
// paintLabel.setColor(focusTextColor);
// float focusTextLead = FontUtil.getFontLeading(paintLabel);
// float focusTextHeight = FontUtil.getFontHeight(paintLabel);
try {
for (PointF point : focusInfo.getPoints()) {
//水平参考线
canvas.drawLine(rectChart.left, point.y, rectChart.right, point.y, paint);
//中心点
canvas.drawCircle(point.x, point.y, 10.0f, paint);
}
}catch (Exception e){
e.printStackTrace();
}
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(lineList!=null && lineList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,616 @@
package com.openxu.cview.xmstock20201030;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.hkchart.config.YAxisMark;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.NumberFormatUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:26
* className : SyzsLinesChart
* version : 1.0
* description : 4. 收益走势图,贝塞尔曲线
*/
public class SyzsLinesChart extends BaseChart {
//设置数据 每个元素表示:时间、实际累计跌涨浮、历史累计跌涨幅 ["20201016","0.51","0.55"]
private List<List<String>> dataList;
private String[] lableXList;
//计算后的数据
private List<DataPoint> lableXPointList;
private List<List<DataPoint>> linePointList;
//y轴刻度设置
private YAxisMark yAxisMark;
/**可以设置的属性*/
//设置线条颜色
private int[] lineColor = new int[]{Color.parseColor("#fb4051"),
Color.parseColor("#ffcecb")};
//设置曲线粗细
private int lineSize = DensityUtil.dip2px(getContext(), 1.5f);
//设置坐标文字大小
private int textSizeX = (int)getResources().getDimension(R.dimen.ts_chart_xy);
//设置坐标文字颜色
private int textColorX = getResources().getColor(R.color.tc_chart_xy);
//设置X坐标字体与横轴的距离
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);
//设置动画类型
private AnimType animType = AnimType.SLOW_DRAW;
//设置焦点线颜色 及 粗细
private int focusLineColor = Color.parseColor("#5E5E5E");
private int focusLineSize = DensityUtil.dip2px(getContext(), 1f);
//焦点面板矩形,只记录矩形宽高
private int foucsRectTextSpace = DensityUtil.dip2px(getContext(), 3);
private int foucsRectSpace = DensityUtil.dip2px(getContext(), 6);
private float dotRadius = DensityUtil.dip2px(getContext(), 3); //焦点面板小圆点半径
private float dotRadius1 = DensityUtil.dip2px(getContext(), 5); //最高收益小圆点半径
private float foucsRectWidth;
private float foucsRectHeight;
public enum AnimType{
// LEFT_TO_RIGHT, //动画从左往右
// BOTTOM_TO_TOP, //动画从下网上上升
SLOW_DRAW //动画缓慢绘制
}
public SyzsLinesChart(Context context) {
this(context, null);
}
public SyzsLinesChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SyzsLinesChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
touchEnable = true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
//20201028 --> 2020-10-28
private String formatDate(String date){
String str = date.substring(0, 4) + "-"+date.substring(4, 6)+"-"+date.substring(6);
Log.v(TAG, date+ "---->"+str);
return str;
}
/**
* 设置数据
*/
public void setData(List<List<String>> dataList){
if(null==dataList)
return;
this.dataList.clear();
this.dataList.addAll(dataList);
//[20200803,"1582.08","30.00"],
Log.v(TAG, "---->"+dataList.get(0));
//重组X轴刻度, 取其中5个日期
lableXList = new String[5];
int part = dataList.size()/4;
lableXList[0] = dataList.get(0).get(0).toString(); //第一天
// Log.v(TAG, 0+ "---->"+Integer.parseInt(dataList.get(0).get(0).toString()));
lableXList[1] = dataList.get(part).get(0).toString(); //
lableXList[2] = dataList.get(dataList.size()/2).get(0).toString(); //中间一天
lableXList[3] = dataList.get(part*3).get(0).toString(); //
lableXList[4] = dataList.get(dataList.size()-1).get(0).toString(); //最后一天
for(int i = 0; i<lableXList.length; i++){
lableXList[i] = formatDate(lableXList[i]);
}
yAxisMark = new YAxisMark.Builder(getContext())
.lableNum(5)
.textSize(textSizeX)
.textColor(textColorX)
.build();
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
isLoading = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(dataList.size()<=0)
return;
/**③、计算Y刻度最大值和最小值以及幅度*/
//时间、实际累计跌涨浮、历史累计跌涨幅 ["20201016","0.51","0.55"]
calYLable(yAxisMark);
/**①、计算字体相关以及图表原点坐标*/
paintLabel.setTextSize(textSizeX);
float xlableHeight = FontUtil.getFontHeight(paintLabel);
float xlableLead = FontUtil.getFontLeading(paintLabel);
paintLabel.setTextSize(yAxisMark.textSize);
float ylableHeight = FontUtil.getFontHeight(paintLabel);
float ylableMaxLength = Math.max(FontUtil.getFontlength(paintLabel, (int)yAxisMark.cal_mark_max+"%"),
FontUtil.getFontlength(paintLabel, (int)yAxisMark.cal_mark_min+"%"));
float yFoucsLenght = FontUtil.getFontlength(paintLabel, "实际累计涨跌幅:-10.54%");
//图表主体矩形
rectChart = new RectF(getPaddingLeft() + yAxisMark.textSpace + ylableMaxLength,
getPaddingTop() + ylableMaxLength/2,
getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom() - xlableHeight - textSpaceX);
//焦点面板
//2020-10-16
//实际累计涨跌幅:-10.54%
//历史累计涨跌幅:-10.54%
foucsRectWidth = dotRadius*2 + foucsRectTextSpace +yFoucsLenght + foucsRectSpace*2;
foucsRectHeight = xlableHeight + ylableHeight * 2 + foucsRectTextSpace*2 + foucsRectSpace*2;
/**②、计算X标签绘制坐标*/
float lableXSpace = 0;
for(String lableX : lableXList){
lableXSpace += FontUtil.getFontlength(paintLabel, lableX);
}
lableXSpace = (rectChart.right - rectChart.left - lableXSpace)/(lableXList.length -1);
lableXPointList = new ArrayList<>();
if(lableXSpace>0){
float left = rectChart.left;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
left,rectChart.bottom + textSpaceX + xlableLead)));
left += (FontUtil.getFontlength(paintLabel, lableXList[i])+lableXSpace);
}
}else{
//如果X轴标签字体过长的情况需要特殊处理
float oneWidth = (rectChart.right - rectChart.left)/lableXList.length;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
float xLen = FontUtil.getFontlength(paintLabel, lableX);
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
rectChart.left+i*oneWidth+(oneWidth-xLen)/2,rectChart.bottom + textSpaceX + xlableLead)));
}
}
/**④、计算点的坐标,如果有动画的情况下,边绘制边计算会耗费性能,所以先计算*/
linePointList = new ArrayList<>();
//两条线
linePointList.add(new ArrayList<>());
linePointList.add(new ArrayList<>());
float oneSpace = (rectChart.right - rectChart.left) / (dataList.size()-1);
for(int i = 0; i < dataList.size(); i++){
//时间、实际累计跌涨浮、历史累计跌涨幅 ["20201016","0.51","0.55"]
List<String> onePart = dataList.get(i);
try {
//第一条线的数据
if(onePart!=null && onePart.size()>=2&& onePart.get(1)!=null && onePart.get(1).length()>0) {
float valueY = Float.parseFloat(onePart.get(1));
PointF point = new PointF(rectChart.left + i * oneSpace,
rectChart.bottom - (rectChart.bottom - rectChart.top) / (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) * (valueY - yAxisMark.cal_mark_min));
linePointList.get(0).add(new DataPoint(onePart.get(0), valueY, point));
}
//第二条线的数据
if(onePart!=null && onePart.size()>=3&& onePart.get(2)!=null && onePart.get(2).length()>0) {
float valueY = Float.parseFloat(onePart.get(2));
// Log.w(TAG, i+"计算右侧y值"+valueY);
PointF point = new PointF(rectChart.left + i * oneSpace,
rectChart.bottom - (rectChart.bottom - rectChart.top) / (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) * (valueY - yAxisMark.cal_mark_min));
linePointList.get(1).add(new DataPoint(onePart.get(0), valueY, point));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
private FocusData focusData;
@Override
protected void onTouchMoved(PointF point) {
if(null==dataList)
return;
onFocus = (null != point);
if(null != point && null!=dataList && dataList.size()>0) {
//避免滑出
// if(point.x > linePointList.get(0).get(dataList.size()-1).getPoint().x)
// point.x = linePointList.get(0).get(dataList.size()-1).getPoint().x;
// if(point.x < linePointList.get(0).get(0).getPoint().x)
// point.x = linePointList.get(0).get(0).getPoint().x;
point.x = Math.max(point.x, rectChart.left);
point.x = Math.min(point.x, rectChart.right);
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * dataList.size() / (rectChart.right - rectChart.left));
LogUtil.e(getClass().getSimpleName(), "========焦点索引:"+index+" 数据总数:"+dataList.size()+" 线条数量:"+linePointList.size());
index = Math.max(0, Math.min(index, dataList.size() - 1));
focusData = new FocusData();
focusData.setData(dataList.get(index));
focusData.setPoints(new ArrayList<>());
if(index<linePointList.get(0).size()) {
focusData.getPoints().add(linePointList.get(0).get(index));
}else{
focusData.getPoints().add(new DataPoint(null, 0, null));
}
if(index<linePointList.get(1).size()) {
focusData.getPoints().add(linePointList.get(1).get(index));
}else{
focusData.getPoints().add(new DataPoint(null, 0, null));
}
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusData);
}
invalidate();
}
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
public void onfocus(FocusData focusData);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
drawGrid(canvas);
drawXLable(canvas);
drawYLable(canvas);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
drawDataPath(canvas);
drawFocus(canvas);
}
/**绘制X轴方向辅助网格*/
private float yMark0; //y轴0对应的y坐标
private void drawGrid(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(yAxisMark.lableNum-1);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
//竖直黑色实线
paint.setColor(Color.parseColor("#3d3d3d"));
canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(defColor);
// canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
// canvas.drawLine(rectChart.right, rectChart.top, rectChart.right, rectChart.bottom, paint);
PathEffect effects = new DashPathEffect(new float[]{15,6,15,6},0);
Path path = new Path();
for (int i = 0; i < yAxisMark.lableNum; i++) {
if(yAxisMark.cal_mark_min+i*yAxisMark.cal_mark == 0){
//实线
yMark0 = rectChart.bottom-yMarkSpace*i;
canvas.drawLine(rectChart.left, yMark0, rectChart.right, yMark0, paint);
}else{
path.reset();
path.moveTo(rectChart.left, rectChart.bottom-yMarkSpace*i);
path.lineTo(rectChart.right,rectChart.bottom-yMarkSpace*i);
paintEffect.setPathEffect(effects);
canvas.drawPath(path, paintEffect);
}
}
}
/**绘制X轴刻度*/
private void drawXLable(Canvas canvas){
paintLabel.setTextSize(textSizeX);
paintLabel.setColor(textColorX);
for(DataPoint lable : lableXPointList){
canvas.drawText(lable.getValueX(), lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制Y轴刻度*/
private void drawYLable(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(yAxisMark.lableNum-1);
paintLabel.setTextSize(yAxisMark.textSize);
paintLabel.setColor(yAxisMark.textColor);
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableLead = FontUtil.getFontLeading(paintLabel);
for (int i = 0; i < yAxisMark.lableNum; i++) {
String text = (int) (yAxisMark.cal_mark_min + i * yAxisMark.cal_mark) + "%";
canvas.drawText(text,
rectChart.left - yAxisMark.textSpace - FontUtil.getFontlength(paintLabel, text),
rectChart.bottom - yMarkSpace * i - lableHeight/2 + lableLead, paintLabel);
}
}
/**绘制曲线*/
private void drawDataPath(Canvas canvas) {
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(lineSize);
PointF pointMax = null; //最高收益点
for(int j = 0; j < linePointList.size(); j++){
List<DataPoint> lineList = linePointList.get(j);
if(lineList==null || lineList.size()<=0) //有可能只有一条???
continue;
if(j == 0) {
Log.w(TAG, "当前绘制的是第1条实际跌涨幅,需要标注最高收益");
pointMax = new PointF(0, Integer.MAX_VALUE);
}
//一条一条的绘制
Path path = new Path();
PointF lastPoint = null;
float minYValue = 0;
for(int i = 0 ; i<lineList.size()-1; i++){
if(i>lineList.size()*animPro) //动画
break;
lastPoint = lineList.get(i+1).getPoint();
minYValue = Math.min(lastPoint.y,minYValue);
if(j == 0 && lastPoint.y<pointMax.y){
pointMax.x = lastPoint.x;
pointMax.y = lastPoint.y;
}
if(i == 0){ //二阶
path.moveTo(lineList.get(i).getPoint().x, lineList.get(i).getPoint().y);
float x = lineList.get(i).getPoint().x + (lineList.get(i+1).getPoint().x - lineList.get(i).getPoint().x)/2;
float y = lineList.get(i+1).getPoint().y;
path.quadTo(x,y,lastPoint.x,lastPoint.y);
}else if(i<lineList.size()-2){ //三阶
float x1 = lineList.get(i).getPoint().x + (lineList.get(i+1).getPoint().x - lineList.get(i).getPoint().x)/2;
float y1 = lineList.get(i).getPoint().y;
float x2 = lineList.get(i).getPoint().x + (lineList.get(i+1).getPoint().x - lineList.get(i).getPoint().x)/2;
float y2 = lineList.get(i+1).getPoint().y;
path.cubicTo(x1, y1, x2, y2, lastPoint.x,lastPoint.y);
}else if(i == lineList.size()-2){ //最后一个 二阶
float x = lineList.get(i).getPoint().x + (lineList.get(i+1).getPoint().x - lineList.get(i).getPoint().x)/2;
float y = lineList.get(i).getPoint().y;
path.quadTo(x,y,lastPoint.x,lastPoint.y);
}
}
paint.setColor(lineColor[j]);
canvas.drawPath(path, paint);
//绘制阴影
path.lineTo(lastPoint.x, yMark0);
path.close();
//线性渐变:前两个参数是渐变开始的点坐标,第三四个参数是渐变结束的点的坐标;渐变的颜色,渐变颜色的分布,模式
Shader mShader = new LinearGradient(rectChart.left, rectChart.top ,rectChart.left, rectChart.bottom,
new int[] {lineColor[j],Color.TRANSPARENT},null,Shader.TileMode.CLAMP);
paintEffect.setStyle(Paint.Style.FILL);
paintEffect.setShader(mShader);
canvas.drawPath(path, paintEffect);
paintEffect.setShader(null);
}
Log.w(TAG, "绘制最高收益:"+pointMax);
//绘制最高收益
if(pointMax!=null){
paintLabel.setTextSize(yAxisMark.textSize);
paintLabel.setColor(lineColor[0]);
paint.setStyle(Paint.Style.FILL);
paint.setColor(lineColor[0]);
canvas.drawCircle(pointMax.x , pointMax.y, dotRadius1, paint);
float textlenght = FontUtil.getFontlength(paintLabel, "最高收益");
float x = pointMax.x - textlenght;
x = Math.max(x, rectChart.left);
x = Math.min(x, rectChart.right - textlenght);
canvas.drawText("最高收益", x,
pointMax.y - dotRadius1 - foucsRectTextSpace - FontUtil.getFontHeight(paintLabel) + FontUtil.getFontLeading(paintLabel), paintLabel);
}
}
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusData)
return;
PointF point1 = focusData.getPoints().get(0).getPoint();
PointF point2 = focusData.getPoints().get(1).getPoint();
//绘制竖直虚线
PathEffect effects = new DashPathEffect(new float[]{15,10,15,10},0);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(focusLineSize);
paintEffect.setColor(focusLineColor);
paintEffect.setPathEffect(effects);
Path path = new Path();
path.moveTo(point1.x, rectChart.bottom);
path.lineTo(point1.x, rectChart.top);
canvas.drawPath(path , paintEffect);
//绘制焦点
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineSize);
paint.setColor(lineColor[0]);
canvas.drawCircle(point1.x, point1.y, dotRadius, paint);
paint.setColor(lineColor[1]);
canvas.drawCircle(point2.x, point2.y, dotRadius, paint);
//面板
boolean showLeft = point1.x-rectChart.left > (rectChart.right - rectChart.left)/2;
RectF rect = new RectF(
showLeft?point1.x - foucsRectWidth - 30:point1.x + 30,
rectChart.top + (rectChart.bottom - rectChart.top)/2 - foucsRectHeight/2,
showLeft? point1.x - 30 : point1.x + foucsRectWidth + 30,
rectChart.top + (rectChart.bottom - rectChart.top)/2 + foucsRectHeight/2
);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
paint.setAlpha(230);
canvas.drawRect(rect , paint);
//面板中的文字
//2020-10-16
//实际累计涨跌幅:+10.54%
//历史累计涨跌幅:-10.54%
paintLabel.setTextSize(textSizeX);
paintLabel.setColor(textColorX);
float lableHeight = FontUtil.getFontHeight(paintLabel);
float lableLead = FontUtil.getFontLeading(paintLabel);
float top = rect.top + foucsRectSpace;
float left = rect.left + foucsRectSpace;
canvas.drawText(formatDate(focusData.getData().get(0)), left, top + lableLead, paintLabel);
top += (lableHeight+foucsRectTextSpace);
paintLabel.setTextSize(yAxisMark.textSize);
paintLabel.setColor(lineColor[0]);
paint.setColor(lineColor[0]);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
canvas.drawCircle(left + dotRadius, top + lableHeight/2, dotRadius, paint);
if(focusData.getPoints().get(0).getValueX() == null){
canvas.drawText("实际累计涨跌幅:",
left+dotRadius*2+foucsRectTextSpace, top + lableLead, paintLabel);
}else{
String dzf = NumberFormatUtil.formattedDecimal(focusData.getPoints().get(0).getValueY());
canvas.drawText("实际累计涨跌幅:"+(focusData.getPoints().get(0).getValueY()>0?"+":"")+dzf+"%",
left+dotRadius*2+foucsRectTextSpace, top + lableLead, paintLabel);
}
top += (lableHeight+foucsRectTextSpace);
paintLabel.setTextSize(yAxisMark.textSize);
paintLabel.setColor(lineColor[1]);
paint.setColor(lineColor[1]);
lableLead = FontUtil.getFontLeading(paintLabel);
canvas.drawCircle(left + dotRadius, top + lableHeight/2, dotRadius, paint);
if(focusData.getPoints().get(1).getValueX() == null){
canvas.drawText("历史累计涨跌幅:",
left+dotRadius*2+foucsRectTextSpace, top + lableLead, paintLabel);
}else{
String dzf = NumberFormatUtil.formattedDecimal(focusData.getPoints().get(1).getValueY());
canvas.drawText("历史累计涨跌幅:"+(focusData.getPoints().get(1).getValueY()>0?"+":"")+dzf+"%",
left+dotRadius*2+foucsRectTextSpace, top + lableLead, paintLabel);
}
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
/**焦点数据*/
public static class FocusData {
private List<DataPoint> points;
private List<String> data;
public List<DataPoint> getPoints() {
return points;
}
public void setPoints(List<DataPoint> points) {
this.points = points;
}
public List<String> getData() {
return data;
}
public void setData(List<String> data) {
this.data = data;
}
}
/**
* 根据传入的Y轴信息,从数据中获取最小最大值,并获取到Y刻度点值
*/
private void calYLable(YAxisMark axisMark){
//时间、实际累计跌涨浮、历史累计跌涨幅 ["20201016","0.51","0.55"]
axisMark.cal_mark_max = Float.MIN_VALUE; //Y轴刻度最大值
axisMark.cal_mark_min = Float.MAX_VALUE; //Y轴刻度最小值
for(List<String> data : dataList){
try {
if(data!=null && data.size()>=2&& data.get(1)!=null && data.get(1).length()>0) {
axisMark.cal_mark_max = Math.max(axisMark.cal_mark_max, Float.parseFloat(data.get(1)));
axisMark.cal_mark_min = Math.min(axisMark.cal_mark_min, Float.parseFloat(data.get(1)));
}
if(data!=null && data.size()>=3 && data.get(2)!=null && data.get(2).length()>0) {
axisMark.cal_mark_max = Math.max(axisMark.cal_mark_max, Float.parseFloat(data.get(2)));
axisMark.cal_mark_min = Math.min(axisMark.cal_mark_min, Float.parseFloat(data.get(2)));
}
}catch (Exception e){
}
}
LogUtil.i(TAG, "Y轴真实axisMark.cal_mark_min="+axisMark.cal_mark_min+" axisMark.cal_mark_max="+axisMark.cal_mark_max);
//保证有一个0刻度
if(axisMark.cal_mark_max > 0 && axisMark.cal_mark_min>0){
axisMark.cal_mark_min = 0;
axisMark.cal_mark = (int)(axisMark.cal_mark_max/axisMark.lableNum);
if(axisMark.cal_mark_max%axisMark.lableNum!=0)
axisMark.lableNum+=1;
}
if(axisMark.cal_mark_max < 0 && axisMark.cal_mark_min<0){
axisMark.cal_mark_max = 0;
axisMark.cal_mark = (int)(-axisMark.cal_mark_min/axisMark.lableNum);
if(axisMark.cal_mark_min%axisMark.lableNum!=0)
axisMark.lableNum+=1;
}
if(axisMark.cal_mark_max > 0 && axisMark.cal_mark_min<0){
axisMark.cal_mark = (int)((axisMark.cal_mark_max-axisMark.cal_mark_min)/axisMark.lableNum);
axisMark.lableNum = 1; //从0刻度开始
int a = 0;
while(true){
a += axisMark.cal_mark;
axisMark.lableNum ++;
if(a>=axisMark.cal_mark_max){
axisMark.cal_mark_max = a;
break;
}
}
a = 0;
while(true){
a -= axisMark.cal_mark;
axisMark.lableNum ++;
if(a<=axisMark.cal_mark_min){
axisMark.cal_mark_min = a;
break;
}
}
}
LogUtil.i(TAG, "Y轴axisMark.cal_mark_min="+axisMark.cal_mark_min+" axisMark.cal_mark_max="+axisMark.cal_mark_max+" axisMark.cal_mark="+axisMark.cal_mark+" 总共"+axisMark.lableNum+"条横线");
}
}
@@ -0,0 +1,535 @@
package com.openxu.cview.xmstock20201030;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.PointF;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import com.openxu.cview.R;
import com.openxu.cview.chart.anim.AngleEvaluator;
import com.openxu.cview.xmstock.BaseChart;
import com.openxu.cview.xmstock.bean.DataPoint;
import com.openxu.cview.xmstock.bean.FocusInfo;
import com.openxu.utils.DensityUtil;
import com.openxu.utils.FontUtil;
import com.openxu.utils.LogUtil;
import com.openxu.utils.NumberFormatUtil;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:26
* className : QsrdLinesChart
* version : 1.0
* description : 券商热点走势对比图
*/
public class aaLinesChart extends BaseChart {
//设置数据
private List<List<String>> dataList;
private String[] lableXList = new String[]{"09:30", "10:30", "11:30/13:00", "14:00", "15:00"};
private int dataNumCount = 60 * 4; //60*4 一分钟一个,四小时
//计算后的数据
private List<DataPoint> lableXPointList;
private List<ArrayList<DataPoint>> linePointList;
/**可以设置的属性*/
//设置Y轴刻度数量
private int YMARK_NUM = 5;
private YMARK_TYPE yMarkType = YMARK_TYPE.INTEGER;
//设置线条颜色
private int[] lineColor = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW};
//设置曲线粗细
private int lineSize = DensityUtil.dip2px(getContext(), 1.5f);
//曲线数量
private int LINE_NUM = 0;
//设置坐标文字大小
private int textSize = (int)getResources().getDimension(R.dimen.ts_chart_xy);
//设置坐标文字颜色
private int textColor = getResources().getColor(R.color.tc_chart_xy);
//设置X坐标字体与横轴的距离
private int textSpaceX = DensityUtil.dip2px(getContext(), 5);
//设置Y坐标字体与横轴的距离
private int textSpaceY = DensityUtil.dip2px(getContext(), 3);
//设置动画类型
private AnimType animType = AnimType.SLOW_DRAW;
//设置焦点线颜色 及 粗细
private int focusLineColor = getResources().getColor(R.color.tc_chart_focus_line);
private int focusLineSize = DensityUtil.dip2px(getContext(), 0.8f);
public enum YMARK_TYPE{
PERCENTAGE, /*百分比*/
INTEGER
}
public enum AnimType{
LEFT_TO_RIGHT, //动画从左往右
BOTTOM_TO_TOP, //动画从下网上上升
SLOW_DRAW //动画缓慢绘制
}
/**需要计算相关值*/
private float lableLead, lableHeight;
/*字体绘制相关*/
private float YMARK = 1; //Y轴刻度间隔
private float YMARK_MAX = Float.MIN_VALUE; //Y轴刻度最大值
private float YMARK_MIN = Float.MAX_VALUE; //Y轴刻度最小值
public aaLinesChart(Context context) {
this(context, null);
}
public aaLinesChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public aaLinesChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void init(Context context, AttributeSet attrs, int defStyleAttr) {
dataList = new ArrayList<>();
touchEnable = true;
}
public void setYMARK_NUM(int YMARK_NUM) {
this.YMARK_NUM = YMARK_NUM;
}
public void setyMarkType(YMARK_TYPE yMarkType) {
this.yMarkType = yMarkType;
}
/***********************************设置属性set方法**********************************/
/**设置数据的数量,默认是60*4*/
public void setDataNumCount(int dataNumCount) {
this.dataNumCount = dataNumCount;
}
public void setLineColor(int[] lineColor) {
this.lineColor = lineColor;
}
public void setAnimType(AnimType animType) {
this.animType = animType;
}
public void setLINE_NUM(int LINE_NUM) {
this.LINE_NUM = LINE_NUM;
}
/***********************************设置属性set方法over**********************************/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize, heightSize);
evaluatorByData();
invalidate();
}
/***************************************************/
/***************************************************/
/**
* 设置数据
* @param dataList 折线数据
* @param lableXList X轴显示的刻度,默认是9:30-15:00(传null)
* 如果需要显示其他的数据,需要重新设置
*/
public void setData(List<List<String>> dataList, String[] lableXList){
if(null==dataList)
return;
this.dataList.clear();
this.dataList.addAll(dataList);
if(null!=lableXList && lableXList.length>0)
this.lableXList = lableXList;
if(getMeasuredWidth()>0) {
evaluatorByData();
startDraw = false;
invalidate();
}
}
/**设置数据后,计算相关值*/
private void evaluatorByData(){
if(dataList.size()<=0)
return;
dataNumCount = dataList.size();
LogUtil.w(TAG, "总共"+dataNumCount+"条数据");
/**①、计算字体相关以及图表原点坐标*/
paintLabel.setTextSize(textSize);
lableHeight = FontUtil.getFontHeight(paintLabel);
lableLead = FontUtil.getFontLeading(paintLabel);
//图表主体矩形
rectChart = new RectF(getPaddingLeft(),getPaddingTop() + lableHeight + textSpaceY,
getMeasuredWidth()-getPaddingRight(),
getMeasuredHeight()-getPaddingBottom() - lableHeight - textSpaceX);
/**②、计算X标签绘制坐标*/
float lableXSpace = 0;
for(String lableX : lableXList){
lableXSpace += FontUtil.getFontlength(paintLabel, lableX);
}
lableXSpace = (rectChart.right - rectChart.left - lableXSpace)/(lableXList.length -1);
lableXPointList = new ArrayList<>();
if(lableXSpace>0){
float left = rectChart.left;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
left,rectChart.bottom + textSpaceX + lableLead)));
left += (FontUtil.getFontlength(paintLabel, lableXList[i])+lableXSpace);
}
}else{
//如果X轴标签字体过长的情况需要特殊处理
float oneWidth = (rectChart.right - rectChart.left)/lableXList.length;
for(int i = 0; i<lableXList.length; i++){
String lableX = lableXList[i];
float xLen = FontUtil.getFontlength(paintLabel, lableX);
lableXPointList.add(new DataPoint(lableX, 0, new PointF(
rectChart.left+i*oneWidth+(oneWidth-xLen)/2,rectChart.bottom + textSpaceX + lableLead)));
}
}
/**③、计算Y刻度最大值和最小值以及幅度*/
int lineNum = LINE_NUM==0?(dataList.get(0).size()-1):LINE_NUM;
YMARK_MAX = Float.MIN_VALUE; //Y轴刻度最大值
YMARK_MIN = Float.MAX_VALUE; //Y轴刻度最小值
for(List<String> list : dataList){
for(int i = 1; i< lineNum+1 ; i++){
String str = list.get(i);
try {
if(str.contains("%")){
str = str.substring(0,str.indexOf("%"));//百分数不能直接强转
YMARK = Float.parseFloat(str) /100.0f;
}else{
YMARK = Float.parseFloat(str);
}
// LogUtil.d(TAG, str+" Y轴 被识别为Float = "+YMARK);
if(YMARK>YMARK_MAX)
YMARK_MAX = YMARK;
if(YMARK<YMARK_MIN)
YMARK_MIN = YMARK;
}catch (Exception e){
}
}
}
LogUtil.w(TAG, "Y轴真实YMARK_MIN="+YMARK_MIN+" YMARK_MAX="+YMARK_MAX);
if(YMARK_MAX>0)
YMARK_MAX *= 1.1f;
else
YMARK_MAX /= 1.1f;
if(YMARK_MIN>0)
YMARK_MIN /= 1.1f;
else
YMARK_MIN *= 1.1f;
if(YMARK_MIN>0)
YMARK_MIN = 0;
YMARK = (YMARK_MAX-YMARK_MIN)/(YMARK_NUM - 1);
if(yMarkType == YMARK_TYPE.INTEGER){
YMARK = (int)YMARK + 1;
YMARK_MIN = (int)YMARK_MIN;
YMARK_MAX = YMARK_MIN+YMARK*(YMARK_NUM-1);
}else if(yMarkType == YMARK_TYPE.PERCENTAGE){
}
LogUtil.i(TAG, "Y轴YMARK_MIN="+YMARK_MIN+" YMARK_MAX="+YMARK_MAX+" YMARK="+YMARK);
/**④、计算点的坐标,如果有动画的情况下,边绘制边计算会耗费性能,所以先计算*/
//创建集合,用于存放每条线上每个点的坐标数据
List<String> group = dataList.get(0);
linePointList = new ArrayList<>();
for(int i = 0; i<group.size()-1; i++)
linePointList.add(new ArrayList<DataPoint>());
float oneSpace = (rectChart.right - rectChart.left) / (dataNumCount-1);
List<DataPoint> lastDataGroup = null; //最后一组数据
for(int i = 0; i < dataList.size(); i++){
//一组数据,包含每条线的一个item数据
group = dataList.get(i);
for(int j = 1; j < group.size(); j++){
try {
float valueY;
if(group.get(j).contains("%"))
valueY = Float.parseFloat(group.get(j).substring(0,group.get(j).indexOf("%"))) /100.0f;
else
valueY = Float.parseFloat(group.get(j));
PointF point = new PointF();
//只有需要绘制的线才计算坐标
if(j<lineNum+1){
point.x = rectChart.left + i * oneSpace;
//根据最高价和最低价,计算当前数据在图表上Y轴的坐标
point.y = rectChart.bottom -
(rectChart.bottom-rectChart.top)/(YMARK_MAX - YMARK_MIN) * (valueY-YMARK_MIN);
}
linePointList.get(j-1).add(new DataPoint(group.get(0), valueY, point));
// LogUtil.w(TAG, "绘制"+j+"曲线"+(int)point.x+", "+(int)point.y);
/**默认最后一组数据为显示的lable数据*/
if(null!=onFocusChangeListener && i==dataList.size()-1){
if(null==focusInfo) {
focusInfo = new FocusInfo();
lastDataGroup = new ArrayList<>();
focusInfo.setPoint(point);
focusInfo.setFocusData(lastDataGroup);
}
lastDataGroup.add(new DataPoint(group.get(0), valueY, point));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusInfo);
}
private FocusInfo focusInfo;
@Override
protected void onTouchMoved(PointF point) {
if(null==dataList)
return;
onFocus = (null != point);
if(null != point && null!=dataList && dataList.size()>0) {
//获取焦点对应的数据的索引
int index = (int) ((point.x - rectChart.left) * dataNumCount / (rectChart.right - rectChart.left));
//避免滑出
List<DataPoint> dataPoints = linePointList.get(0);
if(point.x > dataPoints.get(dataPoints.size()-1).getPoint().x)
point.x = dataPoints.get(dataPoints.size()-1).getPoint().x;
if(point.x < dataPoints.get(0).getPoint().x)
point.x = dataPoints.get(0).getPoint().x;
index = Math.max(0, Math.min(index, dataPoints.size() - 1));
focusInfo = new FocusInfo();
focusInfo.setPoint(point);
List<DataPoint> objs = new ArrayList<>();
for(List<DataPoint> points : linePointList){
objs.add(points.get(index));
}
focusInfo.setFocusData(objs);
if(null!=onFocusChangeListener)
onFocusChangeListener.onfocus(focusInfo);
}
invalidate();
}
private OnFocusChangeListener onFocusChangeListener;
public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
this.onFocusChangeListener = onFocusChangeListener;
}
public interface OnFocusChangeListener{
public void onfocus(FocusInfo focusInfo);
}
/**绘制图表基本框架*/
@Override
public void drawDefult(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
drawGrid(canvas);
drawXLable(canvas);
drawYLable(canvas);
}
/**绘制debug辅助线*/
@Override
public void drawDebug(Canvas canvas) {
super.drawDebug(canvas);
}
/**绘制图表*/
@Override
public void drawChart(Canvas canvas) {
if(null==linePointList || linePointList.size()<=0)
return;
drawDataPath(canvas);
drawFocus(canvas);
}
/**绘制X轴方向辅助网格*/
private void drawGrid(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(YMARK_NUM-1);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(defColor);
paintEffect.setStyle(Paint.Style.STROKE);
paintEffect.setStrokeWidth(lineWidth);
paintEffect.setColor(defColor);
canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
canvas.drawLine(rectChart.right, rectChart.top, rectChart.right, rectChart.bottom, paint);
for (int i = 0; i < YMARK_NUM; i++) {
if(i==0||i ==YMARK_NUM-1) { //实线
canvas.drawLine(rectChart.left, rectChart.bottom-yMarkSpace*i,
rectChart.right, rectChart.bottom-yMarkSpace*i, paint);
} else { //虚线
Path path = new Path();
path.moveTo(rectChart.left, rectChart.bottom-yMarkSpace*i);
path.lineTo(rectChart.right,rectChart.bottom-yMarkSpace*i);
PathEffect effects = new DashPathEffect(new float[]{15,8,15,8},0);
paintEffect.setPathEffect(effects);
canvas.drawPath(path, paintEffect);
}
}
}
/**绘制X轴刻度*/
private void drawXLable(Canvas canvas){
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for(DataPoint lable : lableXPointList){
canvas.drawText(lable.getValueX(), lable.getPoint().x, lable.getPoint().y, paintLabel);
}
}
/**绘制Y轴刻度*/
private void drawYLable(Canvas canvas){
float yMarkSpace = (rectChart.bottom - rectChart.top)/(YMARK_NUM-1);
paintLabel.setTextSize(textSize);
paintLabel.setColor(textColor);
for (int i = 0; i < YMARK_NUM; i++) {
if(yMarkType == YMARK_TYPE.INTEGER) {
canvas.drawText((int) (YMARK_MIN + i * YMARK) + "", rectChart.left,
rectChart.bottom - yMarkSpace * i - lableHeight - textSpaceY + lableLead, paintLabel);
}else if(yMarkType == YMARK_TYPE.PERCENTAGE){
canvas.drawText(NumberFormatUtil.formattedDecimalToPercentage((YMARK_MIN + i * YMARK), 2), rectChart.left,
rectChart.bottom - yMarkSpace * i - lableHeight - textSpaceY + lableLead, paintLabel);
}
}
}
/**绘制曲线*/
private void drawDataPath(Canvas canvas) {
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(lineSize);
int lineNum = LINE_NUM==0?(dataList.get(0).size()-1):LINE_NUM;
for(int j = 0; j < lineNum; j++){
// for(int j = 0; j < linePointList.size(); j++){
List<DataPoint> lineList = linePointList.get(j);
paint.setColor(lineColor[j]);
//一条一条的绘制
Path path = new Path();
PointF lastPoint = null;
for(int i = 0 ; i<lineList.size(); i++){
DataPoint dataPoint = lineList.get(i);
if(i == 0){
if(animType == AnimType.LEFT_TO_RIGHT){
path.moveTo(rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro, dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.moveTo(dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else{
path.moveTo(dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}else{
//quadTo:二阶贝塞尔曲线连接前后两点,这样使得曲线更加平滑
if(animType == AnimType.LEFT_TO_RIGHT){
path.quadTo(rectChart.left+(lastPoint.x-rectChart.left)*animPro, lastPoint.y,
rectChart.left+(dataPoint.getPoint().x-rectChart.left)*animPro , dataPoint.getPoint().y);
}else if(animType == AnimType.BOTTOM_TO_TOP){
path.quadTo(lastPoint.x, rectChart.bottom-(rectChart.bottom-lastPoint.y)* animPro,
dataPoint.getPoint().x, rectChart.bottom-(rectChart.bottom- dataPoint.getPoint().y)* animPro);
}else if(animType == AnimType.SLOW_DRAW){
if(i>lineList.size()*animPro)
break;
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}else{
path.quadTo(lastPoint.x, lastPoint.y, dataPoint.getPoint().x, dataPoint.getPoint().y);
}
}
lastPoint = dataPoint.getPoint();
}
canvas.drawPath(path, paint);
}
}
/**绘制焦点*/
private void drawFocus(Canvas canvas){
if(!onFocus || null==focusInfo)
return;
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(focusLineSize);
paint.setColor(focusLineColor);
//竖直线
canvas.drawLine(focusInfo.getPoint().x, rectChart.bottom, focusInfo.getPoint().x, rectChart.top, paint);
/*
paintLabel.setTextSize(focusTextSize);
paintLabel.setColor(focusTextColor);
float focusTextLead = FontUtil.getFontLeading(paintLabel);
float focusTextHeight = FontUtil.getFontHeight(paintLabel);
try {
for (Object obj : focusInfo.getObjs()) {
DataPoint dataPoint = (DataPoint)obj;
//水平参考线
canvas.drawLine(rectChart.left, dataPoint.getPoint().y, rectChart.right, dataPoint.getPoint().y, paint);
//中心点
canvas.drawCircle(dataPoint.getPoint().x, dataPoint.getPoint().y, 2.0f, paint);
//绘制刻度值
String str = "("+ dataPoint.getValueX()+",";
if(yMarkType == YMARK_TYPE.INTEGER) {
str += dataPoint.getValueY()+")";
}else if(yMarkType == YMARK_TYPE.PERCENTAGE){
str += formattedDecimalToPercentage(dataPoint.getValueY())+")";
}
float textLength = FontUtil.getFontlength(paintLabel,str);
if((rectChart.right- dataPoint.getPoint().x)>textLength){
canvas.drawText(str, dataPoint.getPoint().x + 10,
dataPoint.getPoint().y-focusTextHeight+focusTextLead - 10, paintLabel);
}else{
canvas.drawText(str, dataPoint.getPoint().x - 10 - textLength,
dataPoint.getPoint().y-focusTextHeight+focusTextLead - 10, paintLabel);
}
}
}catch (Exception e){
e.printStackTrace();
}
*/
}
private float animPro; //动画计算的占比数量
/**创建动画*/
@Override
protected ValueAnimator initAnim() {
if(dataList.size()>0) {
ValueAnimator anim = ValueAnimator.ofObject(new AngleEvaluator(), 0f, 1f);
anim.setInterpolator(new AccelerateDecelerateInterpolator());
return anim;
}
return null;
}
/**动画值变化之后计算数据*/
@Override
protected void evaluatorData(ValueAnimator animation) {
animPro = (float)animation.getAnimatedValue();
}
}
@@ -0,0 +1,40 @@
package com.openxu.cview.xmstock20201030.bean;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : CalandarList
* version : 1.0
* description : 日历数据
*/
public class CalandarList {
private int code;
private String err_info;
private CalendarListData data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public CalendarListData getData() {
return data;
}
public void setData(CalendarListData data) {
this.data = data;
}
public String getErr_info() {
return err_info;
}
public void setErr_info(String err_info) {
this.err_info = err_info;
}
}
@@ -0,0 +1,49 @@
package com.openxu.cview.xmstock20201030.bean;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : HotDetailData
* version : 1.0
* description : 日历数据
*/
public class CalendarData {
private String date;
private String week;
private List<CalendarDataStock> stock_list;
@Override
public String toString() {
return "CalendarData{" +
"date='" + date + '\'' +
", week='" + week + '\'' +
'}';
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getWeek() {
return week;
}
public void setWeek(String week) {
this.week = week;
}
public List<CalendarDataStock> getStock_list() {
return stock_list;
}
public void setStock_list(List<CalendarDataStock> stock_list) {
this.stock_list = stock_list;
}
}
@@ -0,0 +1,134 @@
package com.openxu.cview.xmstock20201030.bean;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : HotDetailData
* version : 1.0
* description : 日历数据
*/
public class CalendarDataStock implements Parcelable {
private String name;
private String code;
private String symbol;
private int flag;
private String chg;
private String count;
private String max_real_chg;
private List<List<String>> trend_line;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public int getFlag() {
return flag;
}
public void setFlag(int flag) {
this.flag = flag;
}
public String getChg() {
return chg;
}
public void setChg(String chg) {
this.chg = chg;
}
public String getCount() {
return count;
}
public void setCount(String count) {
this.count = count;
}
public String getMax_real_chg() {
return max_real_chg;
}
public void setMax_real_chg(String max_real_chg) {
this.max_real_chg = max_real_chg;
}
public List<List<String>> getTrend_line() {
return trend_line;
}
public void setTrend_line(List<List<String>> trend_line) {
this.trend_line = trend_line;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeString(this.code);
dest.writeString(this.symbol);
dest.writeInt(this.flag);
dest.writeString(this.chg);
dest.writeString(this.count);
dest.writeString(this.max_real_chg);
dest.writeList(this.trend_line);
}
public CalendarDataStock() {
}
protected CalendarDataStock(Parcel in) {
this.name = in.readString();
this.code = in.readString();
this.symbol = in.readString();
this.flag = in.readInt();
this.chg = in.readString();
this.count = in.readString();
this.max_real_chg = in.readString();
this.trend_line = new ArrayList<List<String>>();
in.readList(this.trend_line, List.class.getClassLoader());
}
public static final Parcelable.Creator<CalendarDataStock> CREATOR = new Parcelable.Creator<CalendarDataStock>() {
@Override
public CalendarDataStock createFromParcel(Parcel source) {
return new CalendarDataStock(source);
}
@Override
public CalendarDataStock[] newArray(int size) {
return new CalendarDataStock[size];
}
};
}
@@ -0,0 +1,97 @@
package com.openxu.cview.xmstock20201030.bean;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : HotDetailData
* version : 1.0
* description : 日历数据
*/
public class CalendarDetail {
private String id;
private String date;
private String week;
private String title;
private String importance;
private List<String> relative_sectors;
private String start_date;
private String end_date;
private List<CalendarDataStock> stock_list;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getWeek() {
return week;
}
public void setWeek(String week) {
this.week = week;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImportance() {
return importance;
}
public void setImportance(String importance) {
this.importance = importance;
}
public List<String> getRelative_sectors() {
return relative_sectors;
}
public void setRelative_sectors(List<String> relative_sectors) {
this.relative_sectors = relative_sectors;
}
public String getStart_date() {
return start_date;
}
public void setStart_date(String start_date) {
this.start_date = start_date;
}
public String getEnd_date() {
return end_date;
}
public void setEnd_date(String end_date) {
this.end_date = end_date;
}
public List<CalendarDataStock> getStock_list() {
return stock_list;
}
public void setStock_list(List<CalendarDataStock> stock_list) {
this.stock_list = stock_list;
}
}
@@ -0,0 +1,32 @@
package com.openxu.cview.xmstock20201030.bean;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : HotDetailData
* version : 1.0
* description : 日历数据
*/
public class CalendarListData {
private List<CalendarData> calendar;
private List<CalendarDetail> calendar_detail;
public List<CalendarData> getCalendar() {
return calendar;
}
public void setCalendar(List<CalendarData> calendar) {
this.calendar = calendar;
}
public List<CalendarDetail> getCalendar_detail() {
return calendar_detail;
}
public void setCalendar_detail(List<CalendarDetail> calendar_detail) {
this.calendar_detail = calendar_detail;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
package com.openxu.cview.xmstock20201030.bean;
/**
* Author: openXu
* Time: 2020/11/4 12:04
* class: DayFinish
* Description:
*/
public class DayFinish {
public int day;
public int all;
public int finish;
public DayFinish(int day, int finish, int all) {
this.day = day;
this.all = all;
this.finish = finish;
}
}
@@ -0,0 +1,77 @@
package com.openxu.cview.xmstock20201030.bean;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : TopDetailStock
* version : 1.0
* description : 券商热点与走势对比
*/
public class DetailStock {
private String name;
private String code;
private String symbol;
private String remark;
private String trade;
private String updownrate;
private String circ_mv;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getTrade() {
return trade;
}
public void setTrade(String trade) {
this.trade = trade;
}
public String getUpdownrate() {
return updownrate;
}
public void setUpdownrate(String updownrate) {
this.updownrate = updownrate;
}
public String getCirc_mv() {
return circ_mv;
}
public void setCirc_mv(String circ_mv) {
this.circ_mv = circ_mv;
}
}
@@ -0,0 +1,40 @@
package com.openxu.cview.xmstock20201030.bean;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : HotDetail
* version : 1.0
* description : 概念走势
*/
public class HotDetail {
private int code;
private String err_info;
private HotDetailData data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public HotDetailData getData() {
return data;
}
public void setData(HotDetailData data) {
this.data = data;
}
public String getErr_info() {
return err_info;
}
public void setErr_info(String err_info) {
this.err_info = err_info;
}
}
@@ -0,0 +1,97 @@
package com.openxu.cview.xmstock20201030.bean;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : HotDetailData
* version : 1.0
* description : 概念走势
*/
public class HotDetailData {
private String concept_id; //529683333575
private String concept_name;
private String intro;
private String pub_time;
private List<HotDetailNew> news;
//实时行情走势图,每个元素字段分别是:时间、现价、均价 、跌涨浮["0930",1639.83,1625.58,"0.10"]
private List<List<Object>> real_trend_line;
private HotDetailHotInfo hot_info; //实时行情数据
//概念走势图, 每个元素表示:时间、概念、热度 [20200803,"1582.08","30.00"],
private List<List<Object>> trend_line;
private List<DetailStock> stock_list;
public String getConcept_id() {
return concept_id;
}
public void setConcept_id(String concept_id) {
this.concept_id = concept_id;
}
public String getConcept_name() {
return concept_name;
}
public void setConcept_name(String concept_name) {
this.concept_name = concept_name;
}
public String getIntro() {
return intro;
}
public void setIntro(String intro) {
this.intro = intro;
}
public String getPub_time() {
return pub_time;
}
public void setPub_time(String pub_time) {
this.pub_time = pub_time;
}
public List<HotDetailNew> getNews() {
return news;
}
public void setNews(List<HotDetailNew> news) {
this.news = news;
}
public List<List<Object>> getReal_trend_line() {
return real_trend_line;
}
public void setReal_trend_line(List<List<Object>> real_trend_line) {
this.real_trend_line = real_trend_line;
}
public HotDetailHotInfo getHot_info() {
return hot_info;
}
public void setHot_info(HotDetailHotInfo hot_info) {
this.hot_info = hot_info;
}
public List<List<Object>> getTrend_line() {
return trend_line;
}
public void setTrend_line(List<List<Object>> trend_line) {
this.trend_line = trend_line;
}
public List<DetailStock> getStock_list() {
return stock_list;
}
public void setStock_list(List<DetailStock> stock_list) {
this.stock_list = stock_list;
}
}
@@ -0,0 +1,102 @@
package com.openxu.cview.xmstock20201030.bean;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : TopDetailNew
* version : 1.0
* description : 概念走势
*/
public class HotDetailHotInfo {
private String uptime;
private String concept_name;
private String concept_summary;
private float trade;
private float updownvalue;
private float updownrate;
private float up_count;
private float zhangting_count;
private float down_count;
private float dieting_count;
public String getUptime() {
return uptime;
}
public void setUptime(String uptime) {
this.uptime = uptime;
}
public String getConcept_name() {
return concept_name;
}
public void setConcept_name(String concept_name) {
this.concept_name = concept_name;
}
public String getConcept_summary() {
return concept_summary;
}
public void setConcept_summary(String concept_summary) {
this.concept_summary = concept_summary;
}
public float getTrade() {
return trade;
}
public void setTrade(float trade) {
this.trade = trade;
}
public float getUpdownvalue() {
return updownvalue;
}
public void setUpdownvalue(float updownvalue) {
this.updownvalue = updownvalue;
}
public float getUpdownrate() {
return updownrate;
}
public void setUpdownrate(float updownrate) {
this.updownrate = updownrate;
}
public float getUp_count() {
return up_count;
}
public void setUp_count(float up_count) {
this.up_count = up_count;
}
public float getZhangting_count() {
return zhangting_count;
}
public void setZhangting_count(float zhangting_count) {
this.zhangting_count = zhangting_count;
}
public float getDown_count() {
return down_count;
}
public void setDown_count(float down_count) {
this.down_count = down_count;
}
public float getDieting_count() {
return dieting_count;
}
public void setDieting_count(float dieting_count) {
this.dieting_count = dieting_count;
}
}
@@ -0,0 +1,39 @@
package com.openxu.cview.xmstock20201030.bean;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : TopDetailNew
* version : 1.0
* description : 概念走势
*/
public class HotDetailNew {
private String id;
private String title;
private String ctime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCtime() {
return ctime;
}
public void setCtime(String ctime) {
this.ctime = ctime;
}
}
@@ -0,0 +1,40 @@
package com.openxu.cview.xmstock20201030.bean;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : TopDetail
* version : 1.0
* description : 券商热点与走势对比
*/
public class TopDetail {
private int code;
private String err_info;
private TopDetailData data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public TopDetailData getData() {
return data;
}
public void setData(TopDetailData data) {
this.data = data;
}
public String getErr_info() {
return err_info;
}
public void setErr_info(String err_info) {
this.err_info = err_info;
}
}
@@ -0,0 +1,86 @@
package com.openxu.cview.xmstock20201030.bean;
import java.util.List;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : TopDetailData
* version : 1.0
* description : 券商热点与走势对比
*/
public class TopDetailData {
private String concept_id;
private String concept_name;
private String title;
private String content;
private String opentime;
private List<DetailStock> stock_list;
private List<List<Object>> trend_line;
private List<TopDetailNew> news;
public String getConcept_id() {
return concept_id;
}
public void setConcept_id(String concept_id) {
this.concept_id = concept_id;
}
public String getConcept_name() {
return concept_name;
}
public void setConcept_name(String concept_name) {
this.concept_name = concept_name;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getOpentime() {
return opentime;
}
public void setOpentime(String opentime) {
this.opentime = opentime;
}
public List<DetailStock> getStock_list() {
return stock_list;
}
public void setStock_list(List<DetailStock> stock_list) {
this.stock_list = stock_list;
}
public List<List<Object>> getTrend_line() {
return trend_line;
}
public void setTrend_line(List<List<Object>> trend_line) {
this.trend_line = trend_line;
}
public List<TopDetailNew> getNews() {
return news;
}
public void setNews(List<TopDetailNew> news) {
this.news = news;
}
}
@@ -0,0 +1,30 @@
package com.openxu.cview.xmstock20201030.bean;
/**
* autour : xiami
* date : 2020/11/13 14:59
* className : TopDetailNew
* version : 1.0
* description : 券商热点与走势对比
*/
public class TopDetailNew {
private String title;
private String publishdate;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getPublishdate() {
return publishdate;
}
public void setPublishdate(String publishdate) {
this.publishdate = publishdate;
}
}
@@ -0,0 +1,351 @@
package com.openxu.hkchart
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.Scroller
import com.openxu.cview.chart.anim.AngleEvaluator
import com.openxu.hkchart.config.ChartConfigBase
import com.openxu.hkchart.config.MultipartBarConfig
import com.openxu.hkchart.config.MultipartBarData
import com.openxu.hkchart.data.FocusData
import com.openxu.hkchart.loading.BallPulseIndicator
import com.openxu.hkchart.loading.LoadingIndicator
import com.openxu.utils.DensityUtil
import com.openxu.utils.LogUtil
import com.openxu.utils.SharedData
import kotlin.math.abs
/**
* Author: openXu
* Time: 2021/5/11 11:01
* class: BaseChart
* Description:
*/
open abstract class BaseChart<T> : View, View.OnTouchListener {
protected var TAG = javaClass.simpleName
protected var debug = SharedData.getInstance().sp.getBoolean(SharedData.KEY_DEBUG, false)
//画笔
protected var paint: Paint
protected var paintText: Paint
protected var paintEffect: Paint
//屏幕宽高
protected var screenWidth = 0
protected var screenHeight = 0
protected lateinit var rectDrawBounds : RectF //图表绘制矩形区域(每次刷新数据固定不变)
protected lateinit var rectChart : RectF //图表主体绘制矩形(刷新数据时会重新计算)
protected lateinit var centerPoint : Point //chart中心点坐标
//坐标轴辅助线宽度
protected var axisLineWidth = DensityUtil.dip2px(context, 0.8f)
/**设置是否正在加载*/
var loading = true
set(value){
field = value
if (value) {
loadingIndicator.start()
} else {
loadingIndicator.stop()
postInvalidate()
}
}
//动画
private lateinit var chartAnim: ValueAnimator
protected var chartAnimValue = 1f //动画值
protected var chartAnimStarted = false
private lateinit var loadingIndicator: LoadingIndicator
constructor(context: Context) :this(context, null)
constructor(context: Context, attrs: AttributeSet?) :this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int):super(context, attrs, defStyle){
mGestureDetector = GestureDetector(getContext(), MyOnGestureListener())
mScaleGestureDetector = ScaleGestureDetector(context, MyOnScaleGestureListener())
mScroller = Scroller(context)
setOnTouchListener(this)
val dm = resources.displayMetrics
screenHeight = dm.heightPixels
screenWidth = dm.widthPixels
paint = Paint()
paintText = Paint()
paintEffect = Paint()
paint.isAntiAlias = true
paintText.isAntiAlias = true
paintEffect.isAntiAlias = true
//加载动画
setLoadingIndicator("BallPulseIndicator")
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
centerPoint = Point(measuredWidth / 2, measuredHeight / 2)
rectDrawBounds = RectF(paddingLeft.toFloat(), paddingTop.toFloat(), (measuredWidth - paddingRight).toFloat(),
(measuredHeight - paddingBottom).toFloat())
rectChart = RectF(rectDrawBounds.left, rectDrawBounds.top, rectDrawBounds.right,rectDrawBounds.bottom)
loadingIndicator.setBounds(rectDrawBounds.left.toInt(), rectDrawBounds.top.toInt(),
rectDrawBounds.right.toInt(), rectDrawBounds.bottom.toInt())
initial()
}
override fun onDraw(canvas: Canvas) {
// Log.e(TAG, "=================绘制图表");
if (debug) drawDebug(canvas)
if (loading) {
val saveCount = canvas.save()
// canvas.translate(getPaddingLeft(), getPaddingTop());
loadingIndicator!!.draw(canvas)
canvas.restoreToCount(saveCount)
return
}
if(chartConfig==null){
drawChart(canvas)
}else{
if (chartConfig!!.showAnim && !chartAnimStarted) {
chartAnimStarted = true
startChartAnimation()
} else {
drawChart(canvas)
}
}
}
private fun drawDebug(canvas: Canvas) {
//绘制debug辅助线
paint.style = Paint.Style.STROKE //设置空心
paint.strokeWidth = axisLineWidth.toFloat()
//绘制边界--chart区域
paint.color = Color.BLACK
var r = RectF(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat())
canvas.drawRect(r, paint)
paint.color = Color.RED
r = RectF(paddingLeft.toFloat(), paddingTop.toFloat(), (measuredWidth - paddingRight).toFloat(), (measuredHeight - paddingBottom).toFloat())
canvas.drawRect(r, paint)
paint.color = Color.GREEN
canvas.drawRect(rectDrawBounds, paint)
}
/***************************1. API👇👇👇***************************/
var chartConfig : ChartConfigBase? = null
/***************************1. API👆👆👆***************************/
/***************************2. 子类重写👇👇👇***************************/
/**初步计算,当设置数据 & size发生变化时调用*/
open fun initial() :Boolean{
if(!this::rectDrawBounds.isInitialized)
return true
return false
}
/**绘制图表 */
open abstract fun drawChart(canvas: Canvas?)
/**焦点坐标*/
open abstract fun onFocusTouch(point: PointF?)
/**缩放开始*/
open abstract fun onScaleBegin(detector: ScaleGestureDetector)
/**缩放*/
open abstract fun onScale(detector: ScaleGestureDetector, beginScrollx: Float)
/**缩放结束*/
open fun onScaleEnd(detector: ScaleGestureDetector){}
/***************************2. 子类重写👆👆👆***************************/
/***************************3. 事件👇👇👇***************************/
private var mGestureDetector : GestureDetector //手势监听,处理滑动
private var mScroller : Scroller //滚动帮助类,处理快速滚动(惯性)
private var mScaleGestureDetector : ScaleGestureDetector //缩放手势监听,处理缩放
/*计算*/
protected var scrollXMax = 0f //最大滚动距离,是一个负值
protected var scrollx = 0f //当前滚动距离,范围:scrollXMax ~ 0 (scrollXMax会自动计算)。从第一条数据绘制(scrollx==0),如果从最后一条数据绘制(scrollx==scrollXMax)
protected var scalex = 1f //x方向缩放系数(范围默认1~2,可根据需求在MyOnScaleGestureListener类中修改)
protected var focusData: FocusData<T>? = null //焦点数据
protected var focusIndex = 0 //焦点落在数据集合的索引值
/**
* 重写dispatchTouchEvent,并调用requestDisallowInterceptTouchEvent申请父控件不要拦截事件,将事件处理权交给图表
*
* 这对图表来说是非常重要的,比如图表放在ScrollerView里面时,如果不调用requestDisallowInterceptTouchEvent(true),
* 图表接受的事件将由ScrollerView决定,一旦ScrollerView发现竖直滚动则会拦截事件,导致图表不能再接受到事件
*
* 此处首先申请父控件不要拦截事件,所有事件都将传到图表中,由图表决定自己是否处理事件,如果不需要处理(竖直方向滑动距离大于水平方向)则让父控件处理
* 需要注意的是一旦放弃处理,剩下的事件将不会被收到
*/
private var mDownX = 0f //dispatchTouchEvent方法中手指按下时的坐标,用于决定什么时候拦截事件
private var mDownY = 0f
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mDownX = event.x
mDownY = event.y
parent.requestDisallowInterceptTouchEvent(true) //ACTION_DOWN的时候,赶紧把事件hold住
}
//不处于缩放状态,竖直滑动的距离大于阈值,将事件还给父控件
MotionEvent.ACTION_MOVE -> if (!scaleing && abs(event.y - mDownY) > abs(event.x - mDownX) * 1.5) {
parent.requestDisallowInterceptTouchEvent(false)
}
}
return super.dispatchTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return super.onTouchEvent(event)
}
protected val focusPoint = PointF() //焦点坐标
override fun onTouch(v: View?, event: MotionEvent): Boolean {
mScaleGestureDetector.onTouchEvent(event)
mGestureDetector.onTouchEvent(event)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
focusPoint.x = event.x
focusPoint.y = event.y
onFocusTouch(focusPoint)
}
MotionEvent.ACTION_MOVE -> {
focusPoint.x = event.x
focusPoint.y = event.y
onFocusTouch(focusPoint)
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> onFocusTouch(null)
}
postInvalidate()
return true
}
inner class MyOnGestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
if (!mScroller.isFinished) mScroller.forceFinished(true)
return true //事件被消费,下次才能继续收到事件
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
if (!scaleing) {
scrollx -= distanceX //distanceX左正右负
scrollx = Math.min(scrollx, 0f)
scrollx = Math.max(scrollXMax, scrollx)
LogUtil.i(TAG, "---------滚动:$distanceX $scrollx")
postInvalidate()
}
return false
}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
// LogUtil.e(TAG,"onFling------------>velocityX="+velocityX+" velocityY="+velocityY);
/**
* 从当前位置scrollx开始滚动,
* 最小值为scrollXMax -- 滚动到最后
* 最大值为0 -- 滚动到开始
*/
mScroller.fling(scrollx.toInt(), 0, velocityX.toInt(), 0,
scrollXMax.toInt(), 0, 0, 0
)
return false
}
}
override fun computeScroll() {
super.computeScroll()
if (mScroller.isFinished) return
if (mScroller.computeScrollOffset()) {
// Log.d(TAG, "滚动后计算:"+mScroller.getCurrX());
scrollx = mScroller.currX.toFloat()
invalidate()
}
}
/**缩放监听*/
private var scaleing = false //是否正在缩放
private inner class MyOnScaleGestureListener : ScaleGestureDetector.OnScaleGestureListener {
private var beginScrollx = 0f
override fun onScale(detector: ScaleGestureDetector): Boolean {
this@BaseChart.onScale(detector, beginScrollx)
postInvalidate()
return true
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
LogUtil.i(TAG, ">>>>>>>>>>>>>>>>>>>缩放开始了")
beginScrollx = scrollx
scaleing = true
this@BaseChart.onScaleBegin(detector)
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) {
LogUtil.i(TAG, "<<<<<<<<<<<<<<<<<<<<缩放结束了")
scaleing = false
this@BaseChart.onScaleEnd(detector)
}
}
/***************************3. 事件👆👆👆***************************/
/***************************4. 动画👇👇👇***************************/
private fun setLoadingIndicator(indicatorName: String) {
var indicatorName = indicatorName
if (TextUtils.isEmpty(indicatorName)) return
loadingIndicator = BallPulseIndicator(context)
indicatorName = "com.openxu.hkchart.loading.$indicatorName"
try {
loadingIndicator = Class.forName(indicatorName).getConstructor(Context::class.java).newInstance(context) as LoadingIndicator
loadingIndicator.callback = this
} catch (e: Exception) {
Log.e(TAG, "Didn't find your class , check the name again !")
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if(loading && this::loadingIndicator.isInitialized)
loadingIndicator.start()
}
override fun onDetachedFromWindow() {
if(this::loadingIndicator.isInitialized)
loadingIndicator.stop()
if(this::chartAnim.isInitialized)
chartAnim.cancel()
super.onDetachedFromWindow()
}
override fun verifyDrawable(who: Drawable): Boolean {
return who === loadingIndicator || super.verifyDrawable(who)
}
override fun invalidateDrawable(dr: Drawable) {
if (verifyDrawable(dr)) {
val dirty = dr.bounds
invalidate(dirty)
} else {
super.invalidateDrawable(dr)
}
}
private fun startChartAnimation() {
if(!this::chartAnim.isInitialized){
chartAnim = ValueAnimator.ofObject(AngleEvaluator(), 0f, 1f)
chartAnim.duration = 1000
chartAnim.interpolator = AccelerateDecelerateInterpolator()
chartAnim.addUpdateListener { animation ->
chartAnimValue = animation.animatedValue as Float
postInvalidate()
}
}
chartAnim.reverse()
chartAnim.start()
LogUtil.i(TAG, "-----------------开始动画")
}
/***************************4. 动画👆👆👆***************************/
}
@@ -0,0 +1,251 @@
package com.openxu.hkchart.bar
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.ScaleGestureDetector
import com.openxu.hkchart.BaseChart
import com.openxu.hkchart.config.*
import com.openxu.utils.DensityUtil
import com.openxu.utils.FontUtil
import com.openxu.utils.LogUtil
/**
* Author: openXu
* Time: 2021/5/11 13:47
* class: BarChart
* Description:
*/
class BarChart : BaseChart<Bar> {
constructor(context: Context) :this(context, null)
constructor(context: Context, attrs: AttributeSet?) :this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int):super(context, attrs, defStyle){
}
/**设置 */
private var barData = mutableListOf<Bar>()
/**计算 */
fun setData(barData: List<Bar>?) {
this.barData.clear()
barData?.let {
this.barData.addAll(barData)
}
initial()
chartConfig?.let {
if (it.showAnim) chartAnimStarted = false
}
loading = false
}
private var groupWidth = 0f
private lateinit var yAxisMark : YAxisMark
private lateinit var xAxisMark : XAxisMark
private var barColor = intArrayOf(
Color.parseColor("#f46763"),
Color.parseColor("#3cd595"),
Color.parseColor("#4d7bff")) //柱颜色
private var barWidth = DensityUtil.dip2px(context, 15f).toFloat() //柱宽度
private var barSpace = DensityUtil.dip2px(context, 1f).toFloat() //双柱间的间距
private var groupSpace = DensityUtil.dip2px(context, 25f).toFloat() //一组柱之间的间距(只有scrollAble==true时才生效)
override fun initial(): Boolean {
if(super.initial()) return true
if(barData.isNullOrEmpty()) return true
if(chartConfig==null)
throw RuntimeException("---------请配置图表")
val config = chartConfig as BarChartConfig
if(null==config.xAxisMark)
throw RuntimeException("---------请设置x坐标")
if(null==config.yAxisMark)
throw RuntimeException("---------请设置y坐标")
xAxisMark = config.xAxisMark!!
yAxisMark = config.yAxisMark!!
barColor = config.barColor
barWidth = config.barWidth
barSpace = config.barSpace
groupSpace = config.groupSpace
paintText.textSize = xAxisMark.textSize.toFloat()
xAxisMark.textHeight = FontUtil.getFontHeight(paintText)
xAxisMark.textLead = FontUtil.getFontLeading(paintText)
//确定图表最下放绘制位置
rectChart.bottom = measuredHeight - paddingBottom - xAxisMark.textHeight - xAxisMark.textSpace
xAxisMark.drawPointY = rectChart.bottom + xAxisMark.textSpace + xAxisMark.textLead
calculateYMark()
paintText.textSize = yAxisMark.textSize.toFloat()
yAxisMark.textHeight = FontUtil.getFontHeight(paintText)
yAxisMark.textLead = FontUtil.getFontLeading(paintText)
val maxLable = yAxisMark.getMarkText(yAxisMark.cal_mark_max)
rectChart.left = (paddingLeft + yAxisMark.textSpace + FontUtil.getFontlength(paintText, maxLable))
rectChart.top = rectChart.top + yAxisMark.textHeight / 2
val barNum: Int = barData[0].valuey.size
if (config.scrollAble) {
groupWidth = (config.barWidth * barNum + config.barSpace * (barNum - 1) + config.groupSpace).toFloat()
val allWidth: Float = groupWidth * barData.size //总宽度
scrollXMax = -(allWidth - rectChart.width())
scrollx = if (config.displayScheme==DisplayScheme.SHOW_BEGIN) 0f else scrollXMax
} else {
config.groupSpace = (rectChart.width() - barData.size * (config.barWidth * barNum + config.barSpace * (barNum - 1))) / barData.size
groupWidth = (config.barWidth * barNum + config.barSpace * (barNum - 1) + config.groupSpace).toFloat()
scrollXMax = 0f
scrollx = scrollXMax
}
Log.w(TAG, "计算groupWidth=$groupWidth config.barWidth=$config.barWidth scrollx=$scrollx")
return true
}
override fun drawChart(canvas: Canvas?) {
val yMarkSpace = (rectChart.bottom - rectChart.top) / (yAxisMark.lableNum - 1)
paint.style = Paint.Style.STROKE
paint.strokeWidth = yAxisMark.lineColor.toFloat()
paint.color = yAxisMark.lineColor
paintEffect.style = Paint.Style.STROKE
paintEffect.strokeWidth = yAxisMark.lineWidth.toFloat()
paintEffect.color = yAxisMark.lineColor
paintText.textSize = yAxisMark.textSize.toFloat()
paintText.color = yAxisMark.textColor
// canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
// canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
val effects: PathEffect = DashPathEffect(floatArrayOf(15f, 6f, 15f, 6f), 0f)
paintEffect.pathEffect = effects
for (i in 0 until yAxisMark.lableNum) {
/**绘制横向线 */
canvas!!.drawLine(rectChart.left, rectChart.bottom - yMarkSpace * i,
rectChart.right, rectChart.bottom - yMarkSpace * i, paint)
/**绘制y刻度 */
val text = yAxisMark.getMarkText(yAxisMark.cal_mark_min + i * yAxisMark.cal_mark)
canvas!!.drawText(text,
rectChart.left - yAxisMark.textSpace - FontUtil.getFontlength(paintText, text),
rectChart.bottom - yMarkSpace * i - yAxisMark.textHeight / 2 + yAxisMark.textLead, paintText)
}
/**绘制柱状 */
paint.style = Paint.Style.FILL
val rect = RectF()
val rectArc = RectF()
val path = Path()
//预算需要绘制的组的开始和结尾index,避免不必要的计算浪费性能
val startIndex = (-scrollx / groupWidth).toInt()
var endIndex = ((-scrollx + rectChart.width()) / groupWidth).toInt()
endIndex = Math.min(endIndex, barData.size - 1)
// for(int i = 0; i<barData.size(); i++){
// for(int i = 0; i<barData.size(); i++){
for (i in startIndex..endIndex) {
val group = barData[i]
//一组
/**绘制X刻度 */
paintText.textSize = xAxisMark.textSize.toFloat()
paintText.color = xAxisMark.textColor
rect.left = scrollx + rectChart.left + i * groupWidth
rect.right = rect.left + groupWidth
//过滤掉绘制区域外的组
// if(rect.right < rectChart.left || rect.left > rectChart.right) {
// Log.w(TAG, "第"+i+"组超界了 "+rect.left+" "+rect.right);
// continue;
// }
//裁剪画布,避免x刻度超出
var restoreCount = canvas!!.save()
canvas!!.clipRect(RectF(rectChart.left, rectChart.bottom,
rectChart.right, rectChart.bottom + xAxisMark.textSpace + xAxisMark.textHeight))
canvas!!.drawText(group.valuex,
rect.left + groupWidth / 2 - FontUtil.getFontlength(paintText, group.valuex) / 2,
xAxisMark.drawPointY, paintText)
canvas!!.restoreToCount(restoreCount)
/**绘制柱状 */
// 记录当前画布信息
restoreCount = canvas!!.save()
/**使用Canvas的clipRect和clipPath方法限制View的绘制区域 */
canvas!!.clipRect(rectChart) //裁剪画布,只绘制rectChart的范围
for (j in group.valuey.indices) {
paint.color = barColor[j]
// float top = (zeroPoint.y - YMARK_ALL_H * (bean.getNum() / YMARK_MAX) * animPro);
rect.left = rectChart.left + i * groupWidth + groupSpace / 2 + j * (barSpace + barWidth) + scrollx
rect.right = rect.left + barWidth
//过滤掉绘制区域外的柱
if (rect.right < rectChart.left || rect.left > rectChart.right) continue
if (group.valuey[j] == null) {
rect.top = rectChart.bottom
} else {
rect.top = (rectChart.bottom - rectChart.height() / (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) *
(group.valuey[j] - yAxisMark.cal_mark_min) * chartAnimValue)
}
rect.bottom = rectChart.bottom
rectArc.left = rect.left
rectArc.top = rect.top
rectArc.right = rect.right
rectArc.bottom = rect.top + barWidth
path.reset()
path.moveTo(rect.left, rectChart.bottom)
path.lineTo(rectArc.left, rectArc.bottom - rectArc.height() / 2)
path.arcTo(rectArc, 180f, 180f)
path.lineTo(rect.right, rect.bottom)
path.close()
// Log.w(TAG, "---绘制"+i+"*"+j+" = "+group.get(j).getValuey()+" " +rect.top +" "+rectChart.bottom);
canvas!!.drawPath(path, paint)
/**绘制y值 */
if (group.valuey[j] != null) {
canvas!!.drawText(yAxisMark.getMarkText(group.valuey[j]),
rectArc.left + barWidth / 2 - FontUtil.getFontlength(paintText, yAxisMark.getMarkText(group.valuey[j])) / 2,
rectArc.top - yAxisMark.textSpace - yAxisMark.textHeight + yAxisMark.textLead, paintText)
}
}
//恢复到裁切之前的画布
canvas!!.restoreToCount(restoreCount)
}
}
override fun onFocusTouch(point: PointF?) {
}
override fun onScaleBegin(detector: ScaleGestureDetector) {
}
override fun onScale(detector: ScaleGestureDetector, beginScrollx: Float) {
}
private fun calculateYMark() {
val redundance = 1.01f //y轴最大和最小值冗余
yAxisMark.cal_mark_max = -Float.MIN_VALUE //Y轴刻度最大值
yAxisMark.cal_mark_min = Float.MAX_VALUE //Y轴刻度最小值
for (data in barData) {
for (valuey in data.valuey) {
yAxisMark.cal_mark_max = Math.max(yAxisMark.cal_mark_max, valuey)
yAxisMark.cal_mark_min = Math.min(yAxisMark.cal_mark_min, valuey)
}
}
LogUtil.i(TAG, "Y轴真实cal_mark_min=" + yAxisMark.cal_mark_min.toString() + " cal_mark_max=" + yAxisMark.cal_mark_max)
if (yAxisMark.markType === MarkType.Integer) {
val min = 0
val max = yAxisMark.cal_mark_max.toInt()
var mark = (max - min) / (yAxisMark.lableNum - 1) + if ((max - min) % (yAxisMark.lableNum - 1) > 0) 1 else 0
val first = (mark.toString() + "").substring(0, 1).toInt() + 1
LogUtil.i(TAG, "mark=$mark first=$first")
if ((mark.toString() + "").length == 1) {
//YMARK = 1、2、5、10
mark = if (mark == 3 || mark == 4 || mark == 6 || mark == 7 || mark == 8 || mark == 9) if (mark == 3 || mark == 4) 5 else 10 else mark
} else if ((mark.toString() + "").length == 2) {
mark = first * 10
} else if ((mark.toString() + "").length == 3) {
mark = first * 100
} else if ((mark.toString() + "").length == 4) {
mark = first * 1000
} else if ((mark.toString() + "").length == 5) {
mark = first * 10000
} else if ((mark.toString() + "").length == 6) {
mark = first * 100000
}
yAxisMark.cal_mark_min = 0f
yAxisMark.cal_mark_max = (mark * (yAxisMark.lableNum - 1)).toFloat()
yAxisMark.cal_mark = mark.toFloat()
} else { //Float //Percent
yAxisMark.cal_mark_max *= redundance
yAxisMark.cal_mark_min /= redundance
yAxisMark.cal_mark = (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) / (yAxisMark.lableNum - 1)
}
LogUtil.i(TAG, " cal_mark_min=" + yAxisMark.cal_mark_min.toString() + " cal_mark_max=" + yAxisMark.cal_mark_max.toString() + " yAxisMark.cal_mark=" + yAxisMark.cal_mark)
}
}
@@ -0,0 +1,330 @@
package com.openxu.hkchart.bar
import android.content.Context
import android.graphics.*
import android.text.TextUtils
import android.util.AttributeSet
import android.view.ScaleGestureDetector
import com.openxu.hkchart.BaseChart
import com.openxu.hkchart.config.*
import com.openxu.utils.DensityUtil
import com.openxu.utils.FontUtil
import com.openxu.utils.LogUtil
/**
* Author: openXu
* Time: 2021/5/11 14:07
* class: HorizontalBarChart
* Description:
*/
class HorizontalBarChart : BaseChart<HBar> {
/**计算 */
private val chartRect = RectF() //图表矩形
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
}
/**设置 */
private val barData = mutableListOf<HBar>()
private lateinit var yAxisMark: YAxisMark
private lateinit var xAxisMark: XAxisMark
private var barWidth = DensityUtil.dip2px(context, 26f)
private var barSpace = DensityUtil.dip2px(context, 10f)
private var barColor = intArrayOf(
Color.parseColor("#F46863"),
Color.parseColor("#2DD08A"),
Color.parseColor("#567CF6"),
Color.parseColor("#F5B802"),
Color.parseColor("#CC71F7"))
fun setData(barData: List<HBar>?) {
if(chartConfig==null)
throw RuntimeException("---------请配置图表")
LogUtil.w(TAG, "设置数据:$barData")
this.barData.clear()
barData?.let {
this.barData.addAll(barData)
}
var config = chartConfig as HorizontalBarConfig
if(null==config.xAxisMark)
throw RuntimeException("---------请设置x坐标")
if(null==config.yAxisMark)
throw RuntimeException("---------请设置y坐标")
xAxisMark = config.xAxisMark!!
yAxisMark = config.yAxisMark!!
barWidth = config.barWidth
barSpace = config.barSpace
barColor = config.barColor
if (config.showAnim) chartAnimStarted = false
calculateYMark()
loading = false
requestLayout()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
LogUtil.v(TAG, "测量建议:" + MeasureSpec.UNSPECIFIED + " " + MeasureSpec.EXACTLY + " " + MeasureSpec.AT_MOST)
LogUtil.v(TAG, "宽测量建议:$widthSize*$widthMode")
LogUtil.v(TAG, "高度测量建议:$heightSize*$heightMode")
scrollXMax = 0f
scrollx = 0f
var height = 0
when (heightMode) {
MeasureSpec.EXACTLY -> height = heightSize
MeasureSpec.UNSPECIFIED, MeasureSpec.AT_MOST -> //计算需要的高度
if (!barData.isNullOrEmpty()) {
height += paddingTop
height += paddingBottom
paintText.textSize = yAxisMark.textSize.toFloat()
paintText.typeface = yAxisMark.numberTypeface
yAxisMark.textHeight = FontUtil.getFontHeight(paintText)
yAxisMark.textLead = FontUtil.getFontLeading(paintText)
height += yAxisMark.textSpace
height += yAxisMark.textHeight.toInt()
height += (barWidth + barSpace) * barData.size
//
/* if(height>heightSize){
}
scrollAble = false;
scrollXMax = 0;
scrollx = 0;*/LogUtil.v(TAG, "实际需要高度:$heightSize")
} else {
//默认最小高度用于显示正在加载中
height = DensityUtil.dip2px(context, 150f)
}
}
LogUtil.v(TAG, "测量:$widthSize*$height")
setMeasuredDimension(widthSize, height)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (barData.isNullOrEmpty())
return
paintText.textSize = xAxisMark.textSize.toFloat()
paintText.typeface = Typeface.DEFAULT
xAxisMark.textHeight = FontUtil.getFontHeight(paintText)
xAxisMark.textLead = FontUtil.getFontLeading(paintText)
var lableMax = 0f
for (hbar in barData) {
lableMax = Math.max(lableMax, FontUtil.getFontlength(paintText, hbar.lable))
}
chartRect.left = paddingLeft + lableMax + xAxisMark.textSpace
chartRect.top = yAxisMark.textSpace + yAxisMark.textHeight
paintText.textSize = yAxisMark.textSize.toFloat()
paintText.typeface = yAxisMark.numberTypeface
chartRect.right = rectChart.right - FontUtil.getFontlength(paintText, yAxisMark.getMarkText(yAxisMark.cal_mark_max)) / 2
chartRect.bottom = rectChart.bottom
LogUtil.w(TAG, "重新计算绘制范围:$chartRect")
}
private var rect = RectF() //备用,后面绘制矩形
private var path = Path()
override fun drawChart(canvas: Canvas?) {
if (barData.isNullOrEmpty()) return
var axis0x = 0f //y轴0刻度的x坐标
var top: Float = chartRect.top + barSpace / 2 //绘制柱状开始的最上方位置
paintText.textSize = xAxisMark.textSize.toFloat()
paintText.color = xAxisMark.textColor
paintText.typeface = Typeface.DEFAULT
xAxisMark.textHeight = FontUtil.getFontHeight(paintText)
xAxisMark.textLead = FontUtil.getFontLeading(paintText)
paint.style = Paint.Style.FILL
for (i in 0 until barData.size) {
val bar = barData[i]
//绘制x标签
val lable: String = getXValue(bar.lable)
val textWidth = FontUtil.getFontlength(paintText, lable)
canvas!!.drawText(lable,
chartRect.left - xAxisMark.textSpace - textWidth,
top + barWidth / 2 - xAxisMark.textHeight / 2 + xAxisMark.textLead, paintText)
//绘制底色
paint.color = Color.parseColor("#f0f0f0")
rect.left = chartRect.left
rect.top = top
rect.right = chartRect.right
rect.bottom = top + barWidth
canvas!!.drawRect(rect, paint)
top += barWidth + barSpace
}
paint.style = Paint.Style.STROKE
paint.strokeWidth = yAxisMark.lineWidth.toFloat()
paint.color = yAxisMark.lineColor
paintEffect.style = Paint.Style.STROKE
paintEffect.strokeWidth = yAxisMark.lineWidth.toFloat()
paintEffect.color = yAxisMark.lineColor
val effects: PathEffect = DashPathEffect(floatArrayOf(15f, 6f, 15f, 6f), 0f)
paintEffect.pathEffect = effects
paintText.textSize = yAxisMark.textSize.toFloat()
paintText.color = yAxisMark.textColor
paintText.typeface = yAxisMark.numberTypeface
val yMarkSpace = chartRect.width() / (yAxisMark.lableNum - 1)
for (i in 0 until yAxisMark.lableNum) {
/**竖直线 */
if (index0 === i) axis0x = chartRect.left + yMarkSpace * i
//canvas.drawLine()给paint设置DashPathEffect(虚线)无效。后面发现是硬件加速的锅。 解决方法就是,在view层关闭硬件加速
// setLayerType(View.LAYER_TYPE_SOFTWARE, null);
// canvas.drawLine(chartRect.left + yMarkSpace*i, chartRect.top,
// chartRect.left + yMarkSpace*i,chartRect.bottom, index0 == i?paint:paintEffect);
//canvas.drawPath()可以绘制虚线,不用关闭硬件加速
path.reset()
path.moveTo(chartRect.left + yMarkSpace * i, chartRect.top)
path.lineTo(chartRect.left + yMarkSpace * i, chartRect.bottom)
canvas!!.drawPath(path, if (index0 === i) paint else paintEffect)
/**绘制y刻度 */
val text = yAxisMark.getMarkText(yAxisMark.cal_mark_min + i * yAxisMark.cal_mark)
canvas!!.drawText(text,
chartRect.left + yMarkSpace * i - FontUtil.getFontlength(paintText, text) / 2,
rectChart.top + yAxisMark.textLead, paintText)
}
/**绘制数据*/
/**绘制数据 */
paint.style = Paint.Style.FILL
paintText.textSize = xAxisMark.textSize.toFloat()
paintText.color = xAxisMark.textColor
paintText.typeface = yAxisMark.numberTypeface
//两种不同的字体设置后要重新计算 文字高度和基线,否则会出现意想不到的效果
//两种不同的字体设置后要重新计算 文字高度和基线,否则会出现意想不到的效果
xAxisMark.textHeight = FontUtil.getFontHeight(paintText)
xAxisMark.textLead = FontUtil.getFontLeading(paintText)
//y值每一份对应的宽度
//y值每一份对应的宽度
val once = chartRect.width() / (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min)
top = chartRect.top + barSpace / 2
for (i in 0 until barData.size) {
val bar = barData[i]
//绘制颜色柱子
rect.top = top
rect.bottom = top +barWidth
if (bar.value >= 0) {
rect.left = axis0x
rect.right = rect.left + once * bar.value * chartAnimValue
} else {
rect.right = axis0x
rect.left = rect.right + once * bar.value * chartAnimValue
}
paint.color = barColor.get(i % barColor.size)
canvas!!.drawRect(rect, paint)
//绘制文字
val lable = yAxisMark.getMarkText(bar.value) + yAxisMark.unit
val textWidth = FontUtil.getFontlength(paintText, lable)
var x = if (rect.width() < textWidth) if (bar.value >= 0) rect.left else rect.right - textWidth else rect.left + rect.width() / 2 - textWidth / 2
if (x < chartRect.left + xAxisMark.textSpace) x = chartRect.left + xAxisMark.textSpace
if (x + textWidth > chartRect.right) x = chartRect.right - textWidth - xAxisMark.textSpace
paintText.typeface = yAxisMark.numberTypeface
canvas!!.drawText(lable,
x,
top + barWidth / 2 - xAxisMark.textHeight / 2 + xAxisMark.textLead, paintText)
top += barWidth + barSpace
}
}
override fun onFocusTouch(point: PointF?) {
}
override fun onScaleBegin(detector: ScaleGestureDetector) {
}
override fun onScale(detector: ScaleGestureDetector, beginScrollx: Float) {
}
//截取
private fun getXValue(value: String): String {
var value = value
if (TextUtils.isEmpty(value)) return ""
if (xAxisMark.splitSubLen > 0 && value.length > xAxisMark.splitSubLen) {
value = value.substring(0, xAxisMark.splitSubLen)
}
return value
}
private var index0 = 0 //0刻度对应的标签索引
private fun calculateYMark() {
if (barData == null || barData.size <= 0) return
yAxisMark.cal_mark_max = barData[0].value
yAxisMark.cal_mark_min = barData[0].value
for (data in barData) {
yAxisMark.cal_mark_max = Math.max(yAxisMark.cal_mark_max, data.value)
yAxisMark.cal_mark_min = Math.min(yAxisMark.cal_mark_min, data.value)
}
LogUtil.w(TAG, "真实的最小值=" + yAxisMark.cal_mark_min + " 最大值=" + yAxisMark.cal_mark_max + " y刻度数量:" + yAxisMark.lableNum)
if (yAxisMark.cal_mark_min > 0 && yAxisMark.cal_mark_max > 0) {
yAxisMark.cal_mark_min = 0f
} else if (yAxisMark.cal_mark_min < 0 && yAxisMark.cal_mark_max < 0) {
yAxisMark.cal_mark_max = 0f
}
val z = ((yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) / (yAxisMark.lableNum - 1)).toInt()
val y = ((yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) % (yAxisMark.lableNum - 1)).toInt()
var mark = z + if (y > 0) 1 else 0
LogUtil.w(TAG, "取整 $z 余 $y 计算mark=$mark")
mark = if (mark == 0) 1 else mark //最大值和最小值都为0的情况
LogUtil.w(TAG, yAxisMark.cal_mark_min.toString() + "~" + yAxisMark.cal_mark_max + "计算mark=" + mark)
if (mark <= 10) {
//YMARK = 1、2、5、10
mark = if (mark == 3 || mark == 4 || mark == 6 || mark == 7 || mark == 8 || mark == 9) if (mark == 3 || mark == 4) 5 else 10 else mark
} else {
//mark前两位,比如 4549 取mark1=4 mark2=5
var mark1 = (mark.toString() + "").substring(0, 1).toInt()
var mark2 = (mark.toString() + "").substring(1, 2).toInt()
LogUtil.w(TAG, "mark前两位=$mark1 $mark2")
if (mark2 < 5) {
mark2 = 5
} else {
mark2 = 0
mark1 += 1
}
val ws = (mark.toString() + "").length
LogUtil.w(TAG, "mark前两位=$mark1 $mark2 位数:$ws")
mark = mark1 * getWs(ws) + mark2 * getWs(ws - 1)
}
LogUtil.w(TAG, "取值mark=$mark")
if (yAxisMark.cal_mark_min < 0 && yAxisMark.cal_mark_max > 0) {
//需要显示0
index0 = (-yAxisMark.cal_mark_min / mark).toInt()+ if (-yAxisMark.cal_mark_min % mark != 0f) 1 else 0
while (checkMark(index0, mark, yAxisMark.cal_mark_max)) {
yAxisMark.lableNum++
LogUtil.w(TAG, "检测到正值可能越界,增加标签数量=" + yAxisMark.lableNum)
}
LogUtil.w(TAG, "一正一负的情况mark=" + mark + " index0=" + index0 + " lableNum=" + yAxisMark.lableNum)
yAxisMark.cal_mark_min = -mark * index0.toFloat()
yAxisMark.cal_mark_max = yAxisMark.cal_mark_min + mark * (yAxisMark.lableNum - 1)
} else if (yAxisMark.cal_mark_min == 0f) {
index0 = 0
yAxisMark.cal_mark_max = mark * (yAxisMark.lableNum - 1).toFloat()
} else if (yAxisMark.cal_mark_max == 0f) {
index0 = yAxisMark.lableNum - 1
yAxisMark.cal_mark_min = -mark * (yAxisMark.lableNum - 1).toFloat()
}
yAxisMark.cal_mark = mark.toFloat()
LogUtil.w(TAG, "最终取值=" + yAxisMark.cal_mark_min + "~" + yAxisMark.cal_mark_max + " mark=" + mark)
}
private fun getWs(ws: Int): Int {
return if (ws == 1) 1 else if (ws == 2) 10 else if (ws == 3) 100 else if (ws == 4) 1000 else if (ws == 5) 10000 else if (ws == 6) 100000 else if (ws == 7) 1000000 else if (ws == 8) 10000000 else if (ws == 9) 100000000 else 1
}
private fun checkMark(index0: Int, mark: Int, max: Float): Boolean {
return (yAxisMark.lableNum - 1 - index0) * mark < max
}
}
@@ -0,0 +1,590 @@
package com.openxu.hkchart.bar
import android.content.Context
import android.graphics.*
import android.text.TextUtils
import android.util.AttributeSet
import android.view.ScaleGestureDetector
import com.openxu.hkchart.BaseChart
import com.openxu.hkchart.config.*
import com.openxu.hkchart.data.FocusData
import com.openxu.hkchart.data.FocusPanelText
import com.openxu.utils.DensityUtil
import com.openxu.utils.FontUtil
import com.openxu.utils.LogUtil
import java.util.regex.Pattern
/**
* Author: openXu
* Time: 2021/5/9 12:00
* class: MultipartBarChart
* Description: 一个柱子分为多种颜色多部份的柱状图
*
* 特色:支持 缩放、滚动、惯性滚动、y坐标缩放变化、x坐标滚动变化
*
* 缩放因子:scalex 范围默认1~2
* 滑动距离:scrollx 范围:scrollXMax ~ 0 (scrollXMax会自动计算)
*
*/
class MultipartBarChart : BaseChart<MultipartBarData>{
constructor(context: Context) :this(context, null)
constructor(context: Context, attrs: AttributeSet?) :this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int):super(context, attrs, defStyle){
}
/***************************1. API👇👇👇***************************/
/**设置数据*/
private var _datas = mutableListOf<MultipartBarData>()
var datas: MutableList<MultipartBarData>
get() {return _datas}
set(value) {
_datas.clear()
_datas.addAll(value)
initial()
chartConfig?.let {
if (it.showAnim) chartAnimStarted = false
}
loading = false
}
/***************************1. API👆👆👆***************************/
/***************************2. 子类重写👇👇👇***************************/
/**配置*/
private lateinit var yAxisMark : YAxisMark
private lateinit var xAxisMark : XAxisMark
private lateinit var barColor : IntArray
private var focusPanelText: Array<FocusPanelText>? = null //焦点面板显示内容
private var dataTotalCount : Int = -1
/**初步计算*/
private var barWidth : Float = 0f //柱宽度
private var spacingRatio = 1f
private var barSpace : Float = 0f //柱间的间距
private var oneDataWidth : Float = 0f //单个柱子+间距 的宽度 ...( | |)
private var allDataWidth : Float = 0f //所有柱子+间距 的宽度 ( | | | |...| | )
private var foucsRectWidth = 0f //焦点面板矩形宽高
private var foucsRectHeight = 0f
override fun initial():Boolean{
if(super.initial()) return true
if(_datas.isNullOrEmpty()) return true
if(chartConfig==null)
throw RuntimeException("---------请配置图表")
var config = chartConfig as MultipartBarConfig
if(null==config.xAxisMark)
throw RuntimeException("---------请设置x坐标")
if(null==config.yAxisMark)
throw RuntimeException("---------请设置y坐标")
xAxisMark = config.xAxisMark!!
yAxisMark = config.yAxisMark!!
barColor = config.barColor
barWidth = config.barWidth
spacingRatio = config.spacingRatio
barSpace = barWidth * spacingRatio
dataTotalCount = config.dataTotalCount
if(dataTotalCount<0)
dataTotalCount = datas.size
focusPanelText = config.focusPanelText
/**重新确定表体矩形rectChart*/
paintText.textSize = xAxisMark.textSize.toFloat()
xAxisMark.textHeight = FontUtil.getFontHeight(paintText)
xAxisMark.textLead = FontUtil.getFontLeading(paintText)
//确定图表最下放绘制位置
rectChart.bottom = rectDrawBounds.bottom - (xAxisMark.textHeight + xAxisMark.textSpace)
xAxisMark.drawPointY = rectChart.bottom + xAxisMark.textSpace + xAxisMark.textLead
LogUtil.e(TAG, "--------------设置数据后第一次计算所有数据y轴刻度,以确定图标左侧位置")
calculateYMark(true)
paintText.textSize = yAxisMark.textSize.toFloat()
yAxisMark.textHeight = FontUtil.getFontHeight(paintText)
yAxisMark.textLead = FontUtil.getFontLeading(paintText)
var maxLable: String = yAxisMark.getMarkText(yAxisMark.cal_mark_max)
val minLable: String = yAxisMark.getMarkText(yAxisMark.cal_mark_min)
val maxYWidth = FontUtil.getFontlength(paintText, if(maxLable.length>minLable.length) maxLable else minLable)
rectChart.left = rectDrawBounds.left + yAxisMark.textSpace + maxYWidth
LogUtil.w(TAG, "原始顶部:${rectChart.top} 单位高度${if (TextUtils.isEmpty(yAxisMark.unit)) 0f else (yAxisMark.textHeight + yAxisMark.textSpace)} y一半:${yAxisMark.textHeight / 2}")
rectChart.top = rectDrawBounds.top + yAxisMark.textHeight / 2 + (if (TextUtils.isEmpty(yAxisMark.unit)) 0f else (yAxisMark.textHeight + yAxisMark.textSpace))
rectChart.right = rectDrawBounds.right
LogUtil.v(TAG, "确定表格矩形 $rectChart 宽度 ${rectChart.width()} 高度${rectChart.height()}")
/**重新计算柱子宽度 和 间距*/
LogUtil.e(TAG, "--------------根据显示配置和数据,计算柱子宽度和间距")
//根据设置的柱子宽度和间距,计算所有数据宽度
allDataWidth = dataTotalCount * barWidth + (dataTotalCount+1) * barSpace
when(config.displayScheme){
DisplayScheme.SHOW_ALL->{ //全部显示
if(allDataWidth > rectChart.width()){ //超出时,重新计算barWidth
// barWidth * dataTotalCount + barWidth*config.spacingRatio*(dataTotalCount+1) = rectChart.width()
barWidth = rectChart.width()/(dataTotalCount + spacingRatio*(dataTotalCount+1))
barSpace = barWidth * spacingRatio
LogUtil.w(TAG, "全部展示时宽度超过,重新计算柱子宽度$barWidth 间距 $barSpace")
}
}
DisplayScheme.SHOW_BEGIN->{}//从第一条数据开始展示,柱子宽度就是设置的宽度
DisplayScheme.SHOW_END->{} //从最后一条数据开始展示,柱子宽度就是设置的宽度
}
//最大放大倍数 = 设置的柱子宽度的3倍 / 当前柱子宽度
scaleXMax = config.barWidth * 3f / barWidth
LogUtil.v(TAG, "确定柱子宽度 $barWidth 间距 $barSpace 最大放大倍数$scaleXMax")
/**确定第一条数据的绘制x坐标 计算滚动最大值*/
oneDataWidth = barWidth + barSpace
allDataWidth = dataTotalCount * barWidth + (dataTotalCount+1) * barSpace
scrollx = 0f
scrollXMax = 0f
scalex = 1f
if(allDataWidth>rectChart.width()){
scrollXMax = rectChart.width() -allDataWidth //最大滚动距离,是一个负值
}
when(config.displayScheme){
DisplayScheme.SHOW_ALL->{ }//全部显示
DisplayScheme.SHOW_BEGIN->{
scrollx = 0f
}
DisplayScheme.SHOW_END->{
scrollx = scrollXMax
}
}
LogUtil.v(TAG, "单个柱子+间距 $oneDataWidth 所有数据宽度 $allDataWidth")
focusPanelText?.let {
//计算焦点面板
//2020-10-16 06:00
//零序电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
foucsRectWidth = 0f
foucsRectHeight = foucsRectSpace * 2.toFloat()
var text: String
maxLable = ((if (yAxisMark.getMarkText(yAxisMark.cal_mark_max).length >
yAxisMark.getMarkText(yAxisMark.cal_mark_min).length)
yAxisMark.getMarkText(yAxisMark.cal_mark_max) else
yAxisMark.getMarkText(yAxisMark.cal_mark_min))
+ if (TextUtils.isEmpty(yAxisMark.unit)) "" else yAxisMark.unit)
for (i in it.indices) {
if (it[i].show) {
paintText.textSize = it[i].textSize.toFloat()
if (i == 0) {//x轴数据
foucsRectWidth = Math.max(foucsRectWidth, FontUtil.getFontlength(paintText, it[i].text))
foucsRectHeight += FontUtil.getFontHeight(paintText)
} else {
text = it[i].text + maxLable
foucsRectWidth = Math.max(foucsRectWidth, FontUtil.getFontlength(paintText, text))
LogUtil.w(TAG, "计算面板:$text $foucsRectWidth")
foucsRectHeight += foucsRectTextSpace + FontUtil.getFontHeight(paintText)
}
}
}
foucsRectWidth += foucsRectSpace * 4.toFloat()
}
return true
}
override fun drawChart(canvas: Canvas?) {
LogUtil.e(TAG, "-----------开始绘制,当前缩放系数$scalex 偏移量$scrollx")
if(_datas.isNullOrEmpty())
return
//预算需要绘制的组的开始和结尾index,避免不必要的计算浪费性能
// caculateIndex()
//计算Y轴刻度值
calculateYMark(false)
//计算x轴刻度值
caculateXMark()
val yMarkSpace = (rectChart.bottom - rectChart.top) / (yAxisMark.lableNum - 1)
paintEffect.style = Paint.Style.STROKE
paintEffect.strokeWidth = yAxisMark.lineWidth.toFloat()
paintEffect.color = yAxisMark.lineColor
paintText.textSize = yAxisMark.textSize.toFloat()
paintText.color = yAxisMark.textColor
val effects: PathEffect = DashPathEffect(floatArrayOf(15f, 6f, 15f, 6f), 0f)
paintEffect.pathEffect = effects
paint.style = Paint.Style.STROKE
paint.strokeWidth = yAxisMark.lineWidth.toFloat()
paint.color = yAxisMark.lineColor
for (i in 0 until yAxisMark.lableNum) {
/**绘制横向线 */
canvas!!.drawLine(rectChart.left, rectChart.bottom - yMarkSpace * i,
rectChart.right, rectChart.bottom - yMarkSpace * i, paint)
/**绘制y刻度 */
val text = yAxisMark.getMarkText(yAxisMark.cal_mark_min + i * yAxisMark.cal_mark)
canvas!!.drawText(text,
rectChart.left - yAxisMark.textSpace - FontUtil.getFontlength(paintText, text),
rectChart.bottom - yMarkSpace * i - yAxisMark.textHeight / 2 + yAxisMark.textLead, paintText)
}
//绘制Y轴单位
if (!TextUtils.isEmpty(yAxisMark.unit)) {
canvas!!.drawText(yAxisMark.unit,
rectChart.left - yAxisMark.textSpace - FontUtil.getFontlength(paintText, yAxisMark.unit),
//y = 图表顶部 - 单位文字距离 - 单位文字高度 + 最上方y刻度高度/2
rectChart.top - yAxisMark.textSpace - yAxisMark.textHeight * 3 / 2 + yAxisMark.textLead, paintText)
}
val rect = RectF()
paint.style = Paint.Style.FILL
for(index:Int in startIndex..endIndex){
//计算柱体x,y坐标
if(allDataWidth*scalex<=rectChart.width()){
rect.left = rectChart.left + (rectChart.width() - allDataWidth*scalex)/2 +
index * oneDataWidth*scalex + barSpace*scalex
// LogUtil.v(TAG, "数据不够填充表,居中显示,当前数据x坐标 ${rect.left}")
}else{
rect.left = scrollx + rectChart.left + index * oneDataWidth*scalex + barSpace*scalex
// LogUtil.v(TAG, "数据超过表,当前数据x坐标 ${rect.left}")
}
rect.right = rect.left + barWidth*scalex
rect.bottom = rectChart.bottom
rect.top = rectChart.bottom
//★★★顺便为焦点数据设置x坐标,方便下一步绘制焦点
focusData?.let {
if(focusIndex == index)
it.point.x = rect.left + barWidth*scalex/2
}
// LogUtil.d(TAG, "$i 绘制:"+_datas[i].valuey)
/**绘制柱状 */
//过滤掉绘制区域外的柱
var barLayer:Int? = null
if(index == startIndex || index == endIndex){
/**
* Canvas有两种坐标系:
* 1. Canvas自己的坐标系:(0,0,canvas.width,canvas.height),它是固定不变的
* 2. 绘图坐标系:用于绘制,通过Matrix让Canvas平移translate,旋转rotate,缩放scale 等时实际上操作的是绘图坐标系
* 由于绘图坐标系中Matrix的改变是不可逆的,所以产生了状态栈和Layer栈,它们分别运用于save方法和saveLayer方法,使得绘图坐标系恢复到保存时的状态
* 1. 状态栈:save()、restore()保存和还原变换操作Matrix以及Clip剪裁,也可以restoretoCount()直接还原到对应栈的保存状态
* 2. Layer栈:saveLayer()时会新建一个透明图层(离屏Bitmap-离屏缓冲),并且将saveLayer之前的一些Canvas操作延续过来,
* 后续的绘图操作都在新建的layer上面进行,当调用restore或者restoreToCount时更新到对应的图层和画布上
*
* 需要注意的是saveLayer会造成过渡绘制,可以考虑用 canvas?.save() canvas?.clipRect(rectChart)组合代替
*/
/* barLayer = canvas?.saveLayer(rectChart.left, rectChart.top, rectChart.right,
rectChart.bottom*//* + xAxisMark.textSpace + xAxisMark.textHeight*//*
, paint, Canvas.ALL_SAVE_FLAG)*/
//裁剪画布,避免x刻度超出
barLayer = canvas?.save()
canvas?.clipRect(rectChart)
}
for(vindex : Int in _datas[index].valuey.indices){
paint.color = barColor[vindex]
if (_datas[index].valuey[vindex] != null) {
val vh = rectChart.height() / (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) *
(_datas[index].valuey[vindex] - yAxisMark.cal_mark_min) * chartAnimValue
rect.top -= vh
canvas?.drawRect(rect, paint)
rect.bottom = rect.top
}
}
if(barLayer!=null)
canvas?.restoreToCount(barLayer)//还原画布,将柱子更新到画布上
/**绘制x坐标 */
//测试:绘制索引
// canvas?.drawText("$i", rect.left + (barWidth*scalex) / 2 - FontUtil.getFontlength(paintText, "$i") / 2, xAxisMark.drawPointY, paintText)
//从第一条数据开始每隔xIndexSpace绘制一个x刻度
if((index - startIndex) % xIndexSpace == 0){
val x = rect.left + (barWidth*scalex) / 2 - FontUtil.getFontlength(paintText, _datas[index].valuex) / 2
//过滤掉超出图表范围的x值绘制,通常是第一条和最后一条
if(x < paddingLeft || x+FontUtil.getFontlength(paintText, _datas[index].valuex) > measuredWidth - paddingRight)
continue
canvas?.drawText(_datas[index].valuex, x,xAxisMark.drawPointY, paintText)
}
}
drawFocus(canvas)
}
/**绘制焦点 */
private val focusLineColor = Color.parseColor("#319A5A")
private val focusLineSize = DensityUtil.dip2px(context, 1f)
private val foucsRectTextSpace = DensityUtil.dip2px(context, 3f)
private val foucsRectSpace = DensityUtil.dip2px(context, 6f)
private fun drawFocus(canvas: Canvas?) {
if (null == focusData || null==canvas) return
if(focusData!!.point.x<rectChart.left ||focusData!!.point.x>rectChart.right) //上次获取的焦点因为滑出矩形,不显示
return
//绘制竖直虚线
val effects: PathEffect = DashPathEffect(floatArrayOf(8f, 5f, 8f, 5f), 0f)
paintEffect.style = Paint.Style.STROKE
paintEffect.strokeWidth = focusLineSize.toFloat()
paintEffect.color = focusLineColor
paintEffect.pathEffect = effects
val path = Path()
path.moveTo(focusData!!.point.x, rectChart.bottom)
path.lineTo(focusData!!.point.x, rectChart.top)
canvas.drawPath(path, paintEffect)
//面板
val showLeft: Boolean = focusData!!.point.x - rectChart.left > (rectChart.right - rectChart.left) / 2
val rect = RectF(
if (showLeft) focusData!!.point.x - foucsRectWidth - 30 else focusData!!.point.x + 30,
rectChart.top /*+ (rectChart.bottom - rectChart.top)/2 - foucsRectHeight/2*/,
if (showLeft) focusData!!.point.x - 30 else focusData!!.point.x + foucsRectWidth + 30,
rectChart.top + foucsRectHeight /*+ (rectChart.bottom - rectChart.top)/2 + foucsRectHeight/2*/
)
paint.style = Paint.Style.FILL
paint.color = Color.WHITE
paint.alpha = 230
canvas.drawRect(rect, paint)
//面板中的文字
//2020-10-16 06:00
//零序电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
var text = ""
var top: Float = rect.top + foucsRectSpace
val currentPoint = PointF()
val radius = DensityUtil.dip2px(context, 2.5f).toFloat()
focusPanelText?.let {
for (i in it.indices) {
if (it[i].show) {
paintText.textSize = it[i].textSize.toFloat()
paintText.color = it[i].textColor
if (i == 0) { //x轴数据
text = focusData!!.data.valuex
} else {
top += foucsRectTextSpace.toFloat()
text = (it[i].text +
(if (focusData!!.data.valuey[i - 1] == null) ""
else YAxisMark.formattedDecimal(focusData!!.data.valuey[i - 1].toDouble(), 2))
+ yAxisMark.unit)
}
canvas.drawText(text,
rect.left + foucsRectSpace,
top + FontUtil.getFontLeading(paintText), paintText)
top += FontUtil.getFontHeight(paintText)
}
}
}
}
/***************************事件👇👇👇***************************/
private var oldScalex = 0f
override fun onScaleBegin(detector: ScaleGestureDetector) {
// val width = -scrollx + (detector.focusX - rectChart.left)
// val zs = (width / (oneDataWidth*scalex)).toInt()
// val ys = width % (oneDataWidth*scalex)
// focusIndex = zs + if(ys>(barWidth/2+barSpace)*scalex)1 else 0
oldScalex = scalex
//设置焦点为两个手指中间
focusPoint.x = detector.focusX
focusPoint.y = detector.focusY
onFocusTouch(focusPoint)
LogUtil.i(TAG, "缩放开始了,焦点索引为$focusIndex") // 缩放因子
}
private var scaleXMax = 3f //X轴方向最大放大倍数,需要根据柱子是否能填充图表计算
override fun onScale(detector: ScaleGestureDetector, beginScrollx: Float) {
scalex *= detector.scaleFactor
LogUtil.e(TAG, "--------------------当前缩放值$scalex 缩放${detector.scaleFactor} 缩放之后${scalex*detector.scaleFactor}")
//缩放范围约束
scalex = scalex.coerceAtMost(scaleXMax)
scalex = scalex.coerceAtLeast(1f)
LogUtil.e(TAG, "--------------------最终值$scalex ")
//重新计算最大偏移量
if(allDataWidth * scalex > rectChart.width()){
scrollXMax = rectChart.width() - allDataWidth * scalex
//为了保证焦点对应的点位置不变,是使用公式: beginScrollx + rectChart.left + focusIndex*beginPointWidth = scrollx + rectChart.left + focusIndex*pointWidth
scrollx = beginScrollx + focusIndex * (oneDataWidth * oldScalex - oneDataWidth*scalex)
scrollx = Math.min(scrollx, 0f)
scrollx = Math.max(scrollXMax, scrollx)
LogUtil.i(TAG, "缩放后偏移:"+scrollx);
}else{
scrollXMax = 0f //数据不能填充时,居中展示
scrollx = 0f
}
}
override fun onFocusTouch(point: PointF?) {
try {
focusData = null
point?.let {
if (!_datas.isNullOrEmpty()) {
val scaleOneWidth = oneDataWidth*scalex
val allWidth = allDataWidth*scalex
//避免滑出
var left = rectChart.left
var right = rectChart.right
if(allWidth<rectChart.width()){
left = rectChart.left + (rectChart.width()-allWidth)/2
right = left + allWidth
}
LogUtil.e(TAG, "========焦点位置${point.x}")
point.x = Math.max(point.x, left)
point.x = Math.min(point.x, right)
LogUtil.e(TAG, "========左右范围:${left}*${right} 焦点纠正后$point.x")
val width = if(allWidth<rectChart.width()){
point.x - left
}else{
-scrollx + (point.x - left)
}
//获取焦点对应的数据的索引
focusIndex = (width / scaleOneWidth).toInt() - 1 //计算的是索引(从0开始),所以-1
LogUtil.e(TAG, "========单个宽度$scaleOneWidth 宽度$width 整数索引$focusIndex")
if(width % scaleOneWidth > barSpace*scalex/2) {
focusIndex += 1
LogUtil.e(TAG, "========焦点在下一个点范围了:$focusIndex")
}
focusIndex = Math.max(0, Math.min(focusIndex, _datas.size - 1))
LogUtil.e(TAG, "========焦点索引:$focusIndex")
focusData = FocusData(_datas[focusIndex], it)
}
}
// postInvalidate()
} catch (e: Exception) {
e.printStackTrace()
}
}
/***************************事件👆👆👆***************************/
/***************************2. 子类重写👆👆👆***************************/
/***************************3. 特殊👇👇👇***************************/
private var startIndex = 0
private var endIndex = 0
/**计算当前缩放、移动状态下,需要绘制的数据的起始和结束索引*/
private fun caculateIndex(){
//预算需要绘制的组的开始和结尾index,避免不必要的计算浪费性能
val scaleOneWidth = oneDataWidth*scalex
if(allDataWidth*scalex<=rectChart.width()){
startIndex = 0
endIndex = _datas.size-1
}else{
startIndex = (-scrollx / scaleOneWidth).toInt()
endIndex = ((-scrollx + rectChart.width()) / scaleOneWidth).toInt() - 1
// LogUtil.w(TAG, "总宽度:${-scrollx + rectChart.width()} 当前状态下一个柱子及间隙宽度$scaleOneWidth 最后一条数据索引取整$endIndex")
val nextVisible = (-scrollx + rectChart.width()) % scaleOneWidth>=barSpace*scalex
endIndex += if(nextVisible)1 else 0
// LogUtil.w(TAG, "取余:${(-scrollx + rectChart.width()) % scaleOneWidth} 柱子宽度${barSpace*scalex} 是否可见$nextVisible 结束索引$endIndex")
endIndex = endIndex.coerceAtMost(_datas.size - 1)
}
}
/**y值累加*/
private fun getTotalValuey(data : MultipartBarData) : Float{
var valuey = 0f
for(v in data.valuey)
valuey+=v
return valuey
}
/**获取startIndex~endIndex的数据最大最小值,并根据需要显示几个y刻度计算出递增值*/
private fun calculateYMark(all:Boolean) {
val redundance = 1.1f //y轴最大和最小值冗余
yAxisMark.cal_mark_max = -Float.MAX_VALUE //Y轴刻度最大值
yAxisMark.cal_mark_min = Float.MAX_VALUE //Y轴刻度最小值
var startIdx = 0
var endIdx = _datas.size-1
if(!all){
caculateIndex()
startIdx = startIndex
endIdx = endIndex
}
for(index in startIdx..endIdx){
val valuey = getTotalValuey(_datas[index])
yAxisMark.cal_mark_max = Math.max(yAxisMark.cal_mark_max, valuey)
yAxisMark.cal_mark_min = Math.min(yAxisMark.cal_mark_min, valuey)
}
LogUtil.w(TAG, "$startIdx ~ $endIdx 真实最小最大值:" + yAxisMark.cal_mark_min + " " + yAxisMark.cal_mark_max)
//只有一个点的时候
if (yAxisMark.cal_mark_min == yAxisMark.cal_mark_max) {
when {
yAxisMark.cal_mark_min > 0 -> {
yAxisMark.cal_mark_min = 0f
}
yAxisMark.cal_mark_min == 0f -> {
yAxisMark.cal_mark_max = 1f
}
yAxisMark.cal_mark_min < 0 -> {
yAxisMark.cal_mark_max = 0f
}
}
}
if (yAxisMark.markType == MarkType.Integer) {
val min = if (yAxisMark.cal_mark_min > 0) 0 else yAxisMark.cal_mark_min.toInt()
val max = yAxisMark.cal_mark_max.toInt()
var mark = (max - min) / (yAxisMark.lableNum - 1) + if ((max - min) % (yAxisMark.lableNum - 1) > 0) 1 else 0
mark = if (mark == 0) 1 else mark //最大值和最小值都为0的情况
val first = (mark.toString() + "").substring(0, 1).toInt() + 1
if ((mark.toString() + "").length == 1) {
//YMARK = 1、2、5、10
mark = if (mark == 3 || mark == 4 || mark == 6 || mark == 7 || mark == 8 || mark == 9) if (mark == 3 || mark == 4) 5 else 10 else mark
} else if ((mark.toString() + "").length == 2) {
mark = first * 10
} else if ((mark.toString() + "").length == 3) {
mark = first * 100
} else if ((mark.toString() + "").length == 4) {
mark = first * 1000
} else if ((mark.toString() + "").length == 5) {
mark = first * 10000
} else if ((mark.toString() + "").length == 6) {
mark = first * 100000
}
yAxisMark.cal_mark_min = 0f
yAxisMark.cal_mark_max = mark * (yAxisMark.lableNum - 1).toFloat()
yAxisMark.cal_mark = mark.toFloat()
} else { //Float //Percent
yAxisMark.cal_mark_max = if (yAxisMark.cal_mark_max < 0) yAxisMark.cal_mark_max / redundance else yAxisMark.cal_mark_max * redundance
// yAxisMark.cal_mark_min = if (yAxisMark.cal_mark_min < 0) yAxisMark.cal_mark_min * redundance else yAxisMark.cal_mark_min / redundance
yAxisMark.cal_mark_min = 0f
yAxisMark.cal_mark = (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) / (yAxisMark.lableNum - 1)
}
//小数点位
if (yAxisMark.digits == 0) {
val mark = (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) / (yAxisMark.lableNum - 1)
if (mark < 1) {
val pattern = "[1-9]"
val p = Pattern.compile(pattern)
val m = p.matcher(mark.toString() + "") // 获取 matcher 对象
m.find()
val index = m.start()
yAxisMark.digits = index - 1
LogUtil.w(TAG, mark.toString() + "第一个大于0的数字位置:" + index + " 保留小数位数:" + yAxisMark.digits)
}
}
// LogUtil.w(TAG, "最终最小最大值:" + yAxisMark.cal_mark_min + " " + yAxisMark.cal_mark_max + " " + yAxisMark.cal_mark)
}
/**根据startIndex~endIndex计算x标签间隔数量*/
//从当前绘制的第一条数据开始,每隔多少展示一个x标签
private var xIndexSpace: Int = 0
private fun caculateXMark() {
caculateIndex()
paintText.textSize = xAxisMark.textSize.toFloat()
//计算当前显示的数据的x轴文字长度最大值
var xTextMaxLength = 0f
for(index in startIndex..endIndex){
xTextMaxLength = xTextMaxLength.coerceAtLeast(FontUtil.getFontlength(paintText, _datas[index].valuex))
}
//当前绘制宽度 / x刻度最长
var xNumber = ((allDataWidth * scalex).coerceAtMost(rectChart.width()) / xTextMaxLength).toInt()
val dataNumber = endIndex - startIndex + 1
LogUtil.e(TAG, "绘制的数据条数${endIndex-startIndex+1} X轴文字最长长度$xTextMaxLength 理论最多可显示$xNumber 个")
xNumber = Math.min(xNumber, xAxisMark.lableNum)
when(xNumber){
1->xIndexSpace = endIndex - startIndex + 10 //只显示第一个
2->xIndexSpace = endIndex - startIndex //显示第一个和最后一个
3->{ //取中点
when(dataNumber % 2){
0->xIndexSpace = (dataNumber-1)/2 //数据条数为偶数 变为奇数取中点
1->xIndexSpace = dataNumber/2 //数据条数为奇数取中点
}
}
else->{
xIndexSpace = when(dataNumber%xNumber){
0-> dataNumber/xNumber //数据条数 整除 lable数 时,取除数
else-> dataNumber/xNumber + 1 //不能整除时 +1
}
}
}
}
/***************************3. 特殊👆👆👆***************************/
}
@@ -0,0 +1,24 @@
package com.openxu.hkchart.config
import android.content.Context
import android.graphics.Color
import com.openxu.utils.DensityUtil
/**
* Author: openXu
* Time: 2021/5/9 12:14
* class: MultipartBarConfig
* Description:
*/
class BarChartConfig(context: Context) : ChartConfigBase(){
var scrollAble = true //是否支持滚动
var barWidth = DensityUtil.dip2px(context, 15f).toFloat() //柱宽度
var barSpace = DensityUtil.dip2px(context, 1f).toFloat() //双柱间的间距
var groupSpace = DensityUtil.dip2px(context, 25f).toFloat() //一组柱之间的间距(只有scrollAble==true时才生效)
var barColor = intArrayOf(
Color.parseColor("#f46763"),
Color.parseColor("#3cd595"),
Color.parseColor("#4d7bff")) //柱颜色
}
class Bar(val valuex: String,val valuey: List<Float>)
@@ -0,0 +1,38 @@
package com.openxu.hkchart.config
/**
* Author: openXu
* Time: 2021/5/9 11:58
* class: DisplayScheme
* Description: 首次展示模式
*/
enum class DisplayScheme {
SHOW_ALL, //第一次全部显示
SHOW_BEGIN, //从第一条数据开始展示固定条数
SHOW_END //从最后一条数据开始展示固定条数
}
/**
* Author: openXu
* Time: 2021/5/9 11:58
* class: ChartConfigBase
* Description: 图表配置基类
*/
open class ChartConfigBase{
//默认第一次展示全部数据
open var displayScheme:DisplayScheme = DisplayScheme.SHOW_ALL
//柱颜色
open var xAxisMark: XAxisMark?=null
open var yAxisMark: YAxisMark?=null
//是否展示动画
open var showAnim = true
//设置数据总数,可选并且设置的数据总数可以大于data集合总数,如果没有设置默认dataTotalCount=_data.size
//有些图表的数据总数和设置的数据可能不同,比如股票分时图:总数据量 = 一天交易时间分钟数,当前返回的数据量 = 开盘时间到当前时间分钟的数据
open var dataTotalCount : Int = -1
}
@@ -0,0 +1,28 @@
package com.openxu.hkchart.config
import android.content.Context
import android.graphics.Color
import android.graphics.Region
import com.openxu.utils.DensityUtil
/**
* Author: openXu
* Time: 2021/5/9 12:14
* class: MultipartBarConfig
* Description:
*/
class HorizontalBarConfig(context: Context) : ChartConfigBase(){
var barWidth = DensityUtil.dip2px(context, 26f)
var barSpace = DensityUtil.dip2px(context, 10f)
var barColor = intArrayOf(
Color.parseColor("#F46863"),
Color.parseColor("#2DD08A"),
Color.parseColor("#567CF6"),
Color.parseColor("#F5B802"),
Color.parseColor("#CC71F7"))
}
class HBar(val lable: String,val value: Float){
}
@@ -0,0 +1,30 @@
package com.openxu.hkchart.config
import android.content.Context
import android.graphics.Color
import com.openxu.hkchart.data.FocusPanelText
import com.openxu.utils.DensityUtil
/**
* Author: openXu
* Time: 2021/5/9 12:14
* class: MultipartBarConfig
* Description:
*/
class LineChartConfig(context: Context) : ChartConfigBase(){
var lineType : LineType = LineType.BROKEN
var lineWidth = DensityUtil.dip2px(context, 1f).toFloat()
var lineColor = intArrayOf(
Color.parseColor("#f46763"),
Color.parseColor("#3cd595"),
Color.parseColor("#4d7bff"),
Color.parseColor("#4d7bff"))
var pageShowNum = 0 //第一次页面总数据量 没有设置展示数据量,则默认为全部展示
/**设置焦点面板显示内容*/
var focusPanelText: Array<FocusPanelText>? = null
}
enum class LineType {
CURVE, //曲线
BROKEN //折线
}
@@ -0,0 +1,13 @@
package com.openxu.hkchart.config;
/**
* Author: openXu
* Time: 2020/11/20 11:25
* class: MarkType
* Description:
*/
public enum MarkType {
Integer,
Float,
Percent
}
@@ -0,0 +1,37 @@
package com.openxu.hkchart.config
import android.content.Context
import android.graphics.Color
import com.openxu.hkchart.data.FocusPanelText
import com.openxu.utils.DensityUtil
/**
* Author: openXu
* Time: 2021/5/9 12:14
* class: MultipartBarConfig
* Description:
*/
class MultipartBarConfig(context: Context) : ChartConfigBase(){
//默认柱宽度15dp
var barWidth = DensityUtil.dip2px(context, 15f).toFloat()
//默认柱间的间距占比,间距 = barWidth*spacingRatio
var spacingRatio = 1f
//柱颜色
var barColor: IntArray = intArrayOf(
Color.parseColor("#f46763"),
Color.parseColor("#3cd595"),
Color.parseColor("#4d7bff"))
/**设置焦点面板显示内容*/
var focusPanelText: Array<FocusPanelText>? = null
}
/**
* Author: openXu
* Time: 2021/5/9 12:14
* class: MultipartBarData
* Description: 数据
*/
class MultipartBarData(val valuey: List<Float>, val valuex: String)
@@ -0,0 +1,64 @@
package com.openxu.hkchart.config;
import android.content.Context;
import android.graphics.Color;
import com.openxu.utils.DensityUtil;
/**
* 坐标轴刻度标签
*/
public class XAxisMark {
/**可设置*/
public int textSize; //设置坐标文字大小
public int textColor; //设置坐标文字颜色
public int textSpace; //设置坐标字体与横轴的距离
//2选1设置
public int lableNum = 5;
public int splitSubLen = -1;
public String[] lables;
// public int lineWidth;
// public int lineColor;
/**计算*/
public float textHeight, textLead, drawPointY;
private XAxisMark() {
}
public static class Builder{
private XAxisMark axisMark;
public Builder(Context context) {
axisMark = new XAxisMark();
axisMark.textSize = DensityUtil.sp2px(context, 10);
axisMark.textColor = Color.parseColor("#333333");
axisMark.textSpace = DensityUtil.dip2px(context, 5);
// axisMark.lineWidth = DensityUtil.dip2px(context, 0.7f);
// axisMark.lineColor = Color.parseColor("#5E5E5E");
}
public Builder splitSubLen(int splitSubLen) {
axisMark.splitSubLen = splitSubLen;
return this;
}
public Builder textSize(int textSize) {
axisMark.textSize = textSize;
return this;
}
public Builder textColor(int textColor) {
axisMark.textColor = textColor;
return this;
}
public Builder lables(String[] lables) {
axisMark.lables = lables;
axisMark.lableNum = lables.length;
return this;
}
public Builder lableNum(int lableNum) {
axisMark.lableNum = lableNum;
return this;
}
public XAxisMark build(){
return axisMark;
}
}
}
@@ -0,0 +1,158 @@
package com.openxu.hkchart.config;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Typeface;
import com.openxu.utils.DensityUtil;
import java.math.RoundingMode;
import java.text.NumberFormat;
/**
* 坐标轴刻度标签
*/
public class YAxisMark {
private String TAG = "YAxisMark";
/**可设置*/
public int textSize; //设置坐标文字大小
public int textColor; //设置坐标文字颜色
public int textSpace; //设置坐标字体与横轴的距离
public Typeface numberTypeface = Typeface.DEFAULT;
public int lineWidth;
public int lineColor;
public int lableNum;
public MarkType markType;
public int digits; //小数点保留位数
public String unit = ""; //单位 KW
/**自动计算*/
// public List<MarkPoint> markPointList = new ArrayList<>();
public float textHeight, textLead;
public float cal_mark; //轴刻度间隔
public float cal_mark_max = Float.MIN_VALUE; //Y轴刻度最大值
public float cal_mark_min = Float.MAX_VALUE; //Y轴刻度最小值
private YAxisMark() {
}
public static class Builder{
private YAxisMark axisMark;
public Builder(Context context) {
axisMark = new YAxisMark();
axisMark.textSize = DensityUtil.sp2px(context, 10);
axisMark.textColor = Color.parseColor("#BCBCBC");
axisMark.textSpace = DensityUtil.dip2px(context, 5);
axisMark.lineWidth = DensityUtil.dip2px(context, 0.7f);
axisMark.lineColor = Color.parseColor("#e7eaef");
axisMark.lableNum = 5;
axisMark.markType = MarkType.Integer;
}
public Builder numberTypeface(Typeface numberTypeface) {
axisMark.numberTypeface = numberTypeface;
return this;
}
public Builder textSize(int textSize) {
axisMark.textSize = textSize;
return this;
}
public Builder textColor(int textColor) {
axisMark.textColor = textColor;
return this;
}
public Builder lineWidth(int lineWidth) {
axisMark.lineWidth = lineWidth;
return this;
}
public Builder lableNum(int lableNum) {
axisMark.lableNum = lableNum;
return this;
}
public Builder markType(MarkType markType) {
axisMark.markType = markType;
return this;
}
public Builder digits(int digits) {
axisMark.digits = digits;
return this;
}
public Builder unit(String unit) {
axisMark.unit = unit;
return this;
}
public YAxisMark build(){
return axisMark;
}
}
public String getMarkText(float value){
switch (markType){
case Integer:
return (long)value+"";
case Float:
return formattedDecimal(value, digits);
case Percent:
return formattedDecimalToPercentage(value, digits);
}
return value+"";
}
public static class MarkPoint {
public float value; //对应轴上的值
public PointF point; //绘制的点的坐标
public MarkPoint(float value, PointF point) {
this.value = value;
this.point = point;
}
}
/**
* double 类型转换成保留2位小数百分数 0.1 -> 10.00%
* @param decimal
* @return
*/
public static String formattedDecimalToPercentage(double decimal, int digits){
//获取格式化对象
NumberFormat format = NumberFormat.getPercentInstance();
// format.setGroupingUsed(true);
//设置百分数精确度2即保留两位小数
format.setMinimumFractionDigits(digits);
return format.format(decimal);
}
/**
* double 类型转换成保留2位小数 0.1 -> 0.10
* @param decimal
* @return
*/
public static String formattedDecimal(double decimal, int digits){
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(digits);
/*
* setMinimumFractionDigits设置成2
* 如果不这么做,那么当value的值是100.00的时候返回100
* 而不是100.00
*/
// nf.setMinimumFractionDigits(digits);
nf.setRoundingMode(RoundingMode.HALF_UP);
/*
* 如果想输出的格式用逗号隔开,可以设置成true
*/
nf.setGroupingUsed(false);
return nf.format(decimal);
}
}
@@ -0,0 +1,12 @@
package com.openxu.hkchart.data;
/**
* (共用)数据转换器,将服务器返回的数据转换为对应图表需要的数据格式
* @param <T>
* @param <R>
*/
public interface DataTransform <T, R>{
R transform(T t);
}
@@ -0,0 +1,12 @@
package com.openxu.hkchart.data
import android.graphics.PointF
/**
* Author: openXu
* Time: 2021/5/11 10:50
* class: FocusData
* Description: (共用)焦点数据
*/
class FocusData<T>(val data: T, val point: PointF) {
}
@@ -0,0 +1,21 @@
package com.openxu.hkchart.data;
/**
* Author: openXu
* Time: 2020/11/11 15:56
* class: FocusPanelText
* Description: (共用)焦点文字设置
*/
public class FocusPanelText {
public boolean show;
public int textSize; //设置坐标文字大小
public int textColor; //设置坐标文字颜色
public String text;
public FocusPanelText(boolean show, int textSize, int textColor, String text) {
this.show = show;
this.textSize = textSize;
this.textColor = textColor;
this.text = text;
}
}
@@ -0,0 +1,55 @@
package com.openxu.hkchart.data;
/**
* LineChart 的数据
*/
public class LinePoint {
private float valuey ; //y轴值
private String valuex; //x轴刻度值
private String valuexfocus; //x轴焦点值,如果为null,则默认为valuex
// private PointF point;
public LinePoint(String valuex, float valuey) {
this.valuey = valuey;
this.valuex = valuex;
}
public LinePoint(String valuex, String valuexfocus, float valuey) {
this.valuey = valuey;
this.valuex = valuex;
this.valuexfocus = valuexfocus;
}
public String getValuexfocus() {
return valuexfocus;
}
public void setValuexfocus(String valuexfocus) {
this.valuexfocus = valuexfocus;
}
// public PointF getPoint() {
// return point;
// }
//
// public void setPoint(PointF point) {
// this.point = point;
// }
public float getValuey() {
return valuey;
}
public void setValuey(float valuey) {
this.valuey = valuey;
}
public String getValuex() {
return valuex;
}
public void setValuex(String valuex) {
this.valuex = valuex;
}
}
@@ -0,0 +1,641 @@
package com.openxu.hkchart.line
import android.content.Context
import android.graphics.*
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.view.ScaleGestureDetector
import com.openxu.hkchart.BaseChart
import com.openxu.hkchart.config.*
import com.openxu.hkchart.data.FocusData
import com.openxu.hkchart.data.FocusPanelText
import com.openxu.hkchart.data.LinePoint
import com.openxu.utils.DensityUtil
import com.openxu.utils.FontUtil
import com.openxu.utils.LogUtil
import com.openxu.utils.NumberFormatUtil
import java.util.*
import java.util.regex.Pattern
/**
* Author: openXu
* Time: 2021/5/11 12:43
* class: LineChart
* Description:
*/
class LineChart : BaseChart<MutableList<LinePoint?>> {
constructor(context: Context) :this(context, null)
constructor(context: Context, attrs: AttributeSet?) :this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyle: Int):super(context, attrs, defStyle){
}
/***************************1. API👇👇👇***************************/
/**设置数据*/
private var _datas = mutableListOf<MutableList<LinePoint>>()
var datas: MutableList<MutableList<LinePoint>>
get() {return _datas}
set(value) {
_datas.clear()
_datas.addAll(value)
initial()
chartConfig?.let {
if (it.showAnim) chartAnimStarted = false
}
loading = false
}
/***************************1. API👆👆👆***************************/
/***************************2. 子类重写👇👇👇***************************/
/**配置*/
private lateinit var yAxisMark : YAxisMark
private lateinit var xAxisMark : XAxisMark
private var lineType : LineType = LineType.BROKEN
private var lineWidth = DensityUtil.dip2px(context, 1f).toFloat()
private var lineColor = intArrayOf(
Color.parseColor("#f46763"),
Color.parseColor("#3cd595"),
Color.parseColor("#4d7bff"),
Color.parseColor("#4d7bff"))
private var pageShowNum = 0 //第一次页面总数据量 没有设置展示数据量,则默认为全部展示
private var dataTotalCount : Int = -1
/**设置焦点面板显示内容*/
private var focusPanelText: Array<FocusPanelText>? = null
/**初步计算*/
private var maxPointNum = 0 //点最多的线的点数量
private var maxPointIndex = 0//点最多的线的索引
private var pointWidthMin= 0f //最初的每个点占据的宽度,最小缩放值
private var pointWidthMax = 0f //最初的每个点占据的宽度,最大放大值
private var pointWidth = 0f //每个点占据的宽度
override fun initial():Boolean{
if(super.initial()) return true
if(_datas.isNullOrEmpty()) return true
if(chartConfig==null)
throw RuntimeException("---------请配置图表")
var config = chartConfig as LineChartConfig
xAxisMark = config.xAxisMark?:XAxisMark.Builder(context)
.lableNum(5)
.build()
yAxisMark = config.yAxisMark?:YAxisMark.Builder(context)
.lableNum(6)
.markType(MarkType.Integer)
.unit("")
.build()
lineType = config.lineType
lineWidth = config.lineWidth
lineColor = config.lineColor
pageShowNum = config.pageShowNum
dataTotalCount = config.dataTotalCount
if(dataTotalCount<0)
dataTotalCount = datas.size
focusPanelText = config.focusPanelText
maxPointNum = 0
maxPointIndex = 0
for (i in 0 until _datas.size) {
if (maxPointNum <= _datas[i].size) {
maxPointNum = _datas[i].size
maxPointIndex = i
}
}
Log.w(TAG, "点最多的线索引为:$maxPointIndex 点数:$maxPointNum")
paintText.textSize = xAxisMark.textSize.toFloat()
xAxisMark.textHeight = FontUtil.getFontHeight(paintText)
xAxisMark.textLead = FontUtil.getFontLeading(paintText)
Log.w(TAG, "x轴字体高度:" + xAxisMark.textHeight.toString() + " textLead:" + xAxisMark.textLead.toString() + " xAxisMark.textSpace" + xAxisMark.textSpace)
//确定图表最下放绘制位置
rectChart.bottom = rectDrawBounds.bottom - xAxisMark.textHeight - xAxisMark.textSpace
Log.w(TAG, "rectDrawBounds.bottom:" + rectDrawBounds.bottom + " rectChart.bottom=" + rectChart.bottom)
xAxisMark.drawPointY = rectChart.bottom + xAxisMark.textSpace + xAxisMark.textLead
calculateYMark()
paintText.textSize = yAxisMark.textSize.toFloat()
yAxisMark.textHeight = FontUtil.getFontHeight(paintText)
yAxisMark.textLead = FontUtil.getFontLeading(paintText)
Log.w(TAG, "y轴字体高度:" + yAxisMark.textHeight.toString() + " textLead:" + yAxisMark.textLead)
var maxLable = if (yAxisMark.getMarkText(yAxisMark.cal_mark_max).length > yAxisMark.getMarkText(yAxisMark.cal_mark_min).length) yAxisMark.getMarkText(yAxisMark.cal_mark_max) else yAxisMark.getMarkText(yAxisMark.cal_mark_min)
LogUtil.w(TAG, "Y刻度最大字符串:$maxLable")
if (!TextUtils.isEmpty(yAxisMark.unit)) maxLable = if (yAxisMark.unit.length > maxLable.length) yAxisMark.unit else maxLable
LogUtil.w(TAG, "Y刻度最大字符串:$maxLable")
rectChart.left = paddingLeft + yAxisMark.textSpace + FontUtil.getFontlength(paintText, maxLable)
rectChart.top = rectDrawBounds.top + yAxisMark.textHeight / 2 +
if (TextUtils.isEmpty(yAxisMark.unit)) 0f else yAxisMark.textHeight + yAxisMark.textSpace
rectChart.right = rectDrawBounds.right
//没有设置展示数据量,则默认为全部展示
if (pageShowNum <= 0) pageShowNum = maxPointNum
if (maxPointNum < pageShowNum) //最多的点小于需要显示的点,则全部展示
pageShowNum = maxPointNum
Log.w(TAG, "pageShowNum=${pageShowNum}")
pointWidthMin = rectChart.width() / (maxPointNum - 1) //缩小到全部显示
pointWidth = rectChart.width() / (pageShowNum - 1)
pointWidthMax = rectChart.width() / 4 //最大只能放大到每个标签显示5个点
// pointWidthMax = rectChart.width() / (xAxisMark.lableNum-1) / 5; //最大只能放大到每个标签显示5个点
// pointWidthMax = rectChart.width() / (xAxisMark.lableNum-1) / 5; //最大只能放大到每个标签显示5个点
Log.w(TAG, "缩放最小最大宽度=$pointWidthMin $pointWidthMax")
//数据没有展示完,说明可以滚动
scrollXMax = 0f
if (pageShowNum < maxPointNum) scrollXMax = -(pointWidth * (maxPointNum - 1) - rectChart.width()) //最大滚动距离,是一个负值
scrollx = if (config.displayScheme==DisplayScheme.SHOW_BEGIN) 0f else scrollXMax
caculateXMark()
focusPanelText?.let {
//计算焦点面板
//2020-10-16 06:00
//零序电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
foucsRectWidth = 0f
foucsRectHeight = foucsRectSpace * 2f
var text: String
maxLable = (if (yAxisMark.getMarkText(yAxisMark.cal_mark_max).length > yAxisMark.getMarkText(yAxisMark.cal_mark_min).length) yAxisMark.getMarkText(yAxisMark.cal_mark_max) else yAxisMark.getMarkText(yAxisMark.cal_mark_min))
.toString() + if (TextUtils.isEmpty(yAxisMark.unit)) "" else yAxisMark.unit
for (i in it.indices) {
if (it[i].show) {
paintText.textSize = it[i].textSize.toFloat()
if (i == 0) {
//x轴数据
foucsRectWidth = Math.max(foucsRectWidth, FontUtil.getFontlength(paintText, it[i].text))
foucsRectHeight += FontUtil.getFontHeight(paintText)
} else {
// text = focusPanelText[i].text+maxLable+ yAxisMark.unit;
text = it[i].text + maxLable
foucsRectWidth = Math.max(foucsRectWidth, FontUtil.getFontlength(paintText, text))
Log.w(TAG, "计算面板:$text $foucsRectWidth")
foucsRectHeight += foucsRectTextSpace + FontUtil.getFontHeight(paintText)
}
}
}
foucsRectWidth += foucsRectSpace * 4
}
/**计算点坐标 */
/* for (int i = 0; i < _datas.size(); i++) {
List<LinePoint> linePoints = _datas.get(i);
for (int j = 0; j < linePoints.size(); j++) {
if (linePoints.get(j).getValuey() == null)
continue;
linePoints.get(j).setPoint(new PointF(
rectChart.left + j * pointWidth,
rectChart.bottom - (rectChart.bottom - rectChart.top) /
(yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) * (linePoints.get(j).getValuey() - yAxisMark.cal_mark_min)
));
}
}*/Log.w(TAG, "计算scrollXMax=$scrollXMax scrollx=$scrollx")
return true
}
override fun drawChart(canvas: Canvas?) {
LogUtil.e(TAG, "-----------开始绘制,当前缩放系数$scalex 偏移量$scrollx")
if(_datas.isNullOrEmpty())
return
val startTime = System.currentTimeMillis()
val yMarkSpace = (rectChart.bottom - rectChart.top) / (yAxisMark.lableNum - 1)
paintEffect.style = Paint.Style.STROKE
paintEffect.strokeWidth = yAxisMark.lineWidth.toFloat()
paintEffect.color = yAxisMark.lineColor
paintText.textSize = yAxisMark.textSize.toFloat()
paintText.color = yAxisMark.textColor
// canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
// canvas.drawLine(rectChart.left, rectChart.top, rectChart.left, rectChart.bottom, paint);
val effects: PathEffect = DashPathEffect(floatArrayOf(15f, 6f, 15f, 6f), 0f)
paintEffect.pathEffect = effects
paint.style = Paint.Style.STROKE
paint.color = yAxisMark.lineColor
for (i in 0 until yAxisMark.lableNum) {
/**绘制横向线 */
paint.strokeWidth = if (i == 0) yAxisMark.lineWidth * 2.5f else yAxisMark.lineWidth.toFloat()
canvas!!.drawLine(rectChart.left, rectChart.bottom - yMarkSpace * i,
rectChart.right, rectChart.bottom - yMarkSpace * i, paint)
/**绘制y刻度 */
val text = yAxisMark.getMarkText(yAxisMark.cal_mark_min + i * yAxisMark.cal_mark)
canvas!!.drawText(text,
rectChart.left - yAxisMark.textSpace - FontUtil.getFontlength(paintText, text),
rectChart.bottom - yMarkSpace * i - yAxisMark.textHeight / 2 + yAxisMark.textLead, paintText)
}
//绘制Y轴单位
if (!TextUtils.isEmpty(yAxisMark.unit)) {
canvas!!.drawText(yAxisMark.unit,
rectChart.left - yAxisMark.textSpace - FontUtil.getFontlength(paintText, yAxisMark.unit),
rectChart.top - yAxisMark.textSpace - yAxisMark.textHeight * 3 / 2 + yAxisMark.textLead, paintText)
}
/**绘制x轴刻度 */
// if(xAxisMark.lables!=null){
// //绘制固定的
// drawFixedXLable(canvas);
// lables = xAxisMark.lables;
// }else{
// drawXLable(canvas);
// }
/**绘制折线 */
if (maxPointNum <= 0) return
paintText.textSize = xAxisMark.textSize.toFloat()
paintText.color = xAxisMark.textColor
paint.strokeWidth = lineWidth
val radius = DensityUtil.dip2px(context, 3f).toFloat()
val path = Path()
val lastPoint = PointF()
val currentPoint = PointF()
var startIndex = (-scrollx / pointWidth).toInt()
var endIndex = ((-scrollx + rectChart.width()) / pointWidth + 1).toInt()
startIndex = Math.max(startIndex, 0)
endIndex = Math.min(endIndex, maxPointNum - 1)
// Log.w(TAG, "绘制索引:"+startIndex+" 至 "+endIndex+" scrollx="+scrollx);
// Log.w(TAG, "绘制索引:"+startIndex+" 至 "+endIndex+" scrollx="+scrollx);
val clipRect = RectF(rectChart.left - radius - lineWidth / 2, rectChart.top,
rectChart.right + radius + lineWidth / 2,
rectChart.bottom + xAxisMark.textSpace + xAxisMark.textHeight)
canvas!!.saveLayer(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, paint, Canvas.ALL_SAVE_FLAG)
var drawXLable = false
for (i in _datas.indices) {
val linePoints: List<LinePoint> = _datas[i]
if (linePoints.isNullOrEmpty()) break
path.reset()
for (j in startIndex..endIndex) {
if (j > startIndex + (endIndex - startIndex) * chartAnimValue) break
//每条线的点数量可能不一样
if (j >= linePoints.size /* || linePoints.get(j).getValuey() == null*/) continue
currentPoint.x = scrollx + rectChart.left + j * pointWidth
currentPoint.y = rectChart.bottom - (rectChart.bottom - rectChart.top) /
(yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) * (linePoints[j].valuey - yAxisMark.cal_mark_min)
if (lineType == LineType.BROKEN) {
if (path.isEmpty) {
path.moveTo(currentPoint.x, currentPoint.y)
} else {
path.lineTo(currentPoint.x, currentPoint.y)
}
} else {
if (j == startIndex) {
path.moveTo(currentPoint.x, currentPoint.y)
} else if (j == startIndex + 1) { //二阶
val x = lastPoint.x + (currentPoint.x - lastPoint.x) / 2
val y = currentPoint.y
path.quadTo(x, y, currentPoint.x, currentPoint.y)
} else if (j <= endIndex - 1) { //三阶
val x1 = lastPoint.x + (currentPoint.x - lastPoint.x) / 2
val y1 = lastPoint.y
val y2 = currentPoint.y
path.cubicTo(x1, y1, x1, y2, currentPoint.x, currentPoint.y)
} else if (j == endIndex) { //最后一个 二阶
val x = lastPoint.x + (currentPoint.x - lastPoint.x) / 2
val y = lastPoint.y
path.quadTo(x, y, currentPoint.x, currentPoint.y)
}
lastPoint.x = currentPoint.x
lastPoint.y = currentPoint.y
}
drawXLable = false
if (i == maxPointIndex) {
drawXLable = if (xlables.size > 0 && xlables.contains(linePoints[j].valuex)) {
true
} else {
if (chartConfig!!.displayScheme==DisplayScheme.SHOW_BEGIN) {
j % xindexSpace === 0
} else {
(j - (maxPointNum - 1)) % xindexSpace === 0
}
}
if (drawXLable) {
// FLog.v(xlables.size()+"绘制x轴刻度"+linePoints.get(j).getValuex());
var x: Float
x = if (j == 0) {
currentPoint.x
} else if (j == maxPointNum - 1) {
currentPoint.x - FontUtil.getFontlength(paintText, linePoints[j].valuex)
} else {
currentPoint.x - FontUtil.getFontlength(paintText, linePoints[j].valuex) / 2
}
canvas!!.drawText(linePoints[j].valuex, x,
rectChart.bottom + xAxisMark.textSpace + xAxisMark.textLead, paintText)
}
}
}
paint.style = Paint.Style.STROKE
paint.strokeWidth = lineWidth
paint.color = lineColor[i]
/**
* Xfermode 有三个实现类: AvoidXfermode,PixelXorXfermode,PorterDuffXfermode
*
* 1. AvoidXfermode:对原来的像素进行处理,AvoidXfermode不支持硬件加速,使用它需要关闭硬件加速。其次,最好在新建图层上绘制. 构造方法参数分别代表:
* opColor被处理的像素颜色
* 容差值(原像素在一定范围内与传入的像素相似则处理)
* 模式: TARGET模式判断画布上是否有与opColor相似(容差)的颜色,如果有,则把该区域“染”上一层我们”画笔的颜色“,
* AVOID与TARGET相反,将画布上与传入opColor不相似的染上画笔颜色
* 比如下面的代码中首先绘制一个图片,然后对图片上的白色像素进行处理,染色为画笔的红色
* canvas.drawBitmap(mBmp,null,new Rect(0,0,width,height),mPaint);
* mPaint.setXfermode(new AvoidXfermode(Color.WHITE,100, AvoidXfermode.Mode.TARGET));
* mPaint.setColor(Color.RED)
* canvas.drawRect(0,0,width,height,mPaint);
*
* 2. PixelXofXermode 没设么用,不支持硬件加速
*
* 3. PorterDuffXfermode是最常用的,它用于描述2D图像图像合成的模式,一共有12中模式描述数字图像合成的基本手法,包括
* Clear、Source Only、Destination Only、Source Over、Source In、Source
* Out、Source Atop、Destination Over、Destination In、Destination
* Out、Destination Atop、XOR。通过组合使用 Porter-Duff 操作,可完成任意 2D
* 图像的合成。在绘图时会先检查该画笔Paint对象有没有设置Xfermode,如果没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;
* 如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。
*
* 使用时通常结合canvas.saveLayer(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, paint, Canvas.ALL_SAVE_FLAG)创建一个全透明的layer层,否则会产生不可预期的结果
*
* 使用它时要搞清楚两个概念,DST表示在画笔设置它之前画布上已经绘制的内容,SRC表示设置之后绘制的内容,PorterDuffXfermode就是将两个部分的像素按照一定的模式进行合并
*/
//这里设置DST_OVER,目的是将绘制path之前已经绘制的线上的点显示的线之上,要不然线会遮住小圆点
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
canvas!!.drawPath(path, paint)
paint.xfermode = null
}
// canvas.restore();
// Log.w(TAG, "绘制一次需要:"+(System.currentTimeMillis() - startTime)+ " ms");
// canvas.restore();
// Log.w(TAG, "绘制一次需要:"+(System.currentTimeMillis() - startTime)+ " ms");
drawFocus(canvas)
}
/**绘制焦点 */
private val focusLineColor = Color.parseColor("#319A5A")
private val focusLineSize = DensityUtil.dip2px(context, 1f)
private val foucsRectTextSpace = DensityUtil.dip2px(context, 3f)
private val foucsRectSpace = DensityUtil.dip2px(context, 6f)
//焦点面板矩形宽高
private var foucsRectWidth = 0f
private var foucsRectHeight = 0f
private fun drawFocus(canvas: Canvas?) {
try {
if (null == focusData || maxPointNum == 0 || null==canvas) return
//绘制竖直虚线
val effects: PathEffect = DashPathEffect(floatArrayOf(8f, 5f, 8f, 5f), 0f)
paintEffect.style = Paint.Style.STROKE
paintEffect.strokeWidth = focusLineSize.toFloat()
paintEffect.color = focusLineColor
paintEffect.pathEffect = effects
val path = Path()
path.moveTo(focusData!!.point.x, rectChart.bottom)
path.lineTo(focusData!!.point.x, rectChart.top)
canvas.drawPath(path, paintEffect)
//面板
val showLeft: Boolean = focusData!!.point.x - rectChart.left > (rectChart.right - rectChart.left) / 2
val rect = RectF(
if (showLeft) focusData!!.point.x - foucsRectWidth - 30 else focusData!!.point.x + 30,
rectChart.top /*+ (rectChart.bottom - rectChart.top)/2 - foucsRectHeight/2*/,
if (showLeft) focusData!!.point.x - 30 else focusData!!.point.x + foucsRectWidth + 30,
rectChart.top + foucsRectHeight /*+ (rectChart.bottom - rectChart.top)/2 + foucsRectHeight/2*/
)
paint.style = Paint.Style.FILL
paint.color = Color.WHITE
paint.alpha = 230
canvas.drawRect(rect, paint)
//面板中的文字
//2020-10-16 06:00
//零序电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
//A相电流:15.2KW
var text = ""
var top = rect.top + foucsRectSpace
val currentPoint = PointF()
val radius = DensityUtil.dip2px(context, 2.5f).toFloat()
focusPanelText?.let {
for (i in it.indices) {
if (it[i].show) {
paintText.textSize = it[i].textSize.toFloat()
paintText.color = it[i].textColor
if (i == 0) {
//x轴数据
for (point in focusData!!.data) {
if (point != null) {
text = if (TextUtils.isEmpty(point.valuexfocus)) point.valuex else point.valuexfocus
break
}
}
} else {
top += foucsRectTextSpace.toFloat()
/* text = focusPanelText[i].text+
(focusData!!.data.get(i-1)==null?"":getFocusYText(focusData!!.data.get(i-1).getValuey()))
+ yAxisMark.unit;*/
text = (it[i].text +
(if (focusData!!.data[i - 1] == null) "" else YAxisMark.formattedDecimal(focusData!!.data[i - 1]!!.valuey.toDouble(), 2))
+ yAxisMark.unit)
//绘制焦点圆圈
if (focusData!!.data[i - 1] != null) {
currentPoint.x = focusData!!.point.x
currentPoint.y = rectChart.bottom - (rectChart.bottom - rectChart.top) /
(yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) * (focusData!!.data[i - 1]!!.valuey - yAxisMark.cal_mark_min)
paint.style = Paint.Style.STROKE
paint.color = lineColor.get(i - 1)
canvas.drawCircle(currentPoint.x, currentPoint.y, radius, paint)
paint.style = Paint.Style.FILL
paint.color = Color.WHITE
canvas.drawCircle(currentPoint.x, currentPoint.y, radius - lineWidth / 2, paint)
}
}
canvas.drawText(text,
rect.left + foucsRectSpace,
top + FontUtil.getFontLeading(paintText), paintText)
top += FontUtil.getFontHeight(paintText)
}
}
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
/***************************事件👇👇👇***************************/
private var beginPointWidth = 0f
override fun onScaleBegin(detector: ScaleGestureDetector) {
beginPointWidth = pointWidth
}
override fun onScale(detector: ScaleGestureDetector, beginScrollx: Float) {
pointWidth *= detector.scaleFactor
//缩放范围约束
// pointWidthMin = rectChart.width() / (maxPointNum-1); //缩小到全部显示
// pointWidth = rectChart.width() / (pageShowNum-1);
// pointWidthMax = rectChart.width() / 4; //最大只能放大到每个标签显示5个点
pointWidth = Math.min(pointWidth, pointWidthMax)
pointWidth = Math.max(pointWidth, pointWidthMin)
//重新计算最大偏移量
scrollXMax = -(pointWidth * (maxPointNum - 1) - rectChart.width()) //最大滚动距离,是一个负值
//计算当前偏移量
Log.i(TAG, "=============================当前偏移:$scrollx 两点宽度 = $pointWidth")
//为了保证焦点对应的点位置不变,是使用公式: beginScrollx + rectChart.left + focusIndex*beginPointWidth = scrollx + rectChart.left + focusIndex*pointWidth
scrollx = beginScrollx + focusIndex * (beginPointWidth - pointWidth)
scrollx = Math.min(scrollx, 0f)
scrollx = Math.max(scrollXMax, scrollx)
caculateXMark()
// Log.i(TAG, "缩放后偏移:"+scrollx);
}
override fun onFocusTouch(point: PointF?) {
focusData = null
point?.let {
if (!_datas.isNullOrEmpty() && focusPanelText!=null) {
//避免滑出
point.x = Math.max(point.x, rectChart.left);
point.x = Math.min(point.x, rectChart.right);
//获取焦点对应的数据的索引
focusIndex =((-scrollx + (point.x - rectChart.left)) / pointWidth).toInt()
if ((-scrollx + (point.x - rectChart.left)) - focusIndex * pointWidth > pointWidth / 2) {
LogUtil.e(TAG, "========焦点在下一个点范围了:" + focusIndex);
focusIndex += 1;
}
LogUtil.e(TAG, "========焦点索引:" + focusIndex);
focusIndex = Math.max(0, Math.min(focusIndex, maxPointNum - 1));
point.x = rectChart.left + (focusIndex * pointWidth + scrollx);
val datas = mutableListOf<LinePoint?>()
for (line in _datas) {
if (focusIndex < line.size) datas.add(line[focusIndex])
else datas.add(null)
}
focusData = FocusData(datas, it)
}
}
}
/***************************事件👆👆👆***************************/
/***************************2. 子类重写👆👆👆***************************/
/***************************3. 特殊👇👇👇***************************/
private var xindexSpace: Int = 0
private val xlables: MutableList<String> = ArrayList()
private fun caculateXMark() {
xlables.clear()
if (xAxisMark.lables != null && xAxisMark.lables.size > 0) {
xlables.addAll(Arrays.asList(*xAxisMark.lables))
return
}
val markSpace = rectChart.width() / (xAxisMark.lableNum - 1)
//每隔多少展示一个标签
xindexSpace = (markSpace / pointWidth).toInt()
xindexSpace = Math.max(xindexSpace, 1)
}
private fun calculateYMark() {
if (maxPointNum == 0) {
//没有数据
yAxisMark.cal_mark_min = 0f
yAxisMark.cal_mark_max = yAxisMark.lableNum - 1.toFloat()
yAxisMark.cal_mark = 1f
return
}
val redundance = 1.01f //y轴最大和最小值冗余
yAxisMark.cal_mark_max = -Float.MAX_VALUE //Y轴刻度最大值
yAxisMark.cal_mark_min = Float.MAX_VALUE //Y轴刻度最小值
for (linePoints in _datas) {
for (point in linePoints) {
yAxisMark.cal_mark_max = Math.max(yAxisMark.cal_mark_max, point.valuey)
yAxisMark.cal_mark_min = Math.min(yAxisMark.cal_mark_min, point.valuey)
}
}
LogUtil.w(TAG, "真实最小最大值:" + yAxisMark.cal_mark_min + " " + yAxisMark.cal_mark_max)
//只有一个点的时候
if (yAxisMark.cal_mark_min == yAxisMark.cal_mark_max) {
if (yAxisMark.cal_mark_min > 0) {
yAxisMark.cal_mark_min = 0f
} else if (yAxisMark.cal_mark_min == 0f) {
yAxisMark.cal_mark_max = 1f
} else if (yAxisMark.cal_mark_min < 0) {
yAxisMark.cal_mark_max = 0f
}
}
if (yAxisMark.markType == MarkType.Integer) {
val min = if (yAxisMark.cal_mark_min > 0) 0 else yAxisMark.cal_mark_min.toInt()
val max = yAxisMark.cal_mark_max.toInt()
var mark = (max - min) / (yAxisMark.lableNum - 1) + if ((max - min) % (yAxisMark.lableNum - 1) > 0) 1 else 0
mark = if (mark == 0) 1 else mark //最大值和最小值都为0的情况
val first = (mark.toString() + "").substring(0, 1).toInt() + 1
if ((mark.toString() + "").length == 1) {
//YMARK = 1、2、5、10
mark = if (mark == 3 || mark == 4 || mark == 6 || mark == 7 || mark == 8 || mark == 9) if (mark == 3 || mark == 4) 5 else 10 else mark
} else if ((mark.toString() + "").length == 2) {
mark = first * 10
} else if ((mark.toString() + "").length == 3) {
mark = first * 100
} else if ((mark.toString() + "").length == 4) {
mark = first * 1000
} else if ((mark.toString() + "").length == 5) {
mark = first * 10000
} else if ((mark.toString() + "").length == 6) {
mark = first * 100000
}
yAxisMark.cal_mark_min = 0f
yAxisMark.cal_mark_max = mark * (yAxisMark.lableNum - 1).toFloat()
yAxisMark.cal_mark = mark.toFloat()
} else { //Float //Percent
yAxisMark.cal_mark_max = if (yAxisMark.cal_mark_max < 0) yAxisMark.cal_mark_max / redundance else yAxisMark.cal_mark_max * redundance
yAxisMark.cal_mark_min = if (yAxisMark.cal_mark_min < 0) yAxisMark.cal_mark_min * redundance else yAxisMark.cal_mark_min / redundance
yAxisMark.cal_mark = (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) / (yAxisMark.lableNum - 1)
}
//小数点位
if (yAxisMark.digits == 0) {
val mark = (yAxisMark.cal_mark_max - yAxisMark.cal_mark_min) / (yAxisMark.lableNum - 1)
if (mark < 1) {
val pattern = "[1-9]"
val p = Pattern.compile(pattern)
val m = p.matcher(mark.toString() + "") // 获取 matcher 对象
m.find()
val index = m.start()
yAxisMark.digits = index - 1
LogUtil.w(TAG, mark.toString() + "第一个大于0的数字位置:" + index + " 保留小数位数:" + yAxisMark.digits)
}
}
LogUtil.w(TAG, "最终最小最大值:" + yAxisMark.cal_mark_min + " " + yAxisMark.cal_mark_max + " " + yAxisMark.cal_mark)
}
/**绘制焦点 */
/**********************************3. 测量和绘制👇 */
/**获取焦点面板上数值展示字符串 */
fun getFocusYText(value: Float): String? {
when (yAxisMark.markType) {
MarkType.Integer -> return (value as Long).toString()
MarkType.Float -> return NumberFormatUtil.formattedDecimal(value.toDouble())
MarkType.Percent -> return NumberFormatUtil.formattedDecimalToPercentage(value.toDouble(), 2)
}
return value.toString() + ""
}
/**绘制 XAxisMark.lables 设置的固定x刻度, */
private fun drawFixedXLable(canvas: Canvas) {
val oneWidth = (-scrollXMax + rectChart.width()) / (xAxisMark.lables.size - 1)
Log.w(TAG, "最大滚动:" + scrollXMax + " 图表宽度" + rectChart.width() + " lable数量" + xAxisMark.lables.size + " 单个跨度:" + oneWidth)
paintText.textSize = xAxisMark.textSize.toFloat()
paintText.color = xAxisMark.textColor
var x: Float
val restoreCount = canvas.save()
canvas.clipRect(RectF(rectChart.left, rectChart.bottom, rectChart.right, rectChart.bottom + xAxisMark.textSpace + xAxisMark.textHeight))
for (i in xAxisMark.lables.indices) {
val text = xAxisMark.lables[i]
x = if (i == 0) {
scrollx + rectChart.left + i * oneWidth
} else if (i == xAxisMark.lables.size - 1) {
scrollx + rectChart.left + i * oneWidth - FontUtil.getFontlength(paintText, text)
} else {
scrollx + rectChart.left + i * oneWidth - FontUtil.getFontlength(paintText, text) / 2
}
canvas.drawText(text, x,
rectChart.bottom + xAxisMark.textSpace + xAxisMark.textLead, paintText)
}
canvas.restoreToCount(restoreCount)
}
/***************************3. 特殊👆👆👆***************************/
}
@@ -0,0 +1,80 @@
package com.openxu.hkchart.loading;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import com.openxu.utils.DensityUtil;
import java.util.ArrayList;
public class BallPulseIndicator extends LoadingIndicator {
private float loadRadius;
private float loadCircleSpace;
private float loadingAnimValueMax = 1;
private float loadingAnimValueMin = 0.4f;
private int[] loadCircleColors = new int[]{
Color.parseColor("#DB4528"),
Color.parseColor("#5f93e7"),
Color.parseColor("#fda33c")};
private float[] scaleFloats = new float[]{loadingAnimValueMax, loadingAnimValueMax, loadingAnimValueMax};
public BallPulseIndicator(Context context) {
super(context);
loadRadius = DensityUtil.dip2px(context, 5);
loadCircleSpace = DensityUtil.dip2px(context, 4);
}
@Override
public void draw(Canvas canvas, Paint paint) {
Rect rect = getBounds();
paint.setStyle(Paint.Style.FILL);
for(int i = 0; i<loadCircleColors.length; i++){
paint.setColor(loadCircleColors[i]);
canvas.drawCircle(rect.left+rect.width()/2 - loadRadius*2 - loadCircleSpace + i*(loadRadius*2 + loadCircleSpace),
rect.top+rect.height()/2,
loadRadius*scaleFloats[i],paint);
}
/* float circleSpacing = 4;
float radius=(rect.width()-circleSpacing*(loadCircleColors.length-1))/(loadCircleColors.length*2);
float x = rect.width()/ 2-(radius*2+circleSpacing);
float y = rect.height() / 2;
for (int i = 0; i < loadCircleColors.length; i++) {
paint.setColor(loadCircleColors[i]);
canvas.save();
float translateX=x+(radius*2)*i+circleSpacing*i;
canvas.translate(translateX, y);
canvas.scale(scaleFloats[i], scaleFloats[i]);
canvas.drawCircle(0, 0, radius, paint);
canvas.restore();
}*/
}
@Override
public ArrayList<ValueAnimator> onCreateAnimators() {
ArrayList<ValueAnimator> animators=new ArrayList<>();
int[] delays=new int[]{120,240,360};
for (int i = 0; i < 3; i++) {
final int index=i;
ValueAnimator scaleAnim=ValueAnimator.ofFloat(loadingAnimValueMax, loadingAnimValueMin,loadingAnimValueMax);
scaleAnim.setDuration(900);
scaleAnim.setRepeatCount(-1);
scaleAnim.setStartDelay(delays[i]);
addUpdateListener(scaleAnim, animation -> {
scaleFloats[index] = (float) animation.getAnimatedValue();
// Log.w(TAG, index + "刷新loading "+scaleFloats[index]);
invalidateSelf();
});
animators.add(scaleAnim);
}
return animators;
}
}
@@ -0,0 +1,131 @@
package com.openxu.hkchart.loading;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import androidx.annotation.Nullable;
/**
* https://github.com/81813780/AVLoadingIndicatorView/blob/master/library/src/main/java/com/wang/avi/Indicator.java
*
*
*/
public abstract class LoadingIndicator extends Drawable implements Animatable {
protected String TAG = getClass().getSimpleName();
protected List<ValueAnimator> mAnimators;
private HashMap<ValueAnimator,ValueAnimator.AnimatorUpdateListener> mUpdateListeners;
// protected Rect drawBounds;
private Paint mPaint;
public LoadingIndicator(Context context) {
mUpdateListeners = new HashMap<>();
// drawBounds = new Rect();
mPaint=new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
}
@Override
public void draw(Canvas canvas) {
draw(canvas,mPaint);
}
/**绘制loading*/
protected abstract void draw(Canvas canvas, Paint paint);
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
//没有透明格式
return PixelFormat.OPAQUE;
}
public abstract ArrayList<ValueAnimator> onCreateAnimators();
/**
* Your should use this to add AnimatorUpdateListener when
* create animator , otherwise , animator doesn't work when
* the animation restart .
* @param updateListener
*/
public void addUpdateListener(ValueAnimator animator, ValueAnimator.AnimatorUpdateListener updateListener){
mUpdateListeners.put(animator,updateListener);
}
@Override
public void start() {
if(mAnimators==null || mAnimators.size()<=0)
mAnimators = onCreateAnimators();
if (mAnimators == null)
return;
// If the animators has not ended, do nothing.
if (isStarted()) {
return;
}
Log.w(TAG, "执行加载动画");
for (int i = 0; i < mAnimators.size(); i++) {
ValueAnimator animator = mAnimators.get(i);
//when the animator restart , add the updateListener again because they
// was removed by animator stop .
ValueAnimator.AnimatorUpdateListener updateListener=mUpdateListeners.get(animator);
if (updateListener!=null)
animator.addUpdateListener(updateListener);
animator.start();
}
}
private boolean isStarted() {
for (ValueAnimator animator : mAnimators)
return animator.isStarted();
return false;
}
@Override
public boolean isRunning() {
for (ValueAnimator animator : mAnimators)
return animator.isRunning();
return false;
}
@Override
public void stop() {
if (mAnimators!=null){
for (ValueAnimator animator : mAnimators) {
if (animator != null && animator.isStarted()) {
animator.removeAllUpdateListeners();
animator.end();
}
}
}
}
}
@@ -0,0 +1,113 @@
package com.openxu.utils;
import android.util.Log;
import com.openxu.cview.xmstock20201030.build.AxisMark;
import java.util.Arrays;
/**
* 图表计算工具
*/
public class ChartCalUtil {
private static String TAG = "ChartCalUtil";
/**
* 计算X轴刻度点lable值
* @param axisMark
*/
public static void calXLable(AxisMark axisMark){/*List datas, String field, int num){*/
Object[] newDatas;
if(axisMark.datas==null || axisMark.field==null) {
axisMark.lables = new String[]{};
}
if(axisMark.datas.size()<=axisMark.lableNum){
axisMark.lables = new String[axisMark.datas.size()];
newDatas = axisMark.datas.toArray();
}else{
axisMark.lables = new String[axisMark.lableNum];
newDatas = new Object[axisMark.lableNum];
//取第一个和最后一个
newDatas[0] = axisMark.datas.get(0);
newDatas[axisMark.lableNum-1] = axisMark.datas.get(axisMark.datas.size()-1);
int part = axisMark.datas.size() / axisMark.lableNum;
Log.i(TAG, "均匀取值:"+part);
for(int i = 1; i< axisMark.lableNum-1; i++){
newDatas[i] = axisMark.datas.get(i*part);
}
}
for(int j = 0; j<newDatas.length; j++){
axisMark.lables[j] = ReflectUtil.getField(newDatas[j], axisMark.field).toString();
}
}
/**
* 根据传入的Y轴信息,从数据中获取最小最大值,并获取到Y刻度点值
* @param axisMark
* @return
*/
public static AxisMark calYLable(AxisMark axisMark){
axisMark.cal_mark_max = Float.MIN_VALUE; //Y轴刻度最大值
axisMark.cal_mark_min = Float.MAX_VALUE; //Y轴刻度最小值
for(Object data : axisMark.datas){
String str = ReflectUtil.getField(data, axisMark.field).toString();
try {
if(str.contains("%")){
str = str.substring(0,str.indexOf("%"));//百分数不能直接强转
axisMark.cal_mark = Float.parseFloat(str) /100.0f;
}else{
axisMark.cal_mark = Float.parseFloat(str);
}
// LogUtil.d(TAG, str+" Y轴 被识别为Float = "+YMARK);
if(axisMark.cal_mark>axisMark.cal_mark_max)
axisMark.cal_mark_max = axisMark.cal_mark;
if(axisMark.cal_mark<axisMark.cal_mark_min)
axisMark.cal_mark_min = axisMark.cal_mark;
}catch (Exception e){
}
}
LogUtil.i(TAG, "Y轴真实axisMark.cal_mark_min="+axisMark.cal_mark_min+" axisMark.cal_mark_max="+axisMark.cal_mark_max);
if(axisMark.cal_mark_max>0)
axisMark.cal_mark_max *= 1.1f;
else
axisMark.cal_mark_max /= 1.1f;
if(axisMark.cal_mark_min>0)
axisMark.cal_mark_min /= 1.1f;
else
axisMark.cal_mark_min *= 1.1f;
if(axisMark.cal_mark_min>0)
axisMark.cal_mark_min = 0;
axisMark.cal_mark = (axisMark.cal_mark_max-axisMark.cal_mark_min)/(axisMark.lableNum - 1);
axisMark.lables = new String[axisMark.lableNum];
if(axisMark.lableType == AxisMark.LABLE_TYPE.INTEGER){
axisMark.cal_mark = (int)axisMark.cal_mark + 1;
axisMark.cal_mark_min = (int)axisMark.cal_mark_min;
axisMark.cal_mark_max = axisMark.cal_mark_min+axisMark.cal_mark*(axisMark.lableNum-1);
}else if(axisMark.lableType == AxisMark.LABLE_TYPE.PERCENTAGE){
}else if(axisMark.lableType == AxisMark.LABLE_TYPE.FLOAT){
}
LogUtil.i(TAG, "Y轴axisMark.cal_mark_min="+axisMark.cal_mark_min+" axisMark.cal_mark_max="+axisMark.cal_mark_max+" axisMark.cal_mark="+axisMark.cal_mark);
for(int i = 0; i< axisMark.lableNum; i++){
float num = axisMark.cal_mark_min + i * axisMark.cal_mark;
if(axisMark.lableType == AxisMark.LABLE_TYPE.INTEGER){
axisMark.lables[i] = ((int)num)+"";
}else if(axisMark.lableType == AxisMark.LABLE_TYPE.PERCENTAGE){
axisMark.lables[i] = NumberFormatUtil.formattedDecimalToPercentage(num, 2);
}else if(axisMark.lableType == AxisMark.LABLE_TYPE.FLOAT){
axisMark.lables[i] = NumberFormatUtil.formattedDecimal(num);
}
}
Log.i(TAG, "计算Y轴刻度:"+ Arrays.asList(axisMark.lables));
return axisMark;
}
}

Some files were not shown because too many files have changed in this diff Show More