的
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -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 = '' //项目主页,可以不写
|
||||
//}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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" // 版本号
|
||||
}
|
||||
}
|
||||
@@ -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" // 版本号
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+21
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+383
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+540
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+285
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+638
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+162
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+673
@@ -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
+46
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+618
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+40
@@ -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;
|
||||
}
|
||||
}
|
||||
+49
@@ -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;
|
||||
}
|
||||
}
|
||||
+134
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
+97
@@ -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;
|
||||
}
|
||||
}
|
||||
+32
@@ -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;
|
||||
}
|
||||
}
|
||||
+77
@@ -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;
|
||||
}
|
||||
}
|
||||
+97
@@ -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;
|
||||
}
|
||||
}
|
||||
+102
@@ -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;
|
||||
}
|
||||
}
|
||||
+39
@@ -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;
|
||||
}
|
||||
}
|
||||
+86
@@ -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;
|
||||
}
|
||||
}
|
||||
+30
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.openxu.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public class DensityUtil {
|
||||
/**
|
||||
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
|
||||
*/
|
||||
public static int dip2px(Context context, float dpValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
|
||||
*/
|
||||
public static int px2dip(Context context, float pxValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (pxValue / scale + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将px值转换为sp值,保证文字大小不变
|
||||
*
|
||||
* @param pxValue
|
||||
* @return
|
||||
*/
|
||||
public static int px2sp(Context context, float pxValue) {
|
||||
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
|
||||
return (int) (pxValue / fontScale + 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将sp值转换为px值,保证文字大小不变
|
||||
* @param spValue
|
||||
* @return
|
||||
*/
|
||||
public static int sp2px(Context context, float spValue) {
|
||||
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
|
||||
return (int) (spValue * fontScale + 0.5f);
|
||||
}
|
||||
|
||||
public static int getScreenWidth(Context context){
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
int width = wm.getDefaultDisplay().getWidth();
|
||||
int height = wm.getDefaultDisplay().getHeight();
|
||||
return width;
|
||||
}
|
||||
|
||||
|
||||
public static int getScreenHeight(Context context){
|
||||
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
int height = wm.getDefaultDisplay().getHeight();
|
||||
return height;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
package com.openxu.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Display;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.RequiresPermission;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
|
||||
import static android.Manifest.permission.EXPAND_STATUS_BAR;
|
||||
|
||||
|
||||
/**
|
||||
* Author: openX
|
||||
* Time: 2019/3/14 11:09
|
||||
* class: FBarUtils
|
||||
* Description:状态栏工具
|
||||
*
|
||||
* getStatusBarHeight : 获取状态栏高度(px)
|
||||
* setStatusBarVisibility : 设置状态栏是否可见
|
||||
* isStatusBarVisible : 判断状态栏是否可见
|
||||
* setStatusBarLightMode : 设置状态栏是否为浅色模式
|
||||
* addMarginTopEqualStatusBarHeight : 为 view 增加 MarginTop 为状态栏高度
|
||||
* subtractMarginTopEqualStatusBarHeight: 为 view 减少 MarginTop 为状态栏高度
|
||||
* setStatusBarColor : 设置状态栏颜色
|
||||
* setStatusBarColor4Drawer : 为 DrawerLayout 设置状态栏颜色
|
||||
* getActionBarHeight : 获取 ActionBar 高度
|
||||
* setNotificationBarVisibility : 设置通知栏是否可见
|
||||
* getNavBarHeight : 获取导航栏高度
|
||||
* setNavBarVisibility : 设置导航栏是否可见
|
||||
* isNavBarVisible : 判断导航栏是否可见
|
||||
* setNavBarColor : 设置导航栏颜色
|
||||
* getNavBarColor : 获取导航栏颜色
|
||||
* isSupportNavBar : 判断是否支持导航栏
|
||||
*/
|
||||
public final class FBarUtils {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// status bar
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final String TAG_STATUS_BAR = "TAG_STATUS_BAR";
|
||||
private static final String TAG_OFFSET = "TAG_OFFSET";
|
||||
private static final int KEY_OFFSET = -123;
|
||||
|
||||
private FBarUtils() {
|
||||
throw new UnsupportedOperationException("u can't instantiate me...");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the status bar's height.
|
||||
*
|
||||
* @return the status bar's height
|
||||
*/
|
||||
public static int getStatusBarHeight() {
|
||||
Resources resources = Resources.getSystem();
|
||||
int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
|
||||
return resources.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's visibility.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @param isVisible True to set status bar visible, false otherwise.
|
||||
*/
|
||||
public static void setStatusBarVisibility(@NonNull final Activity activity,
|
||||
final boolean isVisible) {
|
||||
setStatusBarVisibility(activity.getWindow(), isVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's visibility.
|
||||
*
|
||||
* @param window The window.
|
||||
* @param isVisible True to set status bar visible, false otherwise.
|
||||
*/
|
||||
public static void setStatusBarVisibility(@NonNull final Window window,
|
||||
final boolean isVisible) {
|
||||
if (isVisible) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
showStatusBarView(window);
|
||||
addMarginTopEqualStatusBarHeight(window);
|
||||
} else {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
hideStatusBarView(window);
|
||||
subtractMarginTopEqualStatusBarHeight(window);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the status bar is visible.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isStatusBarVisible(@NonNull final Activity activity) {
|
||||
int flags = activity.getWindow().getAttributes().flags;
|
||||
return (flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's light mode.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @param isLightMode True to set status bar light mode, false otherwise.
|
||||
*/
|
||||
public static void setStatusBarLightMode(@NonNull final Activity activity,
|
||||
final boolean isLightMode) {
|
||||
setStatusBarLightMode(activity.getWindow(), isLightMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's light mode.
|
||||
*
|
||||
* @param window The window.
|
||||
* @param isLightMode True to set status bar light mode, false otherwise.
|
||||
*/
|
||||
public static void setStatusBarLightMode(@NonNull final Window window,
|
||||
final boolean isLightMode) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
View decorView = window.getDecorView();
|
||||
if (decorView != null) {
|
||||
int vis = decorView.getSystemUiVisibility();
|
||||
if (isLightMode) {
|
||||
vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
} else {
|
||||
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
}
|
||||
decorView.setSystemUiVisibility(vis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the status bar light mode.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isStatusBarLightMode(@NonNull final Activity activity) {
|
||||
return isStatusBarLightMode(activity.getWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the status bar light mode.
|
||||
*
|
||||
* @param window The window.
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isStatusBarLightMode(@NonNull final Window window) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
View decorView = window.getDecorView();
|
||||
if (decorView != null) {
|
||||
int vis = decorView.getSystemUiVisibility();
|
||||
return (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the top margin size equals status bar's height for view.
|
||||
*
|
||||
* @param view The view.
|
||||
*/
|
||||
public static void addMarginTopEqualStatusBarHeight(@NonNull View view) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
view.setTag(TAG_OFFSET);
|
||||
Object haveSetOffset = view.getTag(KEY_OFFSET);
|
||||
if (haveSetOffset != null && (Boolean) haveSetOffset) return;
|
||||
MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
|
||||
layoutParams.setMargins(layoutParams.leftMargin,
|
||||
layoutParams.topMargin + getStatusBarHeight(),
|
||||
layoutParams.rightMargin,
|
||||
layoutParams.bottomMargin);
|
||||
view.setTag(KEY_OFFSET, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract the top margin size equals status bar's height for view.
|
||||
*
|
||||
* @param view The view.
|
||||
*/
|
||||
public static void subtractMarginTopEqualStatusBarHeight(@NonNull View view) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
Object haveSetOffset = view.getTag(KEY_OFFSET);
|
||||
if (haveSetOffset == null || !(Boolean) haveSetOffset) return;
|
||||
MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
|
||||
layoutParams.setMargins(layoutParams.leftMargin,
|
||||
layoutParams.topMargin - getStatusBarHeight(),
|
||||
layoutParams.rightMargin,
|
||||
layoutParams.bottomMargin);
|
||||
view.setTag(KEY_OFFSET, false);
|
||||
}
|
||||
|
||||
private static void addMarginTopEqualStatusBarHeight(final Window window) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
|
||||
if (withTag == null) return;
|
||||
addMarginTopEqualStatusBarHeight(withTag);
|
||||
}
|
||||
|
||||
private static void subtractMarginTopEqualStatusBarHeight(final Window window) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET);
|
||||
if (withTag == null) return;
|
||||
subtractMarginTopEqualStatusBarHeight(withTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's color.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @param color The status bar's color.
|
||||
*/
|
||||
public static View setStatusBarColor(@NonNull final Activity activity,
|
||||
@ColorInt final int color) {
|
||||
return setStatusBarColor(activity, color, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's color.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @param color The status bar's color.
|
||||
* @param isDecor True to add fake status bar in DecorView,
|
||||
* false to add fake status bar in ContentView.
|
||||
*/
|
||||
public static View setStatusBarColor(@NonNull final Activity activity,
|
||||
@ColorInt final int color,
|
||||
final boolean isDecor) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null;
|
||||
transparentStatusBar(activity);
|
||||
return applyStatusBarColor(activity, color, isDecor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's color.
|
||||
*
|
||||
* @param fakeStatusBar The fake status bar view.
|
||||
* @param color The status bar's color.
|
||||
*/
|
||||
public static void setStatusBarColor(@NonNull final View fakeStatusBar,
|
||||
@ColorInt final int color) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
Activity activity = getActivityByView(fakeStatusBar);
|
||||
if (activity == null) return;
|
||||
transparentStatusBar(activity);
|
||||
fakeStatusBar.setVisibility(View.VISIBLE);
|
||||
ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
|
||||
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
layoutParams.height = getStatusBarHeight();
|
||||
fakeStatusBar.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom status bar.
|
||||
*
|
||||
* @param fakeStatusBar The fake status bar view.
|
||||
*/
|
||||
public static void setStatusBarCustom(@NonNull final View fakeStatusBar) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
Activity activity = getActivityByView(fakeStatusBar);
|
||||
if (activity == null) return;
|
||||
transparentStatusBar(activity);
|
||||
fakeStatusBar.setVisibility(View.VISIBLE);
|
||||
ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams();
|
||||
if (layoutParams == null) {
|
||||
layoutParams = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
getStatusBarHeight()
|
||||
);
|
||||
fakeStatusBar.setLayoutParams(layoutParams);
|
||||
} else {
|
||||
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
layoutParams.height = getStatusBarHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's color for DrawerLayout.
|
||||
* <p>DrawLayout must add {@code android:fitsSystemWindows="true"}</p>
|
||||
*
|
||||
* @param drawer The DrawLayout.
|
||||
* @param fakeStatusBar The fake status bar view.
|
||||
* @param color The status bar's color.
|
||||
*/
|
||||
public static void setStatusBarColor4Drawer(@NonNull final DrawerLayout drawer,
|
||||
@NonNull final View fakeStatusBar,
|
||||
@ColorInt final int color) {
|
||||
setStatusBarColor4Drawer(drawer, fakeStatusBar, color, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the status bar's color for DrawerLayout.
|
||||
* <p>DrawLayout must add {@code android:fitsSystemWindows="true"}</p>
|
||||
*
|
||||
* @param drawer The DrawLayout.
|
||||
* @param fakeStatusBar The fake status bar view.
|
||||
* @param color The status bar's color.
|
||||
* @param isTop True to set DrawerLayout at the top layer, false otherwise.
|
||||
*/
|
||||
public static void setStatusBarColor4Drawer(@NonNull final DrawerLayout drawer,
|
||||
@NonNull final View fakeStatusBar,
|
||||
@ColorInt final int color,
|
||||
final boolean isTop) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
Activity activity = getActivityByView(fakeStatusBar);
|
||||
if (activity == null) return;
|
||||
transparentStatusBar(activity);
|
||||
drawer.setFitsSystemWindows(false);
|
||||
setStatusBarColor(fakeStatusBar, color);
|
||||
for (int i = 0, count = drawer.getChildCount(); i < count; i++) {
|
||||
drawer.getChildAt(i).setFitsSystemWindows(false);
|
||||
}
|
||||
if (isTop) {
|
||||
hideStatusBarView(activity);
|
||||
} else {
|
||||
setStatusBarColor(activity, color, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static View applyStatusBarColor(final Activity activity,
|
||||
final int color,
|
||||
boolean isDecor) {
|
||||
ViewGroup parent = isDecor ?
|
||||
(ViewGroup) activity.getWindow().getDecorView() :
|
||||
(ViewGroup) activity.findViewById(android.R.id.content);
|
||||
View fakeStatusBarView = parent.findViewWithTag(TAG_STATUS_BAR);
|
||||
if (fakeStatusBarView != null) {
|
||||
if (fakeStatusBarView.getVisibility() == View.GONE) {
|
||||
fakeStatusBarView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
fakeStatusBarView.setBackgroundColor(color);
|
||||
} else {
|
||||
fakeStatusBarView = createStatusBarView(activity, color);
|
||||
parent.addView(fakeStatusBarView);
|
||||
}
|
||||
return fakeStatusBarView;
|
||||
}
|
||||
|
||||
private static void hideStatusBarView(final Activity activity) {
|
||||
hideStatusBarView(activity.getWindow());
|
||||
}
|
||||
|
||||
private static void hideStatusBarView(final Window window) {
|
||||
ViewGroup decorView = (ViewGroup) window.getDecorView();
|
||||
View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
|
||||
if (fakeStatusBarView == null) return;
|
||||
fakeStatusBarView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private static void showStatusBarView(final Window window) {
|
||||
ViewGroup decorView = (ViewGroup) window.getDecorView();
|
||||
View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR);
|
||||
if (fakeStatusBarView == null) return;
|
||||
fakeStatusBarView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private static View createStatusBarView(final Activity activity,
|
||||
final int color) {
|
||||
View statusBarView = new View(activity);
|
||||
statusBarView.setLayoutParams(new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight()));
|
||||
statusBarView.setBackgroundColor(color);
|
||||
statusBarView.setTag(TAG_STATUS_BAR);
|
||||
return statusBarView;
|
||||
}
|
||||
|
||||
private static void transparentStatusBar(final Activity activity) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
Window window = activity.getWindow();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
int vis = window.getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||
window.getDecorView().setSystemUiVisibility(option | vis);
|
||||
} else {
|
||||
window.getDecorView().setSystemUiVisibility(option);
|
||||
}
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
} else {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// action bar
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return the action bar's height.
|
||||
*
|
||||
* @return the action bar's height
|
||||
*/
|
||||
public static int getActionBarHeight() {
|
||||
TypedValue tv = new TypedValue();
|
||||
if (FUtils.getApp().getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
|
||||
return TypedValue.complexToDimensionPixelSize(
|
||||
tv.data, FUtils.getApp().getResources().getDisplayMetrics()
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// notification bar
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Set the notification bar's visibility.
|
||||
* <p>Must hold {@code <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />}</p>
|
||||
*
|
||||
* @param isVisible True to set notification bar visible, false otherwise.
|
||||
*/
|
||||
@RequiresPermission(EXPAND_STATUS_BAR)
|
||||
public static void setNotificationBarVisibility(final boolean isVisible) {
|
||||
String methodName;
|
||||
if (isVisible) {
|
||||
methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
|
||||
} else {
|
||||
methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
|
||||
}
|
||||
invokePanels(methodName);
|
||||
}
|
||||
|
||||
private static void invokePanels(final String methodName) {
|
||||
try {
|
||||
@SuppressLint("WrongConstant")
|
||||
Object service = FUtils.getApp().getSystemService("statusbar");
|
||||
@SuppressLint("PrivateApi")
|
||||
Class<?> statusBarManager = Class.forName("android.app.StatusBarManager");
|
||||
Method expand = statusBarManager.getMethod(methodName);
|
||||
expand.invoke(service);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// navigation bar
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return the navigation bar's height.
|
||||
*
|
||||
* @return the navigation bar's height
|
||||
*/
|
||||
public static int getNavBarHeight() {
|
||||
Resources res = Resources.getSystem();
|
||||
int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
|
||||
if (resourceId != 0) {
|
||||
return res.getDimensionPixelSize(resourceId);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the navigation bar's visibility.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @param isVisible True to set navigation bar visible, false otherwise.
|
||||
*/
|
||||
public static void setNavBarVisibility(@NonNull final Activity activity, boolean isVisible) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
setNavBarVisibility(activity.getWindow(), isVisible);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the navigation bar's visibility.
|
||||
*
|
||||
* @param window The window.
|
||||
* @param isVisible True to set navigation bar visible, false otherwise.
|
||||
*/
|
||||
public static void setNavBarVisibility(@NonNull final Window window, boolean isVisible) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
|
||||
final ViewGroup decorView = (ViewGroup) window.getDecorView();
|
||||
for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
|
||||
final View child = decorView.getChildAt(i);
|
||||
final int id = child.getId();
|
||||
if (id != View.NO_ID) {
|
||||
String resourceEntryName = FUtils.getApp()
|
||||
.getResources()
|
||||
.getResourceEntryName(id);
|
||||
if ("navigationBarBackground".equals(resourceEntryName)) {
|
||||
child.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
if (isVisible) {
|
||||
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~uiOptions);
|
||||
} else {
|
||||
decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | uiOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the navigation bar visible.
|
||||
* <p>Call it in onWindowFocusChanged will get right result.</p>
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isNavBarVisible(@NonNull final Activity activity) {
|
||||
return isNavBarVisible(activity.getWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the navigation bar visible.
|
||||
* <p>Call it in onWindowFocusChanged will get right result.</p>
|
||||
*
|
||||
* @param window The window.
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isNavBarVisible(@NonNull final Window window) {
|
||||
boolean isVisible = false;
|
||||
ViewGroup decorView = (ViewGroup) window.getDecorView();
|
||||
for (int i = 0, count = decorView.getChildCount(); i < count; i++) {
|
||||
final View child = decorView.getChildAt(i);
|
||||
final int id = child.getId();
|
||||
if (id != View.NO_ID) {
|
||||
String resourceEntryName = FUtils.getApp()
|
||||
.getResources()
|
||||
.getResourceEntryName(id);
|
||||
if ("navigationBarBackground".equals(resourceEntryName)
|
||||
&& child.getVisibility() == View.VISIBLE) {
|
||||
isVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isVisible) {
|
||||
int visibility = decorView.getSystemUiVisibility();
|
||||
isVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
|
||||
}
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the navigation bar's color.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @param color The navigation bar's color.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static void setNavBarColor(@NonNull final Activity activity, @ColorInt final int color) {
|
||||
setNavBarColor(activity.getWindow(), color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the navigation bar's color.
|
||||
*
|
||||
* @param window The window.
|
||||
* @param color The navigation bar's color.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static void setNavBarColor(@NonNull final Window window, @ColorInt final int color) {
|
||||
window.setNavigationBarColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the color of navigation bar.
|
||||
*
|
||||
* @param activity The activity.
|
||||
* @return the color of navigation bar
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static int getNavBarColor(@NonNull final Activity activity) {
|
||||
return getNavBarColor(activity.getWindow());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the color of navigation bar.
|
||||
*
|
||||
* @param window The window.
|
||||
* @return the color of navigation bar
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static int getNavBarColor(@NonNull final Window window) {
|
||||
return window.getNavigationBarColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the navigation bar visible.
|
||||
*
|
||||
* @return {@code true}: yes<br>{@code false}: no
|
||||
*/
|
||||
public static boolean isSupportNavBar() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
WindowManager wm = (WindowManager) FUtils.getApp().getSystemService(Context.WINDOW_SERVICE);
|
||||
if (wm == null) return false;
|
||||
Display display = wm.getDefaultDisplay();
|
||||
Point size = new Point();
|
||||
Point realSize = new Point();
|
||||
display.getSize(size);
|
||||
display.getRealSize(realSize);
|
||||
return realSize.y != size.y || realSize.x != size.x;
|
||||
}
|
||||
boolean menu = ViewConfiguration.get(FUtils.getApp()).hasPermanentMenuKey();
|
||||
boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
|
||||
return !menu && !back;
|
||||
}
|
||||
|
||||
private static Activity getActivityByView(@NonNull final View view) {
|
||||
Context context = view.getContext();
|
||||
while (context instanceof ContextWrapper) {
|
||||
if (context instanceof Activity) {
|
||||
return (Activity) context;
|
||||
}
|
||||
context = ((ContextWrapper) context).getBaseContext();
|
||||
}
|
||||
Log.e("BarUtils", "the view's Context is not an Activity.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user