This commit is contained in:
coco
2026-07-03 15:56:07 +08:00
commit caef23209c
5767 changed files with 1004268 additions and 0 deletions
@@ -0,0 +1 @@
/build
@@ -0,0 +1,59 @@
apply plugin: 'com.android.library'
//apply plugin: 'com.novoda.bintray-release'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
versionCode 2
versionName "0.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
}
beta {
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
api 'com.google.code.gson:gson:2.8.5'
}
// ./gradlew clean build bintrayUpload
// -PbintrayUser=wgyscsf
// -PbintrayKey=xxxxxxxxxxxxxxxxxxxxxx
// -PdryRun=false
//添加
//publish {
// repoName ='maven'//和在Bintray网站创建的仓库名对应
// userOrg = 'wgyscsf'//bintray.com用户名
// groupId = 'com.wgyscsf'//jcenter上的路径
// artifactId = 'financialLib'//项目名称
// publishVersion = '0.0.2'//版本号
// desc = 'this is the first version'//描述,不重要
// website = 'https://github.com/scsfwgy/FinancialCustomerView'//网站,不重要;尽量模拟github上的地址,例如我这样的;当然你有地址最好了
//}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1 @@
<manifest package="com.tophold.trade"/>
@@ -0,0 +1,3 @@
#### TODO
1. 对于默认值不显示的指标,比如ma,假如算出来的本身就和默认值一样就会导致"误判".
2. 首页的ma边界判断应该有问题,参考vol ma
@@ -0,0 +1,12 @@
package com.tophold.trade;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/05/19 16:28
* 描 述
* ============================================================
*/
public class Constant {
public static final String ESPECIAL_TAG="ESPECIAL_TAG";
}
@@ -0,0 +1,153 @@
package com.tophold.trade.utils;
import android.util.Log;
import java.math.BigDecimal;
import java.util.Locale;
public class FormatUtil {
/**
* 浮点数格式化
*
* @param isPercentage 是否为分数
* @param needSign 是否需要正号
* @param isMoney 是否使用金钱格式(每3位用","分隔)
* @param digit 需要保留的位数
* @param num 需要格式化的值(int、float、double、String、byte均可)
*/
public static String format(boolean isPercentage, boolean needSign, boolean isMoney, int digit, Object num) {
digit = digit < 0 ? 0 : digit;
if (num == null) num = 0;
double converted;//需求大多四舍五入 float保留位数会舍去后面的
try {
converted = Double.parseDouble(num.toString());
} catch (Throwable e) {
Log.e("FormatErr", "Input number is not kind number");
converted = 0d;
}
StringBuilder sb = new StringBuilder("%");
if (needSign && (converted > 0)) {
sb.append("+");
}
if (isMoney) {
sb.append(",");
}
sb.append(".").append(digit).append("f");
if (isPercentage) {
sb.append("%%");
}
return String.format(Locale.getDefault(), sb.toString(), converted);
}
public static String numFormat(Object num, int digit) {
return numFormat(false, digit, num);
}
public static String numFormat(boolean needSign, int digit, Object num) {
return format(false, needSign, false, digit, num);
}
/**
* 带%格式化
*/
public static String percentageFormat(Object num) {
return percentageFormat(true, num);
}
public static String percentageFormat(boolean needSign, Object num) {
return percentageFormat(needSign, 2, num);
}
public static String percentageFormat(int digit, Object num) {
return percentageFormat(false, digit, num);
}
public static String percentageFormat(boolean needSign, int digit, Object num) {
return format(true, needSign, false, digit, num);
}
/**
* 以金钱格式表示的数字
*/
public static String moneyFormat(Object num) {
return moneyFormat(true, num);
}
public static String moneyFormat(boolean isPercentage, Object num) {
return moneyFormat(isPercentage, 2, num);
}
public static String moneyFormat(boolean isPercentage, int digit, Object num) {
return format(isPercentage, false, true, digit, num);
}
public static String stringAppend(Object object) {
if (object != null)
return String.format(Locale.getDefault(), "%s", object);
else
return "- -";
}
/**
* 不四舍五入取n位小数
*/
public static String formatBySubString(Object obj, int digit) {
if (obj == null) return "0";
String num = String.valueOf(obj);
digit = digit < 0 ? 0 : digit;
int i = num.indexOf(".");
if (i >= 0) {
if (num.length() - ++i > digit) {
num = num.substring(0, i + digit);
}
}
return num;
}
/**
* 进位处理
*
* @param scale 保留几位
*/
public static String roundUp(int scale, Object num) {
BigDecimal decimal = new BigDecimal(num.toString());
return decimal.setScale(scale, BigDecimal.ROUND_UP).toString();
}
/**
* 直接舍弃多余小数
*/
public static String roundDown(int scale, Object num) {
BigDecimal decimal = new BigDecimal(num.toString());
return decimal.setScale(scale, BigDecimal.ROUND_DOWN).toString();
}
public static void main(String args[]) {
double num1 = -123456.789000;
double num2 = 0;
double num3 = 1.00;
double num4 = 1444444444;
double num5 = 123.123;
double num6 = 123.123;
double num7 = 123.123;
double num8 = 123.123;
double num9 = 123.123;
double num10 = 123.123;
String numStr1 = format(false, false, false, 5, num1);
String numStr2 = format(true, false, false, 5, num1);
String numStr3 = format(false, true, false, 5, num1);
String numStr4 = format(false, false, true, 5, num1);
System.out.println(numStr1);
System.out.println(numStr2);
System.out.println(numStr3);
System.out.println(numStr4);
}
}
@@ -0,0 +1,78 @@
package com.tophold.trade.utils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Map;
/**
* <p>GSON工具类</p>
*
* @author
* @version $Id: GsonUtil.java
*/
public class GsonUtil {
private static Gson gson = null;
private static Gson prettyGson = null;
static {
gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd HH:mm:ss").create();
prettyGson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss")
.setPrettyPrinting()
.create();
}
/**
* 小写下划线的格式解析JSON字符串到对象
* <p>例如 is_success->isSuccess</p>
*
* @param json
* @param classOfT
* @return
*/
public static <T> T fromJsonUnderScoreStyle(String json, Class<T> classOfT) {
return gson.fromJson(json, classOfT);
}
/**
* JSON字符串转为Map<String,String>
*
* @param json
* @return
*/
@SuppressWarnings("all")
public static <T> T fronJson2Map(String json) {
return gson.fromJson(json, new TypeToken<Map<String, String>>() {
}.getType());
}
/**
* 小写下划线的格式将对象转换成JSON字符串
*
* @param src
* @return
*/
public static String toJson(Object src) {
return gson.toJson(src);
}
public static String toPrettyString(Object src) {
return prettyGson.toJson(src);
}
public static <T> T fromJson2Object(String src, Class<T> t) {
return gson.fromJson(src, t);
}
public static <T> T fromJson2Object(String src, Type typeOfT) {
return gson.fromJson(src, typeOfT);
}
public static Gson getGson() {
return gson;
}
}
@@ -0,0 +1,41 @@
package com.tophold.trade.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/10/25 16:08
* 描 述
* ============================================================
**/
public class RegxUtils {
public static float getPureDouble(String str) {
if (str == null || str.length() == 0) return 0;
float result = 0;
try {
Pattern compile = Pattern.compile("(\\d+\\.\\d+)|(\\d+)");//如何提取带负数d ???
Matcher matcher = compile.matcher(str);
matcher.find();
String string = matcher.group();//提取匹配到的结果
result = Float.parseFloat(string);
} catch (NumberFormatException e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args){
test();
}
public static void test(){
System.out.println(getPureDouble("12"));
System.out.println(getPureDouble("wew3423.36"));
System.out.println(getPureDouble("wewsf"));
System.out.println(getPureDouble("000"));
System.out.println(getPureDouble(null));
}
}
@@ -0,0 +1,121 @@
package com.tophold.trade.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.NinePatch;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
/**
* ================================================
* 作 者:JayGoo
* 版 本:
* 创建日期:2018/5/8
* 描 述:
* ================================================
*/
public class RenderUtils {
/**
* make a drawable to a bitmap
* @param drawable drawable you want convert
* @return converted bitmap
*/
public static Bitmap drawableToBitmap(int size, Drawable drawable) {
Bitmap bitmap = null;
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && bitmap.getHeight() > 0) {
Matrix matrix = new Matrix();
float scaleHeight = size * 1.0f / bitmapDrawable.getIntrinsicHeight();
matrix.postScale(scaleHeight, scaleHeight);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
return bitmap;
}
}
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
/**
* make a drawable to a bitmap
*
* @param drawable drawable you want convert
* @return converted bitmap
*/
public static Bitmap drawableToBitmap(Drawable drawable) {
return drawableToBitmap(drawable, 0, 0);
}
/**
* make a drawable to a bitmap
*
* @param drawable drawable you want convert
* @return converted bitmap
*/
public static Bitmap drawableToBitmap(Drawable drawable, float width, float hight) {
Bitmap bitmap = null;
float scaleWidth = width > 0 ? width : drawable.getIntrinsicWidth();
float scaleHeight = hight > 0 ? hight : drawable.getIntrinsicHeight();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && bitmap.getHeight() > 0) {
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
return bitmap;
}
}
bitmap = Bitmap.createBitmap((int) scaleWidth, (int) scaleHeight, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
/**
* draw 9Path
*
* @param canvas Canvas
* @param bmp 9path bitmap
* @param rect 9path rect
*/
public static void drawNinePath(Canvas canvas, Bitmap bmp, Rect rect) {
NinePatch patch = new NinePatch(bmp, bmp.getNinePatchChunk(), null);
patch.draw(canvas, rect);
}
public static int dp2px(Context context, float dpValue) {
if (context == null || compareFloat(0f,dpValue) == 0)return 0;
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* Compare the size of two floating point numbers
* @param a
* @param b
* @return 1 is a > b
* -1 is a < b
* 0 is a == b
*/
public static int compareFloat(float a, float b) {
int ta = Math.round(a * 100000);
int tb = Math.round(b * 100000);
if (ta > tb) {
return 1;
} else if (ta < tb) {
return -1;
} else {
return 0;
}
}
}
@@ -0,0 +1,243 @@
package com.tophold.trade.utils;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import java.lang.reflect.Method;
/**
* 获取屏幕宽高等 相关工具类
*/
@SuppressWarnings("WeakerAccess")
public class ScreenUtils {
public static int screenWidth;
public static int screenHeight;
public static float density;
public static float scaledDensity;
public static int statusbarheight;
public static int navbarheight;
public static int realScreenHeight;
public static int realScreenWidth;
static {
init();
}
private static void init() {
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
screenWidth = displayMetrics.widthPixels;
screenHeight = displayMetrics.heightPixels;
density = displayMetrics.density;
scaledDensity = displayMetrics.scaledDensity;
}
/**
* 获取屏幕宽度
*/
public static int getScreenWidth() {
return screenWidth;
}
/**
* 获取屏幕高度(不包含底部虚拟按键)
*/
public static int getScreenHeight() {
return screenHeight;
}
public static int px2dip(float pxValue) {
return (int) (pxValue / density + 0.5f);
}
public static int dip2px(float dpValue) {
return (int) (dpValue * density + 0.5f);
}
/**
* 将px值转换为sp值,保证文字大小不变
*/
public static float px2sp(float pxValue) {
return pxValue / scaledDensity;
}
/**
* 将sp值转换为px值,保证文字大小不变
*/
public static float sp2px(float spValue) {
return spValue * scaledDensity;
}
/**
* 获取状态栏高度
*/
public static int getStatusBarHight() {
if (statusbarheight != 0) {
return statusbarheight;
}
int result = 0;
int resourceId = Resources.getSystem().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusbarheight = result = Resources.getSystem().getDimensionPixelSize(resourceId);
}
return result;
}
/**
* 获取底部导航栏高度(不显示则为0)
*/
public static int getNavigationBarHeight(Context context) {
if (navbarheight != 0) {
return navbarheight;
}
return navbarheight = getRealScreenHeight(context) - getScreenHeight();
}
/**
* 获取底部导航栏高度(无论显示与否)
*/
public static int getRealNavigationBarHeight() {
int result = 0;
int resourceId = Resources.getSystem().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
result = Resources.getSystem().getDimensionPixelSize(resourceId);
}
return result;
}
/**
* 底部导航栏高度是否显示
*/
public static boolean hasNavigationBar(Context context) {
return getNavigationBarHeight(context) > 0;
}
/**
* 获取真实屏幕高度(带虚拟按键)
*/
public static int getRealScreenHeight(Context context) {
if (realScreenHeight != 0) {
return realScreenHeight;
}
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
assert wm != null;
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
if (Build.VERSION.SDK_INT >= 17) {//17以上可以直接获取 以下反射获取
display.getRealMetrics(dm);
} else {
@SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, dm);
} catch (Exception e) {
e.printStackTrace();
}
}
return realScreenHeight = dm.heightPixels;
}
/*用于横屏时获取真实宽度*/
public static int getRealScreenWidth(Context context) {
if (realScreenWidth != 0) {
return realScreenWidth;
}
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
assert wm != null;
Display display = wm.getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
if (Build.VERSION.SDK_INT >= 17) {//17以上可以直接获取 以下反射获取
display.getRealMetrics(dm);
} else {
@SuppressWarnings("rawtypes")
Class c;
try {
c = Class.forName("android.view.Display");
@SuppressWarnings("unchecked")
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, dm);
} catch (Exception e) {
e.printStackTrace();
}
}
return realScreenWidth = dm.widthPixels;
}
/**
* 获取view的矩形坐标
*/
public RectF getViewRectF(View view) {
int[] location = new int[2];
view.getLocationOnScreen(location);
return new RectF(location[0], location[1], location[0] + view.getWidth(), location[1] + view.getHeight());
}
/**
* 判断是否触摸在view上
*/
public boolean isViewTouched(View view, float rawX, float rawY) {
return getViewRectF(view).contains(rawX, rawY);
}
public boolean isViewTouched(View view, MotionEvent event) {
// event.getX(); 获取相对于控件自身左上角的 x 坐标值
// event.getY(); 获取相对于控件自身左上角的 y 坐标值
float rawX = event.getRawX(); // 获取相对于屏幕左上角的 x 坐标值
float rawY = event.getRawY();// 获取相对于屏幕左上角的 y 坐标值
return isViewTouched(view, rawX, rawY);
}
/**
* 判断一个view的矩形是否包含另一view的矩形
*/
public boolean containsRectF(View view1, View view2) {
return getViewRectF(view1).contains(getViewRectF(view2));
}
public boolean isInRectF(RectF rectF, float rawX, float rawY) {
return rectF.contains(rawX, rawY);
}
/**
* 获取软键盘高度(不显示则为0)
*/
public static int getSoftInputHeight(Activity activity) {
if (activity == null || activity.isFinishing()) return 0;
//获取显示区域
Rect rect = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
//rect.bottom为显示区域底部高度(不包含键盘) 屏幕高度减去显示区域高度 即为软键盘高度
return screenHeight - rect.bottom;
}
/**
* 软件盘是否显示
*/
public static boolean isSoftInputShown(Activity activity) {
return getSoftInputHeight(activity) > 0;
}
public static int getImageMaxEdge() {
return (int) (165.0 / 320.0 * screenWidth);
}
public static int getImageMinEdge() {
return (int) (76.0 / 320.0 * screenWidth);
}
}
@@ -0,0 +1,67 @@
package com.tophold.trade.utils;
import java.util.List;
import java.util.Random;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/11/23 10:25
* 描 述
* ============================================================
**/
public class StringUtils {
public static boolean isEmptyString(String str) {
if (str == null || str.isEmpty()) return true;
return false;
}
public static boolean isNotEmptyString(String str) {
return !isEmptyString(str);
}
public static boolean isEmptyList(List list) {
if (list == null || list.isEmpty()) return true;
return false;
}
public static boolean isNotEmptyList(List list) {
return !isEmptyList(list);
}
public static boolean isBlank(String... strs) {
for (String str : strs) {
if (str == null || str.equals("")) return true;
}
return false;
}
public static boolean isTrimBlank(String... strs) {
for (String str : strs) {
if (str == null || str.trim().equals("")) return true;
}
return false;
}
public static boolean isEmpty(List list) {
if (list == null || list.size() == 0) return true;
return false;
}
/**
* 随机获取[m,n]之间的一个数字
*
* @param min
* @param max
* @return
*/
public static int getRadomNum(int min, int max) {
Random rdm = new Random();
return rdm.nextInt(max - min + 1) + min;
}
public static String getString(){
return getRadomNum(0,1)==0?null:"sdaas";
}
}
@@ -0,0 +1,43 @@
package com.tophold.trade.utils;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2017/03/13
* desc : 时间相关常量
* </pre>
*/
public final class TimeConstants {
/**
* 毫秒与毫秒的倍数
*/
public static final int MSEC = 1;
/**
* 秒与毫秒的倍数
*/
public static final int SEC = 1000;
/**
* 分与毫秒的倍数
*/
public static final int MIN = 60000;
/**
* 时与毫秒的倍数
*/
public static final int HOUR = 3600000;
/**
* 天与毫秒的倍数
*/
public static final int DAY = 86400000;
@IntDef({MSEC, SEC, MIN, HOUR, DAY})
@Retention(RetentionPolicy.SOURCE)
public @interface Unit {
}
}
@@ -0,0 +1,178 @@
package com.tophold.trade.view;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
import static android.view.View.MeasureSpec.AT_MOST;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 20180404
* 描 述 :整个自定义view最基础的类,在这里做一些基础重复的操作。
* ============================================================
*/
public abstract class BaseView extends View {
protected String TAG;
protected Context mContext;
//长按阀值,默认多长时间算长按(ms)。不再设置为final,允许用户修改。
protected long def_longpress_length = 700;
//单击阀值
protected long def_clickpress_length = 100;
//移动阀值。手指移动多远算移动的阀值(单位:sp)
protected long def_pull_length = 5;
//onFling的阀值
protected float def_onfling = 5;
//控件默认宽高。当控件的宽高设置为wrap_content时会采用该参数进行默认的设置(单位:sp)。
//不允许用户修改,想要修改宽高,使用mWidth、mBaseHeight。
protected final float DEF_WIDTH = 650;
protected final float DEF_HIGHT = 400;
//测量的控件宽高,会在onMeasure中进行测量。
protected int mBaseWidth;
protected int mBaseHeight;
public BaseView(Context context) {
this(context, null);
}
public BaseView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TAG = this.getClass().getSimpleName();
mContext = context;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == AT_MOST && heightSpecMode == AT_MOST) {
setMeasuredDimension((int) DEF_WIDTH, (int) DEF_HIGHT);
} else if (widthSpecMode == AT_MOST) {
setMeasuredDimension((int) DEF_WIDTH, heightSpecSize);
} else if (heightSpecMode == AT_MOST) {
setMeasuredDimension(widthSpecSize, (int) DEF_HIGHT);
} else {
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
mBaseWidth = getMeasuredWidth();
mBaseHeight = getMeasuredHeight();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
/**
* 根据颜色id获取颜色
*
* @param colorId
* @return
*/
protected int getColor(@ColorRes int colorId) {
return ContextCompat.getColor(mContext, colorId);
}
protected String getString(@StringRes int stringId) {
return getResources().getString(stringId);
}
protected Drawable getDrawable(@DrawableRes int drawableId) {
return ContextCompat.getDrawable(mContext, drawableId);
}
protected int dp2px(float dp) {
final float scale = mContext.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
protected int sp2px(float sp) {
final float fontScale = mContext.getResources().getDisplayMetrics().scaledDensity;
return (int) (sp * fontScale + 0.5f);
}
/**
* 测量指定画笔的文字的高度
*
* @param fontSize
* @param paint
* @return
*/
protected float getFontHeight(float fontSize, Paint paint) {
paint.setTextSize(fontSize);
Paint.FontMetrics fm = paint.getFontMetrics();
return (float) (Math.ceil(fm.descent - fm.top) + 2f);
}
//----------------------对用户暴露可以修改的参数------------------
public long getDef_longpress_length() {
return def_longpress_length;
}
public void setDef_longpress_length(long def_longpress_length) {
this.def_longpress_length = def_longpress_length;
}
public long getDef_clickpress_length() {
return def_clickpress_length;
}
public void setDef_clickpress_length(long def_clickpress_length) {
this.def_clickpress_length = def_clickpress_length;
}
public long getDef_pull_length() {
return def_pull_length;
}
public void setDef_pull_length(long def_pull_length) {
this.def_pull_length = def_pull_length;
}
public float getDEF_WIDTH() {
return DEF_WIDTH;
}
public float getDEF_HIGHT() {
return DEF_HIGHT;
}
public int getBaseWidth() {
return mBaseWidth;
}
public void setBaseWidth(int baseWidth) {
mBaseWidth = baseWidth;
}
public int getBaseHeight() {
return mBaseHeight;
}
public void setBaseHeight(int baseHeight) {
mBaseHeight = baseHeight;
}
}
@@ -0,0 +1,30 @@
package com.tophold.trade.view.fund;
import com.tophold.trade.utils.RegxUtils;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/10/25 14:50
* 描 述
* ============================================================
**/
public class FundMode {
//x轴原始时间数据,ms
public long datetime;
//y轴的原始数据
public String originDataY;
//y轴的转换后的数据
public float dataY;
//在自定义view:FundView中的位置坐标
public float floatX;
public float floatY;
public FundMode(long timestamp, String actual) {
this.datetime = timestamp;
this.originDataY = actual;
this.dataY = RegxUtils.getPureDouble(originDataY);//提取后的Y周的值
}
}
@@ -0,0 +1,789 @@
package com.tophold.trade.view.fund;
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.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import java.text.SimpleDateFormat;
import java.util.List;
import com.tophold.trade.R;
import com.tophold.trade.view.BaseView;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/10/25 14:47update:2018/04/05
* 描 述 :蚂蚁财富基金收益折线图。
* 想要定制:哪些字段可以修改?哪些字段不能修改?
* 可以修改:各种画笔的状态(颜色、样式、粗细、文字大小)、边界padding、宽高等;
* 不可以修改:计算出来的最大值最小值、boolean类型的临时状态、长按所赋的值、单元大小等
* (随便修改不建议修改的字段可能会发生不可预料的问题你,但是所有字段都会暴露出去(尽量),给更大的灵活性去定制)。
* 注意:对于Paint的子属性请直接调用Piant然后再进行设置,
* 不要(程序已经限制,因为会和外部调用Paint去设置属性混乱)使用该内部的Paint的设置子属性的方法。
* ============================================================
**/
public class FundView extends BaseView {
//数据源
List<FundMode> mFundModeList;
//上下左右padding,允许修改
protected float mBasePaddingTop = 100;
protected float mBasePaddingBottom = 70;
protected float mBasePaddingLeft = 50;
protected float mBasePaddingRight = 50;
//Y轴对应的最大值和最小值,注意,这里存的是对象。原则上不允许修改。
protected FundMode mMinFundMode;
protected FundMode mMaxFundMode;
//X、Y轴每一个data对应的大小。原则上不允许修改。
protected float mPerX;
protected float mPerY;
//正在加载中,允许修改
protected Paint mLoadingPaint;
protected float mLoadingTextSize = 20;
protected String mLoadingText = "数据加载,请稍后";
//原则上不允许修改。
protected boolean mDrawLoadingPaint = true;
//外围X、Y轴线文字。允许修改。
protected Paint mXYPaint;
//x、y轴指示文字字体的大小
protected float mXYTextSize = 14;
//左侧文字距离左边线线的距离
protected float mLeftTxtPadding = 16;
//底部文字距离底部线的距离
protected float mBottomTxtPadding = 20;
//内部X轴虚线。允许修改。
protected Paint mInnerXPaint;
protected float mInnerXStrokeWidth = 1;
//折线。允许修改。
protected Paint mBrokenPaint;
//单位:sp.。允许修改。
protected float mBrokenStrokeWidth = 1;
//长按的十字线,允许修改。
protected Paint mLongPressPaint;
//原则上不允许修改。
protected boolean mDrawLongPressPaint = false;
//长按处理,原则上不允许修改。
protected long mPressTime;
protected float mPressX;
protected float mPressY;
//最上面默认显示累计收益金额,允许修改。
protected Paint mDefAllIncomePaint;
protected float mDefAllIncomeTextSize = 20;
//长按情况下x轴和y轴要显示的文字,允许修改。
protected Paint mLongPressTxtPaint;
protected float mLongPressTextSize = 20;
public FundView(Context context) {
this(context, null);
}
public FundView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public FundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//setDefAttrs();
//默认加载loading界面
showLoadingPaint(canvas);
if (mFundModeList == null || mFundModeList.size() == 0) return;
//加载三个核心Paint
drawInnerXPaint(canvas);
drawBrokenPaint(canvas);
drawXYPaint(canvas);
drawTopTxtPaint(canvas);
drawLongPress(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPressTime = event.getDownTime();
break;
case MotionEvent.ACTION_MOVE:
if (event.getEventTime() - mPressTime > def_longpress_length) {
Log.d(TAG, "onTouchEvent: 长按了。。。");
mPressX = event.getX();
mPressY = event.getY();
//处理长按后的逻辑
showLongPressView();
}
break;
case MotionEvent.ACTION_UP:
//处理松手后的逻辑
hiddenLongPressView();
break;
default:
break;
}
return true;
}
private void initAttrs() {
initLoadingPaint();
initInnerXPaint();
initXYPaint();
initBrokenPaint();
initLongPressPaint();
initTopTxt();
}
private void initLoadingPaint() {
mLoadingPaint = new Paint();
mLoadingPaint.setColor(getColor(R.color.color_fundView_xyTxtColor));
mLoadingPaint.setTextSize(mLoadingTextSize);
mLoadingPaint.setAntiAlias(true);
}
//初始化绘制虚线的画笔
private void initInnerXPaint() {
mInnerXPaint = new Paint();
mInnerXPaint.setColor(getColor(R.color.color_fundView_xLineColor));
mInnerXPaint.setStrokeWidth(mInnerXStrokeWidth);
mInnerXPaint.setStyle(Paint.Style.STROKE);
setLayerType(LAYER_TYPE_SOFTWARE, null);//禁用硬件加速
PathEffect effects = new DashPathEffect(new float[]{5, 5, 5, 5}, 1);
mInnerXPaint.setPathEffect(effects);
}
private void initXYPaint() {
mXYPaint = new Paint();
mXYPaint.setColor(getColor(R.color.color_fundView_xyTxtColor));
mXYPaint.setTextSize(mXYTextSize);
mXYPaint.setAntiAlias(true);
}
private void initBrokenPaint() {
mBrokenPaint = new Paint();
mBrokenPaint.setColor(getColor(R.color.color_fundView_brokenLineColor));
mBrokenPaint.setStyle(Paint.Style.STROKE);
mBrokenPaint.setAntiAlias(true);
mBrokenPaint.setStrokeWidth(mBrokenStrokeWidth);
}
private void initLongPressPaint() {
mLongPressPaint = new Paint();
mLongPressPaint.setColor(getColor(R.color.color_fundView_longPressLineColor));
mLongPressPaint.setStyle(Paint.Style.FILL);
mLongPressPaint.setAntiAlias(true);
mLongPressPaint.setTextSize(mLongPressTextSize);
}
//折线上面显示文字信息
private void initTopTxt() {
mDefAllIncomePaint = new Paint();
mDefAllIncomePaint.setColor(getColor(R.color.color_fundView_defIncomeTxt));
mDefAllIncomePaint.setTextSize(mLongPressTextSize);
mDefAllIncomePaint.setAntiAlias(true);
mLongPressTxtPaint = new Paint();
mLongPressTxtPaint.setColor(getColor(R.color.color_fundView_longPressLineColor));
mLongPressTxtPaint.setTextSize(mLongPressTextSize);
mLongPressTxtPaint.setAntiAlias(true);
}
/**
* 将画笔使用的属性在这里设置。
* 主要是为了覆盖用户动态设置的属性,
* 因为在构造方法中设置的会无效(用户设置的在构造方法之后)。
* 注意:这个方法不能使用,因为会覆盖Paint内部的设置属性的方法
*/
@Deprecated
private void setDefAttrs() {
mLoadingPaint.setTextSize(mLoadingTextSize);
mInnerXPaint.setStrokeWidth(mInnerXStrokeWidth);
mXYPaint.setTextSize(mXYTextSize);
mBrokenPaint.setStrokeWidth(mBrokenStrokeWidth);
mLongPressPaint.setTextSize(mLongPressTextSize);
mDefAllIncomePaint.setTextSize(mLongPressTextSize);
mLongPressTxtPaint.setTextSize(mLongPressTextSize);
}
private void showLoadingPaint(Canvas canvas) {
if (!mDrawLoadingPaint) return;
//这里特别注意,x轴的起始点要减去文字宽度的一半
canvas.drawText(mLoadingText, mBaseWidth / 2 - mLoadingPaint.measureText(mLoadingText) / 2, mBaseHeight / 2, mLoadingPaint);
}
private void drawInnerXPaint(Canvas canvas) {
//画5条横轴的虚线
//首先确定最大值和最小值的位置
float perHight = (mBaseHeight - mBasePaddingBottom - mBasePaddingTop) / 4;
canvas.drawLine(0 + mBasePaddingLeft, mBasePaddingTop,
mBaseWidth - mBasePaddingRight, mBasePaddingTop, mInnerXPaint);//最上面的那一条
canvas.drawLine(0 + mBasePaddingLeft, mBasePaddingTop + perHight * 1,
mBaseWidth - mBasePaddingRight, mBasePaddingTop + perHight * 1, mInnerXPaint);//2
canvas.drawLine(0 + mBasePaddingLeft, mBasePaddingTop + perHight * 2,
mBaseWidth - mBasePaddingRight, mBasePaddingTop + perHight * 2, mInnerXPaint);//3
canvas.drawLine(0 + mBasePaddingLeft, mBasePaddingTop + perHight * 3,
mBaseWidth - mBasePaddingRight, mBasePaddingTop + perHight * 3, mInnerXPaint);//4
canvas.drawLine(0 + mBasePaddingLeft, mBaseHeight - mBasePaddingBottom,
mBaseWidth - mBasePaddingRight, mBaseHeight - mBasePaddingBottom, mInnerXPaint);//最下面的那一条
}
private void drawBrokenPaint(Canvas canvas) {
//先画第一个点
FundMode fundMode = mFundModeList.get(0);
Path path = new Path();
//这里需要说明一下,x轴的起始点,其实需要加上mPerX,但是加上之后不是从起始位置开始,不好看。
// 同理,for循环内x轴其实需要(i+1)。现在这样处理,最后会留一点空隙,其实挺好看的。
float floatY = mBaseHeight - mBasePaddingBottom - mPerY * (fundMode.dataY - mMinFundMode.dataY);
fundMode.floatX = mBasePaddingLeft;
fundMode.floatY = floatY;
path.moveTo(mBasePaddingLeft, floatY);
for (int i = 1; i < mFundModeList.size(); i++) {
FundMode fm = mFundModeList.get(i);
float floatX2 = mBasePaddingLeft + mPerX * i;
float floatY2 = mBaseHeight - mBasePaddingBottom - mPerY * (fm.dataY - mMinFundMode.dataY);
fm.floatX = floatX2;
fm.floatY = floatY2;
path.lineTo(floatX2, floatY2);
//Log.e(TAG, "drawBrokenPaint: " + mBasePaddingLeft + mPerX * i + "-----" + (mBaseHeight - mClosePerY * (mFundModeList.get(i).dataY - mMinFundMode.dataY) - mBasePaddingBottom));
}
canvas.drawPath(path, mBrokenPaint);
}
private void drawXYPaint(Canvas canvas) {
//先处理y轴方向文字
drawYPaint(canvas);
//处理x轴方向文字
drawXPaint(canvas);
}
private void drawTopTxtPaint(Canvas canvas) {
//先画默认情况下的top文字
drawDefTopTxtpaint(canvas);
//按下的文字信息在按下之后处理,see:drawLongPress(Canvas canvas)
}
/**
* 这里处理画十字的逻辑:这里的十字不是手指按下的位置,这样没有意义。
* 而是当前按下的距离x轴最近的时间(注意:并不一定按下对应的x轴就是有时间的,如果没有取最近的)。
* 当取到x轴的值,之后算出来对应的y轴的值,这个才是十字对应的位置坐标。
* 如何获取x轴最近的时间?我们可以在FundMode中定义x\y的位置参数,遍历对比找到最小即可。
* (see: drawBrokenPaint(canvas);)
*
* @param canvas
*/
private void drawLongPress(Canvas canvas) {
if (!mDrawLongPressPaint) return;
//获取距离最近按下的位置的model
float pressX = mPressX;
//循环遍历,找到距离最短的x轴的mode
FundMode finalFundMode = mFundModeList.get(0);
float minXLen = Integer.MAX_VALUE;
for (int i = 0; i < mFundModeList.size(); i++) {
FundMode currFunMode = mFundModeList.get(i);
float abs = Math.abs(pressX - currFunMode.floatX);
if (abs < minXLen) {
finalFundMode = currFunMode;
minXLen = abs;
}
}
//x
canvas.drawLine(mBasePaddingLeft, finalFundMode.floatY, mBaseWidth - mBasePaddingRight, finalFundMode.floatY, mLongPressPaint);
//y
canvas.drawLine(finalFundMode.floatX, mBasePaddingTop, finalFundMode.floatX, mBaseHeight - mBasePaddingBottom, mLongPressPaint);
//开始处理按下之后top的文字信息
//先画背景
float hight = mBasePaddingTop - 30;
Paint bgColor = new Paint(Paint.ANTI_ALIAS_FLAG);
bgColor.setColor(getColor(R.color.color_fundView_pressIncomeTxtBg));
canvas.drawRect(0, 0, mBaseWidth, hight, bgColor);
//开始画按下之后左边的日期文字
Paint timePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
timePaint.setTextSize(mLongPressTextSize);
timePaint.setColor(getColor(R.color.color_fundView_xyTxtColor));
canvas.drawText(processDateTime(finalFundMode.datetime) + "",
10, hight / 2 + getFontHeight(mLongPressTextSize, timePaint) / 2, timePaint);
//右边红色收益文字
canvas.drawText(finalFundMode.dataY + "",
mBaseWidth - mBasePaddingRight - mLongPressPaint.measureText(finalFundMode.dataY + ""),
hight / 2 + getFontHeight(mLongPressTextSize, timePaint) / 2, mLongPressPaint);
//右边的左边的提示文字
Paint hintPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
hintPaint.setTextSize(mLongPressTextSize);
hintPaint.setColor(getColor(R.color.color_fundView_xyTxtColor));
canvas.drawText(getString(R.string.string_fundView_pressHintTxt),
mBaseWidth - mBasePaddingRight - mLongPressPaint.measureText(finalFundMode.dataY + "")
- hintPaint.measureText(getString(R.string.string_fundView_pressHintTxt)),
hight / 2 + getFontHeight(mLongPressTextSize, timePaint) / 2, hintPaint);
}
//找到最大时间、最小时间和中间时间显示即可
private void drawXPaint(Canvas canvas) {
long beginTime = mFundModeList.get(0).datetime;
long midTime = mFundModeList.get((mFundModeList.size() - 1) / 2).datetime;
long endTime = mFundModeList.get(mFundModeList.size() - 1).datetime;
String bengin = processDateTime(beginTime);
String mid = processDateTime(midTime);
String end = processDateTime(endTime);
//x轴文字的高度
float hight = mBaseHeight - mBasePaddingBottom + mBottomTxtPadding;
canvas.drawText(bengin,
mBasePaddingLeft,
hight, mXYPaint);
canvas.drawText(mid,
mBasePaddingLeft + (mBaseWidth - mBasePaddingLeft - mBasePaddingRight) / 2,
hight, mXYPaint);
canvas.drawText(end,
mBaseWidth - mBasePaddingRight - mXYPaint.measureText(end),
hight, mXYPaint);//特别注意x轴的处理:- mXYPaint.measureText(end)
}
private void drawYPaint(Canvas canvas) {
//现将最小值、最大值画好
//draw min
float txtWigth = mXYPaint.measureText(mMinFundMode.originDataY) + mLeftTxtPadding;
canvas.drawText(mMinFundMode.originDataY + "",
mBasePaddingLeft - txtWigth,
mBaseHeight - mBasePaddingBottom, mXYPaint);
//draw max
canvas.drawText(mMaxFundMode.dataY + "",
mBasePaddingLeft - txtWigth,
mBasePaddingTop, mXYPaint);
//因为横线是均分的,所以只要取到最大值最小值的差值,均分即可。
float perYValues = (mMaxFundMode.dataY - mMinFundMode.dataY) / 4;
float perYWidth = (mBaseHeight - mBasePaddingBottom - mBasePaddingTop) / 4;
//从下到上依次画
for (int i = 1; i <= 3; i++) {
canvas.drawText(mMinFundMode.dataY + perYValues * i + "",
mBasePaddingLeft - txtWigth,
mBaseHeight - mBasePaddingBottom - perYWidth * i, mXYPaint);
}
}
private void drawDefTopTxtpaint(Canvas canvas) {
//画默认情况下前面的蓝色小圆点
Paint buleDotPaint = new Paint();
buleDotPaint.setColor(getColor(R.color.color_fundView_brokenLineColor));
buleDotPaint.setAntiAlias(true);
float r = 6;
buleDotPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(mBasePaddingLeft + r / 2, mBasePaddingTop / 2 + r, r, buleDotPaint);
float txtHight = getFontHeight(mDefAllIncomeTextSize, mDefAllIncomePaint);
//先画hint文字
Paint hintPaint = new Paint();
hintPaint.setColor(getColor(R.color.color_fundView_xyTxtColor));
hintPaint.setAntiAlias(true);
hintPaint.setTextSize(mDefAllIncomeTextSize);
String hintTxt = getString(R.string.string_fundView_defHintTxt);
canvas.drawText(hintTxt, mBasePaddingLeft + r + 10, mBasePaddingTop / 2 + txtHight / 2,
mDefAllIncomePaint);
if (mFundModeList == null || mFundModeList.isEmpty()) return;
canvas.drawText(mFundModeList.get(mFundModeList.size() - 1).dataY + "",
mBasePaddingLeft + r + 10 + hintPaint.measureText(getString(R.string.string_fundView_defHintTxt)) + 5,
mBasePaddingTop / 2 + txtHight / 2, mDefAllIncomePaint);
}
private void showLongPressView() {
mDrawLongPressPaint = true;
invalidate();
}
private void hiddenLongPressView() {
//实现蚂蚁金服延迟消失十字线
postDelayed(new Runnable() {
@Override
public void run() {
mDrawLongPressPaint = false;
invalidate();
}
}, 1000);
}
// 只需要把画笔颜色置为透明即可
private void hiddenLoadingPaint() {
mLoadingPaint.setColor(0x00000000);
mDrawLoadingPaint = false;
}
private String processDateTime(long beginTime) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(beginTime);
}
public float getFontHeight(float fontSize, Paint paint) {
paint.setTextSize(fontSize);
Paint.FontMetrics fm = paint.getFontMetrics();
return (float) (Math.ceil(fm.descent - fm.top) + 2);
}
/**
* 程序入口,设置数据
*/
public void setDataList(List<FundMode> fundModeList) {
if (fundModeList == null || fundModeList.size() == 0) return;
this.mFundModeList = fundModeList;
//开始获取最大值最小值;单个数据尺寸等
mMinFundMode = mFundModeList.get(0);
mMaxFundMode = mFundModeList.get(0);
for (FundMode fundMode : mFundModeList) {
if (fundMode.dataY < mMinFundMode.dataY) {
mMinFundMode = fundMode;
}
if (fundMode.dataY > mMaxFundMode.dataY) {
mMaxFundMode = fundMode;
}
}
//获取单个数据X/y轴的大小
mPerX = (mBaseWidth - mBasePaddingLeft - mBasePaddingRight) / mFundModeList.size();
mPerY = ((mBaseHeight - mBasePaddingTop - mBasePaddingBottom) / (mMaxFundMode.dataY - mMinFundMode.dataY));
Log.e(TAG, "setDataList: " + mMinFundMode + "," + mMaxFundMode + "..." + mPerX + "," + mPerY);
//数据过来,隐藏加载更多
hiddenLoadingPaint();
//刷新界面
invalidate();
}
//-----------------------对开发者暴露可以修改的参数-------
public List<FundMode> getFundModeList() {
return mFundModeList;
}
public FundView setFundModeList(List<FundMode> fundModeList) {
mFundModeList = fundModeList;
return this;
}
public float getBasePaddingTop() {
return mBasePaddingTop;
}
public FundView setBasePaddingTop(float basePaddingTop) {
mBasePaddingTop = basePaddingTop;
return this;
}
public float getBasePaddingBottom() {
return mBasePaddingBottom;
}
public FundView setBasePaddingBottom(float basePaddingBottom) {
mBasePaddingBottom = basePaddingBottom;
return this;
}
public float getBasePaddingLeft() {
return mBasePaddingLeft;
}
public FundView setBasePaddingLeft(float basePaddingLeft) {
mBasePaddingLeft = basePaddingLeft;
return this;
}
public float getBasePaddingRight() {
return mBasePaddingRight;
}
public FundView setBasePaddingRight(float basePaddingRight) {
mBasePaddingRight = basePaddingRight;
return this;
}
public FundMode getMinFundMode() {
return mMinFundMode;
}
public FundView setMinFundMode(FundMode minFundMode) {
mMinFundMode = minFundMode;
return this;
}
public FundMode getMaxFundMode() {
return mMaxFundMode;
}
public FundView setMaxFundMode(FundMode maxFundMode) {
mMaxFundMode = maxFundMode;
return this;
}
public float getPerX() {
return mPerX;
}
public FundView setPerX(float perX) {
mPerX = perX;
return this;
}
public float getPerY() {
return mPerY;
}
public FundView setPerY(float perY) {
mPerY = perY;
return this;
}
public Paint getLoadingPaint() {
return mLoadingPaint;
}
public FundView setLoadingPaint(Paint loadingPaint) {
mLoadingPaint = loadingPaint;
return this;
}
public float getLoadingTextSize() {
return mLoadingTextSize;
}
private FundView setLoadingTextSize(float loadingTextSize) {
mLoadingTextSize = loadingTextSize;
return this;
}
public String getLoadingText() {
return mLoadingText;
}
public FundView setLoadingText(String loadingText) {
mLoadingText = loadingText;
return this;
}
public boolean isDrawLoadingPaint() {
return mDrawLoadingPaint;
}
public FundView setDrawLoadingPaint(boolean drawLoadingPaint) {
mDrawLoadingPaint = drawLoadingPaint;
return this;
}
public Paint getXYPaint() {
return mXYPaint;
}
public FundView setXYPaint(Paint XYPaint) {
mXYPaint = XYPaint;
return this;
}
public float getXYTextSize() {
return mXYTextSize;
}
private FundView setXYTextSize(float XYTextSize) {
mXYTextSize = XYTextSize;
return this;
}
public float getLeftTxtPadding() {
return mLeftTxtPadding;
}
public FundView setLeftTxtPadding(float leftTxtPadding) {
mLeftTxtPadding = leftTxtPadding;
return this;
}
public float getBottomTxtPadding() {
return mBottomTxtPadding;
}
public FundView setBottomTxtPadding(float bottomTxtPadding) {
mBottomTxtPadding = bottomTxtPadding;
return this;
}
public Paint getInnerXPaint() {
return mInnerXPaint;
}
public FundView setInnerXPaint(Paint innerXPaint) {
mInnerXPaint = innerXPaint;
return this;
}
public float getInnerXStrokeWidth() {
return mInnerXStrokeWidth;
}
private FundView setInnerXStrokeWidth(float innerXStrokeWidth) {
mInnerXStrokeWidth = innerXStrokeWidth;
return this;
}
public Paint getBrokenPaint() {
return mBrokenPaint;
}
public FundView setBrokenPaint(Paint brokenPaint) {
mBrokenPaint = brokenPaint;
return this;
}
public float getBrokenStrokeWidth() {
return mBrokenStrokeWidth;
}
private FundView setBrokenStrokeWidth(float brokenStrokeWidth) {
mBrokenStrokeWidth = brokenStrokeWidth;
return this;
}
public Paint getLongPressPaint() {
return mLongPressPaint;
}
public FundView setLongPressPaint(Paint longPressPaint) {
mLongPressPaint = longPressPaint;
return this;
}
public boolean isDrawLongPressPaint() {
return mDrawLongPressPaint;
}
public FundView setDrawLongPressPaint(boolean drawLongPressPaint) {
mDrawLongPressPaint = drawLongPressPaint;
return this;
}
public long getPressTime() {
return mPressTime;
}
public FundView setPressTime(long pressTime) {
mPressTime = pressTime;
return this;
}
public float getPressX() {
return mPressX;
}
public FundView setPressX(float pressX) {
mPressX = pressX;
return this;
}
public float getPressY() {
return mPressY;
}
public FundView setPressY(float pressY) {
mPressY = pressY;
return this;
}
public Paint getDefAllIncomePaint() {
return mDefAllIncomePaint;
}
public FundView setDefAllIncomePaint(Paint defAllIncomePaint) {
mDefAllIncomePaint = defAllIncomePaint;
return this;
}
public float getDefAllIncomeTextSize() {
return mDefAllIncomeTextSize;
}
public FundView setDefAllIncomeTextSize(float defAllIncomeTextSize) {
mDefAllIncomeTextSize = defAllIncomeTextSize;
return this;
}
public Paint getLongPressTxtPaint() {
return mLongPressTxtPaint;
}
public FundView setLongPressTxtPaint(Paint longPressTxtPaint) {
mLongPressTxtPaint = longPressTxtPaint;
return this;
}
public float getLongPressTextSize() {
return mLongPressTextSize;
}
private FundView setLongPressTextSize(float longPressTextSize) {
mLongPressTextSize = longPressTextSize;
return this;
}
}
@@ -0,0 +1,573 @@
package com.tophold.trade.view.kview;
import android.util.Log;
import java.util.List;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/12/18 11:24
* 描 述 :所有金融相关算法全部在这里。算法参考【资料】包中的:常用股票指标计算公式及简单应用.pdf
* ============================================================
**/
// TODO: 2017/12/18 这里的算法需要核实!包括异常情况的处理和边界的处理是否合适。
public class FinancialAlgorithm {
final static String TAG = FinancialAlgorithm.class.getSimpleName();
public static void calculateKDJ(List<Quotes> quotesList) {
calculateKDJ(quotesList, 9, 3, 3);
}
/**
* 【该算法核对过,但是和线上的APP有细微的差距,不知道是否有错!】计算数据集合的kdj。
*
* @param quotesList 对应的数据集合
* @param kPeriod k所对应的周期,可以是:分钟、小时、天等
* @param dPeriod d所对应的周期,可以是:分钟、小时、天等
* @param jPeriod j所对应的周期,可以是:分钟、小时、天等
*/
public static void calculateKDJ(List<Quotes> quotesList, int kPeriod, int dPeriod, int jPeriod) {
//容错
if (quotesList == null || quotesList.isEmpty()) return;
if (kPeriod <= 0) kPeriod = 9;
if (dPeriod <= 0) dPeriod = 3;
if (jPeriod <= 0) jPeriod = 3;
//转换程序员的序号
kPeriod--;
dPeriod--;
jPeriod--;
double kRsv = 0;
//double dRsv=0;
double k = 0;
double d = 0;
double j = 0;
for (int i = 0; i < quotesList.size(); i++) {
Quotes quotes = quotesList.get(i);
//计算k
if (i < kPeriod) {
k = 50;
} else {
double c = 0;
double l = 0;
double h = 0;
double tempL = Integer.MAX_VALUE;
double tempH = Integer.MIN_VALUE;
for (int i1 = i - kPeriod; i1 <= i; i1++) {
Quotes kQuotes = quotesList.get(i1);
if (kQuotes.c < tempL) {
tempL = kQuotes.c;
l = tempL;
}
if (kQuotes.c > tempH) {
tempH = kQuotes.c;
h = tempH;
}
if (i1 == i) {
c = kQuotes.c;
}
}
kRsv = (c - l) / (h - l) * 100;
k = 2 / 3.0 * k + 1 / 3.0 * kRsv;
}
quotes.k = k;
//计算d
if (i < dPeriod) {
d = 50;
} else {
d = 2 / 3.0 * d + 1 / 3.0 * quotes.k;
}
quotes.d = d;
//计算j
j = 3 * quotes.k - 2 * quotes.d;
quotes.j = j;
//计算结束
//打印测试
//Log.e(TAG, "calculateKDJ: k:" + quotes.k + ",d:" + quotes.d + ",j:" + quotes.j);
}
}
public static void calculateMACD(List<Quotes> quotesList) {
calculateMACD(quotesList, 12, 26, 9);
}
/**
* MACD(x,y,z),一般取MACD(12,26,9)。
* MACD(x,y,z),x、y为平滑指数。z暂时不知道用处(不影响算法)。
* `EMAx=((x-1)/(x+1.0)*前一日EMA)+2.0/(x+1)*今日收盘价`;其中第一日的EMA是当日的收盘价。
* `EMA12=(11/13.0)*[前一日EMA12]+2.0/13*[今日quotes.c]`
* `EMA26=(25/27.0)*[前一日EMA26]+2.0/27*[今日quotes.c]`
* DIF:`DIF=EMA12-EMA26`
* DEA:`DEA=8/10.0*(前一日的DEA)+2/10.0*今日DIF`
* MACD:`2*(DIF-DEA)`
*
* @param quotesList 数据集合
* @param d1 平滑指数,一般为12
* @param d2 平滑指数,一般为26
* @param z 暂时未知
*/
public static void calculateMACD(List<Quotes> quotesList, int d1, int d2, int z) {
//容错
if (quotesList == null || quotesList.isEmpty()) return;
double ema12 = 0;
double ema26 = 0;
double dif = 0;
double dea = 0;
double macd = 0;
for (int i = 0; i < quotesList.size(); i++) {
Quotes quotes = quotesList.get(i);
if (i == 0) {
ema12 = quotes.c;
ema26 = quotes.c;
} else {
ema12 = (d1 - 1) / (d1 + 1.0) * ema12 + 2.0 / (d1 + 1) * quotes.c;
ema26 = (d2 - 1) / (d2 + 1.0) * ema26 + 2.0 / (d2 + 1) * quotes.c;
}
//计算dif
dif = ema12 - ema26;
//计算dea
if (i == 0) {
dea = 0;
} else {
dea = dea * 8 / 10.0 + dif * 2 / 10.0;
}
//计算macd
macd = 2 * (dif - dea);
quotes.dif = dif;
quotes.dea = dea;
quotes.macd = macd;
//计算结束
//打印日志
//Log.e(TAG, "calculateMACD: dif:" + quotes.dif + ",dea:" + quotes.dea + ",macd:" + macd);
}
}
public static void calculateRSI(List<Quotes> quotesList) {
calculateRSI(quotesList, 6);
calculateRSI(quotesList, 12);
calculateRSI(quotesList, 24);
}
/**
* 【该算法已核实】计算RSI。RSI(x,y,z),一般取RSI(6,12,24)。
* RSI(x,y,z),x、y、z均为周期单位,计算算法一致,只是周期不同。
* RSIx,在周期x内,upSum="在周期x内的上涨总点数"downSum="在周期x内的下跌总点数"`RSIx=upSum/(upSum+downSum)*100`;
* 注意:RSIx对于最开始的x+1周期内,不存在对应RSI,在图像上表示就是不显示对应RSIx即可。
*
* @param quotes 对应的数据集合
* @param period 周期
*/
public static void calculateRSI(List<Quotes> quotes, int period) {
//容错
if (quotes == null || quotes.isEmpty()) return;
if (period <= 0) period = 6;
//period单位的上涨点数
double upSum = 0f;
//period单位的下跌点数
double downSum = 0f;
//差值
double dis;
//最后计算的值
double rsi;
for (int i = 0; i < quotes.size(); i++) {
Quotes q = quotes.get(i);
if (i > 0) {
dis = q.c - quotes.get(i - 1).c;
if (dis >= 0) {
upSum += dis;
} else {
downSum -= dis;
}
//上面加,这里减。要保证累计的和周期为:period
if (i + 1 > period) {
dis = quotes.get(i - period + 1).c - quotes.get(i - period).c;
if (dis >= 0) {
upSum -= dis;
} else {
downSum += dis;
}
rsi = upSum / (upSum + downSum) * 100;
if (period == 6) q.rsi6 = rsi;
else if (period == 12) q.rsi12 = rsi;
else if (period == 24) q.rsi24 = rsi;
else Log.e(TAG, "calculateRSI: 不存在该周期:" + period);
}
}
}
}
/**
* 【该算法已核实】计算公式:MA =(C1+C2+C3+C4+C5+...+Cn)/n,其中C为收盘价n为移动平均周期数。
* 例如现货黄金的5日移动平均价格计算方法为:MA5=(前四天收盘价+前三天收盘价+前天收盘价+昨天收盘价+今天收盘价)/5。
* 特殊的,假如数据集合中最开始的n个数据,是没法计算MAn的。这里的处理方式是不计算,绘制时直接不绘制对应MA即可。
*
* @param quotesList 数据集合
* @param period MAn中的n,周期,一般是:5、10、20、30、60。
*/
public static void calculateMA(List<Quotes> quotesList, int period, KViewType.MaType maType) {
boolean isMaster = true;
if (maType == KViewType.MaType.volMa5 || maType == KViewType.MaType.volMa10)
isMaster = false;
if (quotesList == null || quotesList.isEmpty()) return;
if (!isMaster && quotesList.get(0).vol <= 0)
throw new IllegalArgumentException("请确保设置vol参数");
if (period < 0 || period > quotesList.size() - 1) return;
//包含今日的n日和
double sum = 0;
for (int i = 0; i < quotesList.size(); i++) {
//计算和
Quotes quotes = quotesList.get(i);
sum += isMaster ? quotes.c : quotes.vol;
if (i > period - 1) {
Quotes q = quotesList.get(i - period);
sum -= isMaster ? q.c : q.vol;
}
//边界
if (i < period - 1) {
continue;
}
double result = sum / period;
if (period == 5) {
if (isMaster) quotes.ma5 = result;
else quotes.volMa5 = result;
} else if (period == 10) {
if (isMaster) quotes.ma10 = result;
else quotes.volMa10 = result;
} else if (period == 20) {
if (isMaster) quotes.ma20 = result;
else {
Log.e(TAG, "calculateMA: 没有该种period" + period + "," + maType);
}
} else {
Log.e(TAG, "calculateMA: 没有该种period" + period + "," + maType);
return;
}
}
}
/**
* 【该算法已核实】BOLL(n)计算公式:
* MA=n日内的收盘价之和÷n。
* MD=n日的平方根(C-MA)的两次方之和除以n
* MB=n1)日的MA
* UP=MB+k×MD
* DN=MBk×MD
* K为参数,可根据股票的特性来做相应的调整,一般默认为2
*
* @param quotesList 数据集合
* @param period 周期,一般为26
* @param k 参数,可根据股票的特性来做相应的调整,一般默认为2
*/
public static void calculateBOLL(List<Quotes> quotesList, int period, int k) {
if (quotesList == null || quotesList.isEmpty()) return;
if (period < 0 || period > quotesList.size() - 1) return;
double up;//上轨线
double mb;//中轨线
double dn;//下轨线
//n日
double sum = 0;
//n-1日
double sum2 = 0;
for (int i = 0; i < quotesList.size(); i++) {
Quotes quotes = quotesList.get(i);
sum += quotes.c;
sum2 += quotes.c;
if (i > period - 1)
sum -= quotesList.get(i - period).c;
if (i > period - 2)
sum2 -= quotesList.get(i - period + 1).c;
//这个范围不计算,在View上的反应就是不显示这个范围的boll线
if (i < period - 1)
continue;
//n日MA
double ma = sum / period;
//n-1日MA
double ma2 = sum2 / (period - 1);
double md = 0;
for (int j = i + 1 - period; j <= i; j++) {
//n日
md += Math.pow(quotesList.get(j).c - ma, 2);
}
md = Math.sqrt(md / period);
//(n1)日的MA
mb = ma2;
up = mb + k * md;
dn = mb - k * md;
quotes.up = up;
quotes.mb = mb;
quotes.dn = dn;
}
}
public static void calculateBOLL(List<Quotes> quotesList) {
calculateBOLL(quotesList, 26, 2);
}
/**
* 找到单个报价中的最小值
*
* @param quotes
* @param masterType
* @return
*/
public static double getMasterMinY(Quotes quotes, KViewType.MasterIndicatrixType masterType) {
double min = Integer.MAX_VALUE;
//ma
if (masterType == KViewType.MasterIndicatrixType.MA || masterType == KViewType.MasterIndicatrixType.MA_BOLL) {
if (quotes.ma5 != 0 && quotes.ma5 < min) {
min = quotes.ma5;
}
if (quotes.ma10 != 0 && quotes.ma10 < min) {
min = quotes.ma10;
}
if (quotes.ma20 != 0 && quotes.ma20 < min) {
min = quotes.ma20;
}
}
//boll
if (masterType == KViewType.MasterIndicatrixType.BOLL || masterType == KViewType.MasterIndicatrixType.MA_BOLL) {
//boll
if (quotes.mb != 0 && quotes.mb < min) {
min = quotes.mb;
}
if (quotes.up != 0 && quotes.up < min) {
min = quotes.up;
}
if (quotes.dn != 0 && quotes.dn < min) {
min = quotes.dn;
}
}
//quotes
if (quotes.l != 0 && quotes.l < min) {
min = quotes.l;
}
//没有找到
if (min == Integer.MAX_VALUE) {
min = 0;
}
return min;
}
/**
* 找到单个报价中的最大值
*
* @param quotes
* @param masterType
* @return
*/
public static double getMasterMaxY(Quotes quotes, KViewType.MasterIndicatrixType masterType) {
double max = Integer.MIN_VALUE;
//ma
//只有在存在ma的情况下才计算
if (masterType == KViewType.MasterIndicatrixType.MA || masterType == KViewType.MasterIndicatrixType.MA_BOLL) {
if (quotes.ma5 != 0 && quotes.ma5 > max) {
max = quotes.ma5;
}
if (quotes.ma10 != 0 && quotes.ma10 > max) {
max = quotes.ma10;
}
if (quotes.ma20 != 0 && quotes.ma20 > max) {
max = quotes.ma20;
}
}
//boll
if (masterType == KViewType.MasterIndicatrixType.BOLL || masterType == KViewType.MasterIndicatrixType.MA_BOLL) {
if (quotes.mb != 0 && quotes.mb > max) {
max = quotes.mb;
}
if (quotes.up != 0 && quotes.up > max) {
max = quotes.up;
}
if (quotes.dn != 0 && quotes.dn > max) {
max = quotes.dn;
}
}
//quotes
if (quotes.h != 0 && quotes.h > max) {
max = quotes.h;
}
//没有找到
if (max == Integer.MIN_VALUE) {
max = 0;
}
return max;
}
/**
* 副图:找到单个报价中的最小值
*
* @param quotes
* @param minorType
* @return
*/
public static double getMinorMinY(Quotes quotes, KViewType.MinorIndicatrixType minorType) {
double min = Integer.MAX_VALUE;
//macd
if (minorType == KViewType.MinorIndicatrixType.MACD) {
if (quotes.dif != 0 && quotes.dif < min) {
min = quotes.dif;
}
if (quotes.dea != 0 && quotes.dea < min) {
min = quotes.dea;
}
if (quotes.macd != 0 && quotes.macd < min) {
min = quotes.macd;
}
}
//RSI
if (minorType == KViewType.MinorIndicatrixType.RSI) {
if (quotes.rsi6 != 0 && quotes.rsi6 < min) {
min = quotes.rsi6;
}
if (quotes.rsi12 != 0 && quotes.rsi12 < min) {
min = quotes.rsi12;
}
if (quotes.rsi24 != 0 && quotes.rsi24 < min) {
min = quotes.rsi24;
}
}
//KDJ
if (minorType == KViewType.MinorIndicatrixType.KDJ) {
if (quotes.k != 0 && quotes.k < min) {
min = quotes.k;
}
if (quotes.d != 0 && quotes.d < min) {
min = quotes.d;
}
if (quotes.j != 0 && quotes.j < min) {
min = quotes.j;
}
}
//没有找到
if (min == Integer.MAX_VALUE) {
min = 0;
}
return min;
}
/**
* 副图:找到单个报价中的最大值
*
* @param quotes
* @param minorType
* @return
*/
public static double getMinorMaxY(Quotes quotes, KViewType.MinorIndicatrixType minorType) {
double max = Integer.MIN_VALUE;
//macd
if (minorType == KViewType.MinorIndicatrixType.MACD) {
if (quotes.dif != 0 && quotes.dif > max) {
max = quotes.dif;
}
if (quotes.dea != 0 && quotes.dea > max) {
max = quotes.dea;
}
if (quotes.macd != 0 && quotes.macd > max) {
max = quotes.macd;
}
}
//RSI
if (minorType == KViewType.MinorIndicatrixType.RSI) {
if (quotes.rsi6 != 0 && quotes.rsi6 > max) {
max = quotes.rsi6;
}
if (quotes.rsi12 != 0 && quotes.rsi12 > max) {
max = quotes.rsi12;
}
if (quotes.rsi24 != 0 && quotes.rsi24 > max) {
max = quotes.rsi24;
}
}
//KDJ
if (minorType == KViewType.MinorIndicatrixType.KDJ) {
if (quotes.k != 0 && quotes.k > max) {
max = quotes.k;
}
if (quotes.d != 0 && quotes.d > max) {
max = quotes.d;
}
if (quotes.j != 0 && quotes.j > max) {
max = quotes.j;
}
}
//没有找到
if (max == Integer.MIN_VALUE) {
max = 0;
}
return max;
}
/**
* 量图:找到最大值
*
* @param quotes
* @return
*/
public static double getVolMaxY(Quotes quotes) {
double max = Integer.MIN_VALUE;
//vol、volma5,volma10
if (quotes.vol != 0 && quotes.vol > max) {
max = quotes.vol;
}
if (quotes.volMa5 != 0 && quotes.volMa5 > max) {
max = quotes.volMa5;
}
if (quotes.volMa10 != 0 && quotes.volMa10 > max) {
max = quotes.volMa10;
}
return max;
}
/**
* 量图:找到最小值
*
* @param quotes
* @return
*/
public static double getVolMinY(Quotes quotes) {
double min = Integer.MAX_VALUE;
//vol、volma5,volma10
if (quotes.vol != 0 && quotes.vol < min) {
min = quotes.vol;
}
if (quotes.volMa5 != 0 && quotes.volMa5 < min) {
min = quotes.volMa5;
}
if (quotes.volMa10 != 0 && quotes.volMa10 < min) {
min = quotes.volMa10;
}
return min;
}
}
@@ -0,0 +1,204 @@
package com.tophold.trade.view.kview;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.LinearLayout;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2018/03/03 15:38
* 描 述 :KView,包含主图和副图,包括手势、加载数据等
* ============================================================
**/
public class KLayoutView extends LinearLayout {
public static String TAG;
public static final float DEF_MINORHRATIO = 0.25f;
public static final float DEF_VOLHRATIO = 0.25f;
protected MasterView mMasterView;
protected MinorView mMinorView;
protected VolView mVolView;
//副图高度占全部高度比
protected float mMinorHRatio = DEF_MINORHRATIO;
//量图高度占全部高度比
protected float mVolHRatio = DEF_VOLHRATIO;
//是否展示副图
protected boolean isShowMinor = true;
//是否展示量图
protected boolean isShowVol = false;
public KLayoutView(Context context) {
this(context, null);
}
public KLayoutView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public KLayoutView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TAG = this.getClass().getSimpleName();
layoutViews();
initDefAttrs();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
}
private void initDefAttrs() {
setShowMinor(true);
setShowVol(false);
}
private void layoutViews() {
setOrientation(VERTICAL);
mMasterView = new MasterView(getContext());
// mMasterView.setBackgroundColor(getResources().getColor(R.color.color_fundView_brokenLineColor));
mMinorView = new MinorView(getContext());
// mMinorView.setBackgroundColor(getResources().getColor(R.color.color_fundView_xLineColor));
mVolView = new VolView(getContext());
//测量高度
measureHeight();
addView(mMasterView);
addView(mMinorView);
addView(mVolView);
mMasterView.setMinorListener(mMinorView.getMinorListener());
mMasterView.setVolListener(mVolView.getVolListener());
}
private void measureHeight() {
LayoutParams params = new LayoutParams(
LayoutParams.MATCH_PARENT, 0);
params.weight = 1 - mMinorHRatio - mVolHRatio;
mMasterView.setLayoutParams(params);
LayoutParams params2 = new LayoutParams(
LayoutParams.MATCH_PARENT, 0);
params2.weight = mMinorHRatio;
mMinorView.setLayoutParams(params2);
LayoutParams params3 = new LayoutParams(
LayoutParams.MATCH_PARENT, 0);
params3.weight = mVolHRatio;
mVolView.setLayoutParams(params3);
}
//-----------------------对开发者暴露可以修改的参数-------
public MasterView getMasterView() {
return mMasterView;
}
public KLayoutView setMasterView(MasterView masterView) {
mMasterView = masterView;
return this;
}
public MinorView getMinorView() {
return mMinorView;
}
public KLayoutView setMinorView(MinorView minorView) {
mMinorView = minorView;
return this;
}
public float getMinorHRatio() {
return mMinorHRatio;
}
public void setMinorHRatio(float minorHRatio) {
mMinorHRatio = minorHRatio;
}
public boolean isShowMinor() {
return isShowMinor;
}
public VolView getVolView() {
return mVolView;
}
public KLayoutView setVolView(VolView volView) {
mVolView = volView;
return this;
}
public float getVolHRatio() {
return mVolHRatio;
}
public KLayoutView setVolHRatio(float volHRatio) {
mVolHRatio = volHRatio;
return this;
}
public boolean isShowVol() {
return isShowVol;
}
@NonNull
private KLayoutView reloadHeight() {
//为什么要这样刷新?先重新测量主图和副图的高度,然后再去测量各自的seekAndCalculateCellData
measureHeight();
postDelayed(() -> {
mMasterView.seekAndCalculateCellData();
mMinorView.seekAndCalculateCellData();
mVolView.seekAndCalculateCellData();
}, 300);
return this;
}
/**
* 这个问题比较奇葩。主要原因是:整个View重新布局了大小,等于重新走了生命周期,导致各种问题。
* 之前一般都是只是走onDraw()方法,所以基本不会有什么问题。
* 现在是高度变了,因此必须重新测量。
* 现在下面这种实现方式仍然会有视觉上的延迟,体验不太好,暂时没有好评的解决方案。
* 另外:平时基本不会这样直接的主图副图显示不显示来回切换。如果在初始化的时候就设置好,不会有任何问题。
*
* @param showMinor
*/
public KLayoutView setShowMinor(boolean showMinor) {
isShowMinor = showMinor;
if (!isShowMinor) {
mMinorHRatio = 0;
} else {
mMinorHRatio = DEF_MINORHRATIO;
}
return reloadHeight();
}
public KLayoutView setShowVol(boolean showVol) {
isShowVol = showVol;
if (!isShowVol) {
mVolHRatio = 0;
} else {
mVolHRatio = DEF_VOLHRATIO;
}
Log.d(TAG, "setShowVol: " + isShowVol);
mVolView.setShowVol(isShowVol);
return reloadHeight();
}
}
@@ -0,0 +1,156 @@
package com.tophold.trade.view.kview;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.tophold.trade.R;
import java.util.List;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2018/03/04 11:06
* 描 述 KView入口
* ============================================================
**/
public final class KView extends KLayoutView {
//主图展示的是蜡烛图还是分时图
protected boolean isShowTimSharing = true;
//设置数据精度
protected int mDigit = 4;
public KView(Context context) {
this(context, null);
}
public KView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public KView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initListener();
}
private void initListener() {
mMasterView.setMaxPostionListener((quotes, x, y) -> {
Log.d(TAG, "initListener1: " + x + "," + y);
});
mMasterView.setMinPostionListener((quotes, x, y) -> {
Log.d(TAG, "initListener2: " + x + "," + y);
});
mMasterView.setLastPostionListener((quotes, x, y) -> {
Log.d(TAG, "initListener3: " + x + "," + y);
});
}
/**
* 数据设置入口
*
* @param quotesList
*/
public void setKViewData(List<Quotes> quotesList, KViewListener.MasterTouchListener masterTouchListener) {
if (quotesList == null || quotesList.isEmpty()) {
Toast.makeText(getContext(), "数据异常1111", Toast.LENGTH_SHORT).show();
Log.e(TAG, "setKViewData: 数据异常111");
return;
}
mMasterView.setTimeSharingData(quotesList, masterTouchListener);
mMinorView.setTimeSharingData(quotesList, masterTouchListener);
mVolView.setTimeSharingData(quotesList, masterTouchListener);
}
/**
* 实时推送过来的数据,实时更新。
* 这个地方可以优化:因为用户不知道什么时候可以Push过来数据,如果不处理。可能存在一种情况:数据还没加载完毕,push就过来了就会出现异常。
*
* @param quotes
*/
public void pushKViewData(Quotes quotes, long period) {
if (quotes == null) {
//Toast.makeText(getContext(), "数据异常", Toast.LENGTH_SHORT).show();
//Log.e(TAG, "setKViewData: 数据异常");
return;
}
mMasterView.pushingTimeSharingData(quotes, period);
mMinorView.pushingTimeSharingData(quotes, period);
mVolView.pushingTimeSharingData(quotes, period);
}
/**
* 加载更多数据
*
* @param quotesList
*/
public void loadKViewData(List<Quotes> quotesList) {
if (quotesList == null || quotesList.isEmpty()) {
Toast.makeText(getContext(), "数据异常", Toast.LENGTH_SHORT).show();
Log.e(TAG, "setKViewData: 数据异常");
return;
}
mMasterView.loadMoreTimeSharingData(quotesList);
mMinorView.loadMoreTimeSharingData(quotesList);
mVolView.loadMoreTimeSharingData(quotesList);
}
/**
* 加载更多失败,在这里添加逻辑
*/
public void loadMoreError() {
mMasterView.loadMoreError();
}
/**
* 加载更多成功,在这里添加逻辑
*/
public void loadMoreSuccess() {
mMasterView.loadMoreSuccess();
}
/**
* 正在加载更多,在这里添加逻辑
*/
public void loadMoreIng() {
mMasterView.loadMoreIng();
}
/**
* 没有更多数据,在这里添加逻辑
*/
public void loadMoreNoData() {
mMasterView.loadMoreNoData();
}
//-----------------------对开发者暴露可以修改的参数-------
public boolean isShowTimSharing() {
return isShowTimSharing;
}
public void setShowTimSharing(boolean showTimSharing) {
isShowTimSharing = showTimSharing;
mMasterView.setViewType(showTimSharing ? KViewType.MasterViewType.TIMESHARING : KViewType.MasterViewType.CANDLE);
}
public int getDigit() {
return mDigit;
}
public void setDigit(int digit) {
mDigit = digit;
mMasterView.setDigits(mDigit);
mMinorView.setDigits(mDigit);
}
}
@@ -0,0 +1,66 @@
package com.tophold.trade.view.kview;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/04/04 18:12
* 描 述
* ============================================================
*/
public class KViewListener {
/**
* 主图的长按、加载更多回调
*/
public interface MasterTouchListener {
void onLongTouch(Quotes preQuotes, Quotes currentQuotes);
void onUnLongTouch();
void needLoadMore();
}
/**
* 主图的监听,主要供副图使用,把这些数据回调给副图,避免副图再做复杂重复的操作。
*/
public interface MinorListener {
/**
* 长按操作
*
* @param pressIndex 按下所对应的索引
* @param currQuotes 按下所对应的点
*/
void masterLongPressListener(int pressIndex, Quotes currQuotes);
/**
* 不再长按回调
*/
void masterNoLongPressListener();
/**
* 缩放
*
* @param beginIndex 缩放后的起始位置索引
* @param endIndex 缩放后的结束索引
* @param shownMaxCount 可见数据总条数
*/
void masteZoomlNewIndex(int beginIndex, int endIndex, int shownMaxCount);
/**
* 左右滑动
*
* @param endIndex 滑动后的结束索引
* @param currPullType 当前PullType类型
* @param shownMaxCount
* @param shownMaxCount 可见数据总条数
*/
void mastelPullmNewIndex(int beginIndex, int endIndex, KViewType.PullType currPullType, int shownMaxCount);
}
/**
* 关键点的监听:可视范围内最大值点、可视范围内最小值点、最后一个点监听(数据集合的最后一个点)
*/
public interface PostionListner {
void postion(Quotes quotes, float x, float y);
}
}
@@ -0,0 +1,79 @@
package com.tophold.trade.view.kview;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/04/04 17:04
* 描 述
* ============================================================
*/
public class KViewType {
/**
* 主图类型:分时图还是蜡烛图
*/
public enum MasterViewType {
TIMESHARING,
CANDLE
}
/**
* 拖拽类型
*/
protected enum PullType {
PULL_RIGHT,//向右滑动
PULL_LEFT,//向左滑动
PULL_RIGHT_STOP,//滑动到最右边
PULL_LEFT_STOP,//滑动到最左边
}
/**
* 主图上面的技术指标类型
*/
public enum MasterIndicatrixType {
NONE,//无
MA,//MA5、10、20
BOLL,//BOLL(26)
MA_BOLL//MA5、10、20和BOLL(26)同时展示
}
/**
* 主图上面的详细技术指标类型,主要用于判断何种具体的线,进行相应的处理
*/
public enum MasterIndicatrixDetailType {
MA5,
MA10,
MA20,
BOLLUP,
BOLLMB,
BOLLDN
}
/**
* 副图上面的技术指标类型
*/
public enum MinorIndicatrixType {
MACD,
RSI,
KDJ
}
/**
* 滑动的类型
*/
public enum MoveType {
STEP,//一点一点移动
ONFLING//具有onfling效果
}
/**
* MA类型:主图:ma5,ma10,ma20;量图:ma5,ma10
*/
public enum MaType {
ma5,//主图ma5
ma10,//主图ma10
ma20,//主图ma20
volMa5,//量图ma5
volMa10//量图ma10
}
}
@@ -0,0 +1,889 @@
package com.tophold.trade.view.kview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import com.tophold.trade.R;
import com.tophold.trade.utils.FormatUtil;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/12/14 17:56
* 描 述 :副图。该view提供各种副图指标。
* ============================================================
**/
public class MinorView extends KBaseView {
/**
* 初始化所有需要的颜色资源
*/
//共有的
int mOuterStrokeColor;
int mInnerXyDashColor;
int mXyTxtColor;
int mLegendTxtColor;
int mLongPressTxtColor;
//macd
int mMacdBuyColor;
int mMacdSellColor;
int mMacdDifColor;
int mMacdDeaColor;
int mMacdMacdColor;
Paint mMacdPaint;
float mMacdLineWidth = 1;
//rsi
int mRsi6Color;
int mRsi12Color;
int mRsi24Color;
Paint mRsiPaint;
float mRsiLineWidth = 1;
//kdj
int mKColor;
int mDColor;
int mJColor;
Paint mKdjPaint;
float mKdjLineWidth = 1;
//当前显示的指标
KViewType.MinorIndicatrixType mMinorType = KViewType.MinorIndicatrixType.MACD;
//y轴上最大值和最小值
protected double mMinY;
protected double mMaxY;
//蜡烛图间隙,大小以单个蜡烛图的宽度的比例算。可修改。
protected float mCandleDiverWidthRatio = 0.1f;
//监听主图的长按事件
private KViewListener.MinorListener mMinorListener;
public MinorView(Context context) {
this(context, null);
}
public MinorView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MinorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs();
initListener();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw: " + mBaseHeight + "," + mBaseWidth);
if (mQuotesList == null || mQuotesList.isEmpty()) {
return;
}
//绘制右侧文字
drawYRightTxt(canvas);
//绘制图例
drawLegend(canvas);
//绘制核心指标线
drawMinorIndicatrix(canvas);
//绘制长按线
drawLongPress(canvas);
}
private void initAttrs() {
initDefAttrs();
initColorRes();
initMacdPaint();
initRsiPaint();
initKdjPaint();
}
private void initKdjPaint() {
mKdjPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mKdjPaint.setColor(mKColor);
mKdjPaint.setAntiAlias(true);
mKdjPaint.setStrokeWidth(mKdjLineWidth);
mKdjPaint.setStyle(Paint.Style.STROKE);
mKdjPaint.setTextSize(mLegendTxtSize);
}
private void initRsiPaint() {
mRsiPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mRsiPaint.setColor(mRsi6Color);
mRsiPaint.setAntiAlias(true);
mRsiPaint.setStrokeWidth(mRsiLineWidth);
mRsiPaint.setStyle(Paint.Style.STROKE);
mRsiPaint.setTextSize(mLegendTxtSize);
}
private void initMacdPaint() {
mMacdPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMacdPaint.setColor(mMacdBuyColor);
mMacdPaint.setAntiAlias(true);
mMacdPaint.setStrokeWidth(mMacdLineWidth);
mMacdPaint.setTextSize(mLegendTxtSize);
}
private void initDefAttrs() {
//重写内边距大小
mInnerTopBlankPadding = 8;
mInnerBottomBlankPadding = 8;
//重写Legend padding
mLegendPaddingTop = 0;
mLegendPaddingRight = 4;
mLegendPaddingLeft = 4;
setShowInnerX(false);
setShowInnerY(true);
}
private void initColorRes() {
//颜色
mOuterStrokeColor = getColor(R.color.color_minorView_outerStrokeColor);
mInnerXyDashColor = getColor(R.color.color_minorView_innerXyDashColor);
mXyTxtColor = getColor(R.color.color_minorView_xYTxtColor);
mLegendTxtColor = getColor(R.color.color_minorView_legendTxtColor);
mLongPressTxtColor = getColor(R.color.color_minorView_longPressTxtColor);
mMacdBuyColor = getColor(R.color.color_minorView_macdBuyColor);
mMacdSellColor = getColor(R.color.color_minorView_macdSellColor);
mMacdDifColor = getColor(R.color.color_minorView_macdDifColor);
mMacdDeaColor = getColor(R.color.color_minorView_macdDeaColor);
mMacdMacdColor = getColor(R.color.color_minorView_macdMacdColor);
mRsi6Color = getColor(R.color.color_minorView_rsi6Color);
mRsi12Color = getColor(R.color.color_minorView_rsi12Color);
mRsi24Color = getColor(R.color.color_minorView_rsi24Color);
mKColor = getColor(R.color.color_minorView_kColor);
mDColor = getColor(R.color.color_minorView_dColor);
mJColor = getColor(R.color.color_minorView_jColor);
}
private void initListener() {
mMinorListener = new KViewListener.MinorListener() {
@Override
public void masterLongPressListener(int pressIndex, Quotes currQuotes) {
mDrawLongPress = true;
mCurrLongPressQuotes = mQuotesList.get(pressIndex);
invalidate();
}
@Override
public void masterNoLongPressListener() {
mDrawLongPress = false;
invalidate();
}
@Override
public void masteZoomlNewIndex(int beginIndex, int endIndex, int shownMaxCount) {
mBeginIndex = beginIndex;
mEndIndex = endIndex;
mShownMaxCount = shownMaxCount;
seekAndCalculateCellData();
}
@Override
public void mastelPullmNewIndex(int beginIndex, int endIndex, KViewType.PullType currPullType, int shownMaxCount) {
mBeginIndex = beginIndex;
mEndIndex = endIndex;
mShownMaxCount = shownMaxCount;
mPullType = currPullType;
//处理右侧内边距
if (currPullType == KViewType.PullType.PULL_RIGHT_STOP) {
//重置到之前的状态
mInnerRightBlankPadding = DEF_INNER_RIGHT_BLANK_PADDING;
} else {
mInnerRightBlankPadding = 0;
}
seekAndCalculateCellData();
}
};
}
private void drawLegend(Canvas canvas) {
//绘制非按下情况下图例
drawNoPressLegend(canvas);
//绘制按下的图例
drawPressLegend(canvas);
}
private void drawMinorIndicatrix(Canvas canvas) {
mMacdPaint.setStyle(Paint.Style.STROKE);
mRsiPaint.setStyle(Paint.Style.STROKE);
mKdjPaint.setStyle(Paint.Style.STROKE);
if (mMinorType == KViewType.MinorIndicatrixType.MACD) {
drawMACD(canvas);
} else if (mMinorType == KViewType.MinorIndicatrixType.RSI) {
drawRSI(canvas);
} else if (mMinorType == KViewType.MinorIndicatrixType.KDJ) {
drawKDJ(canvas);
}
}
private void drawLongPress(Canvas canvas) {
if (!mDrawLongPress) return;
if (mCurrLongPressQuotes == null) return;
//y轴线
canvas.drawLine(mCurrLongPressQuotes.floatX, mBasePaddingTop, mCurrLongPressQuotes.floatX,
mBaseHeight - mBasePaddingBottom, mLongPressPaint);
}
private void drawPressLegend(Canvas canvas) {
if (!mDrawLongPress) return;
mMacdPaint.setStyle(Paint.Style.FILL);
mRsiPaint.setStyle(Paint.Style.FILL);
mKdjPaint.setStyle(Paint.Style.FILL);
float x = (float) (mLegendPaddingLeft + mBasePaddingLeft);
float y = (float) (mLegendPaddingTop + mBasePaddingTop) + getFontHeight(mLegendTxtSize, mMacdPaint);
String showTxt;
switch (mMinorType) {
case MACD:
showTxt = "DIFF:" + FormatUtil.numFormat(mCurrLongPressQuotes.dif, mDigits) + " ";
mMacdPaint.setColor(mMacdDifColor);
canvas.drawText(showTxt, x,
y, mMacdPaint);
float leftWidth11 = mMacdPaint.measureText(showTxt);
showTxt = "DEA:" + FormatUtil.numFormat(mCurrLongPressQuotes.dea, mDigits) + " ";
mMacdPaint.setColor(mMacdDeaColor);
canvas.drawText(showTxt, x + leftWidth11, y, mMacdPaint);
float leftWidth12 = mMacdPaint.measureText(showTxt);
showTxt = "MACD:" + FormatUtil.numFormat(mCurrLongPressQuotes.macd, mDigits) + " ";
mMacdPaint.setColor(mMacdMacdColor);
canvas.drawText(showTxt, (x + leftWidth11 + leftWidth12),
y, mMacdPaint);
break;
case RSI:
showTxt = "RSI6:" + FormatUtil.numFormat(mCurrLongPressQuotes.rsi6, mDigits) + " ";
mRsiPaint.setColor(mRsi6Color);
canvas.drawText(showTxt, (x),
y, mRsiPaint);
float leftWidth21 = mRsiPaint.measureText(showTxt);
showTxt = "RSI12:" + FormatUtil.numFormat(mCurrLongPressQuotes.rsi12, mDigits) + " ";
mRsiPaint.setColor(mRsi12Color);
canvas.drawText(showTxt, (x + leftWidth21),
y, mRsiPaint);
float leftWidth22 = mRsiPaint.measureText(showTxt);
showTxt = "RSI24:" + FormatUtil.numFormat(mCurrLongPressQuotes.rsi24, mDigits) + " ";
mRsiPaint.setColor(mRsi24Color);
canvas.drawText(showTxt, (x + leftWidth21 + leftWidth22),
y, mRsiPaint);
break;
case KDJ:
showTxt = "K:" + FormatUtil.numFormat(mCurrLongPressQuotes.k, mDigits) + " ";
mKdjPaint.setColor(mKColor);
canvas.drawText(showTxt, (x),
y, mKdjPaint);
float leftWidth = mKdjPaint.measureText(showTxt);
showTxt = "D:" + FormatUtil.numFormat(mCurrLongPressQuotes.d, mDigits) + " ";
mKdjPaint.setColor(mDColor);
canvas.drawText(showTxt, (x + leftWidth),
y, mKdjPaint);
float leftWidth2 = mKdjPaint.measureText(showTxt);
showTxt = "J:" + FormatUtil.numFormat(mCurrLongPressQuotes.j, mDigits) + " ";
mKdjPaint.setColor(mJColor);
canvas.drawText(showTxt, (x + leftWidth + leftWidth2),
y, mKdjPaint);
break;
default:
break;
}
}
private void drawNoPressLegend(Canvas canvas) {
if (mDrawLongPress) return;
String showTxt = "";
if (mMinorType == KViewType.MinorIndicatrixType.MACD) {
showTxt = "MACD(12,26,9)";
} else if (mMinorType == KViewType.MinorIndicatrixType.RSI) {
showTxt = "RSI(6,12,24)";
} else if (mMinorType == KViewType.MinorIndicatrixType.KDJ) {
showTxt = "KDJ(9,3,3)";
}
canvas.drawText(showTxt,
(float) (mBaseWidth - mLegendPaddingRight - mBasePaddingRight - mLegendPaint.measureText(showTxt)),
(float) (mLegendPaddingTop + mBasePaddingTop + getFontHeight(mLegendTxtSize, mLegendPaint)), mLegendPaint);
}
private void drawYRightTxt(Canvas canvas) {
//绘制右侧的y轴文字
//现将最小值、最大值画好
float halfTxtHight = getFontHeight(mXYTxtSize, mXYTxtPaint) / 2;//应该/2的,但是不准确,原因不明
float x = mBaseWidth - mBasePaddingRight + mRightTxtPadding;
float maxY = mBasePaddingTop + halfTxtHight + mInnerTopBlankPadding;
float minY = mBaseHeight - mBasePaddingBottom - halfTxtHight - mInnerBottomBlankPadding;
//draw min
canvas.drawText(FormatUtil.numFormat(mMinY, mDigits),
x,
minY, mXYTxtPaint);
//draw max
canvas.drawText(FormatUtil.numFormat(mMaxY, mDigits),
x,
maxY, mXYTxtPaint);
//draw middle
canvas.drawText(FormatUtil.numFormat((mMaxY + mMinY) / 2.0, mDigits),
x, (minY + maxY) / 2.0f,
mXYTxtPaint);
}
private void drawMACD(Canvas canvas) {
//macd
//首先寻找"0"点,这个点是正负macd的分界点
float v = mBaseHeight - mBasePaddingBottom - mInnerBottomBlankPadding;
double zeroY = v - mPerY * (0 - mMinY);
float startX, startY, stopX, stopY;
//dif
float difX, difY;
Path difPath = new Path();
//dea
float deaX, deaY;
Path deaPath = new Path();
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
/*macd*/
//找另外一个y点
double y = v - mPerY * (quotes.macd - mMinY);
startX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mCandleDiverWidthRatio * mPerX / 2;
stopX = mBasePaddingLeft + (i - mBeginIndex + 1) * mPerX - mCandleDiverWidthRatio * mPerX / 2;
startY = (float) zeroY;
stopY = (float) y;
if (quotes.macd > 0) {
mMacdPaint.setColor(mMacdBuyColor);
} else {
mMacdPaint.setColor(mMacdSellColor);
}
// Log.e(TAG, "drawMACD: "+startY+","+stopY +
// ","+(mBasePaddingTop+mInnerTopBlankPadding)+","+
// (mBaseHeight-mBasePaddingBottom-mInnerBottomBlankPadding));
mMacdPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(startX, startY, stopX, stopY, mMacdPaint);
/*dif*/
difX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
difY = (float) (v - mPerY * (quotes.dif - mMinY));
if (i == mBeginIndex) {
difPath.moveTo(difX - mPerX / 2, difY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
difX += mPerX / 2;//最后一个点特殊处理
}
difPath.lineTo(difX, difY);
}
mMacdPaint.setStyle(Paint.Style.STROKE);
mMacdPaint.setColor(mMacdDifColor);
canvas.drawPath(difPath, mMacdPaint);
/*dea*/
deaX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
deaY = (float) (v - mPerY * (quotes.dea - mMinY));
if (i == mBeginIndex) {
deaPath.moveTo(deaX - mPerX / 2, deaY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
deaX += mPerX / 2;//最后一个点特殊处理
}
deaPath.lineTo(deaX, deaY);
}
mMacdPaint.setStyle(Paint.Style.STROKE);
mMacdPaint.setColor(mMacdDeaColor);
canvas.drawPath(deaPath, mMacdPaint);
}
}
private void drawRSI(Canvas canvas) {
float rsiX;
//rsi6
float rsi6Y;
Path rsi6Path = new Path();
//rsi12
float rsi12Y;
Path rsi12Path = new Path();
//rsi24
float rsi24Y;
Path rsi24Path = new Path();
float v = mBaseHeight - mBasePaddingBottom - mInnerBottomBlankPadding;
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
/*rsi6*/
rsiX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
rsi6Y = (float) (v - mPerY * (quotes.rsi6 - mMinY));
if (i == mBeginIndex) {
rsi6Path.moveTo(rsiX - mPerX / 2, rsi6Y);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
rsiX += mPerX / 2;//最后一个点特殊处理
}
rsi6Path.lineTo(rsiX, rsi6Y);
}
mRsiPaint.setColor(mRsi6Color);
canvas.drawPath(rsi6Path, mRsiPaint);
/*rsi12*/
//为什么这里重复再赋一遍值?因为下面有一个"rsiX +="操作
rsiX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
rsi12Y = (float) (v - mPerY * (quotes.rsi12 - mMinY));
if (i == mBeginIndex) {
rsi12Path.moveTo(rsiX - mPerX / 2, rsi12Y);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
rsiX += mPerX / 2;//最后一个点特殊处理
}
rsi12Path.lineTo(rsiX, rsi12Y);
}
mRsiPaint.setColor(mRsi12Color);
canvas.drawPath(rsi12Path, mRsiPaint);
/*rsi24*/
//为什么这里重复再赋一遍值?因为下面有一个"rsiX +="操作
rsiX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
rsi24Y = (float) (v - mPerY * (quotes.rsi24 - mMinY));
if (i == mBeginIndex) {
rsi24Path.moveTo(rsiX - mPerX / 2, rsi24Y);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
rsiX += mPerX / 2;//最后一个点特殊处理
}
rsi24Path.lineTo(rsiX, rsi24Y);
}
mRsiPaint.setColor(mRsi24Color);
canvas.drawPath(rsi24Path, mRsiPaint);
}
}
private void drawKDJ(Canvas canvas) {
float kdjX;
//k
float kY;
Path kPath = new Path();
//d
float dY;
Path dPath = new Path();
//j
float jY;
Path jPath = new Path();
float v = mBaseHeight - mBasePaddingBottom - mInnerBottomBlankPadding;
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
/*k*/
kdjX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
kY = (float) (v - mPerY * (quotes.k - mMinY));
if (i == mBeginIndex) {
kPath.moveTo(kdjX - mPerX / 2, kY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
kdjX += mPerX / 2;//最后一个点特殊处理
}
kPath.lineTo(kdjX, kY);
}
mKdjPaint.setColor(mKColor);
canvas.drawPath(kPath, mKdjPaint);
/*d*/
kdjX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
dY = (float) (v - mPerY * (quotes.d - mMinY));
if (i == mBeginIndex) {
dPath.moveTo(kdjX - mPerX / 2, dY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
kdjX += mPerX / 2;//最后一个点特殊处理
}
dPath.lineTo(kdjX, dY);
}
mKdjPaint.setColor(mDColor);
canvas.drawPath(dPath, mKdjPaint);
/*j*/
kdjX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
jY = (float) (v - mPerY * (quotes.j - mMinY));
if (i == mBeginIndex) {
jPath.moveTo(kdjX - mPerX / 2, jY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
kdjX += mPerX / 2;//最后一个点特殊处理
}
jPath.lineTo(kdjX, jY);
}
mKdjPaint.setColor(mJColor);
canvas.drawPath(jPath, mKdjPaint);
}
}
@Override
protected void seekAndCalculateCellData() {
if (mQuotesList == null || mQuotesList.isEmpty()) return;
if (mMinorType == KViewType.MinorIndicatrixType.MACD) {
FinancialAlgorithm.calculateMACD(mQuotesList);
} else if (mMinorType == KViewType.MinorIndicatrixType.RSI) {
FinancialAlgorithm.calculateRSI(mQuotesList);
} else if (mMinorType == KViewType.MinorIndicatrixType.KDJ) {
FinancialAlgorithm.calculateKDJ(mQuotesList);
}
//找到close最大值和最小值
mMinY = Integer.MAX_VALUE;
mMaxY = Integer.MIN_VALUE;
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
if (i == mBeginIndex) {
mBeginQuotes = quotes;
}
if (i == mEndIndex - 1) {
mEndQuotes = quotes;
}
double min = FinancialAlgorithm.getMinorMinY(quotes, mMinorType);
double max = FinancialAlgorithm.getMinorMaxY(quotes, mMinorType);
if (min <= mMinY) {
mMinY = min;
}
if (max >= mMaxY) {
mMaxY = max;
}
}
mPerX = (mBaseWidth - mBasePaddingLeft - mBasePaddingRight - mInnerRightBlankPadding)
/ (mShownMaxCount);
//不要忘了减去内部的上下Padding
mPerY = (float) ((mBaseHeight - mBasePaddingTop - mBasePaddingBottom - mInnerTopBlankPadding
- mInnerBottomBlankPadding) / (mMaxY - mMinY));
Log.e(TAG, "seekAndCalculateCellData: mMinY" + mMinY + ",mMaxY:" + mMaxY);
//重绘
invalidate();
}
@Override
protected void innerClickListener() {
super.innerClickListener();
if (mMinorType == KViewType.MinorIndicatrixType.MACD) {
mMinorType = KViewType.MinorIndicatrixType.RSI;
} else if (mMinorType == KViewType.MinorIndicatrixType.RSI) {
mMinorType = KViewType.MinorIndicatrixType.KDJ;
} else if (mMinorType == KViewType.MinorIndicatrixType.KDJ) {
mMinorType = KViewType.MinorIndicatrixType.MACD;
}
setMinorType(mMinorType);
}
@Override
protected void innerLongClickListener(float x, float y) {
super.innerLongClickListener(x, y);
}
@Override
protected void innerHiddenLongClick() {
super.innerHiddenLongClick();
}
@Override
protected void innerMoveViewListener(float moveXLen) {
super.innerMoveViewListener(moveXLen);
}
//-----------------------对开发者暴露可以修改的参数-------
public void setMinorType(KViewType.MinorIndicatrixType minorType) {
mMinorType = minorType;
seekAndCalculateCellData();
}
public int getOuterStrokeColor() {
return mOuterStrokeColor;
}
public MinorView setOuterStrokeColor(int outerStrokeColor) {
mOuterStrokeColor = outerStrokeColor;
return this;
}
public int getInnerXyDashColor() {
return mInnerXyDashColor;
}
public MinorView setInnerXyDashColor(int innerXyDashColor) {
mInnerXyDashColor = innerXyDashColor;
return this;
}
public int getXyTxtColor() {
return mXyTxtColor;
}
public MinorView setXyTxtColor(int xyTxtColor) {
mXyTxtColor = xyTxtColor;
return this;
}
public int getLegendTxtColor() {
return mLegendTxtColor;
}
public MinorView setLegendTxtColor(int legendTxtColor) {
mLegendTxtColor = legendTxtColor;
return this;
}
public int getLongPressTxtColor() {
return mLongPressTxtColor;
}
public MinorView setLongPressTxtColor(int longPressTxtColor) {
mLongPressTxtColor = longPressTxtColor;
return this;
}
public int getMacdBuyColor() {
return mMacdBuyColor;
}
public MinorView setMacdBuyColor(int macdBuyColor) {
mMacdBuyColor = macdBuyColor;
return this;
}
public int getMacdSellColor() {
return mMacdSellColor;
}
public MinorView setMacdSellColor(int macdSellColor) {
mMacdSellColor = macdSellColor;
return this;
}
public int getMacdDifColor() {
return mMacdDifColor;
}
public MinorView setMacdDifColor(int macdDifColor) {
mMacdDifColor = macdDifColor;
return this;
}
public int getMacdDeaColor() {
return mMacdDeaColor;
}
public MinorView setMacdDeaColor(int macdDeaColor) {
mMacdDeaColor = macdDeaColor;
return this;
}
public int getMacdMacdColor() {
return mMacdMacdColor;
}
public MinorView setMacdMacdColor(int macdMacdColor) {
mMacdMacdColor = macdMacdColor;
return this;
}
public Paint getMacdPaint() {
return mMacdPaint;
}
public MinorView setMacdPaint(Paint macdPaint) {
mMacdPaint = macdPaint;
return this;
}
public float getMacdLineWidth() {
return mMacdLineWidth;
}
public MinorView setMacdLineWidth(float macdLineWidth) {
mMacdLineWidth = macdLineWidth;
return this;
}
public int getRsi6Color() {
return mRsi6Color;
}
public MinorView setRsi6Color(int rsi6Color) {
mRsi6Color = rsi6Color;
return this;
}
public int getRsi12Color() {
return mRsi12Color;
}
public MinorView setRsi12Color(int rsi12Color) {
mRsi12Color = rsi12Color;
return this;
}
public int getRsi24Color() {
return mRsi24Color;
}
public MinorView setRsi24Color(int rsi24Color) {
mRsi24Color = rsi24Color;
return this;
}
public Paint getRsiPaint() {
return mRsiPaint;
}
public MinorView setRsiPaint(Paint rsiPaint) {
mRsiPaint = rsiPaint;
return this;
}
public float getRsiLineWidth() {
return mRsiLineWidth;
}
public MinorView setRsiLineWidth(float rsiLineWidth) {
mRsiLineWidth = rsiLineWidth;
return this;
}
public int getKColor() {
return mKColor;
}
public MinorView setKColor(int KColor) {
mKColor = KColor;
return this;
}
public int getDColor() {
return mDColor;
}
public MinorView setDColor(int DColor) {
mDColor = DColor;
return this;
}
public int getJColor() {
return mJColor;
}
public MinorView setJColor(int JColor) {
mJColor = JColor;
return this;
}
public Paint getKdjPaint() {
return mKdjPaint;
}
public MinorView setKdjPaint(Paint kdjPaint) {
mKdjPaint = kdjPaint;
return this;
}
public float getKdjLineWidth() {
return mKdjLineWidth;
}
public MinorView setKdjLineWidth(float kdjLineWidth) {
mKdjLineWidth = kdjLineWidth;
return this;
}
public KViewType.MinorIndicatrixType getMinorType() {
return mMinorType;
}
public double getMinY() {
return mMinY;
}
public MinorView setMinY(double minY) {
mMinY = minY;
return this;
}
public double getMaxY() {
return mMaxY;
}
public MinorView setMaxY(double maxY) {
mMaxY = maxY;
return this;
}
public float getCandleDiverWidthRatio() {
return mCandleDiverWidthRatio;
}
public MinorView setCandleDiverWidthRatio(float candleDiverWidthRatio) {
mCandleDiverWidthRatio = candleDiverWidthRatio;
return this;
}
public MinorView setMinorListener(KViewListener.MinorListener minorListener) {
mMinorListener = minorListener;
return this;
}
public KViewListener.MinorListener getMinorListener() {
return mMinorListener;
}
}
@@ -0,0 +1,173 @@
package com.tophold.trade.view.kview;
import java.io.Serializable;
import java.util.Date;
import com.tophold.trade.utils.TimeUtils;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/11/21 11:12
* 描 述 :包装后的Quotes,实际使用的Quotes
* ============================================================
**/
public class Quotes implements Serializable {
/**
* 原始数据
*/
public long t;//开始时间
public double o;
public double h;
public double l;
public double c;
//扩展一个结束时间
public long e;
//vol 量,可选
public double vol;
/**
* 适配数据的构造方法,包括五个参数,全部必须。价格格式为String类型
*
* @param o 闭盘价
* @param h 最高价
* @param l 最低价
* @param c 收盘价
* @param t 时间
*/
public Quotes(String o, String h, String l, String c, String t) {
this(Double.parseDouble(o), Double.parseDouble(h), Double.parseDouble(l), Double.parseDouble(c), TimeUtils.date2Millis(new Date(t)));
}
/**
* 价格格式是double类型
*
* @param o
* @param h
* @param l
* @param c
* @param t
*/
public Quotes(double o, double h, double l, double c, long t) {
this(o, h, l, c, t, -1, -1);
}
public Quotes(double o, double h, double l, double c, long t, double vol) {
this(o, h, l, c, t, -1, vol);
}
/**
* 最原始构造方法
*
* @param o
* @param h
* @param l
* @param c
* @param t
* @param e
*/
public Quotes(double o, double h, double l, double c, long t, long e, double vol) {
this.o = o;
this.h = h;
this.l = l;
this.c = c;
this.t = t;
this.e = e;
this.vol = vol;
}
/**
* 多添加一个时间,包括两个格式的时间,一个开始时间,一个结束时间,标准以开始时间为准。
*
* @param o
* @param h
* @param l
* @param c
* @param s
* @param e
*/
public Quotes(String o, String h, String l, String c, long s, long e) {
this(Double.parseDouble(o), Double.parseDouble(h), Double.parseDouble(l), Double.parseDouble(c), s, e, -1);
}
/**
* 扩展的数据
*/
//实际中展示的时间
private String showTime;
public String getShowTime() {
showTime = TimeUtils.millis2String(t);
return showTime;
}
//在自定义view:FundView中的位置坐标
public float floatX;
public float floatY;
//MA
public double ma5;
public double ma10;
public double ma20;
//BOLL
public double up;//上轨线
public double mb;//中轨线
public double dn;//下轨线
//KDJ
public double k;
public double d;
public double j;
//macd
public double dif;
public double dea;
public double macd;
//rsi
public double rsi6;
public double rsi12;
public double rsi24;
//vol
public double volMa5;
public double volMa10;
@Override
public String toString() {
return "Quotes{" +
"t=" + t +
", o=" + o +
", h=" + h +
", l=" + l +
", c=" + c +
", e=" + e +
", vol=" + vol +
", showTime='" + showTime + '\'' +
", floatX=" + floatX +
", floatY=" + floatY +
", ma5=" + ma5 +
", ma10=" + ma10 +
", ma20=" + ma20 +
", up=" + up +
", mb=" + mb +
", dn=" + dn +
", k=" + k +
", d=" + d +
", j=" + j +
", dif=" + dif +
", dea=" + dea +
", macd=" + macd +
", rsi6=" + rsi6 +
", rsi12=" + rsi12 +
", rsi24=" + rsi24 +
", volMa5=" + volMa5 +
", volMa10=" + volMa10 +
'}';
}
}
@@ -0,0 +1,22 @@
package com.tophold.trade.view.kview;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/07/08 21:24
* 描 述 :量
* ============================================================
*/
public class VolModel {
public boolean cUp;//当前收盘价是否大于等于前一天收盘价
public double vol;//量
public double ma5;
public double ma10;
public VolModel(boolean cUp, double vol, double ma5, double ma10) {
this.cUp = cUp;
this.vol = vol;
this.ma5 = ma5;
this.ma10 = ma10;
}
}
@@ -0,0 +1,863 @@
package com.tophold.trade.view.kview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import com.tophold.trade.R;
import com.tophold.trade.utils.FormatUtil;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 创建日期 2017/12/14 17:56
* 描 述 :量图,从本质上来说基本和副图一致。
* ============================================================
**/
public class VolView extends KBaseView {
/**
* 初始化所有需要的颜色资源
*/
//共有的
int mOuterStrokeColor;
int mInnerXyDashColor;
int mXyTxtColor;
int mLegendTxtColor;
int mLongPressTxtColor;
//macd
int mMacdBuyColor;
int mMacdSellColor;
int mMacdDifColor;
int mMacdDeaColor;
int mMacdMacdColor;
Paint mMacdPaint;
float mMacdLineWidth = 1;
//rsi
int mRsi6Color;
int mRsi12Color;
int mRsi24Color;
Paint mRsiPaint;
float mRsiLineWidth = 1;
//kdj
int mKColor;
int mDColor;
int mJColor;
Paint mKdjPaint;
float mKdjLineWidth = 1;
//当前显示的指标
KViewType.MinorIndicatrixType mMinorType = KViewType.MinorIndicatrixType.MACD;
//y轴上最大值和最小值
protected double mMinY;
protected double mMaxY;
//蜡烛图间隙,大小以单个蜡烛图的宽度的比例算。可修改。
protected float mCandleDiverWidthRatio = 0.1f;
//监听主图的长按事件
private KViewListener.MinorListener mVolListener;
//是否展示量图
private boolean mShowVol = false;
public VolView(Context context) {
this(context, null);
}
public VolView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VolView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs();
initListener();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw: " + mBaseHeight + "," + mBaseWidth);
if (mQuotesList == null || mQuotesList.isEmpty()) {
return;
}
//绘制右侧文字
drawYRightTxt(canvas);
//绘制图例
drawLegend(canvas);
//绘制核心指标线
drawMinorIndicatrix(canvas);
//绘制长按线
drawLongPress(canvas);
}
private void initAttrs() {
initDefAttrs();
initColorRes();
initMacdPaint();
initRsiPaint();
initKdjPaint();
}
private void initKdjPaint() {
mKdjPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mKdjPaint.setColor(mKColor);
mKdjPaint.setAntiAlias(true);
mKdjPaint.setStrokeWidth(mKdjLineWidth);
mKdjPaint.setStyle(Paint.Style.STROKE);
mKdjPaint.setTextSize(mLegendTxtSize);
}
private void initRsiPaint() {
mRsiPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mRsiPaint.setColor(mRsi6Color);
mRsiPaint.setAntiAlias(true);
mRsiPaint.setStrokeWidth(mRsiLineWidth);
mRsiPaint.setStyle(Paint.Style.STROKE);
mRsiPaint.setTextSize(mLegendTxtSize);
}
private void initMacdPaint() {
mMacdPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMacdPaint.setColor(mMacdBuyColor);
mMacdPaint.setAntiAlias(true);
mMacdPaint.setStrokeWidth(mMacdLineWidth);
mMacdPaint.setTextSize(mLegendTxtSize);
}
private void initDefAttrs() {
//重写内边距大小
mInnerTopBlankPadding = 8;
mInnerBottomBlankPadding = 8;
//重写Legend padding
mLegendPaddingTop = 0;
mLegendPaddingRight = 4;
mLegendPaddingLeft = 4;
setShowInnerX(false);
setShowInnerY(true);
}
private void initColorRes() {
//颜色
mOuterStrokeColor = getColor(R.color.color_minorView_outerStrokeColor);
mInnerXyDashColor = getColor(R.color.color_minorView_innerXyDashColor);
mXyTxtColor = getColor(R.color.color_minorView_xYTxtColor);
mLegendTxtColor = getColor(R.color.color_minorView_legendTxtColor);
mLongPressTxtColor = getColor(R.color.color_minorView_longPressTxtColor);
mMacdBuyColor = getColor(R.color.color_minorView_macdBuyColor);
mMacdSellColor = getColor(R.color.color_minorView_macdSellColor);
mMacdDifColor = getColor(R.color.color_minorView_macdDifColor);
mMacdDeaColor = getColor(R.color.color_minorView_macdDeaColor);
mMacdMacdColor = getColor(R.color.color_minorView_macdMacdColor);
mRsi6Color = getColor(R.color.color_minorView_rsi6Color);
mRsi12Color = getColor(R.color.color_minorView_rsi12Color);
mRsi24Color = getColor(R.color.color_minorView_rsi24Color);
mKColor = getColor(R.color.color_minorView_kColor);
mDColor = getColor(R.color.color_minorView_dColor);
mJColor = getColor(R.color.color_minorView_jColor);
}
private void initListener() {
mVolListener = new KViewListener.MinorListener() {
@Override
public void masterLongPressListener(int pressIndex, Quotes currQuotes) {
mDrawLongPress = true;
mCurrLongPressQuotes = mQuotesList.get(pressIndex);
invalidate();
}
@Override
public void masterNoLongPressListener() {
mDrawLongPress = false;
invalidate();
}
@Override
public void masteZoomlNewIndex(int beginIndex, int endIndex, int shownMaxCount) {
mBeginIndex = beginIndex;
mEndIndex = endIndex;
mShownMaxCount = shownMaxCount;
seekAndCalculateCellData();
}
@Override
public void mastelPullmNewIndex(int beginIndex, int endIndex, KViewType.PullType currPullType, int shownMaxCount) {
mBeginIndex = beginIndex;
mEndIndex = endIndex;
mShownMaxCount = shownMaxCount;
mPullType = currPullType;
//处理右侧内边距
if (currPullType == KViewType.PullType.PULL_RIGHT_STOP) {
//重置到之前的状态
mInnerRightBlankPadding = DEF_INNER_RIGHT_BLANK_PADDING;
} else {
mInnerRightBlankPadding = 0;
}
seekAndCalculateCellData();
}
};
}
private void drawLegend(Canvas canvas) {
//绘制非按下情况下图例
drawNoPressLegend(canvas);
//绘制按下的图例
drawPressLegend(canvas);
}
private void drawMinorIndicatrix(Canvas canvas) {
mMacdPaint.setStyle(Paint.Style.STROKE);
drawMACD(canvas);
}
private void drawLongPress(Canvas canvas) {
if (!mDrawLongPress) return;
if (mCurrLongPressQuotes == null) return;
//y轴线
canvas.drawLine(mCurrLongPressQuotes.floatX, mBasePaddingTop, mCurrLongPressQuotes.floatX,
mBaseHeight - mBasePaddingBottom, mLongPressPaint);
}
private void drawPressLegend(Canvas canvas) {
if (!mDrawLongPress) return;
mMacdPaint.setStyle(Paint.Style.FILL);
float x = (float) (mLegendPaddingLeft + mBasePaddingLeft);
float y = (float) (mLegendPaddingTop + mBasePaddingTop) + getFontHeight(mLegendTxtSize, mMacdPaint);
String showTxt = "VOL:" + FormatUtil.numFormat(mCurrLongPressQuotes.vol, mDigits) + " ";
mMacdPaint.setColor(mMacdDifColor);
canvas.drawText(showTxt, x,
y, mMacdPaint);
float leftWidth11 = mMacdPaint.measureText(showTxt);
showTxt = "MA5:" + FormatUtil.numFormat(mCurrLongPressQuotes.volMa5, mDigits) + " ";
mMacdPaint.setColor(mMacdDeaColor);
canvas.drawText(showTxt, x + leftWidth11, y, mMacdPaint);
float leftWidth12 = mMacdPaint.measureText(showTxt);
showTxt = "MA10:" + FormatUtil.numFormat(mCurrLongPressQuotes.volMa10, mDigits) + " ";
mMacdPaint.setColor(mMacdMacdColor);
canvas.drawText(showTxt, (x + leftWidth11 + leftWidth12),
y, mMacdPaint);
}
private void drawNoPressLegend(Canvas canvas) {
if (mDrawLongPress) return;
String showTxt = "MA(510)";
canvas.drawText(showTxt,
(float) (mBaseWidth - mLegendPaddingRight - mBasePaddingRight - mLegendPaint.measureText(showTxt)),
(float) (mLegendPaddingTop + mBasePaddingTop + getFontHeight(mLegendTxtSize, mLegendPaint)), mLegendPaint);
}
private void drawYRightTxt(Canvas canvas) {
//绘制右侧的y轴文字
//现将最小值、最大值画好
float halfTxtHight = getFontHeight(mXYTxtSize, mXYTxtPaint) / 2;//应该/2的,但是不准确,原因不明
float x = mBaseWidth - mBasePaddingRight + mRightTxtPadding;
float maxY = mBasePaddingTop + halfTxtHight + mInnerTopBlankPadding;
float minY = mBaseHeight - mBasePaddingBottom - halfTxtHight - mInnerBottomBlankPadding;
//draw min
canvas.drawText(FormatUtil.numFormat(mMinY, mDigits),
x,
minY, mXYTxtPaint);
//draw max
canvas.drawText(FormatUtil.numFormat(mMaxY, mDigits),
x,
maxY, mXYTxtPaint);
//draw middle
canvas.drawText(FormatUtil.numFormat((mMaxY + mMinY) / 2.0, mDigits),
x, (minY + maxY) / 2.0f,
mXYTxtPaint);
}
private void drawMACD(Canvas canvas) {
//macd
//首先寻找"0"点,这个点是正负macd的分界点
float v = mBaseHeight - mBasePaddingBottom - mInnerBottomBlankPadding;
float startX, startY, stopX, stopY;
//dif
float difX, difY;
Path difPath = new Path();
//dea
float deaX, deaY;
Path deaPath = new Path();
boolean isFirstMa5 = true;
boolean isFirstMa10 = true;
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
/*macd*/
//找另外一个y点
double y = v - mPerY * (quotes.vol - mMinY);
startX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mCandleDiverWidthRatio * mPerX / 2;
stopX = mBasePaddingLeft + (i - mBeginIndex + 1) * mPerX - mCandleDiverWidthRatio * mPerX / 2;
startY = v;
stopY = (float) y;
if (i > 0 && mQuotesList.get(i - 1).c < quotes.c) {
mMacdPaint.setColor(mMacdBuyColor);
} else {
mMacdPaint.setColor(mMacdSellColor);
}
// Log.e(TAG, "drawMACD: "+startY+","+stopY +
// ","+(mBasePaddingTop+mInnerTopBlankPadding)+","+
// (mBaseHeight-mBasePaddingBottom-mInnerBottomBlankPadding));
mMacdPaint.setStyle(Paint.Style.FILL);
canvas.drawRect(startX, startY, stopX, stopY, mMacdPaint);
/*dif*/
difX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
difY = getMasterDetailFloatY(quotes, KViewType.MaType.volMa5);
if (difY != -1) {
if (isFirstMa5) {
isFirstMa5 = false;
if (quotes.volMa5 != 0) difX -= mPerX / 2;//第一个点特殊处理
difPath.moveTo(difX, difY);
} else {
if (i == mEndIndex - 1) difX += mPerX / 2;//最后一个点特殊处理
difPath.lineTo(difX, difY);
}
mMacdPaint.setStyle(Paint.Style.STROKE);
mMacdPaint.setColor(mMacdDifColor);
canvas.drawPath(difPath, mMacdPaint);
}
/*dea*/
deaX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
deaY = getMasterDetailFloatY(quotes, KViewType.MaType.volMa10);
if (deaY != -1) {
if (isFirstMa10) {
isFirstMa10 = false;
if (quotes.volMa10 != 0) deaX -= mPerX / 2;//第一个点特殊处理
deaPath.moveTo(deaX, deaY);
} else {
if (i == mEndIndex - 1) deaX += mPerX / 2;//最后一个点特殊处理
deaPath.lineTo(deaX, deaY);
}
mMacdPaint.setStyle(Paint.Style.STROKE);
mMacdPaint.setColor(mMacdDeaColor);
canvas.drawPath(deaPath, mMacdPaint);
}
}
}
private float getMasterDetailFloatY(Quotes quotes, KViewType.MaType maType) {
double v = 0;
//ma
if (maType == KViewType.MaType.volMa5) {
v = quotes.volMa5 - mMinY;
} else if (maType == KViewType.MaType.volMa10) {
v = quotes.volMa10 - mMinY;
}
//异常,当不存在ma值时的处理.也就是up、mb、dn为0时,这样判断其实有问题,比如算出来的值就是0???
if (v + mMinY == 0) return -1;
double h = v * mPerY;
float y = (float) (mBaseHeight - h - mBasePaddingBottom - mInnerBottomBlankPadding);
//这里的y,存在一种情况,y超过了View的上边界或者超过了下边界,当出现这一种情况时,不显示,当作异常情况
if (y < mBasePaddingTop || y > mBaseHeight - mBasePaddingBottom)
return -1;
return y;
}
private void drawRSI(Canvas canvas) {
float rsiX;
//rsi6
float rsi6Y;
Path rsi6Path = new Path();
//rsi12
float rsi12Y;
Path rsi12Path = new Path();
//rsi24
float rsi24Y;
Path rsi24Path = new Path();
float v = mBaseHeight - mBasePaddingBottom - mInnerBottomBlankPadding;
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
/*rsi6*/
rsiX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
rsi6Y = (float) (v - mPerY * (quotes.rsi6 - mMinY));
if (i == mBeginIndex) {
rsi6Path.moveTo(rsiX - mPerX / 2, rsi6Y);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
rsiX += mPerX / 2;//最后一个点特殊处理
}
rsi6Path.lineTo(rsiX, rsi6Y);
}
mRsiPaint.setColor(mRsi6Color);
canvas.drawPath(rsi6Path, mRsiPaint);
/*rsi12*/
//为什么这里重复再赋一遍值?因为下面有一个"rsiX +="操作
rsiX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
rsi12Y = (float) (v - mPerY * (quotes.rsi12 - mMinY));
if (i == mBeginIndex) {
rsi12Path.moveTo(rsiX - mPerX / 2, rsi12Y);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
rsiX += mPerX / 2;//最后一个点特殊处理
}
rsi12Path.lineTo(rsiX, rsi12Y);
}
mRsiPaint.setColor(mRsi12Color);
canvas.drawPath(rsi12Path, mRsiPaint);
/*rsi24*/
//为什么这里重复再赋一遍值?因为下面有一个"rsiX +="操作
rsiX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
rsi24Y = (float) (v - mPerY * (quotes.rsi24 - mMinY));
if (i == mBeginIndex) {
rsi24Path.moveTo(rsiX - mPerX / 2, rsi24Y);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
rsiX += mPerX / 2;//最后一个点特殊处理
}
rsi24Path.lineTo(rsiX, rsi24Y);
}
mRsiPaint.setColor(mRsi24Color);
canvas.drawPath(rsi24Path, mRsiPaint);
}
}
private void drawKDJ(Canvas canvas) {
float kdjX;
//k
float kY;
Path kPath = new Path();
//d
float dY;
Path dPath = new Path();
//j
float jY;
Path jPath = new Path();
float v = mBaseHeight - mBasePaddingBottom - mInnerBottomBlankPadding;
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
/*k*/
kdjX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
kY = (float) (v - mPerY * (quotes.k - mMinY));
if (i == mBeginIndex) {
kPath.moveTo(kdjX - mPerX / 2, kY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
kdjX += mPerX / 2;//最后一个点特殊处理
}
kPath.lineTo(kdjX, kY);
}
mKdjPaint.setColor(mKColor);
canvas.drawPath(kPath, mKdjPaint);
/*d*/
kdjX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
dY = (float) (v - mPerY * (quotes.d - mMinY));
if (i == mBeginIndex) {
dPath.moveTo(kdjX - mPerX / 2, dY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
kdjX += mPerX / 2;//最后一个点特殊处理
}
dPath.lineTo(kdjX, dY);
}
mKdjPaint.setColor(mDColor);
canvas.drawPath(dPath, mKdjPaint);
/*j*/
kdjX = mBasePaddingLeft + (i - mBeginIndex) * mPerX + mPerX / 2;
jY = (float) (v - mPerY * (quotes.j - mMinY));
if (i == mBeginIndex) {
jPath.moveTo(kdjX - mPerX / 2, jY);//第一个点特殊处理
} else {
if (i == mEndIndex - 1) {
kdjX += mPerX / 2;//最后一个点特殊处理
}
jPath.lineTo(kdjX, jY);
}
mKdjPaint.setColor(mJColor);
canvas.drawPath(jPath, mKdjPaint);
}
}
@Override
protected void seekAndCalculateCellData() {
if (mQuotesList == null || mQuotesList.isEmpty()) return;
if (!isShowVol()) return;
FinancialAlgorithm.calculateMA(mQuotesList, 5, KViewType.MaType.volMa5);
FinancialAlgorithm.calculateMA(mQuotesList, 10, KViewType.MaType.volMa10);
//找到close最大值和最小值
mMinY = Integer.MAX_VALUE;
mMaxY = Integer.MIN_VALUE;
for (int i = mBeginIndex; i < mEndIndex; i++) {
Quotes quotes = mQuotesList.get(i);
if (i == mBeginIndex) {
mBeginQuotes = quotes;
}
if (i == mEndIndex - 1) {
mEndQuotes = quotes;
}
double min = FinancialAlgorithm.getVolMinY(quotes);
double max = FinancialAlgorithm.getVolMaxY(quotes);
if (min <= mMinY) {
mMinY = min;
}
if (max >= mMaxY) {
mMaxY = max;
}
}
mPerX = (mBaseWidth - mBasePaddingLeft - mBasePaddingRight - mInnerRightBlankPadding)
/ (mShownMaxCount);
//不要忘了减去内部的上下Padding
mPerY = (float) ((mBaseHeight - mBasePaddingTop - mBasePaddingBottom - mInnerTopBlankPadding
- mInnerBottomBlankPadding) / (mMaxY - mMinY));
Log.e(TAG, "seekAndCalculateCellData: mMinY" + mMinY + ",mMaxY:" + mMaxY);
//重绘
invalidate();
}
@Override
protected void innerClickListener() {
super.innerClickListener();
if (mMinorType == KViewType.MinorIndicatrixType.MACD) {
mMinorType = KViewType.MinorIndicatrixType.RSI;
} else if (mMinorType == KViewType.MinorIndicatrixType.RSI) {
mMinorType = KViewType.MinorIndicatrixType.KDJ;
} else if (mMinorType == KViewType.MinorIndicatrixType.KDJ) {
mMinorType = KViewType.MinorIndicatrixType.MACD;
}
setMinorType(mMinorType);
}
@Override
protected void innerLongClickListener(float x, float y) {
super.innerLongClickListener(x, y);
}
@Override
protected void innerHiddenLongClick() {
super.innerHiddenLongClick();
}
@Override
protected void innerMoveViewListener(float moveXLen) {
super.innerMoveViewListener(moveXLen);
}
//-----------------------对开发者暴露可以修改的参数-------
public void setMinorType(KViewType.MinorIndicatrixType minorType) {
mMinorType = minorType;
seekAndCalculateCellData();
}
public int getOuterStrokeColor() {
return mOuterStrokeColor;
}
public VolView setOuterStrokeColor(int outerStrokeColor) {
mOuterStrokeColor = outerStrokeColor;
return this;
}
public int getInnerXyDashColor() {
return mInnerXyDashColor;
}
public VolView setInnerXyDashColor(int innerXyDashColor) {
mInnerXyDashColor = innerXyDashColor;
return this;
}
public int getXyTxtColor() {
return mXyTxtColor;
}
public VolView setXyTxtColor(int xyTxtColor) {
mXyTxtColor = xyTxtColor;
return this;
}
public int getLegendTxtColor() {
return mLegendTxtColor;
}
public VolView setLegendTxtColor(int legendTxtColor) {
mLegendTxtColor = legendTxtColor;
return this;
}
public int getLongPressTxtColor() {
return mLongPressTxtColor;
}
public VolView setLongPressTxtColor(int longPressTxtColor) {
mLongPressTxtColor = longPressTxtColor;
return this;
}
public int getMacdBuyColor() {
return mMacdBuyColor;
}
public VolView setMacdBuyColor(int macdBuyColor) {
mMacdBuyColor = macdBuyColor;
return this;
}
public int getMacdSellColor() {
return mMacdSellColor;
}
public VolView setMacdSellColor(int macdSellColor) {
mMacdSellColor = macdSellColor;
return this;
}
public int getMacdDifColor() {
return mMacdDifColor;
}
public VolView setMacdDifColor(int macdDifColor) {
mMacdDifColor = macdDifColor;
return this;
}
public int getMacdDeaColor() {
return mMacdDeaColor;
}
public VolView setMacdDeaColor(int macdDeaColor) {
mMacdDeaColor = macdDeaColor;
return this;
}
public int getMacdMacdColor() {
return mMacdMacdColor;
}
public VolView setMacdMacdColor(int macdMacdColor) {
mMacdMacdColor = macdMacdColor;
return this;
}
public Paint getMacdPaint() {
return mMacdPaint;
}
public VolView setMacdPaint(Paint macdPaint) {
mMacdPaint = macdPaint;
return this;
}
public float getMacdLineWidth() {
return mMacdLineWidth;
}
public VolView setMacdLineWidth(float macdLineWidth) {
mMacdLineWidth = macdLineWidth;
return this;
}
public int getRsi6Color() {
return mRsi6Color;
}
public VolView setRsi6Color(int rsi6Color) {
mRsi6Color = rsi6Color;
return this;
}
public int getRsi12Color() {
return mRsi12Color;
}
public VolView setRsi12Color(int rsi12Color) {
mRsi12Color = rsi12Color;
return this;
}
public int getRsi24Color() {
return mRsi24Color;
}
public VolView setRsi24Color(int rsi24Color) {
mRsi24Color = rsi24Color;
return this;
}
public Paint getRsiPaint() {
return mRsiPaint;
}
public VolView setRsiPaint(Paint rsiPaint) {
mRsiPaint = rsiPaint;
return this;
}
public float getRsiLineWidth() {
return mRsiLineWidth;
}
public VolView setRsiLineWidth(float rsiLineWidth) {
mRsiLineWidth = rsiLineWidth;
return this;
}
public int getKColor() {
return mKColor;
}
public VolView setKColor(int KColor) {
mKColor = KColor;
return this;
}
public int getDColor() {
return mDColor;
}
public VolView setDColor(int DColor) {
mDColor = DColor;
return this;
}
public int getJColor() {
return mJColor;
}
public VolView setJColor(int JColor) {
mJColor = JColor;
return this;
}
public Paint getKdjPaint() {
return mKdjPaint;
}
public VolView setKdjPaint(Paint kdjPaint) {
mKdjPaint = kdjPaint;
return this;
}
public float getKdjLineWidth() {
return mKdjLineWidth;
}
public VolView setKdjLineWidth(float kdjLineWidth) {
mKdjLineWidth = kdjLineWidth;
return this;
}
public KViewType.MinorIndicatrixType getMinorType() {
return mMinorType;
}
public double getMinY() {
return mMinY;
}
public VolView setMinY(double minY) {
mMinY = minY;
return this;
}
public double getMaxY() {
return mMaxY;
}
public VolView setMaxY(double maxY) {
mMaxY = maxY;
return this;
}
public float getCandleDiverWidthRatio() {
return mCandleDiverWidthRatio;
}
public VolView setCandleDiverWidthRatio(float candleDiverWidthRatio) {
mCandleDiverWidthRatio = candleDiverWidthRatio;
return this;
}
public VolView setVolListener(KViewListener.MinorListener volListener) {
mVolListener = volListener;
return this;
}
public KViewListener.MinorListener getVolListener() {
return mVolListener;
}
public boolean isShowVol() {
return mShowVol;
}
public VolView setShowVol(boolean showVol) {
mShowVol = showVol;
return this;
}
}
@@ -0,0 +1,50 @@
package com.tophold.trade.view.pie;
import android.util.Log;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import com.tophold.trade.utils.StringUtils;
import java.util.List;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/07/17 16:50
* 描 述
* ============================================================
*/
public class PieChartAnimation extends Animation {
public static final String TAG = PieChartAnimation.class.getSimpleName();
List<PieEntrys> mPieEntrysList;
float mSumValue;
PieChartView mView;
public void setPieChartData(List<PieEntrys> pieEntrysList, float sumValue, PieChartView view) {
this.mPieEntrysList = pieEntrysList;
this.mSumValue = sumValue;
this.mView = view;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (StringUtils.isEmpty(mPieEntrysList) || mSumValue <= 0 || mView == null) return;
Log.d(TAG, "applyTransformation1: " + interpolatedTime);
if (interpolatedTime == 1.0f) {
mView.setDrawLine(true);
}
for (int i = 0; i < mPieEntrysList.size(); i++) {
PieEntrys data = mPieEntrysList.get(i);
//通过总和来计算百分比
float percentage = data.value / mSumValue;
//通过百分比来计算对应的角度
float angle = percentage * 360;
//根据插入时间来计算角度
angle = angle * interpolatedTime;
data.mSweepAngle = angle;
}
mView.invalidate();
}
}
@@ -0,0 +1,552 @@
package com.tophold.trade.view.pie;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import com.tophold.trade.R;
import com.tophold.trade.utils.GsonUtil;
import com.tophold.trade.utils.ScreenUtils;
import com.tophold.trade.utils.StringUtils;
import com.tophold.trade.view.BaseView;
import java.util.List;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/07/10 20:40
* 描 述 :对饼形图实现给出一个思路。之前看MP实现如果改成这样可能比较麻烦一点,这里就直接进行是实现。
* 饼形图的实现难点不在于图的实现,而在于图周围的指示文字的控制,如果处理不好就会出现挤压问题。这里的处理思路是:给出最小比例,
* 如果小于这个比例直接从最大份中"借"出一点满足最小比例。
* ============================================================
*/
public class PieChartView extends BaseView {
public static final float DEF_PARTSTHRESHOLD = 0.1f;
List<PieEntrys> mPieEntryList;
/**
* 内边距
*/
float basePaddingTop = 45;
float basePaddingBottom = 45;
float basePaddingLeft;
float basePaddingRight;
//内部圆洞的半径
float holdRadius = 40;
//高亮环的宽度
float highLightWidth = 4;
//高亮环距离圆环的距离
float highLightPadding = 5;
//指示线距离圆环的边距
float lineLengthMargin = 10;
//指示线延伸长度
float lineLength = 85;
//指示线的宽度
float lineWidth = 1f;
//是否延伸长度的阀值
float lineThreshold = 10;
//延长的宽度
float lineThresholdLength = 50;
//指示文字下边距
float textPaddingBottom = 2;
//圆点半径
float dotRadius = 2;
//上下两个txt之间的y轴的距离
float txtThresholdmargin = 20;
//上下两个txt之间的y轴的阀值
float txtThreshold = 20;
//文字大小
float textSize = 12;
//饼图item最小的占比。0~1,定义最小的item,防止积压在一起。初始值设置小一点,为了动画好看。
float minPartsThreshold = DEF_PARTSTHRESHOLD;
//没有数据显示
String mEmptyTxt = "还没有交易哦";
//开始角度
float mBeginAngle = -90;
/**
* 自定义动画
*/
private PieChartAnimation mAnimation;
long mAnim = 1000;
//私有属性,不对外暴露
private float centerX;
private float centerY;
private boolean firstCome = true;
boolean mDrawLine = false;
public PieChartView(Context context) {
this(context, null);
}
public PieChartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PieChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs();
}
private void initAttrs() {
mAnimation = new PieChartAnimation();
mAnimation.setDuration(mAnim);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* 所有单位传递标准化参数即可,这里进行转换。
*/
private void allDp2Px() {
if (!firstCome) return;
firstCome = false;
basePaddingLeft = ScreenUtils.dip2px(basePaddingLeft);
basePaddingRight = ScreenUtils.dip2px(basePaddingRight);
basePaddingTop = ScreenUtils.dip2px(basePaddingTop);
basePaddingBottom = ScreenUtils.dip2px(basePaddingBottom);
highLightWidth = ScreenUtils.dip2px(highLightWidth);
highLightPadding = ScreenUtils.dip2px(highLightPadding);
holdRadius = ScreenUtils.dip2px(holdRadius);
lineLength = ScreenUtils.dip2px(lineLength);
lineWidth = ScreenUtils.dip2px(lineWidth);
dotRadius = ScreenUtils.dip2px(dotRadius);
textSize = ScreenUtils.sp2px(textSize);
textPaddingBottom = ScreenUtils.dip2px(textPaddingBottom);
lineThreshold = ScreenUtils.dip2px(lineThreshold);
lineThresholdLength = ScreenUtils.dip2px(lineThresholdLength);
txtThreshold = ScreenUtils.dip2px(txtThreshold);
txtThresholdmargin = ScreenUtils.dip2px(txtThresholdmargin);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
allDp2Px();
centerX = getBaseWidth() / 2.0f;
centerY = getBaseHeight() / 2.0f;
drawCircle(canvas);
}
private void initPieData() {
if (StringUtils.isEmptyList(mPieEntryList)) {
return;
}
float sumValue = 0;
int maxIndex = 0;
float maxData = -1;
//数据预处理:对占比比较小的值多分配一点防止太小。
for (int i = 0; i < mPieEntryList.size(); i++) {
PieEntrys pieEntry = mPieEntryList.get(i);
sumValue += pieEntry.value;
if (pieEntry.value > maxData) {
maxData = pieEntry.value;
maxIndex = i;
}
}
mAnimation.setPieChartData(mPieEntryList, sumValue, this);
/**
* 边界问题处理:防止item太小,出现文字积压问题。
*/
PieEntrys maxPieEntry = mPieEntryList.get(maxIndex);
for (int i = 0; i < mPieEntryList.size(); i++) {
PieEntrys pieEntry = mPieEntryList.get(i);
float tempValue = pieEntry.value;
if (pieEntry.value / sumValue < minPartsThreshold) {
//该块补全最小值
pieEntry.value = sumValue * minPartsThreshold;
float disLen = pieEntry.value - tempValue;
//最大块减去被减去的
maxPieEntry.value -= disLen;
}
}
//startAnimation的时候,如果这个View是不可见的,或者是gone的,就会导致传进去的Animation对象不执行
postDelayed(() -> startAnimation(mAnimation), 300);
}
private void drawCircle(Canvas canvas) {
if (StringUtils.isEmptyList(mPieEntryList)) {
drawEmptyView(canvas);
return;
}
//正式开始处理
float valueW = getBaseWidth() - basePaddingLeft - basePaddingRight;
float valueH = getBaseHeight() - basePaddingTop - basePaddingBottom;
float radius = Math.min(valueW,
valueH) / 2.0f;
float lX = centerX - radius;
float tY = centerY - radius;
float rX = centerX + radius;
float bY = centerY + radius;
//圆环外边界,但是如果圆环成整个圆之后,就是内边界了。
RectF rectF = new RectF(lX, tY, rX, bY);
float beginAngle = mBeginAngle;
float preYpos = 0;
for (PieEntrys pieEntry : mPieEntryList) {
//无动画
//float sweepAngle = 360 * pieEntry.value / mSumValue;
//在这里实现动画
float sweepAngle = pieEntry.mSweepAngle;
//防止item过小
if (sweepAngle < 360 * minPartsThreshold) {
sweepAngle = 360 * minPartsThreshold;
}
//防止绘制过度
if (beginAngle + sweepAngle > 270) {
sweepAngle = 270 - beginAngle;
}
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
//paint.setStrokeWidth(roundWidth);
paint.setColor(pieEntry.colorBg);
canvas.drawArc(rectF, beginAngle, sweepAngle, true, paint);
//高亮逻辑
if (pieEntry.highLight) {
float radius2 = Math.min(
valueW,
valueH) / 2.0f + highLightPadding + highLightWidth / 2.0f;
float lX2 = centerX - radius2;
float tY2 = centerY - radius2;
float rX2 = centerX + radius2;
float bY2 = centerY + radius2;
RectF rectF2 = new RectF(lX2, tY2, rX2, bY2);
Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
paint2.setStyle(Paint.Style.STROKE);
paint2.setStrokeWidth(highLightWidth);
paint2.setColor(pieEntry.colorBg);
canvas.drawArc(rectF2, beginAngle, sweepAngle, false, paint2);
}
if (mDrawLine) {
/**
* 指示文字,重要逻辑
* 思路:取该模块的度数的中间值向外延伸一定距离。如果该度数在[-90,90]之间,在右边显示;否则在左边显示。
*/
//真实的角度
float coreAngle = beginAngle + sweepAngle / 2.0f;//因为从-90开始的
float r = radius;//圆的半径
Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
linePaint.setColor(pieEntry.colorBg);
linePaint.setStrokeWidth(lineWidth);
Paint ratePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
ratePaint.setColor(pieEntry.highLight ? getColor(R.color.color_pie_chart_red) : getColor(R.color.color_pie_chart_gray));
ratePaint.setTextSize(textSize);
Paint symbolPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
symbolPaint.setColor(getColor(R.color.color_pie_chart_gray));
symbolPaint.setTextSize(textSize);
float yPos;
//右边
//Log.d(TAG, "drawCircle: " + coreAngle);
float textPdding = getFontHeight(textSize, ratePaint);
if (coreAngle >= -90 && coreAngle <= 90) {
float circleX = centerX + radius + highLightPadding + highLightWidth;
float xPos = circleX + lineLengthMargin;//x的坐标
boolean isThreshold = false;
if (coreAngle <= 0) {
yPos = (float) (centerY - Math.cos(getAreaAngle(90 + coreAngle)) * r);//y的左边
//在上边界或者下边界x方向延伸长一点
if (r - (centerY - yPos) < lineThreshold) {
xPos -= lineThresholdLength;
isThreshold = true;
}
} else {
yPos = (float) (centerY + Math.cos(getAreaAngle(90 - coreAngle)) * r);//y的左边
//在上边界或者下边界x方向延伸长一点
if (r - (yPos - centerY) < lineThreshold) {
xPos -= lineThresholdLength;
isThreshold = true;
}
}
//又一个边界问题
if (preYpos != 0 && yPos - preYpos < txtThreshold) {
yPos += txtThresholdmargin;
}
preYpos = yPos;
//Log.d(TAG, "drawCircle: " + xPos + "," + yPos);
float lineLeft = !isThreshold ? xPos + lineLength : xPos + lineLength + lineThresholdLength;
float txtLeft = !isThreshold ? xPos : xPos + lineThresholdLength;
canvas.drawLine(xPos, yPos, lineLeft, yPos, linePaint);
canvas.drawCircle(xPos, yPos, dotRadius, linePaint);
float labelLen = lineLength - ratePaint.measureText(pieEntry.label);
canvas.drawText(pieEntry.label, txtLeft + labelLen, yPos - textPaddingBottom, ratePaint);
float symbolLen = lineLength - symbolPaint.measureText(pieEntry.symbol);
canvas.drawText(pieEntry.symbol, txtLeft + symbolLen, yPos + textPaddingBottom + textPdding / 2.0f, symbolPaint);
} else {
float circleX = centerX - radius - highLightPadding - highLightWidth;
float xPos = circleX + lineLengthMargin;//x的坐标
boolean isThreshold = false;
//左边
if (coreAngle <= 180) {
yPos = (float) (centerY + Math.cos(getAreaAngle(coreAngle - 90)) * r);//y的左边
//在上边界或者下边界x方向延伸长一点
if (r - (yPos - centerY) < lineThreshold) {
xPos += lineThresholdLength;
isThreshold = true;
}
} else {
yPos = (float) (centerY - Math.cos(getAreaAngle(270 - coreAngle)) * r);//y的左边
if (r - (centerY - yPos) < lineThreshold) {
xPos += lineThresholdLength;
isThreshold = true;
}
}
//又一个边界问题
if (preYpos != 0 && preYpos - yPos < txtThreshold) {
yPos -= txtThresholdmargin;
}
preYpos = yPos;
//Log.d(TAG, "drawCircle: " + xPos + "," + yPos);
float lineLeft = !isThreshold ? xPos - lineLength : xPos - lineLength - lineThresholdLength;
float txtLeft = !isThreshold ? xPos - lineLength : xPos - lineLength - lineThresholdLength;
canvas.drawLine(xPos, yPos, lineLeft, yPos, linePaint);
canvas.drawCircle(xPos, yPos, dotRadius, linePaint);
canvas.drawText(pieEntry.label, txtLeft, yPos - textPaddingBottom, ratePaint);
canvas.drawText(pieEntry.symbol, txtLeft, yPos + textPaddingBottom + textPdding / 2.0f, symbolPaint);
}
}
beginAngle += sweepAngle;
}
//最后绘制中间的圆洞
Paint holdPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
holdPaint.setColor(ContextCompat.getColor(getContext(), R.color.color_minorView_longPressTxtColor));
canvas.drawCircle(centerX, centerY, holdRadius, holdPaint);
}
private float getAreaAngle(float angle) {
return (float) (Math.PI * angle / 180);
}
private void drawEmptyView(Canvas canvas) {
String txt = mEmptyTxt;
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize(ScreenUtils.sp2px(15));
paint.setColor(getColor(R.color.color_pie_chart_gray));
canvas.drawText(txt, getBaseWidth() / 2.0f - paint.measureText(txt) / 2.0f, getBaseHeight() / 2.0f, paint);
}
public List<PieEntrys> getPieEntryList() {
return mPieEntryList;
}
public PieChartView setPieEntryList(List<PieEntrys> pieEntryList) {
mPieEntryList = pieEntryList;
initPieData();
return this;
}
public float getBasePaddingTop() {
return basePaddingTop;
}
public PieChartView setBasePaddingTop(float basePaddingTop) {
this.basePaddingTop = basePaddingTop;
return this;
}
public float getBasePaddingBottom() {
return basePaddingBottom;
}
public PieChartView setBasePaddingBottom(float basePaddingBottom) {
this.basePaddingBottom = basePaddingBottom;
return this;
}
public float getBasePaddingLeft() {
return basePaddingLeft;
}
public PieChartView setBasePaddingLeft(float basePaddingLeft) {
this.basePaddingLeft = basePaddingLeft;
return this;
}
public float getBasePaddingRight() {
return basePaddingRight;
}
public PieChartView setBasePaddingRight(float basePaddingRight) {
this.basePaddingRight = basePaddingRight;
return this;
}
public float getHighLightWidth() {
return highLightWidth;
}
public PieChartView setHighLightWidth(float highLightWidth) {
this.highLightWidth = highLightWidth;
return this;
}
public float getHighLightPadding() {
return highLightPadding;
}
public PieChartView setHighLightPadding(float highLightPadding) {
this.highLightPadding = highLightPadding;
return this;
}
public float getLineLengthMargin() {
return lineLengthMargin;
}
public PieChartView setLineLengthMargin(float lineLengthMargin) {
this.lineLengthMargin = lineLengthMargin;
return this;
}
public float getLineLength() {
return lineLength;
}
public PieChartView setLineLength(float lineLength) {
this.lineLength = lineLength;
return this;
}
public float getLineWidth() {
return lineWidth;
}
public PieChartView setLineWidth(float lineWidth) {
this.lineWidth = lineWidth;
return this;
}
public float getLineThreshold() {
return lineThreshold;
}
public PieChartView setLineThreshold(float lineThreshold) {
this.lineThreshold = lineThreshold;
return this;
}
public float getLineThresholdLength() {
return lineThresholdLength;
}
public PieChartView setLineThresholdLength(float lineThresholdLength) {
this.lineThresholdLength = lineThresholdLength;
return this;
}
public float getTextPaddingBottom() {
return textPaddingBottom;
}
public PieChartView setTextPaddingBottom(float textPaddingBottom) {
this.textPaddingBottom = textPaddingBottom;
return this;
}
public float getDotRadius() {
return dotRadius;
}
public PieChartView setDotRadius(float dotRadius) {
this.dotRadius = dotRadius;
return this;
}
public float getTxtThresholdmargin() {
return txtThresholdmargin;
}
public PieChartView setTxtThresholdmargin(float txtThresholdmargin) {
this.txtThresholdmargin = txtThresholdmargin;
return this;
}
public float getTxtThreshold() {
return txtThreshold;
}
public PieChartView setTxtThreshold(float txtThreshold) {
this.txtThreshold = txtThreshold;
return this;
}
public float getTextSize() {
return textSize;
}
public PieChartView setTextSize(float textSize) {
this.textSize = textSize;
return this;
}
public float getMinPartsThreshold() {
return minPartsThreshold;
}
public PieChartView setMinPartsThreshold(float minPartsThreshold) {
this.minPartsThreshold = minPartsThreshold;
return this;
}
public String getEmptyTxt() {
return mEmptyTxt;
}
public PieChartView setEmptyTxt(String emptyTxt) {
mEmptyTxt = emptyTxt;
return this;
}
public PieChartView setDrawLine(boolean drawLine) {
mDrawLine = drawLine;
return this;
}
}
@@ -0,0 +1,32 @@
package com.tophold.trade.view.pie;
import android.support.annotation.ColorInt;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/07/10 20:36
* 描 述
* ============================================================
*/
public class PieEntrys {
public float value;
public String label;
@ColorInt
public int colorBg;
public boolean highLight;
public String symbol;
public PieEntrys(float value, String label, int colorBg, boolean highLight, String symbol) {
this.value = value;
this.label = label;
this.colorBg = colorBg;
this.highLight = highLight;
this.symbol = symbol;
}
//一些额外的字段辅助画图
//单个item需要扫过的角度
public float mSweepAngle;
}
@@ -0,0 +1,502 @@
package com.tophold.trade.view.seekbar;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.tophold.trade.utils.RenderUtils;
import com.tophold.trade.view.BaseView;
import java.util.Arrays;
/**
* ============================================================
* 作 者 : wgyscsf@163.com
* 更新时间 2018/11/30 14:00
* 描 述
* ============================================================
*/
public class DoubleThumbSeekBar extends BaseView {
private DoubleTumb mDoubleThumb;
//背景相关
private Paint mBackgroudPaint;
private RectF mBackgroudRectF;
//seekbar四个顶点
private int[] mPostionArr;
//前景图1
private Paint mForegroundPaintA;
private RectF mForegroundRectFA;
private Bitmap mThumbBitmapA;
private Paint mTipPaintA;
//前景图2
private Paint mForegroundPaintB;
private RectF mForegroundRectFB;
private Bitmap mThumbBitmapB;
private Paint mTipPaintB;
//滑动监听
private OnDoubleThumbChangeListener mOnDoubleThumbChangeListener;
//是否拦截事件
private boolean mDisallowIntercept = false;
/**
* 设置监听
*
* @param onDoubleThumbChangeListener
*/
public void setOnDoubleThumbChangeListener(OnDoubleThumbChangeListener onDoubleThumbChangeListener) {
mOnDoubleThumbChangeListener = onDoubleThumbChangeListener;
}
public DoubleThumbSeekBar(Context context) {
this(context, null);
}
public DoubleThumbSeekBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DoubleThumbSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void init(DoubleTumb doubleThumb) {
this.mDoubleThumb = doubleThumb;
initArrts();
}
private void initArrts() {
if (mDoubleThumb == null) {
throw new IllegalArgumentException("请先初始化:init(DoubleThumbSeekBar doubleThumbSeekBar)");
}
initPaint();
}
private void initPaint() {
mBackgroudPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBackgroudPaint.setColor(mDoubleThumb.mBackground);
mBackgroudPaint.setStyle(Paint.Style.FILL);
mBackgroudRectF = new RectF();
/**
* thumbA相关设置
*/
mForegroundPaintA = new Paint(Paint.ANTI_ALIAS_FLAG);
Drawable foregroundDrawableA = mDoubleThumb.mThumbA.mForegroundDrawable;
if (foregroundDrawableA != null) {
// TODO: 03/12/2018
} else {
mForegroundPaintA.setColor(mDoubleThumb.mThumbA.mForeground);
}
mForegroundPaintA.setStyle(Paint.Style.FILL);
mForegroundRectFA = new RectF();
//tipA
mTipPaintA = new Paint(Paint.ANTI_ALIAS_FLAG);
mTipPaintA.setStyle(Paint.Style.STROKE);
mTipPaintA.setColor(mDoubleThumb.mThumbA.mForeground);
mTipPaintA.setTextSize(sp2px(17));
/**
* thumbB相关设置
*/
DoubleTumb.Thumb thumbB = mDoubleThumb.mThumbB;
if (thumbB != null) {
mForegroundPaintB = new Paint(Paint.ANTI_ALIAS_FLAG);
mForegroundPaintB.setColor(thumbB.mForeground);
Drawable foregroundDrawableB = thumbB.mForegroundDrawable;
if (foregroundDrawableB != null) {
// mForegroundPaintA.
// TODO: 03/12/2018
} else {
mForegroundPaintB.setColor(thumbB.mForeground);
}
mForegroundPaintB.setStyle(Paint.Style.FILL);
mForegroundRectFB = new RectF();
//tipB
mTipPaintB = new Paint(Paint.ANTI_ALIAS_FLAG);
mTipPaintB.setStyle(Paint.Style.STROKE);
mTipPaintB.setColor(mDoubleThumb.mThumbB.mForeground);
mTipPaintB.setTextSize(sp2px(17));
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDoubleThumb == null) {
throw new IllegalArgumentException("请初始化init()");
}
drawBg(canvas);
//绘制前景图
drawForegroundThumbA(canvas);
drawForegroundThumbB(canvas);
//绘制thumb
drawThumbA(canvas);
drawThumbB(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
DoubleTumb.Thumb mThumbA = mDoubleThumb.mThumbA;
DoubleTumb.Thumb mThumbB = mDoubleThumb.mThumbB;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mDisallowIntercept) getParent().requestDisallowInterceptTouchEvent(true);
/**
* 判断当前手指是否在触摸thumbA和thumb(存在时)。如果thumbA和thumbB同时存在,优先thumbA为主,且只能触摸一个。
*/
boolean touchThumbA = mThumbA.isTouchThumb(getContext(), x, y);
mThumbA.mTouch = touchThumbA;
if (!touchThumbA) {
if (mThumbB != null) {
mThumbB.mTouch = mThumbB.isTouchThumb(getContext(), x, y);
Log.d(TAG, "onTouchEvent: " + Arrays.toString(mThumbA.getmTumbPosArr()) + "," + x + "," + y + "," + mThumbA.mTouch);
}
} else {
if (mThumbB != null) {
mThumbB.mTouch = false;
}
}
break;
case MotionEvent.ACTION_MOVE:
if (mDisallowIntercept) getParent().requestDisallowInterceptTouchEvent(true);
if (mThumbA.mTouch) {
//注意左右方向
float len = (mPostionArr[2] - mPostionArr[0]);
float part = mThumbA.mFromLeft ? (x - mPostionArr[0]) : -(x - mPostionArr[2]);
mThumbA.progress = part / len;
if (mThumbA.progress <= 0) {
mThumbA.progress = 0;
}
if (mThumbA.progress >= 1) {
mThumbA.progress = 1;
}
if (mOnDoubleThumbChangeListener != null) {
mOnDoubleThumbChangeListener.onProgressChanged(this, mThumbA.progress, true, true);
}
//刷新
invalidate();
} else if (mThumbB != null && mThumbB.mTouch) {
//注意左右方向
float len = (mPostionArr[2] - mPostionArr[0]);
float part = mThumbB.mFromLeft ? (x - mPostionArr[0]) : -(x - mPostionArr[2]);
mThumbB.progress = part / len;
if (mThumbB.progress <= 0) {
mThumbB.progress = 0;
}
if (mThumbB.progress >= 1) {
mThumbB.progress = 1;
}
if (mOnDoubleThumbChangeListener != null) {
mOnDoubleThumbChangeListener.onProgressChanged(this, mThumbB.progress, false, true);
}
//刷新
invalidate();
} else {
}
break;
case MotionEvent.ACTION_UP:
if (mDisallowIntercept)
getParent().requestDisallowInterceptTouchEvent(false);
mThumbA.mTouch = false;
if (mThumbB != null) mThumbB.mTouch = false;
break;
default:
break;
}
return true;
}
private void drawForegroundThumbB(Canvas canvas) {
if (mDoubleThumb.mThumbB == null) return;
DoubleTumb.Thumb mLeftThumb = mDoubleThumb.mThumbB;
boolean mFromLeft = mLeftThumb.mFromLeft;
double progress = mLeftThumb.progress;
int[] ints = getmPostionArr();
float l = mFromLeft ? ints[0] : ints[0] + (float) ((ints[2] - ints[0]) * (1 - progress));
float t = ints[1];
float r = mFromLeft ? ints[0] + (float) ((ints[2] - ints[0]) * progress) : ints[2];
float b = ints[3];
Log.d(TAG, "drawForegroundThumbA: " + progress);
//绘制背景
mForegroundRectFB.set(l, t, r, b);
canvas.drawRoundRect(mForegroundRectFB, mDoubleThumb.mCorners, mDoubleThumb.mCorners, mForegroundPaintB);
}
private void drawThumbB(Canvas canvas) {
if (mDoubleThumb.mThumbB == null) return;
DoubleTumb.Thumb mThumbB = mDoubleThumb.mThumbB;
boolean mFromLeft = mThumbB.mFromLeft;
double progress = mThumbB.progress;
int[] ints = getmPostionArr();
float l = mFromLeft ? ints[0] : ints[0] + (float) ((ints[2] - ints[0]) * (1 - progress));
float t = ints[1];
float r = mFromLeft ? ints[0] + (float) ((ints[2] - ints[0]) * progress) : ints[2];
float b = ints[3];
//开始绘制thumb
Bitmap thumbA = getThumbB();
float[] tumbPosArr = mThumbB.getmTumbPosArr();
tumbPosArr[0] = mFromLeft ? r - mThumbB.getmThumbWidth() / 2.0f : l - mThumbB.getmThumbWidth() / 2.0f;
tumbPosArr[1] = t - dp2px(10);
tumbPosArr[2] = tumbPosArr[0] + mThumbB.getmThumbWidth() / 2.0f;
tumbPosArr[3] = ints[1] + mThumbB.getmThumbHight();
canvas.drawBitmap(thumbA, tumbPosArr[0], tumbPosArr[1], mForegroundPaintB);
//绘制tips
if (mThumbB.mShowTips) {
double len = mDoubleThumb.mMax - mDoubleThumb.mMin;
/**
* 从左边开始计算最小值还是最右边和前景图无关,由开发者手动设置
*/
double realProcess = 0;
if (mThumbB.mFromLeft) {
if (mDoubleThumb.mMinLeft) {
realProcess = progress;
} else {
realProcess = (1 - progress);
}
} else {
if (mDoubleThumb.mMinLeft) {
realProcess = (1 - progress);
} else {
realProcess = progress;
}
}
double result = mDoubleThumb.mMin + len * realProcess;
String format = "%." + mDoubleThumb.mDigit + "f";
String showTipStr = String.format(format, result);
float txtWidth = mTipPaintB.measureText(showTipStr);
float x = (tumbPosArr[0] + tumbPosArr[2]) / 2.0f - txtWidth / 2.0f;
float y = tumbPosArr[1] - getTipsTopOffset();
//边界处理
if (x < ints[0]) {
x = ints[0];
}
if (x > ints[2] - txtWidth) {
x = ints[2] - txtWidth;
}
canvas.drawText(showTipStr, x, y, mTipPaintB);
}
}
/**
* tips向上的偏移量
*
* @return
*/
private int getTipsTopOffset() {
return dp2px(6);
}
/**
* 绘制thumbA前景图,左滑动和右滑动放在了一起,注意三元运算符,
*
* @param canvas
*/
private void drawForegroundThumbA(Canvas canvas) {
DoubleTumb.Thumb mLeftThumb = mDoubleThumb.mThumbA;
boolean mFromLeft = mLeftThumb.mFromLeft;
double progress = mLeftThumb.progress;
int[] ints = getmPostionArr();
float l = mFromLeft ? ints[0] : ints[0] + (float) ((ints[2] - ints[0]) * (1 - progress));
float t = ints[1];
float r = mFromLeft ? ints[0] + (float) ((ints[2] - ints[0]) * progress) : ints[2];
float b = ints[3];
Log.d(TAG, "drawForegroundThumbA: " + progress);
//绘制背景
mForegroundRectFA.set(l, t, r, b);
canvas.drawRoundRect(mForegroundRectFA, mDoubleThumb.mCorners, mDoubleThumb.mCorners, mForegroundPaintA);
}
private void drawThumbA(Canvas canvas) {
DoubleTumb.Thumb mThumbA = mDoubleThumb.mThumbA;
boolean mFromLeft = mThumbA.mFromLeft;
double progress = mThumbA.progress;
//seekbar四个顶点
int[] ints = getmPostionArr();
float l = mFromLeft ? ints[0] : ints[0] + (float) ((ints[2] - ints[0]) * (1 - progress));
float t = ints[1];
float r = mFromLeft ? ints[0] + (float) ((ints[2] - ints[0]) * progress) : ints[2];
float b = ints[3];
//开始绘制thumb
Bitmap thumbA = getThumbA();
float[] tumbPosArr = mThumbA.getmTumbPosArr();
tumbPosArr[0] = mFromLeft ? r - mThumbA.getmThumbWidth() / 2.0f : l - mThumbA.getmThumbWidth() / 2.0f;
tumbPosArr[1] = t - dp2px(10);
tumbPosArr[2] = tumbPosArr[0] + mThumbA.getmThumbWidth() / 2.0f;
tumbPosArr[3] = ints[1] + mThumbA.getmThumbHight();
canvas.drawBitmap(thumbA, tumbPosArr[0], tumbPosArr[1], mForegroundPaintA);
//绘制tips
if (mThumbA.mShowTips) {
double len = mDoubleThumb.mMax - mDoubleThumb.mMin;
/**
* 从左边开始计算最小值还是最右边和前景图无关,由开发者手动设置
*/
double realProcess = 0;
if (mThumbA.mFromLeft) {
if (mDoubleThumb.mMinLeft) {
realProcess = progress;
} else {
realProcess = (1 - progress);
}
} else {
if (mDoubleThumb.mMinLeft) {
realProcess = (1 - progress);
} else {
realProcess = progress;
}
}
double result = mDoubleThumb.mMin + len * realProcess;
String format = "%." + mDoubleThumb.mDigit + "f";
String showTipStr = String.format(format, result);
float txtWidth = mTipPaintA.measureText(showTipStr);
float x = (tumbPosArr[0] + tumbPosArr[2]) / 2.0f - txtWidth / 2.0f;
float y = tumbPosArr[1] - getTipsTopOffset();
//边界处理
if (x < ints[0]) {
x = ints[0];
}
if (x > ints[2] - txtWidth) {
x = ints[2] - txtWidth;
}
canvas.drawText(showTipStr, x, y, mTipPaintA);
}
}
private void drawBg(Canvas canvas) {
int[] ints = getmPostionArr();
mBackgroudRectF.set(ints[0], ints[1], ints[2], ints[3]);
canvas.drawRoundRect(mBackgroudRectF, mDoubleThumb.mCorners, mDoubleThumb.mCorners, mBackgroudPaint);
}
/**
* 获取进度条四个角的位置坐标
*
* @return
*/
private int[] getmPostionArr() {
if (mPostionArr == null) {
mPostionArr = new int[4];
mPostionArr[0] = 0 + mDoubleThumb.mLineLeft;
mPostionArr[1] = 0 + mDoubleThumb.mLineTop;
mPostionArr[2] = getWidth() - mDoubleThumb.mLineRight;
mPostionArr[3] = getHeight() - mDoubleThumb.mLineBottom;
}
return mPostionArr;
}
public boolean isDisallowIntercept() {
return mDisallowIntercept;
}
public DoubleThumbSeekBar setDisallowIntercept(boolean disallowIntercept) {
mDisallowIntercept = disallowIntercept;
return this;
}
/**
* 获取thumbA
*
* @return
*/
@NonNull
private Bitmap getThumbA() {
if (mThumbBitmapA == null) {
DoubleTumb.Thumb mThumbA = mDoubleThumb.mThumbA;
Drawable drawable = mThumbA.getmThumb();
if (drawable == null) {
throw new IllegalArgumentException("请提供mThumbA的Drawable");
}
mThumbBitmapA = RenderUtils.drawableToBitmap(drawable, mThumbA.getmThumbWidth(), mThumbA.getmThumbHight());
}
return mThumbBitmapA;
}
/**
* 获取thumbB
*
* @return
*/
@NonNull
private Bitmap getThumbB() {
if (mDoubleThumb.mThumbB == null) {
throw new IllegalArgumentException("请先初始化ThumbB");
}
if (mThumbBitmapB == null) {
Drawable drawable = null;
DoubleTumb.Thumb mThumbB = mDoubleThumb.mThumbB;
drawable = mThumbB.getmThumb();
if (drawable == null) {
throw new IllegalArgumentException("请提供mThumbB的Drawable");
}
mThumbBitmapB = RenderUtils.drawableToBitmap(drawable, mThumbB.getmThumbWidth(), mThumbB.getmThumbHight());
}
return mThumbBitmapB;
}
public interface OnDoubleThumbChangeListener {
/**
* 监听滑动
*
* @param seekBar 当前seekbar对象
* @param progress 进度百分比,[0,1]
* @param thumbA 是否是thumbA,true:thumbA,false:thumbB。同时只允许滑动一个进度thumb.
* @param fromUser 是否是用户自己手动触发的
*/
void onProgressChanged(DoubleThumbSeekBar seekBar, double progress, boolean thumbA, boolean fromUser);
}
}
@@ -0,0 +1,150 @@
package com.tophold.trade.view.seekbar;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* ============================================================
* : wgyscsf@163.com
* 更新时间 2018/11/30 14:13
*
* ============================================================
*/
public class DoubleTumb {
public double mMin;
public double mMax;
public float mCorners;
@ColorInt
public int mBackground;
@NonNull
public Thumb mThumbA;
@Nullable
public Thumb mThumbB;//是否启用双thumb以这个参数为空不为空所解决当这个不为空则显示双thumb.
public int mDigit = 4;//精度
//最小值从左边开始还是从右边开始,和前景图无关
public boolean mMinLeft = true;
/**
* 内部属性
*/
//背景的四边内间距
public int mLineTop, mLineBottom, mLineLeft, mLineRight;
public DoubleTumb(double mMin, double mMax, float mCorners,
@ColorInt int mBackground,
@NonNull Thumb mThumbA,
@Nullable Thumb mThumbB) {
this.mMin = mMin;
this.mMax = mMax;
this.mCorners = mCorners;
this.mBackground = mBackground;
this.mThumbA = mThumbA;
this.mThumbB = mThumbB;
}
public static class Thumb {
public boolean mFromLeft = true;
@ColorInt
public int mForeground;
public Drawable mForegroundDrawable;
//进度确认当前thumb所在的位置值为百分比
public double progress;
//thumb
private Drawable mThumb;
private int mThumbWidth;
private int mThumbHight;
//是否展示当前htumb的值
public boolean mShowTips;
/**
* 内部使用属性
*/
//thumb的位置坐标四个点的,用于判断手指是否在按压范围内
private float[] mTumbPosArr = new float[4];
//是否正在按压
public boolean mTouch = false;
public int getmThumbWidth() {
if (mThumbWidth <= 0) {
if (mThumb != null)
return mThumb.getIntrinsicWidth();
}
return mThumbWidth;
}
public Thumb setmThumbWidth(int mThumbWidth) {
this.mThumbWidth = mThumbWidth;
return this;
}
public int getmThumbHight() {
if (mThumbHight <= 0) {
if (mThumb != null)
return mThumb.getIntrinsicHeight();
}
return mThumbHight;
}
public Thumb setmThumbHight(int mThumbHight) {
this.mThumbHight = mThumbHight;
return this;
}
public Thumb(int mForeground) {
this.mForeground = mForeground;
}
public Thumb(boolean mFromLeft, Drawable mForegroundDrawable) {
this.mFromLeft = mFromLeft;
this.mForegroundDrawable = mForegroundDrawable;
}
public Drawable getmThumb() {
return mThumb;
}
public Thumb setmThumb(Drawable mThumb) {
this.mThumb = mThumb;
return this;
}
public float[] getmTumbPosArr() {
return mTumbPosArr;
}
/**
* 传递的xy是否在按压范围内
*
* @param context
* @param x
* @param y
* @return
*/
public boolean isTouchThumb(Context context, float x, float y) {
float touchOffset = getTouchOffset(context);
float[] ints = getmTumbPosArr();
ints[0] -= touchOffset;
ints[1] -= touchOffset;
ints[2] += touchOffset;
ints[3] += touchOffset;
return x >= ints[0] && x <= ints[2] && y >= ints[1] && y <= ints[3];
}
/**
* 扩大按压区域
*
* @param context
* @return
*/
public float getTouchOffset(Context context) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (2 * scale + 0.5f);
}
}
}
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<!--++++++++++++++++++基金收益自定义View++++++++++++++++++++-->
<!--基金自定义view相关属性-->
<color name="color_fundView_xyTxtColor">#787274</color>
<color name="color_fundView_xLineColor">#c1bdbe</color>
<color name="color_fundView_brokenLineColor">#3785d9</color>
<color name="color_fundView_longPressLineColor">#ec210f</color>
<color name="color_fundView_pressIncomeTxt">#ec210f</color>
<color name="color_fundView_pressIncomeTxtBg">#87c9e7</color>
<color name="color_fundView_defIncomeTxt">#000000</color>
<!--++++++++++++++++++++KView相关,包括:分时图、副图 开始+++++++++++++++++++++++++++++++++++++++++++++++-->
<!--公共属性-->
<!--外边框颜色-->
<color name="color_kview_outerStrokeColor">#787274</color>
<color name="color_kview_loadingTxtColor">#787274</color>
<!--内部xy虚线-->
<color name="color_kview_innerXyDashColor">#787274</color>
<!--++++++++++++++++++分时图自定义View++++++++++++++++++++-->
<!--分时图相关属性-->
<!--折线图-->
<color name="color_timeSharing_brokenLineColor">#3785d9</color>
<!--折线图最后的小圆点的颜色-->
<color name="color_timeSharing_dotColor">#3785d9</color>
<!--实时横线颜色-->
<color name="color_timeSharing_timingLineColor">#ec210f</color>
<!--折线下面的浅蓝色-->
<color name="color_timeSharing_blowBlueColor">#74caf9</color>
<!--x、y轴文字的颜色-->
<color name="color_timeSharing_xYTxtColor">#787274</color>
<!--实时横线右侧的红色的框和实时数据颜色-->
<color name="color_timeSharing_timingTxtColor">#ffffff</color>
<color name="color_timeSharing_timingTxtBgColor">#ec210f</color>
<!--长按十字线的颜色-->
<color name="color_timeSharing_longPressLineColor">#7b797b</color>
<!--长按十字时间、数据的框和文字颜色-->
<color name="color_timeSharing_longPressTxtColor">#ffffff</color>
<color name="color_timeSharing_longPressTxtBgColor">#7b797b</color>
<!--回调的颜色-->
<color name="color_timeSharing_callBackRed">#ec210f</color>
<color name="color_timeSharing_callBackGreen">#12ec3a</color>
<!--蜡烛图的颜色-->
<color name="color_timeSharing_candleRed">#ec210f</color>
<color name="color_timeSharing_candleGreen">#12ec3a</color>
<color name="color_timeSharing_candleEqual">#5d5dec</color>
<!--主图指标-->
<color name="color_masterView_legendColor">#8f8a8a</color>
<!--MA-->
<color name="color_masterView_ma5Color">#dcb1ad</color>
<color name="color_masterView_ma10Color">#72a5e4</color>
<color name="color_masterView_ma20Color">#3785d9</color>
<!--boll-->
<color name="color_masterView_bollUpColor">#ed978f</color>
<color name="color_masterView_bollMbColor">#136ad6</color>
<color name="color_masterView_bollDnColor">#7897b9</color>
<color name="color_minmax">#f23f09</color>
<!--++++++++++++++++++副图自定义View++++++++++++++++++++-->
<!--共有属性-->
<!--外边框颜色-->
<color name="color_minorView_outerStrokeColor">#787274</color>
<!--内部xy虚线-->
<color name="color_minorView_innerXyDashColor">#787274</color>
<!--x、y轴文字的颜色-->
<color name="color_minorView_xYTxtColor">#787274</color>
<!--图例文字的颜色-->
<color name="color_minorView_legendTxtColor">#787274</color>
<!--长按十字时间、数据的框和文字颜色-->
<color name="color_minorView_longPressTxtColor">#ffffff</color>
<!--macd相关-->
<color name="color_minorView_macdBuyColor">#ec210f</color>
<color name="color_minorView_macdSellColor">#5aab52</color>
<color name="color_minorView_macdDifColor">#dcb1ad</color>
<color name="color_minorView_macdDeaColor">#9941e1</color>
<color name="color_minorView_macdMacdColor">#3785d9</color>
<!--rsi相关-->
<color name="color_minorView_rsi6Color">#dcb1ad</color>
<color name="color_minorView_rsi12Color">#9941e1</color>
<color name="color_minorView_rsi24Color">#3785d9</color>
<!--kdj相关-->
<color name="color_minorView_kColor">#dcb1ad</color>
<color name="color_minorView_dColor">#9941e1</color>
<color name="color_minorView_jColor">#3785d9</color>
<!--++++++++++++++++++++KView相关,包括:分时图、副图 结束+++++++++++++++++++++++++++++++++++++++++++++++-->
<!--饼图-->
<color name="color_pie_chart_red">#ef1e13</color>
<color name="color_pie_chart_gray">#8a8a8b</color>
<!--具体业务-->
<color name="color_tb_indicator">#ef1e13</color>
<color name="color_tb_selectedTxt">#ef1e13</color>
<color name="color_tb_Txt">#8a8a8b</color>
</resources>
@@ -0,0 +1,9 @@
<resources>
<string name="app_name">FinancialCustomerView</string>
<string name="string_fundView_defHintTxt">累计收益:</string>
<string name="string_fundView_pressHintTxt">累计盈亏:</string>
<string name="string_placeHold">--</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>