的
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,48 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion setup.compileSdk
|
||||
buildToolsVersion = setup.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion setup.minSdk
|
||||
targetSdkVersion setup.targetSdk
|
||||
versionCode release.versionCode
|
||||
versionName release.versionName
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
release {
|
||||
minifyEnabled true
|
||||
zipAlignEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = '1.8'
|
||||
targetCompatibility = '1.8'
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation "androidx.appcompat:appcompat:${versions.appcompat}"
|
||||
implementation "androidx.annotation:annotation:${versions.appcompat}"
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation "androidx.constraintlayout:constraintlayout:${versions.constraintLayout}"
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
||||
implementation "com.faithhb:toolslib:${outer.toolslib}"
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
## 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
|
||||
#
|
||||
#
|
||||
#
|
||||
#
|
||||
##############################################
|
||||
##
|
||||
## 混淆知识点:
|
||||
## 1.https://www.jianshu.com/p/60e82aafcfd0
|
||||
##
|
||||
##############################################
|
||||
# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
|
||||
-optimizationpasses 5
|
||||
|
||||
|
||||
# https://www.jianshu.com/p/e8b41382a7c2
|
||||
# 表示混淆时不使用大小写混合类名
|
||||
-dontusemixedcaseclassnames
|
||||
# 表示不跳过library中的非public的类
|
||||
-dontskipnonpubliclibraryclasses
|
||||
# 打印混淆的详细信息
|
||||
-verbose
|
||||
|
||||
# Optimization is turned off by default. Dex does not like code run
|
||||
# through the ProGuard optimize and preverify steps (and performs some
|
||||
# of these optimizations on its own).
|
||||
-dontoptimize
|
||||
# 表示不进行校验,这个校验作用 在java平台上的
|
||||
-dontpreverify
|
||||
# Note that if you want to enable optimization, you cannot just
|
||||
# include optimization flags in your own project configuration file;
|
||||
# instead you will need to point to the
|
||||
# "proguard-android-optimize.txt" file instead of this one from your
|
||||
# project.properties file.
|
||||
|
||||
-keepattributes *Annotation*
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
|
||||
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# keep setters in Views so that animations can still work.
|
||||
# see http://proguard.sourceforge.net/manual/examples.html#beans
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator CREATOR;
|
||||
}
|
||||
|
||||
-keepclassmembers class **.R$* {
|
||||
public static <fields>;
|
||||
}
|
||||
|
||||
# -dontwarn 含义:声明不输出那些未找到的引用和一些错误
|
||||
# The support library contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
-dontwarn android.support.**
|
||||
|
||||
# -dontnote 含义:声明不输出那些潜在的错误和缺失
|
||||
# 处理重复定义的混淆文件问题(there were 11 duplicate class definitions,比如android.net.http.SslError | org.apache.http.params.HttpParams等)
|
||||
-dontnote android.net.http.*
|
||||
-dontnote org.apache.http.**
|
||||
|
||||
# Understand the @Keep support annotation.
|
||||
-keep class android.support.annotation.Keep
|
||||
|
||||
-keep @android.support.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
# 第三方库不混淆
|
||||
-dontwarn com.dongni.**
|
||||
-keep class com.dongni.** { *; }
|
||||
|
||||
# 页面modle避免混淆
|
||||
-keep class com.doyou.cv.bean.**{*; }
|
||||
-keep class com.doyou.cv.WLogger{*; }
|
||||
|
||||
-keep class com.doyou.cv.widget.**
|
||||
-keepclassmembers class com.doyou.cv.widget.** {
|
||||
public *;
|
||||
}
|
||||
|
||||
# 工具类避免混淆
|
||||
-keep class com.doyou.cv.utils.**
|
||||
-keepclassmembers class com.doyou.cv.utils.** {
|
||||
public *;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.doyou.cv"/>
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.doyou.cv.animator;
|
||||
|
||||
import android.animation.TypeEvaluator;
|
||||
|
||||
/**
|
||||
* 曲线估值器
|
||||
* @autor hongbing
|
||||
* @date 2018/12/18
|
||||
*/
|
||||
public class GradientEvaluator implements TypeEvaluator<GradientPoint> {
|
||||
@Override
|
||||
public GradientPoint evaluate(float fraction, GradientPoint startValue, GradientPoint endValue) {
|
||||
GradientPoint startPoint = startValue;
|
||||
GradientPoint endPoint = endValue;
|
||||
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
|
||||
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
|
||||
GradientPoint point = new GradientPoint(x, y);
|
||||
return point;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.doyou.cv.animator;
|
||||
|
||||
/**
|
||||
* 渐变曲线动画轨迹对象
|
||||
* @autor hongbing
|
||||
* @date 2018/12/18
|
||||
*/
|
||||
public class GradientPoint {
|
||||
|
||||
public GradientPoint() {
|
||||
}
|
||||
|
||||
public GradientPoint(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
private float x;
|
||||
private float y;
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(float x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(float y) {
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.doyou.cv.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 圆环实体类
|
||||
* @autor hongbing
|
||||
* @date 2018/10/30
|
||||
*/
|
||||
public class CircleBean implements Serializable {
|
||||
private float startPro; // 开始进度
|
||||
private float centerPro; // 中心点进度
|
||||
private float endPro; // 结束具体在圆环上所占的比例
|
||||
private int count; // 数量
|
||||
private String desc; // 描述
|
||||
|
||||
public float getStartPro() {
|
||||
return startPro;
|
||||
}
|
||||
|
||||
public void setStartPro(float startPro) {
|
||||
this.startPro = startPro;
|
||||
}
|
||||
|
||||
public float getCenterPro() {
|
||||
return centerPro;
|
||||
}
|
||||
|
||||
public void setCenterPro(float centerPro) {
|
||||
this.centerPro = centerPro;
|
||||
}
|
||||
|
||||
public float getEndPro() {
|
||||
return endPro;
|
||||
}
|
||||
|
||||
public void setEndPro(float endPro) {
|
||||
this.endPro = endPro;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CircleBean{" +
|
||||
"startPro=" + startPro +
|
||||
", centerPro=" + centerPro +
|
||||
", endPro=" + endPro +
|
||||
", count=" + count +
|
||||
", desc='" + desc + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.doyou.cv.bean;
|
||||
|
||||
public class GradientColor {
|
||||
|
||||
private int startColor;
|
||||
private int endColor;
|
||||
|
||||
public GradientColor(int startColor, int endColor) {
|
||||
this.startColor = startColor;
|
||||
this.endColor = endColor;
|
||||
}
|
||||
|
||||
public int getStartColor() {
|
||||
return startColor;
|
||||
}
|
||||
|
||||
public void setStartColor(int startColor) {
|
||||
this.startColor = startColor;
|
||||
}
|
||||
|
||||
public int getEndColor() {
|
||||
return endColor;
|
||||
}
|
||||
|
||||
public void setEndColor(int endColor) {
|
||||
this.endColor = endColor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.doyou.cv.bean;
|
||||
|
||||
import android.graphics.RectF;
|
||||
|
||||
/**
|
||||
* 环形重要属性封装对象
|
||||
* @autor hongbing
|
||||
* @date 2018/12/12
|
||||
*/
|
||||
public class RingVBean {
|
||||
|
||||
private String label;
|
||||
private RectF rf;
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public RectF getRf() {
|
||||
return rf;
|
||||
}
|
||||
|
||||
public void setRf(RectF rf) {
|
||||
this.rf = rf;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按下的点是否在图形内
|
||||
* @param rectF
|
||||
* @param x
|
||||
* @param y
|
||||
* @return isIn
|
||||
*/
|
||||
public boolean isInChartArea(RectF rectF, float x, float y) {
|
||||
boolean isIn = false;
|
||||
if (x >= rectF.left && x <= rectF.right && y >= rectF.top && y <= rectF.bottom) {
|
||||
isIn = true;
|
||||
}
|
||||
return isIn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.doyou.cv.bean;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 锥形图数据实体
|
||||
* @autor hongbing
|
||||
* @date 2018/11/2
|
||||
*/
|
||||
public class TaperChartBean implements Serializable {
|
||||
|
||||
private RectF rectF; // 图形矩阵
|
||||
private PointF pointF; // 圆点坐标值
|
||||
private String xValue; // x轴值
|
||||
private float yValue; // y轴值
|
||||
private float dataPAxisH; // 图形顶部y值
|
||||
private int paintColor; // 图形颜色
|
||||
|
||||
public RectF getRectF() {
|
||||
return rectF;
|
||||
}
|
||||
|
||||
public void setRectF(RectF rectF) {
|
||||
this.rectF = rectF;
|
||||
}
|
||||
|
||||
public PointF getPointF() {
|
||||
return pointF;
|
||||
}
|
||||
|
||||
public void setPointF(PointF pointF) {
|
||||
this.pointF = pointF;
|
||||
}
|
||||
|
||||
public String getxValue() {
|
||||
return xValue;
|
||||
}
|
||||
|
||||
public void setxValue(String xValue) {
|
||||
this.xValue = xValue;
|
||||
}
|
||||
|
||||
public float getyValue() {
|
||||
return yValue;
|
||||
}
|
||||
|
||||
public void setyValue(float yValue) {
|
||||
this.yValue = yValue;
|
||||
}
|
||||
|
||||
public float getDataPAxisH() {
|
||||
return dataPAxisH;
|
||||
}
|
||||
|
||||
public void setDataPAxisH(float dataPAxisH) {
|
||||
this.dataPAxisH = dataPAxisH;
|
||||
}
|
||||
|
||||
public int getPaintColor() {
|
||||
return paintColor;
|
||||
}
|
||||
|
||||
public void setPaintColor(int paintColor) {
|
||||
this.paintColor = paintColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按下的点是否在图形内
|
||||
* @param rectF
|
||||
* @param x
|
||||
* @param y
|
||||
* @return
|
||||
*/
|
||||
public boolean isInChartArea(RectF rectF, float x, float y) {
|
||||
boolean isIn = false;
|
||||
if (x >= rectF.left && x <= rectF.right && y >= rectF.top && y <= rectF.bottom) {
|
||||
isIn = true;
|
||||
}
|
||||
return isIn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.doyou.cv.callback;
|
||||
|
||||
/**
|
||||
* 监听设备旋转角度
|
||||
* @autor hongbing
|
||||
* @date 2019-10-08
|
||||
*/
|
||||
public interface Rotatable {
|
||||
// Set parameter 'animation' to true to have animation when rotation.
|
||||
void setOrientation(int orientation,boolean animation);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.doyou.cv.utils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* 格式转换工具
|
||||
* @autor hongbing
|
||||
* @date 2019-11-11
|
||||
*/
|
||||
public final class FormatUtil {
|
||||
|
||||
/**
|
||||
* 格式化数字(保留两位小数)
|
||||
* @param money
|
||||
* @return String
|
||||
*/
|
||||
public static String formatNumToTwoPoint(float money) {
|
||||
DecimalFormat format = new DecimalFormat("#.##");
|
||||
return format.format(money / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(保留两位小数)
|
||||
* @param money
|
||||
* @param pattern
|
||||
* @return String
|
||||
*/
|
||||
public static String formatNumToTwoPoint(float money,String pattern) {
|
||||
DecimalFormat format = new DecimalFormat(pattern);
|
||||
return format.format(money / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(保留两位小数)
|
||||
* @param money
|
||||
* @return String
|
||||
*/
|
||||
public static String formatNumToPercentByTwoPoint(float money) {
|
||||
DecimalFormat format = new DecimalFormat("#.##%");
|
||||
return format.format(money / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(保留两位小数)
|
||||
* @param money
|
||||
* @return String
|
||||
*/
|
||||
public static String formatNumToPercentByTwoPoint(float money,String pattern) {
|
||||
DecimalFormat format = new DecimalFormat(pattern);
|
||||
return format.format(money / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个小数四舍五入,保留两位小数返回
|
||||
* @param originNum
|
||||
* @return
|
||||
*/
|
||||
public static float roundTwo(float originNum) {
|
||||
return (float) (Math.round(originNum * 10) / 10.00);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.doyou.cv.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.doyou.cv.BuildConfig;
|
||||
|
||||
/**
|
||||
* 自定义控件库专属log日志打印工具
|
||||
* @autor hongbing
|
||||
* @date 2019-11-11
|
||||
*/
|
||||
public final class LogUtil {
|
||||
|
||||
public static final boolean DBG = BuildConfig.BUILD_TYPE.equals("debug");
|
||||
|
||||
public static void logD(String tag, String msg) {
|
||||
if (DBG) {
|
||||
Log.d(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void logE(String tag, String msg) {
|
||||
if (DBG) {
|
||||
Log.e(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.doyou.cv.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import com.doyou.cv.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工具类
|
||||
* @autor hongbing
|
||||
* @date 2018/11/19
|
||||
*/
|
||||
public final class Util {
|
||||
|
||||
/**
|
||||
* Turns an array of colors (integer color values) into an ArrayList of
|
||||
* colors.
|
||||
* @param colors
|
||||
* @return
|
||||
*/
|
||||
public static List<Integer> createColors(int[] colors) {
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (int i : colors) {
|
||||
result.add(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头像
|
||||
* @param resources
|
||||
* @param width
|
||||
* @return
|
||||
*/
|
||||
public static Bitmap getAvatar(Resources resources, int width){
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeResource(resources, R.drawable.hb,options);
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inDensity = options.outWidth;
|
||||
options.inTargetDensity = width;
|
||||
return BitmapFactory.decodeResource(resources,R.drawable.hb,options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源ID转Bitmap
|
||||
* @param resources
|
||||
* @param resId
|
||||
* @param width
|
||||
* @return
|
||||
*/
|
||||
public static Bitmap getAvatar(Resources resources, int resId,int width){
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeResource(resources, resId,options);
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inDensity = options.outWidth;
|
||||
options.inTargetDensity = width;
|
||||
return BitmapFactory.decodeResource(resources,resId,options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文字区域宽度
|
||||
* @param text 绘制的文字
|
||||
* @param paint 画笔
|
||||
* @return 文字的宽度
|
||||
*/
|
||||
public static int getTextWidth(String text, Paint paint) {
|
||||
Rect bounds = new Rect();
|
||||
paint.getTextBounds(text, 0, text.length(), bounds);
|
||||
int width = bounds.left + bounds.width();
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文字区域高度
|
||||
* @param text 绘制的文字
|
||||
* @param paint 画笔
|
||||
* @return 文字的高度
|
||||
*/
|
||||
public static int getTextHeight(String text, Paint paint) {
|
||||
Rect bounds = new Rect();
|
||||
paint.getTextBounds(text, 0, text.length(), bounds);
|
||||
int height = bounds.bottom + bounds.height();
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文字的宽度和高度
|
||||
* @param text 绘制的文字
|
||||
* @param paint 画笔
|
||||
* @return 文字的宽高
|
||||
*/
|
||||
public static int[] getTextWH(String text, Paint paint) {
|
||||
Rect bounds = new Rect();
|
||||
paint.getTextBounds(text, 0, text.length(), bounds);
|
||||
int width = bounds.left + bounds.width();
|
||||
int height = bounds.bottom + bounds.height();
|
||||
return new int[]{width, height};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取屏幕宽高
|
||||
* @param context
|
||||
* @return int[]
|
||||
*/
|
||||
public static int[] getScreenWH(Context context) {
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
return new int[]{metrics.widthPixels, metrics.heightPixels};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取z轴在location中的最佳位置
|
||||
* @return
|
||||
*/
|
||||
public static float getZForCamera() {
|
||||
return - 6 * Resources.getSystem().getDisplayMetrics().density;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.doyou.cv.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* 自定义riv的分割线绘制控件(不绘制最后一个item的分割线)
|
||||
* @autor hongbing
|
||||
* @date 2018/2/28
|
||||
*/
|
||||
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
|
||||
public static final int VERTICAL = LinearLayout.VERTICAL;
|
||||
|
||||
private static final String TAG = "DividerItem";
|
||||
private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };
|
||||
|
||||
private Drawable mDivider;
|
||||
|
||||
/**
|
||||
* Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}.
|
||||
*/
|
||||
private int mOrientation;
|
||||
|
||||
private final Rect mBounds = new Rect();
|
||||
|
||||
/**
|
||||
* Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a
|
||||
*
|
||||
* @param context Current context, it will be used to access resources.
|
||||
* @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}.
|
||||
*/
|
||||
public DividerItemDecoration(Context context, int orientation) {
|
||||
final TypedArray a = context.obtainStyledAttributes(ATTRS);
|
||||
mDivider = a.getDrawable(0);
|
||||
if (mDivider == null) {
|
||||
Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "
|
||||
+ "DividerItemDecoration. Please set that attribute all call setDrawable()");
|
||||
}
|
||||
a.recycle();
|
||||
setOrientation(orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the orientation for this divider. This should be called if
|
||||
* {@link RecyclerView.LayoutManager} changes orientation.
|
||||
*
|
||||
* @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
|
||||
*/
|
||||
public void setOrientation(int orientation) {
|
||||
if (orientation != HORIZONTAL && orientation != VERTICAL) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid orientation. It should be either HORIZONTAL or VERTICAL");
|
||||
}
|
||||
mOrientation = orientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Drawable} for this divider.
|
||||
*
|
||||
* @param drawable Drawable that should be used as a divider.
|
||||
*/
|
||||
public void setDrawable(@NonNull Drawable drawable) {
|
||||
if (drawable == null) {
|
||||
throw new IllegalArgumentException("Drawable cannot be null.");
|
||||
}
|
||||
mDivider = drawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
if (parent.getLayoutManager() == null || mDivider == null) {
|
||||
return;
|
||||
}
|
||||
if (mOrientation == VERTICAL) {
|
||||
drawVertical(c, parent);
|
||||
} else {
|
||||
drawHorizontal(c, parent);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawVertical(Canvas canvas, RecyclerView parent) {
|
||||
canvas.save();
|
||||
final int left;
|
||||
final int right;
|
||||
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
|
||||
if (parent.getClipToPadding()) {
|
||||
left = parent.getPaddingLeft();
|
||||
right = parent.getWidth() - parent.getPaddingRight();
|
||||
canvas.clipRect(left, parent.getPaddingTop(), right,
|
||||
parent.getHeight() - parent.getPaddingBottom());
|
||||
} else {
|
||||
left = 0;
|
||||
right = parent.getWidth();
|
||||
}
|
||||
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount - 1; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
parent.getDecoratedBoundsWithMargins(child, mBounds);
|
||||
final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
|
||||
final int top = bottom - mDivider.getIntrinsicHeight();
|
||||
mDivider.setBounds(left, top, right, bottom);
|
||||
mDivider.draw(canvas);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
|
||||
canvas.save();
|
||||
final int top;
|
||||
final int bottom;
|
||||
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
|
||||
if (parent.getClipToPadding()) {
|
||||
top = parent.getPaddingTop();
|
||||
bottom = parent.getHeight() - parent.getPaddingBottom();
|
||||
canvas.clipRect(parent.getPaddingLeft(), top,
|
||||
parent.getWidth() - parent.getPaddingRight(), bottom);
|
||||
} else {
|
||||
top = 0;
|
||||
bottom = parent.getHeight();
|
||||
}
|
||||
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount - 1; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
|
||||
final int right = mBounds.right + Math.round(child.getTranslationX());
|
||||
final int left = right - mDivider.getIntrinsicWidth();
|
||||
mDivider.setBounds(left, top, right, bottom);
|
||||
mDivider.draw(canvas);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||
RecyclerView.State state) {
|
||||
if (mDivider == null) {
|
||||
outRect.set(0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
if (mOrientation == VERTICAL) {
|
||||
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
|
||||
} else {
|
||||
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
package com.doyou.cv.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.DashPathEffect;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.widget.base.BaseGradientView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 渐变曲线自定义
|
||||
*
|
||||
* @autor hongbing
|
||||
* @date 2018/11/23
|
||||
* 参考资料:
|
||||
* 1.https://www.jianshu.com/p/5522470760c1
|
||||
* <p>
|
||||
* 贝塞尔曲线原理:
|
||||
* 2.https://blog.csdn.net/u013831257/article/details/51281136
|
||||
* <p>
|
||||
* canvas和paint相关知识点:
|
||||
* 3.https://juejin.im/entry/58c663f944d9040069956731
|
||||
*/
|
||||
public class GradientLine extends BaseGradientView {
|
||||
|
||||
private Paint mPaint; // 曲线画笔
|
||||
private Paint mImaPaint; // 虚线画笔
|
||||
private Paint mPointPaint; // 圆点
|
||||
private PointF control1, control2; // 控制点
|
||||
private float mRadius; // 小圆半径
|
||||
private float mImaLineL; // 曲线长度
|
||||
private int mLineW; // 图形长度
|
||||
|
||||
public GradientLine(Context context) {
|
||||
this(context,null);
|
||||
}
|
||||
|
||||
public GradientLine(Context context,@Nullable AttributeSet attrs) {
|
||||
this(context,attrs,0);
|
||||
}
|
||||
|
||||
public GradientLine(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public interface LineMode {
|
||||
int FLAT = 0;
|
||||
int UP = 1;
|
||||
int DOWN = 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setStrokeWidth(8f);
|
||||
// mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mPaint.setColor(Color.RED);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPointPaint.setStyle(Paint.Style.STROKE);
|
||||
mPointPaint.setColor(Color.GREEN);
|
||||
mPointPaint.setStrokeWidth(5f);
|
||||
|
||||
mImaPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mImaPaint.setStyle(Paint.Style.STROKE);
|
||||
mImaPaint.setStrokeWidth(5f);
|
||||
mImaPaint.setColor(Color.rgb(200, 200, 200));
|
||||
mImaPaint.setPathEffect(new DashPathEffect(new float[]{16, 4}, 0)); // 参数:数组(param1:线段长度,线段间的间距)
|
||||
|
||||
control1 = new PointF(0f, 0f);
|
||||
control2 = new PointF(0f, 0f);
|
||||
|
||||
mRadius = DensityUtil.dp2px(2.4f);
|
||||
mLineW = DensityUtil.dp2px(80f);
|
||||
mRadian = (1.4f / 3); // 控制点弧度
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
mMode = Mode.values()[mode];
|
||||
log("显示模式-> mMode = " + mMode);
|
||||
if(mIsAnim && mIsRunging){ // 如果当前动画还没结束,有开启其他模式的动画,需要将动画开关关闭,避免同个控件同时刷新数据导致的图形问题
|
||||
mIsAnim = false;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private float mRadian; // 控制点弧度
|
||||
|
||||
public void setRadian(float radian) {
|
||||
mRadian = radian;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
final int width = measureSize(widthMeasureSpec);
|
||||
final int height = measureSize(heightMeasureSpec);
|
||||
log("onMeasure-->width = " + width + "->height = " + height);
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
private void caclControPoint(int w, int h) {
|
||||
if (mMode == Mode.Up) {
|
||||
control1.x = w * mRadian;
|
||||
control1.y = h - mRadius * 2 - mPointPaint.getStrokeWidth();
|
||||
|
||||
control2.x = w * (1.f - mRadian);
|
||||
control2.y = mRadius;
|
||||
} else if (mMode == Mode.Down) {
|
||||
control1.x = w * mRadian;
|
||||
control1.y = mRadius + mPointPaint.getStrokeWidth();
|
||||
|
||||
control2.x = w * (1.f - mRadian);
|
||||
control2.y = h - mRadius * 2 - mPointPaint.getStrokeWidth();
|
||||
}
|
||||
}
|
||||
|
||||
private int measureSize(int measureSpec) {
|
||||
int result;
|
||||
int mode = MeasureSpec.getMode(measureSpec);
|
||||
int size = MeasureSpec.getSize(measureSpec);
|
||||
switch (mode) {
|
||||
case MeasureSpec.EXACTLY:
|
||||
result = size;
|
||||
break;
|
||||
default:
|
||||
result = mLineW;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) { // 控件大小发生改变,invalidate之行不会调用本方法
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
log("->>>>>>onSizeChanged...(w * mRadian) = " + w * mRadian + "->(w * 1 / 4) = " + (w * 1 / 4) + "->w = " + w);
|
||||
caclControPoint(w, h);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected void onLayout(boolean changed, int left, int top, int right, int bottom) { // requestLayout会重新调用该方法
|
||||
// super.onLayout(changed, left, top, right, bottom);
|
||||
// Log.d(TAG,"->>>>>>onLayout->w = " + (right - left));
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
log("->>>>>>onDraw...control1.x = " + control1.x + "->control1.y = " + control1.y
|
||||
+ "->control2.x = " + control2.x + "->control2.y = " + control2.y);
|
||||
|
||||
mView_W = getWidth();
|
||||
mView_H = getHeight();
|
||||
|
||||
caclControPoint(mView_W, mView_H);
|
||||
|
||||
if (mMode == Mode.Flat) {
|
||||
// if (mIsAnim) {
|
||||
// if (mCurrFlatPoint == null) {
|
||||
// mCurrFlatPoint = new GradientPoint(mRadius * 2 + mPointPaint.getStrokeWidth(), mView_H / 2 - mPaint.getStrokeWidth() / 2);
|
||||
// startAnimByFlat(mCurrFlatPoint.getX(), mCurrFlatPoint.getY(),
|
||||
// mView_W - mRadius * 2 - mPointPaint.getStrokeWidth(), mView_H / 2 - mPaint.getStrokeWidth() / 2);
|
||||
// } else {
|
||||
// drawFlat(canvas, mView_W, mView_H);
|
||||
// }
|
||||
// } else {
|
||||
// mCurrFlatPoint = new GradientPoint(mView_W - mRadius * 2 - mPointPaint.getStrokeWidth(),
|
||||
// mView_H / 2 - mPaint.getStrokeWidth() / 2);
|
||||
// drawFlat(canvas, mView_W, mView_H);
|
||||
// }
|
||||
if (mIsAnim && !mIsRunging) {
|
||||
drawFlatByAnim();
|
||||
} else {
|
||||
drawFlat(canvas, mIsAnim);
|
||||
}
|
||||
} else if (mMode == Mode.Up) {
|
||||
if (mIsAnim && !mIsRunging) {
|
||||
drawUpByAnim();
|
||||
} else {
|
||||
drawUp(canvas, mIsAnim);
|
||||
}
|
||||
} else if(mMode == Mode.Down) {
|
||||
if (mIsAnim && !mIsRunging) {
|
||||
drawDownByAnim();
|
||||
} else {
|
||||
drawDown(canvas, mIsAnim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private GradientPoint mCurrFlatPoint;
|
||||
//
|
||||
// private void startAnimByFlat(float start_x, float start_y, float end_x, float end_y) {
|
||||
// GradientPoint startPoint = new GradientPoint(start_x, start_y);
|
||||
// GradientPoint endPoint = new GradientPoint(end_x, end_y);
|
||||
// ValueAnimator animator = ValueAnimator.ofObject(new GradientEvaluator(), startPoint, endPoint);
|
||||
// animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
// @Override
|
||||
// public void onAnimationUpdate(ValueAnimator animation) {
|
||||
// invalidate();
|
||||
// mCurrFlatPoint = (GradientPoint) animation.getAnimatedValue();
|
||||
// }
|
||||
// });
|
||||
// animator.addListener(new AnimatorListenerAdapter() {
|
||||
// @Override
|
||||
// public void onAnimationEnd(Animator animation) {
|
||||
// super.onAnimationEnd(animation);
|
||||
//// mCurrFlatPoint = null;
|
||||
// }
|
||||
// });
|
||||
// animator.setDuration(mAnimDuration);
|
||||
// animator.start();
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void drawBezierPoint(List<Point> bezierPoints, Canvas canvas, Mode mode) {
|
||||
log("画曲线了->bazierPoints.size = " + bezierPoints.size() + "->mode = " + mode.name());
|
||||
if (mode == Mode.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == Mode.Up) {
|
||||
upLgMoreConfig(canvas);
|
||||
} else if (mode == Mode.Down) {
|
||||
downLgMoreCofig(canvas);
|
||||
}else if(mode == Mode.Flat){
|
||||
flatLgMoreConfig(canvas);
|
||||
}
|
||||
|
||||
Path path = new Path();
|
||||
path.moveTo(bezierPoints.get(0).getX(), bezierPoints.get(0).getY());
|
||||
|
||||
int index = 1;
|
||||
int var6 = bezierPoints.size() - 1;
|
||||
if (index <= var6) {
|
||||
while (true) {
|
||||
path.lineTo(bezierPoints.get(index).getX(), bezierPoints.get(index).getY());
|
||||
if (index == var6) {
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
}
|
||||
canvas.drawPath(path, mPaint);
|
||||
}
|
||||
|
||||
private void flatLgMoreConfig(Canvas canvas) {
|
||||
mPaint.setShader(null);
|
||||
mPaint.setColor(Color.rgb(88, 181, 250));
|
||||
// 画左右圆点
|
||||
mPointPaint.setShader(null);
|
||||
mPointPaint.setColor(Color.rgb(88, 181, 250));
|
||||
canvas.drawCircle(mRadius + mPointPaint.getStrokeWidth(), mView_H / 2 - mPaint.getStrokeWidth() / 2, mRadius, mPointPaint);
|
||||
canvas.drawCircle(mView_W - mRadius - mPointPaint.getStrokeWidth(), mView_H / 2 - mPaint.getStrokeWidth() / 2, mRadius, mPointPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawFlat(Canvas canvas, boolean isAnim) {
|
||||
if (isAnim) {
|
||||
updatePath(canvas, getPoints(), mMode);
|
||||
} else {
|
||||
flatLgMoreConfig(canvas);
|
||||
canvas.drawLine(mRadius * 2 + mPointPaint.getStrokeWidth(),
|
||||
mView_H / 2 - mPaint.getStrokeWidth() / 2,
|
||||
mView_W - mRadius * 2 - mPointPaint.getStrokeWidth(),
|
||||
mView_H / 2 - mPaint.getStrokeWidth() / 2, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawFlatByAnim() {
|
||||
clearPointsAll();
|
||||
float y = mView_H / 2 - mPaint.getStrokeWidth() / 2;
|
||||
// 起始点
|
||||
addPoint(mRadius * 2 + mPointPaint.getStrokeWidth(), y);
|
||||
// 结束点
|
||||
addPoint(mView_W - mRadius * 2 - mPointPaint.getStrokeWidth(), y);
|
||||
startAnim();
|
||||
}
|
||||
|
||||
private void upLgMoreConfig(Canvas canvas){
|
||||
LinearGradient lg = new LinearGradient(mRadius * 2, control1.y, mView_W / 2, control2.y + DensityUtil.dp2px(2),
|
||||
Color.rgb(255, 196, 0), Color.rgb(255, 105, 83), Shader.TileMode.CLAMP);
|
||||
mPaint.setShader(lg);
|
||||
|
||||
// 画虚线
|
||||
canvas.drawLine(mView_W - mRadius - mPointPaint.getStrokeWidth(), mRadius * 2 + mPointPaint.getStrokeWidth(),
|
||||
mView_W - mRadius - mPointPaint.getStrokeWidth(), mView_H - mRadius * 2 - mPointPaint.getStrokeWidth(), mImaPaint);
|
||||
|
||||
// 画圆点
|
||||
mPointPaint.setShader(lg);
|
||||
canvas.drawCircle(mRadius + mPointPaint.getStrokeWidth(), mView_H - mRadius - mPointPaint.getStrokeWidth(), mRadius, mPointPaint);
|
||||
canvas.drawCircle(mView_W - mRadius - mPointPaint.getStrokeWidth(), control2.y + DensityUtil.dp2px(2), mRadius, mPointPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawUp(Canvas canvas, boolean isAnim) {
|
||||
// 设置渐变曲线配置
|
||||
if (isAnim) {
|
||||
updatePath(canvas, getPoints(),mMode);
|
||||
} else {
|
||||
upLgMoreConfig(canvas);
|
||||
Path path = new Path();
|
||||
path.moveTo(mRadius * 2 + mPointPaint.getStrokeWidth(), mView_H - mRadius - mPointPaint.getStrokeWidth());
|
||||
path.cubicTo(control1.x, control1.y, control2.x, control2.y, mView_W - (mRadius * 2) - mPointPaint.getStrokeWidth(), control2.y + DensityUtil.dp2px(2));
|
||||
canvas.drawPath(path, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawUpByAnim() {
|
||||
clearPointsAll();
|
||||
// 起始点
|
||||
addPoint(mRadius * 2 + mPointPaint.getStrokeWidth(), mView_H - mRadius - mPointPaint.getStrokeWidth());
|
||||
// 控制点1
|
||||
addPoint(control1.x, control1.y);
|
||||
// 控制点2
|
||||
addPoint(control2.x, control2.y);
|
||||
// 结束点
|
||||
addPoint(mView_W - (mRadius * 2) - mPointPaint.getStrokeWidth(), control2.y + DensityUtil.dp2px(2));
|
||||
startAnim();
|
||||
}
|
||||
|
||||
private void downLgMoreCofig(Canvas canvas){
|
||||
// 设置渐变曲线配置
|
||||
float y0 = control1.y;
|
||||
LinearGradient lg = new LinearGradient(mRadius * 2, y0, mView_W / 2, control2.y + DensityUtil.dp2px(2),
|
||||
Color.rgb(88, 181, 250), Color.rgb(101, 226, 175), Shader.TileMode.CLAMP);
|
||||
mPaint.setShader(lg);
|
||||
|
||||
// 画虚线
|
||||
canvas.drawLine(mRadius + mPointPaint.getStrokeWidth(), mRadius * 2 + mPointPaint.getStrokeWidth(),
|
||||
mRadius + mPointPaint.getStrokeWidth(), mView_H - mRadius * 2 - mPointPaint.getStrokeWidth(), mImaPaint);
|
||||
|
||||
// 画圆点
|
||||
mPointPaint.setShader(lg);
|
||||
canvas.drawCircle(mRadius + mPointPaint.getStrokeWidth(), control1.y, mRadius, mPointPaint);
|
||||
canvas.drawCircle(mView_W - mRadius - mPointPaint.getStrokeWidth(), control2.y, mRadius, mPointPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawDown(Canvas canvas, boolean isAnim) {
|
||||
if (isAnim) {
|
||||
updatePath(canvas, getPoints(),mMode);
|
||||
} else {
|
||||
downLgMoreCofig(canvas);
|
||||
|
||||
Path path = new Path();
|
||||
path.moveTo(mRadius * 2 + mPointPaint.getStrokeWidth(), mRadius + mPointPaint.getStrokeWidth());
|
||||
path.cubicTo(control1.x, control1.y, control2.x, control2.y, mView_W - (mRadius * 2) - mPointPaint.getStrokeWidth(), control2.y);
|
||||
canvas.drawPath(path, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawDownByAnim() {
|
||||
clearPointsAll();
|
||||
// 起始点
|
||||
addPoint(mRadius * 2 + mPointPaint.getStrokeWidth(), mRadius + mPointPaint.getStrokeWidth());
|
||||
// 控制点1
|
||||
addPoint(control1.x, control1.y);
|
||||
// 控制点2
|
||||
addPoint(control2.x, control2.y);
|
||||
// 结束点
|
||||
addPoint(mView_W - (mRadius * 2) - mPointPaint.getStrokeWidth(), control2.y);
|
||||
startAnim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.doyou.cv.widget;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.utils.FormatUtil;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 油表控件
|
||||
*/
|
||||
public class HalfCircleProView extends View {
|
||||
/**
|
||||
* 圆心x坐标
|
||||
*/
|
||||
private float centerX;
|
||||
/**
|
||||
* 圆心y坐标
|
||||
*/
|
||||
private float centerY;
|
||||
|
||||
/**
|
||||
* 扇形所在矩形
|
||||
*/
|
||||
private RectF rectF = new RectF();
|
||||
|
||||
/**
|
||||
* 进度动画
|
||||
*/
|
||||
private ValueAnimator progressAnimator;
|
||||
|
||||
/**
|
||||
* 动画执行时间
|
||||
*/
|
||||
private int duration = 500;
|
||||
/**
|
||||
* 动画延时启动时间
|
||||
*/
|
||||
private int startDelay = 500;
|
||||
|
||||
/**
|
||||
* 圆形的画笔
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
private Paint mProgressPaint;
|
||||
|
||||
/**
|
||||
* 百分比的画笔
|
||||
*/
|
||||
private Paint mTxtPaint;
|
||||
/**
|
||||
* 圆形的宽度
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
private int mPaintWidth;
|
||||
|
||||
/**
|
||||
* @param context
|
||||
*/
|
||||
private Bitmap mBitmap;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
/**
|
||||
* 进度条的半径
|
||||
*/
|
||||
private int mRadius;
|
||||
/**
|
||||
* 内环的半径
|
||||
*/
|
||||
private int mInRadius;
|
||||
private int[] mGradientColors = {0xffffbf05, 0xFFFF6953};
|
||||
private float mProgress;
|
||||
private float mTempProgress;
|
||||
|
||||
/**
|
||||
* 百分比文本的大小
|
||||
*/
|
||||
private int mTxtSize;
|
||||
/**
|
||||
* 百分比的颜色
|
||||
*/
|
||||
private int mTxtColor;
|
||||
|
||||
public HalfCircleProView(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
|
||||
public HalfCircleProView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HalfCircleProView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
init(attrs);
|
||||
}
|
||||
|
||||
private void init(AttributeSet attrs) {
|
||||
getAttr(attrs);
|
||||
mProgressPaint = getPaint(mPaintWidth, Color.RED);
|
||||
initTextPaint();
|
||||
initBitmap();
|
||||
|
||||
}
|
||||
|
||||
private Paint getPaint(int strokeWidth, int color) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStrokeWidth(strokeWidth);
|
||||
paint.setColor(color);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
return paint;
|
||||
}
|
||||
|
||||
private void initTextPaint() {
|
||||
mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTxtPaint.setTextSize(mTxtSize);
|
||||
mTxtPaint.setColor(mTxtColor);
|
||||
mTxtPaint.setTextAlign(Paint.Align.CENTER);
|
||||
mTxtPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
this.mProgress = progress;
|
||||
initAnimation();
|
||||
}
|
||||
|
||||
public void setGradientColors(int[] colors) {
|
||||
this.mGradientColors = colors;
|
||||
}
|
||||
|
||||
|
||||
private void getAttr(AttributeSet attrs) {
|
||||
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.HalfCircleProView);
|
||||
mPaintWidth = typedArray.getDimensionPixelOffset(R.styleable.HalfCircleProView_strokeWidth, DensityUtil.dp2px(2));
|
||||
mTxtColor = typedArray.getColor(R.styleable.HalfCircleProView_txtColor, getResources().getColor(R.color.txt_black));
|
||||
mTxtSize = typedArray.getDimensionPixelSize(R.styleable.HalfCircleProView_txtSize, DensityUtil.dp2px(12));
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
private void initBitmap() {
|
||||
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_half_cicle);
|
||||
LogUtil.logD("201810301418", "initBitmap");
|
||||
mInRadius = mBitmap.getWidth() / 2;
|
||||
mRadius = mInRadius + mPaintWidth /*+ dp2px(1)*/;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
centerX = w / 2;
|
||||
centerY = h;
|
||||
rectF.set(centerX - mRadius, centerY - mRadius, centerX + mRadius, centerY + mRadius);
|
||||
LogUtil.logD("201810301418", "onSizeChanged");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthSize = mRadius * 2 + mPaintWidth + getPaddingLeft() + getPaddingRight();
|
||||
int heightSize = mRadius + mPaintWidth + getPaddingTop() + getPaddingBottom();
|
||||
int width = resolveSize(widthSize, widthMeasureSpec);
|
||||
int height = resolveSize(heightSize, heightMeasureSpec);
|
||||
LogUtil.logD("onMeasure", "width = " + width + "->height = " + height);
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
LinearGradient sweepGradient = new LinearGradient(centerX - mRadius, centerY, centerX + mRadius, centerY, mGradientColors, null, Shader.TileMode.MIRROR);
|
||||
mProgressPaint.setShader(sweepGradient);
|
||||
canvas.drawBitmap(mBitmap, centerX - mInRadius, centerY - mInRadius, mProgressPaint);
|
||||
canvas.drawText(FormatUtil.formatNumToPercentByTwoPoint(mProgress), centerX, centerY, mTxtPaint);
|
||||
mTempProgress = mProgress == 0 ? 0.1f : mProgress;
|
||||
float sweepAngele = mTempProgress * 180 / 100;
|
||||
canvas.drawArc(rectF, 180, sweepAngele, false, mProgressPaint);
|
||||
}
|
||||
|
||||
private void initAnimation() {
|
||||
progressAnimator = ValueAnimator.ofFloat(0, mProgress);
|
||||
progressAnimator.setDuration(duration);
|
||||
progressAnimator.setStartDelay(startDelay);
|
||||
progressAnimator.setInterpolator(new LinearInterpolator());
|
||||
progressAnimator.addUpdateListener(valueAnimator -> {
|
||||
float value = (float) valueAnimator.getAnimatedValue();
|
||||
mProgress = value;
|
||||
invalidate();
|
||||
});
|
||||
progressAnimator.start();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.doyou.cv.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Scroller;
|
||||
|
||||
import com.doyou.cv.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 横向滚动选择view
|
||||
* @autor hongbing
|
||||
* @date 2019/3/27
|
||||
* @since https://github.com/385841539/HorizontalScrollSelectedView
|
||||
*/
|
||||
public class HorScrollSelectedView extends View {
|
||||
|
||||
private Context mContext;
|
||||
// 普通文字的画笔,字体大小,字体颜色
|
||||
private Paint mTextPaint;
|
||||
private float mTextSize;
|
||||
private int mTextColor;
|
||||
// 被选中文字的画笔,字体大小,字体颜色
|
||||
private Paint mSelectedPaint;
|
||||
private float mSelectedTextSize;
|
||||
private int mSelectedTextColor;
|
||||
|
||||
// 数据源字符串数组
|
||||
private List<String> mStrings = new ArrayList<>();
|
||||
private int mSeeSize = 5; // 可见个数
|
||||
private int mWidth,mHeight; // 控件宽高
|
||||
private int mAnInt; // 每个单元的长度
|
||||
private int n; // 中间点索引(选中点)
|
||||
private float mAnOffset;
|
||||
// 测量文本宽高矩阵
|
||||
private Rect mRect = new Rect();
|
||||
private boolean mFirstVisible = true;
|
||||
// 选中文字的高度
|
||||
private int mCenterTextHeight = 0;
|
||||
// 文本宽高
|
||||
private int mTextWidth,mTextHeight;
|
||||
|
||||
private float mDownX;
|
||||
/**
|
||||
* 滑动实例
|
||||
*/
|
||||
private Scroller mScroller;
|
||||
|
||||
public HorScrollSelectedView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HorScrollSelectedView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HorScrollSelectedView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
setWillNotDraw(false);
|
||||
// setClickable(true);
|
||||
initAttrs(attrs);
|
||||
initPaint();
|
||||
mScroller = new Scroller(mContext);
|
||||
}
|
||||
|
||||
private void initAttrs(AttributeSet attrs) {
|
||||
TypedArray ta = mContext
|
||||
.obtainStyledAttributes(attrs, R.styleable.HorScrollSelectedView);
|
||||
mSeeSize = ta.getInteger(R.styleable.HorScrollSelectedView_seeSize, mSeeSize);
|
||||
mSelectedTextSize = ta.getFloat(R.styleable.HorScrollSelectedView_selectedTextSize, 50);
|
||||
mSelectedTextColor = ta.getColor(R.styleable.HorScrollSelectedView_selectedTextColor,
|
||||
getResources().getColor(android.R.color.black));
|
||||
mTextSize = ta.getFloat(R.styleable.HorScrollSelectedView_textSize, 40);
|
||||
mTextColor = ta.getColor(R.styleable.HorScrollSelectedView_textColor,
|
||||
getResources().getColor(android.R.color.darker_gray));
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
private void initPaint() {
|
||||
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTextPaint.setTextSize(mTextSize);
|
||||
mTextPaint.setColor(mTextColor);
|
||||
mSelectedPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
||||
mSelectedPaint.setTextSize(mSelectedTextSize);
|
||||
mSelectedPaint.setColor(mSelectedTextColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
Log.d("20190327","onTouchEvent...");
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDownX = event.getX(); // 获得点下去的x坐标
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
// 抬起手指,偏移量归零,相当于回弹
|
||||
mAnOffset = 0;
|
||||
invalidate();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float scrollX = event.getX();
|
||||
if(n != 0 && n != mStrings.size() - 1){
|
||||
mAnOffset = scrollX - mDownX; // 滑动时的偏移量,用于计算每个数据源文字的坐标值
|
||||
}else {
|
||||
mAnOffset = (float) ((scrollX - mDownX) / 1.5); // 当滑到两端的时候添加一点阻力
|
||||
}
|
||||
if(scrollX > mDownX){
|
||||
// 向右滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字
|
||||
if(scrollX - mDownX >= mAnInt){
|
||||
if(n > 0){
|
||||
mAnOffset = 0;
|
||||
n = n - 1;
|
||||
mDownX = scrollX;
|
||||
}
|
||||
// scrollBy((int) -mAnOffset,0);
|
||||
}
|
||||
}else{
|
||||
// 向左滑动,当滑动距离大于每个单元的长度时,则改变被选中的文字
|
||||
if(mDownX - scrollX >= mAnInt){
|
||||
if(n < mStrings.size() - 1){
|
||||
mAnOffset = 0;
|
||||
n = n + 1;
|
||||
mDownX = scrollX;
|
||||
}
|
||||
// scrollBy((int) mAnOffset,0);
|
||||
}
|
||||
}
|
||||
|
||||
invalidate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// return super.onTouchEvent(event);
|
||||
return true; // 自己消耗手势,避免只执行move_down,其他move,up不执行
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
super.computeScroll();
|
||||
if(mScroller.computeScrollOffset()){
|
||||
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
|
||||
//
|
||||
// if(mScroller.computeScrollOffset()){
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if(mFirstVisible){
|
||||
mWidth = getWidth();
|
||||
mHeight = getHeight();
|
||||
mAnInt = mWidth / mSeeSize;
|
||||
}
|
||||
if (n >= 0 && n <= mStrings.size() - 1) {
|
||||
String s = mStrings.get(n); // 得到被选中的文字
|
||||
// 得到被选中文字,绘制时所需要的宽高
|
||||
mSelectedPaint.getTextBounds(s,0,s.length(),mRect);
|
||||
// 从矩形区域中读出文本内容的宽高
|
||||
int centerTextWidth = mRect.width();
|
||||
mCenterTextHeight = mRect.height();
|
||||
Log.d("20190327","文本内容宽高,宽 = " + centerTextWidth + "->高 = " + mCenterTextHeight + "-n = " + n + "->mAnInt = " + mAnInt);
|
||||
canvas.drawText(mStrings.get(n),mWidth / 2 - centerTextWidth / 2 + mAnOffset,
|
||||
mHeight / 2 + mCenterTextHeight / 2,mSelectedPaint); // 绘制被选中文字,注意点是y坐标
|
||||
|
||||
for (int i = 0; i < mStrings.size(); i++) {
|
||||
if (n > 0 && n < mStrings.size() - 1) {
|
||||
mTextPaint.getTextBounds(mStrings.get(n - 1), 0, mStrings.get(n - 1).length(), mRect);
|
||||
int widthL = mRect.width();
|
||||
mTextPaint.getTextBounds(mStrings.get(n + 1), 0, mStrings.get(n + 1).length(), mRect);
|
||||
int widthR = mRect.width();
|
||||
mTextWidth = (widthL + widthR) / 2;
|
||||
}
|
||||
if(i == 0){ // 得到高,高度是一样的,所以取一遍就行
|
||||
mTextPaint.getTextBounds(mStrings.get(0),0,mStrings.get(0).length(),mRect);
|
||||
mTextHeight = mRect.height();
|
||||
}
|
||||
|
||||
if (i != n) { // 绘制其他文本
|
||||
Log.d("20190327","x = " + ((i - n) * mAnInt + mWidth / 2 - mTextWidth / 2 + mAnOffset));
|
||||
// x,y的算法很关键
|
||||
canvas.drawText(mStrings.get(i),
|
||||
(i - n) * mAnInt + mWidth / 2 - mTextWidth / 2 + mAnOffset,
|
||||
mHeight / 2 + mTextHeight / 2,
|
||||
mTextPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变中间可见文字的数目
|
||||
* @param seeSize 可见数
|
||||
*/
|
||||
public void setSeeSize(int seeSize){
|
||||
if(seeSize > 0){
|
||||
mSeeSize = seeSize;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据源
|
||||
* @param strings
|
||||
*/
|
||||
public void setData(List<String> strings){
|
||||
mStrings = strings;
|
||||
n = mStrings.size() / 2;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得被选中的文本
|
||||
* @return
|
||||
*/
|
||||
public String getSelectedString(){
|
||||
if(mStrings.size() != 0){
|
||||
return mStrings.get(n);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认选中索引值
|
||||
* @param index
|
||||
*/
|
||||
public void setDefSelectIndex(int index) {
|
||||
if (index < 0 || index > mStrings.size() - 1) {
|
||||
return;
|
||||
}
|
||||
n = index;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向左移动一个单元
|
||||
*/
|
||||
public void setAnLeftOffset(){
|
||||
if(n < mStrings.size() - 1){
|
||||
n = n + 1;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向右移动一个单元
|
||||
*/
|
||||
public void setAnRightOffset(){
|
||||
if(n > 0){
|
||||
n = n - 1;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.doyou.cv.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* 贝塞尔曲线绘制
|
||||
* @autor hongbing
|
||||
* @date 2018/10/31
|
||||
*/
|
||||
public class PathMorphBezier extends View {
|
||||
|
||||
private Paint mPaintBezier;
|
||||
private Paint mPaintAuxiliary;
|
||||
private Paint mPaintAuxiliaryText;
|
||||
|
||||
private float mAuxiliaryOneX;
|
||||
private float mAuxiliaryOneY;
|
||||
private float mAuxiliaryTwoX;
|
||||
private float mAuxiliaryTwoY;
|
||||
|
||||
private float mStartPointX;
|
||||
private float mStartPointY;
|
||||
|
||||
private float mEndPointX;
|
||||
private float mEndPointY;
|
||||
|
||||
private boolean isSecondPoint = false;
|
||||
|
||||
private Path mPath = new Path();
|
||||
|
||||
public PathMorphBezier(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public PathMorphBezier(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mPaintBezier = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaintBezier.setStyle(Paint.Style.STROKE);
|
||||
mPaintBezier.setStrokeWidth(8);
|
||||
|
||||
mPaintAuxiliary = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaintAuxiliary.setStyle(Paint.Style.STROKE);
|
||||
mPaintAuxiliary.setStrokeWidth(2);
|
||||
|
||||
mPaintAuxiliaryText = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaintAuxiliaryText.setStyle(Paint.Style.STROKE);
|
||||
mPaintAuxiliaryText.setTextSize(20);
|
||||
}
|
||||
|
||||
public PathMorphBezier(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mStartPointX = w / 4;
|
||||
mStartPointY = h / 2 - 200;
|
||||
|
||||
mEndPointX = w / 4 * 3;
|
||||
mEndPointY = h / 2 - 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
mPath.reset();
|
||||
mPath.moveTo(mStartPointX, mStartPointY);
|
||||
// 辅助点
|
||||
canvas.drawPoint(mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary);
|
||||
canvas.drawText("控制点1", mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliaryText);
|
||||
canvas.drawText("控制点2", mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliaryText);
|
||||
canvas.drawText("起始点", mStartPointX, mStartPointY, mPaintAuxiliaryText);
|
||||
canvas.drawText("终止点", mEndPointX, mEndPointY, mPaintAuxiliaryText);
|
||||
// 辅助线
|
||||
canvas.drawLine(mStartPointX, mStartPointY, mAuxiliaryOneX, mAuxiliaryOneY, mPaintAuxiliary);
|
||||
canvas.drawLine(mEndPointX, mEndPointY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary);
|
||||
canvas.drawLine(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mPaintAuxiliary);
|
||||
// 三阶贝塞尔曲线
|
||||
mPath.cubicTo(mAuxiliaryOneX, mAuxiliaryOneY, mAuxiliaryTwoX, mAuxiliaryTwoY, mEndPointX, mEndPointY);
|
||||
canvas.drawPath(mPath, mPaintBezier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
isSecondPoint = true;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mAuxiliaryOneX = event.getX(0);
|
||||
mAuxiliaryOneY = event.getY(0);
|
||||
if (isSecondPoint) {
|
||||
mAuxiliaryTwoX = event.getX(1);
|
||||
mAuxiliaryTwoY = event.getY(1);
|
||||
}
|
||||
invalidate();
|
||||
break;
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
isSecondPoint = false;
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.doyou.cv.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 图例样式
|
||||
* @autor hongbing
|
||||
* @date 2018/12/20
|
||||
*/
|
||||
public class PointView extends View {
|
||||
|
||||
public interface Point_Style{
|
||||
int CIRCLE = 0;
|
||||
int SQUARE = 1;
|
||||
int LINE = 2;
|
||||
}
|
||||
|
||||
private Paint mPaint;
|
||||
private int mStyle;
|
||||
private int mColor = Color.RED;
|
||||
private int mPointWH;
|
||||
|
||||
public PointView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PointView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PointView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mPointWH = DensityUtil.dp2px(8);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
setMeasuredDimension(mPointWH, mPointWH);
|
||||
}
|
||||
|
||||
public void setStyle(int style) {
|
||||
mStyle = style;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mColor = color;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
mPaint.setColor(mColor);
|
||||
if (mStyle == Point_Style.CIRCLE) {
|
||||
canvas.drawCircle(mPointWH / 2, mPointWH / 2, mPointWH / 2, mPaint);
|
||||
} else if (mStyle == Point_Style.SQUARE) {
|
||||
mPaint.setStrokeWidth(12);
|
||||
canvas.drawPoint(0, mPointWH / 2, mPaint);
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.doyou.cv.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 实现薄荷健康滑动卷尺
|
||||
* @autor hongbing
|
||||
* @date 2019/3/27
|
||||
* @since https://github.com/KnightAndroid/SlidingRuleView
|
||||
* @since https://juejin.im/post/5c2c18ba5188257a937fbd0d#heading-1
|
||||
*/
|
||||
public class SlidingRuleView extends View {
|
||||
public SlidingRuleView(Context context) {
|
||||
this(context,null);
|
||||
}
|
||||
|
||||
public SlidingRuleView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs,0);
|
||||
}
|
||||
|
||||
public SlidingRuleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.doyou.cv.widget.animator;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Camera;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class BmpOverturnView extends View {
|
||||
|
||||
public static final int IMAGE_WIDTH = DensityUtil.dp2px(180);
|
||||
|
||||
Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
Bitmap image;
|
||||
Camera mCamera = new Camera();
|
||||
float left = 0f, top = 0f;
|
||||
float topFlip = 0f;
|
||||
float bottomFlip = 0f;
|
||||
float flipRotation = 0f;
|
||||
|
||||
// 将属性名单独包装下,便于外部和谐引用
|
||||
public String getPropIsTopFlip() {
|
||||
return "topFlip";
|
||||
}
|
||||
|
||||
public String getPropIsBottomFlip() {
|
||||
return "bottomFlip";
|
||||
}
|
||||
|
||||
public String getPropFlipRotation() {
|
||||
return "flipRotation";
|
||||
}
|
||||
|
||||
public float getTopFlip() {
|
||||
return topFlip;
|
||||
}
|
||||
|
||||
public void setTopFlip(float topFlip) {
|
||||
this.topFlip = topFlip;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public float getBottomFlip() {
|
||||
return bottomFlip;
|
||||
}
|
||||
|
||||
public void setBottomFlip(float bottomFlip) {
|
||||
this.bottomFlip = bottomFlip;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public float getFlipRotation() {
|
||||
return flipRotation;
|
||||
}
|
||||
|
||||
public void setFlipRotation(float flipRotation) {
|
||||
this.flipRotation = flipRotation;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void propReset() {
|
||||
setBottomFlip(0f);
|
||||
setTopFlip(0f);
|
||||
setFlipRotation(0f);
|
||||
}
|
||||
|
||||
public BmpOverturnView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
{
|
||||
image = Util.getAvatar(getResources(), IMAGE_WIDTH);
|
||||
mCamera.setLocation(0, 0, Util.getZForCamera());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension((int) (IMAGE_WIDTH * 1.5f), (int) (IMAGE_WIDTH * 1.5f));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
left = (getWidth() - IMAGE_WIDTH) / 2;
|
||||
top = (getHeight() - IMAGE_WIDTH) / 2 - DensityUtil.dp2px(50);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
// 上半部分
|
||||
canvas.save();
|
||||
canvas.translate(left + IMAGE_WIDTH / 2, top + IMAGE_WIDTH / 2);
|
||||
canvas.rotate(-flipRotation);
|
||||
mCamera.save();
|
||||
mCamera.rotateX(topFlip);
|
||||
mCamera.applyToCanvas(canvas);
|
||||
mCamera.restore();
|
||||
canvas.clipRect(-IMAGE_WIDTH, -IMAGE_WIDTH, IMAGE_WIDTH, 0);
|
||||
canvas.rotate(flipRotation);
|
||||
canvas.translate(-(left + IMAGE_WIDTH / 2), -(top + IMAGE_WIDTH / 2));
|
||||
canvas.drawBitmap(image, left, top, mPaint);
|
||||
canvas.restore();
|
||||
|
||||
// 下半部分
|
||||
canvas.save();
|
||||
canvas.translate(left + IMAGE_WIDTH / 2, top + IMAGE_WIDTH / 2);
|
||||
canvas.rotate(-flipRotation);
|
||||
mCamera.save();
|
||||
mCamera.rotateX(bottomFlip);
|
||||
mCamera.applyToCanvas(canvas);
|
||||
mCamera.restore();
|
||||
canvas.clipRect(-IMAGE_WIDTH, 0, IMAGE_WIDTH, IMAGE_WIDTH);
|
||||
canvas.rotate(flipRotation);
|
||||
canvas.translate(-(left + IMAGE_WIDTH / 2), -(top + IMAGE_WIDTH / 2));
|
||||
canvas.drawBitmap(image, left, top, mPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package com.doyou.cv.widget.base;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.doyou.cv.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 曲线控件抽象类
|
||||
* @autor hongbing
|
||||
* @date 2018/12/18
|
||||
*/
|
||||
public abstract class BaseGradientView extends View {
|
||||
|
||||
private static final String TAG = "GradientLine";
|
||||
private static final boolean isDebug = true;
|
||||
protected int mView_W,mView_H; // view的宽高
|
||||
protected Mode mMode;
|
||||
protected boolean mIsAnim; // 是否使用动画
|
||||
protected int mAnimDuration = 320; // 动画时长
|
||||
private List<Point> mPointList = null;
|
||||
private List<Point> mBezierPoints = null;
|
||||
protected float mPer;
|
||||
protected boolean mIsRunging;
|
||||
|
||||
public BaseGradientView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.GradientLine);
|
||||
int ordinal = ta.getInt(R.styleable.GradientLine_gl_mode, Mode.None.ordinal());
|
||||
mMode = Mode.values()[ordinal];
|
||||
ta.recycle();
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 开启硬件加速,不然垂直虚线没效果
|
||||
init();
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
mPointList = new ArrayList<>(5);
|
||||
mBezierPoints = new ArrayList<>(5);
|
||||
}
|
||||
|
||||
protected void log(String ms) {
|
||||
if (isDebug) {
|
||||
Log.d(TAG, ms);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAnim(boolean isAnim) {
|
||||
mIsAnim = isAnim;
|
||||
}
|
||||
|
||||
public void setAnim(boolean isAnim, int duration) {
|
||||
mIsAnim = isAnim;
|
||||
mAnimDuration = duration;
|
||||
}
|
||||
|
||||
public void setAnimDuration(int duration) {
|
||||
mAnimDuration = duration;
|
||||
}
|
||||
|
||||
|
||||
protected void addPoint(float x, float y) {
|
||||
mPointList.add(new Point(x, y));
|
||||
}
|
||||
|
||||
protected void clearPoints() {
|
||||
mPointList.clear();
|
||||
}
|
||||
|
||||
protected void clearBezierPoints(){
|
||||
mBezierPoints.clear();
|
||||
}
|
||||
|
||||
protected void clearPointsAll(){
|
||||
clearPoints();
|
||||
clearBezierPoints();
|
||||
}
|
||||
|
||||
protected List<Point> getPoints(){
|
||||
return mPointList;
|
||||
}
|
||||
|
||||
protected void updatePath(Canvas canvas, List<Point> points,Mode mode) {
|
||||
int size = points.size();
|
||||
log("********per = " + mPer + "->size = " + size);
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
if (size == 1) { // 开始连线
|
||||
mBezierPoints.add(new Point(points.get(0).x, points.get(0).y));
|
||||
drawBezierPoint(mBezierPoints,canvas,mode);
|
||||
|
||||
// 测试数据
|
||||
// Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
// paint.setColor(Color.BLUE);
|
||||
// paint.setStrokeWidth(2);
|
||||
// canvas.drawCircle(points.get(0).x,points.get(0).y,4,paint);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
List<Point> nextPoints = new ArrayList<>();
|
||||
|
||||
int index = 1;
|
||||
int var7 = size - 1;
|
||||
if(index <= var7){
|
||||
while (true){
|
||||
float nextPointX = points.get(index - 1).x - (points.get(index - 1).x - points.get(index).x) * mPer;
|
||||
float nextPointY = points.get(index - 1).y - (points.get(index - 1).y - points.get(index).y) * mPer;
|
||||
nextPoints.add(new Point(nextPointX, nextPointY));
|
||||
log("********nextPointX = " + nextPointX + "********nextPointY = " + nextPointY
|
||||
+ "********nextPoints长度 = " + nextPoints.size());
|
||||
if(index == var7){
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制下一层
|
||||
updatePath(canvas, nextPoints,mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
cancelAnim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画绘制贝塞尔曲线
|
||||
* @param bazierPoints
|
||||
* @param canvas
|
||||
* @param mode 图形模式
|
||||
*/
|
||||
protected abstract void drawBezierPoint(List<Point> bazierPoints, Canvas canvas,Mode mode);
|
||||
|
||||
/**
|
||||
* 画横线
|
||||
* @param canvas
|
||||
* @param isAnim 是否开启动画
|
||||
*/
|
||||
protected abstract void drawFlat(Canvas canvas,boolean isAnim);
|
||||
|
||||
/**
|
||||
* 画横线,通过动画的方式
|
||||
*/
|
||||
protected abstract void drawFlatByAnim();
|
||||
|
||||
/**
|
||||
* 画向上的曲线
|
||||
* @param canvas
|
||||
* @param isAnim 是否开启动画
|
||||
*/
|
||||
protected abstract void drawUp(Canvas canvas,boolean isAnim);
|
||||
|
||||
/**
|
||||
* 画向上的曲线,通过动画的方式
|
||||
*/
|
||||
protected abstract void drawUpByAnim();
|
||||
|
||||
/**
|
||||
* 画向下的曲线
|
||||
* @param canvas
|
||||
* @param isAnim
|
||||
*/
|
||||
protected abstract void drawDown(Canvas canvas,boolean isAnim);
|
||||
|
||||
/**
|
||||
* 画向下的曲线,通过动画的方式
|
||||
*/
|
||||
protected abstract void drawDownByAnim();
|
||||
|
||||
public void handleStartAnim() {
|
||||
if (mIsRunging || mPer > 0) { // 动画首次启动
|
||||
return;
|
||||
}
|
||||
mIsAnim = true;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始图形动画
|
||||
*/
|
||||
private ValueAnimator mAnimator;
|
||||
protected void startAnim() {
|
||||
if (!mIsAnim) {
|
||||
return;
|
||||
}
|
||||
cancelAnim();
|
||||
mIsRunging = true;
|
||||
if(mAnimator == null){
|
||||
mAnimator = ValueAnimator.ofFloat(0f, 1f);
|
||||
mAnimator.setInterpolator(new LinearInterpolator());
|
||||
mAnimator.setDuration(mAnimDuration);
|
||||
}
|
||||
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
mPer = (float) animation.getAnimatedValue();
|
||||
log("动画方法回调时机->onAnimationUpdate...mPer = " + mPer);
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
|
||||
mAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
log("动画方法回调时机->onAnimationEnd...");
|
||||
log("取消动画了,onAnimationEnd...");
|
||||
postDelayed(new Runnable() { // 需要延迟一会,不然会进入动画死循环
|
||||
@Override
|
||||
public void run() {
|
||||
mIsRunging = false;
|
||||
mPer = 0f;
|
||||
}
|
||||
}, 120); // 时间越小越好,避免内存泄漏(前提是保证延时后方法内的变脸重置后,不再会自动刷新)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
super.onAnimationCancel(animation);
|
||||
log("取消动画结束回调...");
|
||||
mIsRunging = false;
|
||||
mPer = 0f;
|
||||
}
|
||||
});
|
||||
mAnimator.start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启动画前先取消动画
|
||||
*/
|
||||
public void cancelAnim(){
|
||||
if(mAnimator != null
|
||||
&& (mAnimator.isRunning() || mIsRunging)){
|
||||
mAnimator.cancel();
|
||||
log("取消动画");
|
||||
}
|
||||
}
|
||||
|
||||
protected enum Mode {
|
||||
Flat, // 平
|
||||
Up, // 升
|
||||
Down, // 降
|
||||
None // 空白,啥也不显示
|
||||
}
|
||||
|
||||
public class Point {
|
||||
private float x;
|
||||
private float y;
|
||||
|
||||
public Point(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(float x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(float y) {
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.doyou.cv.widget.base;
|
||||
|
||||
/**
|
||||
* 圆环中间样式
|
||||
* @autor hongbing
|
||||
* @date 2018/12/21
|
||||
*/
|
||||
public enum CircleCenterStyle {
|
||||
|
||||
/**
|
||||
* 图标
|
||||
*/
|
||||
Icon,
|
||||
/**
|
||||
* 单行文本
|
||||
*/
|
||||
Txt,
|
||||
/**
|
||||
* 两行文本
|
||||
*/
|
||||
Double_Txt,
|
||||
/**
|
||||
* 图标和单行文本
|
||||
*/
|
||||
All
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.doyou.cv.widget.legend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.dongni.tools.EmptyUtils;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.widget.PointView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
/**
|
||||
* 图例控件
|
||||
* @autor hongbing
|
||||
* @date 2019/3/28
|
||||
*/
|
||||
public class LegendView extends LinearLayout {
|
||||
|
||||
private Context mContext;
|
||||
// 图例的列宽
|
||||
private int mColumnWidth;
|
||||
// 图例行间距
|
||||
private int mLegendVerMargin;
|
||||
// 图例字体颜色
|
||||
private int mLabelColor;
|
||||
// 图例字体大小
|
||||
private float mLabelSize;
|
||||
// 图例文案和圆点的间距
|
||||
private int mLegendLabelAndPointMargin;
|
||||
// 图例每行第一个左侧偏移量
|
||||
private int mLegendOffsetLeft;
|
||||
// 动态计算出来的列数
|
||||
private int mColumn;
|
||||
|
||||
public LegendView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public LegendView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public LegendView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
setOrientation(LinearLayout.VERTICAL);
|
||||
setGravity(Gravity.CENTER);
|
||||
initAttr(attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
LogUtil.logD("201812201056",
|
||||
"onLayout->MeasuredWidth = " + getMeasuredWidth() + "->changed = " + changed);
|
||||
|
||||
}
|
||||
|
||||
private void initAttr(AttributeSet attrs) {
|
||||
TypedArray ta = mContext.obtainStyledAttributes(attrs,R.styleable.LegendView);
|
||||
mColumnWidth = ta.getDimensionPixelOffset(R.styleable.LegendView_lv_legend_columnW, DensityUtil.dp2px(42));
|
||||
mLegendVerMargin = ta.getDimensionPixelOffset(R.styleable.LegendView_lv_legend_vertical_margin, DensityUtil.sp2px(mContext, 8));
|
||||
mLabelColor = ta.getColor(R.styleable.LegendView_lv_legend_font_color, Color.rgb(42, 42, 42));
|
||||
mLabelSize = ta.getDimensionPixelSize(R.styleable.LegendView_lv_legend_font_size, DensityUtil.sp2px(mContext, 12));
|
||||
mLegendLabelAndPointMargin = ta.getDimensionPixelOffset(R.styleable.LegendView_lv_legend_labelAndPoint_margin, DensityUtil.dp2px(4));
|
||||
mLegendOffsetLeft = ta.getDimensionPixelOffset(R.styleable.LegendView_lv_legend_offset_left, 0);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
LogUtil.logD("20190328", "onMeasure = " + getMeasuredWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
LogUtil.logD("20190328", "onFinishInflate = " + getMeasuredWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
LogUtil.logD("20190328", "onAttachedToWindow = " + getMeasuredWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
LogUtil.logD("20190328", "onDetachedFromWindow = " + getMeasuredWidth());
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态计算列数
|
||||
*/
|
||||
private void autoCaclColumn() {
|
||||
if (getParent() instanceof ConstraintLayout) {
|
||||
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) getLayoutParams();
|
||||
int margin = params.leftMargin + params.rightMargin;
|
||||
// 计算每行显示列数
|
||||
mColumn = (getMeasuredWidth() - margin) / mColumnWidth;
|
||||
LogUtil.logD("201812201056", "动态计算出的列数 = " + mColumn +
|
||||
"->MeasuredWidth = " + getMeasuredWidth() + "->margin = " + margin + "->left = " + getPaddingLeft() + "->right = " + getPaddingRight());
|
||||
} else {
|
||||
throw new IllegalArgumentException("外层布局需要使用ConstraintLayout");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 没有图例内容
|
||||
*/
|
||||
public void setEmpty() {
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图例数据
|
||||
*
|
||||
* @param legends
|
||||
* @param colors
|
||||
*/
|
||||
public void setData(final List<String> legends, final int[] colors) {
|
||||
if (EmptyUtils.isEmpty(legends)) {
|
||||
setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
if (getVisibility() == View.GONE) {
|
||||
setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
post(() -> {
|
||||
autoCaclColumn();
|
||||
|
||||
int size = legends.size();
|
||||
// 图例行数
|
||||
int circulation = size / mColumn + (size % mColumn > 0 ? 1 : 0);
|
||||
LogUtil.logD("201812201056", "图例行数 = " + circulation + "->集合总数size = " + size
|
||||
+ "->size / mColumn = " + (size / mColumn) + "->size % mColumn = " + (size % mColumn));
|
||||
if (mColumn > size) {
|
||||
mColumn = size;
|
||||
}
|
||||
|
||||
removeAllViews();
|
||||
LinearLayout labelLayout;
|
||||
PointView pointView;
|
||||
TextView labelTv;
|
||||
LayoutParams params = new LayoutParams(
|
||||
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
||||
LayoutParams sonParams = new LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
for (int i = 0; i < circulation; i++) {
|
||||
labelLayout = new LinearLayout(mContext);
|
||||
labelLayout.setLayoutParams(params);
|
||||
// linearLayout.setBackgroundColor(Color.rgb(123, 180, 248));
|
||||
if (circulation == 1) { // 只有一行,水平居中
|
||||
labelLayout.setGravity(Gravity.CENTER);
|
||||
} else { // 左对齐
|
||||
labelLayout.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
|
||||
if (i > 0) { // 大于一行,第一行不设置行间上间距
|
||||
params.topMargin = mLegendVerMargin;
|
||||
}
|
||||
params.leftMargin = mLegendOffsetLeft;
|
||||
}
|
||||
for (int j = 0; j < mColumn; j++) { // 创建label
|
||||
final int pos = i * mColumn + j;
|
||||
|
||||
if (pos > size - 1) { // 全取干净了
|
||||
break;
|
||||
}
|
||||
|
||||
pointView = new PointView(mContext);
|
||||
pointView.setColor(colors[pos]);
|
||||
if (j > 0) {
|
||||
if (legends.get(pos - 1).length() > 2) { // 针对三个文字的间距设置
|
||||
sonParams.leftMargin = DensityUtil.dp2px(12);
|
||||
pointView.setLayoutParams(sonParams);
|
||||
}
|
||||
}
|
||||
labelLayout.addView(pointView);
|
||||
|
||||
labelTv = new TextView(mContext);
|
||||
// if (BuildConfig.DEBUG) {
|
||||
// labelTv.setBackgroundColor(Color.rgb(218, 112, 214));
|
||||
// }
|
||||
labelTv.setGravity(Gravity.CENTER_VERTICAL);
|
||||
labelTv.setPadding(mLegendLabelAndPointMargin, 0, 0, 0);
|
||||
labelTv.setWidth(mColumnWidth);
|
||||
labelTv.setText(legends.get(pos));
|
||||
labelTv.setTextColor(mLabelColor);
|
||||
labelTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLabelSize);
|
||||
labelLayout.addView(labelTv);
|
||||
labelTv.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mListener != null) {
|
||||
mListener.onLegendClick(pos);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
addView(labelLayout); // 会导致onMeasure重新测量
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private LegendListener mListener;
|
||||
public void setLegendListener(LegendListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
public interface LegendListener {
|
||||
void onLegendClick(int label);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
package com.doyou.cv.widget.progress.circle;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 自定义view实现环形带刻度颜色渐变的进度条
|
||||
*
|
||||
* @autor hongbing
|
||||
* @date 2019/2/14
|
||||
*/
|
||||
public class CircleProgress extends View {
|
||||
|
||||
private static final String TAG = "CircleProgress";
|
||||
|
||||
private Context mContext;
|
||||
// 默认大小
|
||||
private int mDefaultSize;
|
||||
// 是否开启抗锯齿
|
||||
private boolean antiAlias;
|
||||
// 绘制提示
|
||||
private TextPaint mHintPaint;
|
||||
private CharSequence mHint;
|
||||
private int mHintColor;
|
||||
private float mHintSize;
|
||||
private float mHintOffset;
|
||||
|
||||
// 绘制单位
|
||||
private TextPaint mUnitPaint;
|
||||
private CharSequence mUnit;
|
||||
private int mUnitColor;
|
||||
private float mUnitSize;
|
||||
private float mUnitOffset;
|
||||
|
||||
// 绘制数值
|
||||
private TextPaint mValuePaint;
|
||||
private float mValue;
|
||||
private float mMaxValue;
|
||||
private float mValueOffset;
|
||||
private int mPrecision;
|
||||
private String mPrecisionFormat;
|
||||
private int mValueColor;
|
||||
private float mValueSize;
|
||||
|
||||
// 绘制圆弧
|
||||
private Paint mArcPaint;
|
||||
private float mArcWidth;
|
||||
private float mStartAngle, mSweepAngle;
|
||||
private RectF mRectF;
|
||||
// 当前进度,[0.0f,1.0f]
|
||||
private float mPercent;
|
||||
// 动画时间
|
||||
private long mAnimTime;
|
||||
private ValueAnimator mAnimator;
|
||||
|
||||
// 绘制背景圆弧
|
||||
private Paint mBgArcPaint;
|
||||
private int mBgArcColor;
|
||||
private int mArcColor;
|
||||
private float mBgArcWidth;
|
||||
|
||||
// 圆心坐标,半径
|
||||
private Point mCenterPoint;
|
||||
private float mRadius;
|
||||
private float mTextOffsetPercentInRadius;
|
||||
|
||||
private int mArcCenterX;
|
||||
// 内部虚线的外部半径
|
||||
private float mExternalDottedLineRadius;
|
||||
// 内部虚线的内部半径
|
||||
private float mInsideDottedLineRadius;
|
||||
|
||||
// 线条数
|
||||
private int mDottedLineCount = 100;
|
||||
// 圆弧跟虚线之间的距离
|
||||
private int mLineDistance = 20;
|
||||
// 线条宽度
|
||||
private float mDottedLineWidth = 40;
|
||||
// 是否使用渐变
|
||||
private boolean useGradient = true;
|
||||
// 前景色起始颜色
|
||||
private int mForeStartColor;
|
||||
// 前景色结束颜色
|
||||
private int mForeEndColor;
|
||||
|
||||
|
||||
public CircleProgress(Context context) {
|
||||
super(context, null);
|
||||
}
|
||||
|
||||
public CircleProgress(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CircleProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
mContext = context;
|
||||
mDefaultSize = DensityUtil.dp2px(150);
|
||||
mAnimator = new ValueAnimator();
|
||||
mRectF = new RectF();
|
||||
mCenterPoint = new Point();
|
||||
initAttrs(attrs);
|
||||
initPaint();
|
||||
setValue(mValue);
|
||||
}
|
||||
|
||||
private void initAttrs(AttributeSet attrs) {
|
||||
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleProgress);
|
||||
antiAlias = typedArray.getBoolean(R.styleable.CircleProgress_antiAlias, true);
|
||||
mHint = typedArray.getString(R.styleable.CircleProgress_hint);
|
||||
mHintColor = typedArray.getColor(R.styleable.CircleProgress_hintColor, Color.BLACK);
|
||||
mHintSize = typedArray.getDimension(R.styleable.CircleProgress_hintSize, 15);
|
||||
mValue = typedArray.getFloat(R.styleable.CircleProgress_value, 50);
|
||||
mMaxValue = typedArray.getFloat(R.styleable.CircleProgress_maxValue, 50);
|
||||
// 内容数值精度格式
|
||||
mPrecision = typedArray.getInt(R.styleable.CircleProgress_precision, 0);
|
||||
mPrecisionFormat = getPrecisionFormat(mPrecision);
|
||||
mValueColor = typedArray.getColor(R.styleable.CircleProgress_valueColor, Color.BLACK);
|
||||
mValueSize = typedArray.getDimension(R.styleable.CircleProgress_valueSize, 15);
|
||||
mUnit = typedArray.getString(R.styleable.CircleProgress_unit);
|
||||
mUnitColor = typedArray.getColor(R.styleable.CircleProgress_unitColor, Color.BLACK);
|
||||
mUnitSize = typedArray.getDimension(R.styleable.CircleProgress_unitSize, 30);
|
||||
mArcWidth = typedArray.getDimension(R.styleable.CircleProgress_arcWidth, 15);
|
||||
mStartAngle = typedArray.getFloat(R.styleable.CircleProgress_startAngle, 270);
|
||||
mSweepAngle = typedArray.getFloat(R.styleable.CircleProgress_sweepAngle, 360);
|
||||
|
||||
mBgArcColor = typedArray.getColor(R.styleable.CircleProgress_bgArcColor, Color.WHITE);
|
||||
mArcColor = typedArray.getColor(R.styleable.CircleProgress_arcColors, Color.RED);
|
||||
mBgArcWidth = typedArray.getDimension(R.styleable.CircleProgress_bgArcWidth, 15);
|
||||
mTextOffsetPercentInRadius = typedArray.getFloat(R.styleable.CircleProgress_textOffsetPercentInRadius, 0.33f);
|
||||
mAnimTime = typedArray.getInt(R.styleable.CircleProgress_animTime, 1000);
|
||||
mDottedLineCount = typedArray.getInteger(R.styleable.CircleProgress_dottedLineCount, mDottedLineCount);
|
||||
mLineDistance = typedArray.getInteger(R.styleable.CircleProgress_lineDistance, mLineDistance);
|
||||
mDottedLineWidth = typedArray.getDimension(R.styleable.CircleProgress_dottedLineWidth, mDottedLineWidth);
|
||||
mForeStartColor = typedArray.getColor(R.styleable.CircleProgress_foreStartColor, Color.BLUE);
|
||||
mForeEndColor = typedArray.getColor(R.styleable.CircleProgress_foreEndColor, Color.BLUE);
|
||||
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
private void initPaint() {
|
||||
mHintPaint = new TextPaint();
|
||||
// 设置抗锯齿,会消耗较大资源,绘制图形速度会变慢
|
||||
mHintPaint.setAntiAlias(antiAlias);
|
||||
// 设置绘制文字大小
|
||||
mHintPaint.setTextSize(mHintSize);
|
||||
// 设置画笔颜色
|
||||
mHintPaint.setColor(mHintColor);
|
||||
// 从中间向两边绘制,不需要再次计算文字
|
||||
mHintPaint.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
mValuePaint = new TextPaint();
|
||||
mValuePaint.setAntiAlias(antiAlias);
|
||||
mValuePaint.setTextSize(mValueSize);
|
||||
mValuePaint.setColor(mValueColor);
|
||||
// 设置TypeFace对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等
|
||||
mValuePaint.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
mValuePaint.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
mUnitPaint = new TextPaint();
|
||||
mUnitPaint.setAntiAlias(antiAlias);
|
||||
mUnitPaint.setTextSize(mUnitSize);
|
||||
mUnitPaint.setColor(mUnitColor);
|
||||
mUnitPaint.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
mArcPaint = new Paint();
|
||||
mArcPaint.setAntiAlias(antiAlias);
|
||||
// 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE
|
||||
mArcPaint.setStyle(Paint.Style.STROKE);
|
||||
// 设置画笔粗细
|
||||
mArcPaint.setStrokeWidth(mArcWidth);
|
||||
// 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式 Cap.ROUND(圆形样式)或Cap.SQUARE(方形样式)
|
||||
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
mBgArcPaint = new Paint();
|
||||
mBgArcPaint.setAntiAlias(antiAlias);
|
||||
mBgArcPaint.setColor(mBgArcColor);
|
||||
mBgArcPaint.setStyle(Paint.Style.STROKE);
|
||||
mBgArcPaint.setStrokeWidth(mBgArcWidth);
|
||||
mBgArcPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(measureView(widthMeasureSpec, mDefaultSize),
|
||||
measureView(heightMeasureSpec, mDefaultSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测量view
|
||||
*
|
||||
* @param measureSpec
|
||||
* @param defaultSize view的默认大小
|
||||
* @return
|
||||
*/
|
||||
private static int measureView(int measureSpec, int defaultSize) {
|
||||
int result = defaultSize;
|
||||
int specMode = View.MeasureSpec.getMode(measureSpec);
|
||||
int specSize = View.MeasureSpec.getSize(measureSpec);
|
||||
|
||||
if (specMode == MeasureSpec.EXACTLY) {
|
||||
result = specSize;
|
||||
} else if (specMode == MeasureSpec.AT_MOST) {
|
||||
result = Math.min(result, specSize);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float getBaselineOffsetFromY(Paint paint) {
|
||||
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
|
||||
return ((Math.abs(fontMetrics.ascent) - fontMetrics.descent)) / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
mArcCenterX = (int) (w / 2.f);
|
||||
LogUtil.logD(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldW = " + oldW + "; oldH = " + oldH);
|
||||
// 求圆弧和背景圆弧的最大宽度
|
||||
float maxArcWidth = Math.max(mArcWidth, mBgArcWidth);
|
||||
// 求最小值作为实际值
|
||||
int minSize = Math.min(w - getPaddingLeft() - getPaddingRight() - 2 * (int) maxArcWidth,
|
||||
h - getPaddingTop() - getPaddingBottom() - 2 * (int) maxArcWidth);
|
||||
// 减去圆弧的宽度,否则会造成部分圆弧绘制在外围
|
||||
mRadius = minSize / 2;
|
||||
// 获取圆的相关参数
|
||||
mCenterPoint.x = w / 2;
|
||||
mCenterPoint.y = h / 2;
|
||||
// 绘制圆弧的边界
|
||||
mRectF.left = mCenterPoint.x - mRadius - maxArcWidth / 2;
|
||||
mRectF.top = mCenterPoint.y - mRadius - maxArcWidth / 2;
|
||||
mRectF.right = mCenterPoint.x + mRadius + maxArcWidth / 2;
|
||||
mRectF.bottom = mCenterPoint.y + mRadius + maxArcWidth / 2;
|
||||
// 计算文字绘制时的 baseline,
|
||||
// 由于文字的baseline、descent、ascent等属性只与textSize和typeface有关,所以此时可以直接计算,
|
||||
// 若value、hint、unit由同一个画笔绘制或者需要动态设置文字的大小,则需要在每次更新后再次计算
|
||||
mValueOffset = mCenterPoint.y + getBaselineOffsetFromY(mValuePaint);
|
||||
mHintOffset = mCenterPoint.y - mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mHintPaint);
|
||||
mUnitOffset = mCenterPoint.y + mRadius * mTextOffsetPercentInRadius + getBaselineOffsetFromY(mUnitPaint);
|
||||
|
||||
if (useGradient) {
|
||||
LinearGradient gradient = new LinearGradient(0, 0, w, h, mForeEndColor, mForeStartColor, Shader.TileMode.CLAMP);
|
||||
mArcPaint.setShader(gradient);
|
||||
} else {
|
||||
mArcPaint.setShader(null);
|
||||
mArcPaint.setColor(mArcColor);
|
||||
}
|
||||
|
||||
LogUtil.logD(TAG, "onSizeChanged: 控件大小 = " + "(" + w + ", " + h + ")"
|
||||
+ "圆心坐标 = " + mCenterPoint.toString()
|
||||
+ ";圆半径 = " + mRadius
|
||||
+ ";圆的外接矩形 = " + mRectF.toString());
|
||||
|
||||
// 虚线的外部半径
|
||||
mExternalDottedLineRadius = (int) (mRectF.width() / 2) + mLineDistance;
|
||||
// 虚线的内部半径
|
||||
mInsideDottedLineRadius = mExternalDottedLineRadius - mDottedLineWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
drawArc(canvas);
|
||||
drawText(canvas);
|
||||
}
|
||||
|
||||
private void drawArc(Canvas canvas) {
|
||||
// 从进度圆弧结束的地方开始重新绘制,优化性能
|
||||
canvas.save();
|
||||
|
||||
// 绘制背景圆弧
|
||||
// 360 * Math.PI / 180
|
||||
// 虚线分割点角度(2个度数一条线)
|
||||
float everyDegrees = (float) (2.0f * Math.PI / mDottedLineCount);
|
||||
// 起始角度在圆环上的坐标点
|
||||
float startDegrees = (float) (135 * Math.PI / 180);
|
||||
// 结束角度在圆环上的坐标点
|
||||
float endDegrees = (float) (225 * Math.PI / 180);
|
||||
for (int i = 0; i < mDottedLineCount; i++) {
|
||||
float degrees = i * everyDegrees;
|
||||
// 过滤底部90度的弧长
|
||||
if (degrees > startDegrees && degrees < endDegrees) {
|
||||
continue;
|
||||
}
|
||||
float startX = mArcCenterX + (float) Math.sin(degrees) * mInsideDottedLineRadius;
|
||||
float startY = mArcCenterX - (float) Math.cos(degrees) * mInsideDottedLineRadius;
|
||||
|
||||
float stopX = mArcCenterX + (float) Math.sin(degrees) * mExternalDottedLineRadius;
|
||||
float stopY = mArcCenterX - (float) Math.cos(degrees) * mExternalDottedLineRadius;
|
||||
|
||||
canvas.drawLine(startX, startY, stopX, stopY, mBgArcPaint);
|
||||
}
|
||||
|
||||
canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y);
|
||||
|
||||
// 第一个参数 oval 为 RectF 类型,即圆弧显示区域
|
||||
// startAngle 和 sweepAngle 均为 float 类型,分别表示圆弧起始角度和圆弧度数
|
||||
// 3点钟方向为0度,顺时针递增
|
||||
// 如果 startAngle < 0 或者 > 360,则相当于 startAngle % 360
|
||||
// useCenter:如果为True时,在绘制圆弧时将圆心包括在内,通常用来绘制扇形
|
||||
float currentAngle = mSweepAngle * mPercent;
|
||||
canvas.drawArc(mRectF, 2, currentAngle, false, mArcPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制内容文字
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawText(Canvas canvas) {
|
||||
canvas.drawText(String.format(mPrecisionFormat, mValue), mCenterPoint.x, mValueOffset, mValuePaint);
|
||||
|
||||
if (mHint != null) {
|
||||
canvas.drawText(mHint.toString(), mCenterPoint.x, mHintOffset, mHintPaint);
|
||||
}
|
||||
|
||||
if (mUnit != null) {
|
||||
canvas.drawText(mUnit.toString(), mCenterPoint.x, mUnitOffset, mUnitPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
// 释放资源
|
||||
mAnimator.cancel();
|
||||
mAnimator = null;
|
||||
}
|
||||
|
||||
public float getMaxValue() {
|
||||
return mMaxValue;
|
||||
}
|
||||
|
||||
public void setMaxValue(float maxValue) {
|
||||
mMaxValue = maxValue;
|
||||
}
|
||||
|
||||
public int getPrecision() {
|
||||
return mPrecision;
|
||||
}
|
||||
|
||||
public void setPrecision(int precision) {
|
||||
mPrecision = precision;
|
||||
mPrecisionFormat = getPrecisionFormat(precision);
|
||||
}
|
||||
|
||||
public long getAnimTime() {
|
||||
return mAnimTime;
|
||||
}
|
||||
|
||||
public void setAnimTime(long animTime) {
|
||||
mAnimTime = animTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
public void reset() {
|
||||
startAnimator(mPercent, 0.0f, 1000L);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前值
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
public void setValue(float value) {
|
||||
if (value > mMaxValue) {
|
||||
value = mMaxValue;
|
||||
}
|
||||
float start = mPercent;
|
||||
float end = value / mMaxValue;
|
||||
startAnimator(start, end, mAnimTime);
|
||||
}
|
||||
|
||||
private void startAnimator(float start, float end, long animTime) {
|
||||
mAnimator = ValueAnimator.ofFloat(start, end);
|
||||
mAnimator.setDuration(animTime);
|
||||
mAnimator.addUpdateListener(animation -> {
|
||||
mPercent = (float) animation.getAnimatedValue();
|
||||
mValue = mPercent * mMaxValue;
|
||||
LogUtil.logD(TAG, "onAnimationUpdate: percent = " + mPercent
|
||||
+ ";currentAngle = " + (mSweepAngle * mPercent)
|
||||
+ ";value = " + mValue);
|
||||
invalidate();
|
||||
});
|
||||
mAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数值精度格式化字符串
|
||||
* @param precision
|
||||
* @return
|
||||
*/
|
||||
private String getPrecisionFormat(int precision) {
|
||||
return "%." + precision + "f";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
package com.doyou.cv.widget.progress.circle;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.utils.FormatUtil;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/**
|
||||
* Created by Allen on 2017/5/14.
|
||||
* 自定义圆环进度条
|
||||
* 1.支持外环宽度大于内环
|
||||
* 2.支持中间文本显示
|
||||
* 3.支持圆环进度动画
|
||||
* 4.支持圆环底部小三角
|
||||
*/
|
||||
public class CircleProgressBarView extends View {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
/**
|
||||
* 默认进度圆环渐变色值集合
|
||||
*/
|
||||
private int[] mDefColors = {Color.rgb(101, 226, 175), Color.rgb(88, 181, 250)};
|
||||
/**
|
||||
* 100%进度圆环渐变色值集合
|
||||
*/
|
||||
private int[] mEndColors = {Color.rgb(255, 196, 0), Color.rgb(255, 110, 77)};
|
||||
private int mStartColor = Color.rgb(101, 226, 175);
|
||||
private int mEndColor = Color.rgb(88, 181, 250);
|
||||
|
||||
// 圆心x坐标
|
||||
private float centerX,centerY;
|
||||
// 圆的半径
|
||||
private float radius;
|
||||
// 进度
|
||||
private float mProgress;
|
||||
// 当前进度
|
||||
private float currentProgress;
|
||||
// 圆形进度条底色画笔
|
||||
private Paint circleBgPaint;
|
||||
// 圆形进度条进度画笔
|
||||
private Paint progressPaint;
|
||||
// 进度条背景颜色
|
||||
private int circleBgColor = Color.rgb(225, 229, 232);
|
||||
// 进度条颜色
|
||||
private int progressColor = Color.RED;
|
||||
// 默认圆环的宽度
|
||||
private int defaultStrokeWidth = 10;
|
||||
// 圆形背景画笔宽度
|
||||
private int circleBgStrokeWidth = defaultStrokeWidth;
|
||||
// 圆形进度画笔宽度
|
||||
private int progressStrokeWidth = defaultStrokeWidth;
|
||||
private int lineWidth;
|
||||
private boolean isDrawCenterProgressText;
|
||||
private int centerProgressTextSize = 23;
|
||||
private int centerProgressTextColor = Color.BLACK;
|
||||
private int targetTextSize = 10;
|
||||
private int targetTextColor = Color.GRAY;
|
||||
private int targetNumSize = 20;
|
||||
private int targetNumColor = Color.BLACK;
|
||||
private int lineColor = Color.GRAY;
|
||||
|
||||
// 各种画笔
|
||||
private Paint centerProgressTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Paint trainPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Paint targetTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Paint targetNumPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
|
||||
// 扇形所在矩形
|
||||
private RectF rectF = new RectF();
|
||||
// 进度动画
|
||||
private ValueAnimator progressAnimator;
|
||||
// 动画执行时间
|
||||
private int duration = 1000;
|
||||
// 动画延时启动时间
|
||||
private int startDelay = 500;
|
||||
|
||||
private ProgressListener progressListener;
|
||||
private String targetText;
|
||||
private String targetNum = "0";
|
||||
private int dp1,dp2,dp4,dp5,dp6,dp7,dp8,dp30;
|
||||
|
||||
public CircleProgressBarView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CircleProgressBarView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
dp1 = DensityUtil.dp2px(1);
|
||||
dp2 = DensityUtil.dp2px(2);
|
||||
dp4 = DensityUtil.dp2px(4);
|
||||
dp5 = DensityUtil.dp2px(5);
|
||||
dp6 = DensityUtil.dp2px(6);
|
||||
dp7 = DensityUtil.dp2px(7);
|
||||
dp8 = DensityUtil.dp2px(8);
|
||||
dp30 = DensityUtil.dp2px(30);
|
||||
getAttr(attrs);
|
||||
initPaint();
|
||||
}
|
||||
|
||||
private void getAttr(AttributeSet attrs) {
|
||||
TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.CircleProgressBarView);
|
||||
|
||||
circleBgStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBarView_circleBgStrokeWidth, defaultStrokeWidth);
|
||||
progressStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBarView_progressStrokeWidth, defaultStrokeWidth);
|
||||
|
||||
circleBgColor = typedArray.getColor(R.styleable.CircleProgressBarView_circleBgColor, circleBgColor);
|
||||
progressColor = typedArray.getColor(R.styleable.CircleProgressBarView_progressColor, progressColor);
|
||||
|
||||
duration = typedArray.getColor(R.styleable.CircleProgressBarView_circleAnimationDuration, duration);
|
||||
|
||||
isDrawCenterProgressText = typedArray.getBoolean(R.styleable.CircleProgressBarView_isDrawCenterProgressText, false);
|
||||
|
||||
centerProgressTextColor = typedArray.getColor(R.styleable.CircleProgressBarView_centerProgressTextColor, mEndColor);
|
||||
centerProgressTextSize = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBarView_centerProgressTextSize, DensityUtil.sp2px(mContext,centerProgressTextSize));
|
||||
lineWidth = typedArray.getDimensionPixelOffset(R.styleable.CircleProgressBarView_lineWidth, DensityUtil.dp2px(1));
|
||||
lineColor = typedArray.getColor(R.styleable.CircleProgressBarView_lineColor, circleBgColor);
|
||||
targetNumColor = typedArray.getColor(R.styleable.CircleProgressBarView_targetNumColor, targetNumColor);
|
||||
targetNumSize = typedArray.getDimensionPixelSize(R.styleable.CircleProgressBarView_targetNumSize, DensityUtil.sp2px(mContext,20));
|
||||
targetTextSize = typedArray.getDimensionPixelSize(R.styleable.CircleProgressBarView_targetTextSize, DensityUtil.sp2px(mContext,12));
|
||||
targetTextColor = typedArray.getColor(R.styleable.CircleProgressBarView_targetTextColor, targetTextColor);
|
||||
targetText = typedArray.getString(R.styleable.CircleProgressBarView_target_text);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
public void setCenterProgressTextColor(int color) {
|
||||
this.centerProgressTextColor =color;
|
||||
centerProgressTextPaint.setColor(color);
|
||||
}
|
||||
|
||||
private void initPaint() {
|
||||
circleBgPaint = getPaint(circleBgStrokeWidth, circleBgColor);
|
||||
|
||||
progressPaint = getPaint(progressStrokeWidth, progressColor);
|
||||
trainPaint = getPaint(DensityUtil.dp2px(1), progressColor);
|
||||
trainPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
linePaint = getPaint(lineWidth, lineColor);
|
||||
|
||||
// 目标文本画笔配置
|
||||
targetTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
targetTextPaint.setTextSize(targetTextSize);
|
||||
targetTextPaint.setColor(targetTextColor);
|
||||
targetTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
// 目标数字画笔配置
|
||||
targetNumPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
targetNumPaint.setTextSize(targetNumSize);
|
||||
targetNumPaint.setColor(targetNumColor);
|
||||
targetNumPaint.setTextAlign(Paint.Align.CENTER);
|
||||
|
||||
// 中间文本画笔配置
|
||||
centerProgressTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
centerProgressTextPaint.setTextSize(centerProgressTextSize);
|
||||
centerProgressTextPaint.setColor(centerProgressTextColor);
|
||||
centerProgressTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||
}
|
||||
|
||||
private Paint getPaint(int strokeWidth, int color) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStrokeWidth(strokeWidth);
|
||||
paint.setColor(color);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
return paint;
|
||||
}
|
||||
|
||||
private LinearGradient mShader;
|
||||
|
||||
private void configShader() {
|
||||
Log.d("201801251336", "progress = " + mProgress);
|
||||
// int colors[] = {
|
||||
// Color.rgb(97, 212, 198), Color.rgb(100, 225, 176),
|
||||
// Color.rgb(93, 198, 221), Color.rgb(90, 187, 239)
|
||||
// };
|
||||
// float positions[] = {
|
||||
// 0.2f, 0.3f, 0.3f, 0.2f
|
||||
// };
|
||||
// 清空画笔
|
||||
progressPaint.setShader(null);
|
||||
if (mProgress >= 100) {
|
||||
mShader = new LinearGradient(centerX, 0, centerX, getHeight(),
|
||||
mEndColors, null, Shader.TileMode.CLAMP);
|
||||
} else {
|
||||
mShader = new LinearGradient(centerX, 0, centerX, getHeight(),
|
||||
mDefColors, null, Shader.TileMode.CLAMP);
|
||||
}
|
||||
|
||||
// 圆内纵向直径为着色路径,圆环的左半边和右半边的色值有点对称的感觉
|
||||
// mShader = new LinearGradient(centerX, centerY - radius, centerX, centerY + radius,
|
||||
// mColors,
|
||||
// null, Shader.TileMode.CLAMP);
|
||||
// 圆内横向直径为着色路径,可以达到首尾渐变效果,但进度不超过50%,看不出渐变后的效果
|
||||
// mShader = new LinearGradient(centerX + radius, centerY, centerX - radius, centerY,
|
||||
// mColors,
|
||||
// null, Shader.TileMode.CLAMP);
|
||||
|
||||
// mShader = new SweepGradient(centerX,centerY,mColors,new float[]{0.5f,0.5f});
|
||||
// mShader = new SweepGradient(centerX,centerY,colors,null);
|
||||
// mShader = new SweepGradient(centerX,centerY,mEndColor,mStartColor);
|
||||
// mShader = new RadialGradient(centerX,centerY,radius,mColors,null, Shader.TileMode.CLAMP);
|
||||
|
||||
progressPaint.setShader(mShader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (progressAnimator != null) {
|
||||
progressAnimator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean proAnimIsEnd = false;
|
||||
|
||||
private void initAnimation() {
|
||||
proAnimIsEnd = false;
|
||||
progressAnimator = ValueAnimator.ofFloat(0, mProgress);
|
||||
progressAnimator.setDuration(duration);
|
||||
progressAnimator.setStartDelay(startDelay);
|
||||
progressAnimator.setInterpolator(new LinearInterpolator());
|
||||
progressAnimator.addUpdateListener(valueAnimator -> {
|
||||
float value = (float) valueAnimator.getAnimatedValue();
|
||||
mProgress = value;
|
||||
currentProgress = value * 360 / 100;
|
||||
if (progressListener != null) {
|
||||
progressListener.currentProgressListener(FormatUtil.roundTwo(value));
|
||||
}
|
||||
invalidate();
|
||||
});
|
||||
progressAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
proAnimIsEnd = true;
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setTargetNum(String num) {
|
||||
targetNum = num;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
centerX = w / 2;
|
||||
centerY = h / 2;
|
||||
|
||||
// 半径再缩小点,给小三角形空出距离
|
||||
radius = Math.min(w, h) / 2 - Math.max(circleBgStrokeWidth, progressStrokeWidth) - dp4;
|
||||
|
||||
rectF.set(centerX - radius,
|
||||
centerY - radius,
|
||||
centerX + radius,
|
||||
centerY + radius);
|
||||
trainPaint.setColor(circleBgColor);
|
||||
|
||||
configShader();
|
||||
}
|
||||
|
||||
public void setColors(int[] colors) {
|
||||
mDefColors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
LogUtil.logD("201801251106", "centerX = " + centerX + "->centerY = "
|
||||
+ centerY + "->mProgress = " + mProgress);
|
||||
canvas.drawCircle(centerX, centerY, radius, circleBgPaint);
|
||||
canvas.drawArc(rectF, -90, currentProgress, false, progressPaint);
|
||||
if (isDrawCenterProgressText) {
|
||||
drawCenterProgressText(canvas, formatNum(mProgress) + "%");
|
||||
}
|
||||
if (proAnimIsEnd) {
|
||||
drawTriangle(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCenterProgressText(Canvas canvas, String currentProgress) {
|
||||
Paint.FontMetricsInt fontMetrics1 = centerProgressTextPaint.getFontMetricsInt();
|
||||
int baseline = (int) ((rectF.bottom + rectF.top - fontMetrics1.bottom - fontMetrics1.top) / 2);
|
||||
//文字绘制到整个布局的中心位置
|
||||
canvas.drawLine(centerX - dp30, centerY, centerX + dp30, centerY, linePaint);
|
||||
canvas.drawText(currentProgress, rectF.centerX(), centerY - dp5, centerProgressTextPaint);
|
||||
|
||||
Paint.FontMetricsInt fontMetrics2 = targetNumPaint.getFontMetricsInt();
|
||||
int baseline2 = baseline + (fontMetrics2.bottom - fontMetrics2.top);
|
||||
canvas.drawText(targetText, rectF.centerX(), baseline + dp7, targetTextPaint);
|
||||
|
||||
canvas.drawText(targetNum, rectF.centerX(), baseline2 + dp5, targetNumPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制三角形
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawTriangle(Canvas canvas) {
|
||||
Path path = new Path();
|
||||
PointF pf1, pf2, pf3;
|
||||
LogUtil.logD("20190124", "绘制小三角 mProgress = " + mProgress);
|
||||
if (mProgress < 49) {
|
||||
trainPaint.setColor(circleBgColor);
|
||||
pf1 = new PointF(centerX - dp8, centerY * 2 - progressStrokeWidth - dp2);
|
||||
pf2 = new PointF(centerX - dp1, getHeight() - dp2);
|
||||
pf3 = new PointF(centerX + dp6, centerY * 2 - progressStrokeWidth - dp2);
|
||||
} else if (mProgress >= 49 && mProgress < 51) {
|
||||
float[] positions;
|
||||
int pro = (int) (mProgress * 100);
|
||||
if (mProgress >= 49 && pro < 49.2 * 100) {
|
||||
positions = new float[]{0.39f, 0.39f};
|
||||
LogUtil.logD("20190124", "阶段1 -> pro = " + pro);
|
||||
} else if (pro >= 49.2 * 100 && pro < 49.4 * 100) {
|
||||
positions = new float[]{0.46f, 0.46f};
|
||||
LogUtil.logD("20190124", "阶段2");
|
||||
} else if (pro >= 49.4 * 100 && pro < 49.6 * 100) {
|
||||
positions = new float[]{0.5f, 0.5f};
|
||||
LogUtil.logD("20190124", "阶段3 -> pro = " + pro);
|
||||
} else if (pro >= 49.6 * 100 && pro < 49.8 * 100) {
|
||||
positions = new float[]{0.62f, 0.38f};
|
||||
LogUtil.logD("20190124", "阶段4 -> pro = " + pro);
|
||||
} else if (pro >= 49.8 * 100 && pro < 50 * 100) {
|
||||
positions = new float[]{0.64f, 0.36f};
|
||||
LogUtil.logD("20190124", "阶段5 -> pro = " + pro);
|
||||
} else if (pro >= 50 * 100 && pro < 50.2 * 100) {
|
||||
positions = new float[]{0.7f, 0.3f};
|
||||
LogUtil.logD("20190124", "阶段6 -> pro = " + pro);
|
||||
} else if (pro >= 50.2 * 100 && pro < 50.4 * 100) {
|
||||
positions = new float[]{0.75f, 0.25f};
|
||||
LogUtil.logD("20190124", "阶段7 -> pro = " + pro);
|
||||
} else if (pro >= 50.4 * 100 && pro < 50.6 * 100) {
|
||||
positions = new float[]{0.8f, 0.2f};
|
||||
LogUtil.logD("20190124", "阶段8 -> pro = " + pro);
|
||||
} else if (pro >= 50.6 * 100 && pro <= 50.8 * 100) {
|
||||
positions = new float[]{0.9f, 0.1f};
|
||||
LogUtil.logD("20190124", "阶段9 -> pro = " + pro);
|
||||
} else {
|
||||
positions = new float[]{1.0f, 0.f};
|
||||
LogUtil.logD("20190124", "阶段 else");
|
||||
}
|
||||
LinearGradient triangleGdt = new LinearGradient(
|
||||
centerX + dp6,
|
||||
0,
|
||||
centerX - dp8,
|
||||
0,
|
||||
new int[]{Color.rgb(88, 181, 250), Color.rgb(225, 229, 232)},
|
||||
positions, Shader.TileMode.CLAMP);
|
||||
trainPaint.setShader(triangleGdt);
|
||||
pf1 = new PointF(centerX - dp8, centerY * 2 - progressStrokeWidth - dp2);
|
||||
pf2 = new PointF(centerX - dp1, getHeight() - dp2);
|
||||
pf3 = new PointF(centerX + dp6, centerY * 2 - progressStrokeWidth - dp2);
|
||||
} else {
|
||||
trainPaint.setShader(null); // 不要忘记清空着色器,不能设置的color没有效果,会优先显示shader
|
||||
if (mProgress >= 100) {
|
||||
trainPaint.setColor(mEndColors[mEndColors.length - 1]);
|
||||
} else {
|
||||
trainPaint.setColor(mDefColors[mDefColors.length - 1]);
|
||||
}
|
||||
pf1 = new PointF(centerX - dp8, centerY * 2 - progressStrokeWidth - dp2);
|
||||
pf2 = new PointF(centerX - dp1, getHeight());
|
||||
pf3 = new PointF(centerX + dp6, centerY * 2 - progressStrokeWidth - dp2);
|
||||
}
|
||||
path.moveTo(pf1.x, pf1.y);
|
||||
path.lineTo(pf2.x, pf2.y);
|
||||
path.lineTo(pf3.x, pf3.y);
|
||||
canvas.drawPath(path, trainPaint);
|
||||
path.reset();
|
||||
}
|
||||
|
||||
|
||||
public void startProgressAnimation() {
|
||||
progressAnimator.start();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public void pauseProgressAnimation() {
|
||||
progressAnimator.pause();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public void resumeProgressAnimation() {
|
||||
progressAnimator.resume();
|
||||
}
|
||||
|
||||
public void stopProgressAnimation() {
|
||||
progressAnimator.end();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 传入一个进度值,从0到progress动画变化
|
||||
*
|
||||
* @param progress
|
||||
* @return
|
||||
*/
|
||||
public CircleProgressBarView setProgressWithAnimation(float progress) {
|
||||
// if (progress <= 0f || String.valueOf(progress).equals(String.valueOf(Float.NaN))) {
|
||||
if (progress <= 0f || Float.isNaN(progress)) {
|
||||
setCurrentProgress(0f, true);
|
||||
return this;
|
||||
}
|
||||
mProgress = progress;
|
||||
// if (progress >= 100) {
|
||||
// configShader();
|
||||
// }else{
|
||||
configShader();
|
||||
// }
|
||||
initAnimation();
|
||||
startProgressAnimation();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时进度,适用于下载进度回调时候之类的场景
|
||||
*
|
||||
* @param progress
|
||||
* @return
|
||||
*/
|
||||
public CircleProgressBarView setCurrentProgress(float progress) {
|
||||
mProgress = progress;
|
||||
currentProgress = progress * 360 / 100;
|
||||
if (progress >= 100) {
|
||||
configShader();
|
||||
}
|
||||
invalidate();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时进度,适用于下载进度回调时候之类的场景
|
||||
*
|
||||
* @param progress
|
||||
* @param isArrow 是否有小箭头
|
||||
* @return
|
||||
*/
|
||||
public CircleProgressBarView setCurrentProgress(float progress, boolean isArrow) {
|
||||
proAnimIsEnd = isArrow;
|
||||
setCurrentProgress(progress);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(保留两位小数)
|
||||
*
|
||||
* @param money
|
||||
* @return
|
||||
*/
|
||||
public static String formatNum(float money) {
|
||||
if (((int) money * 100) == (int) (money * 100)) {
|
||||
//如果是一个整数
|
||||
return String.valueOf((int) money);
|
||||
}
|
||||
DecimalFormat format = new DecimalFormat("0.00");
|
||||
return format.format(money);
|
||||
}
|
||||
|
||||
public interface ProgressListener {
|
||||
void currentProgressListener(float currentProgress);
|
||||
}
|
||||
|
||||
public CircleProgressBarView setProgressListener(ProgressListener listener) {
|
||||
progressListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
// public int getColor(float radio) {
|
||||
// int redStart = Color.red(mStartColor);
|
||||
// int blueStart = Color.blue(mStartColor);
|
||||
// int greenStart = Color.green(mStartColor);
|
||||
// int redEnd = Color.red(mEndColor);
|
||||
// int blueEnd = Color.blue(mEndColor);
|
||||
// int greenEnd = Color.green(mEndColor);
|
||||
//
|
||||
// int red = (int) (redStart + ((redEnd - redStart) + 0.5));
|
||||
// int greed = (int) (greenStart + ((greenEnd - greenStart) + 0.5));
|
||||
// int blue = (int) (blueStart + ((blueEnd - blueStart) + 0.5));
|
||||
// return Color.argb(255, red, greed, blue);
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.doyou.cv.widget.progress.circle;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.dongni.tools.EmptyUtils;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.bean.CircleBean;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
import com.doyou.cv.widget.base.CircleCenterStyle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义分段圆环(内含图例)
|
||||
* @author hongbing
|
||||
* @since 20181219
|
||||
*/
|
||||
public class CircleView extends ProgressBar {
|
||||
private static final String TAG = CircleView.class.getSimpleName();
|
||||
public final static int[] COLORS = {
|
||||
Color.rgb(88, 181, 250), // 蓝色
|
||||
Color.rgb(255, 196, 0), // 黄色
|
||||
Color.rgb(101, 226, 175),// 绿色
|
||||
Color.rgb(255, 105, 83), //红色
|
||||
Color.rgb(202, 129, 222), // 紫色
|
||||
Color.rgb(153, 153, 153), // 灰色
|
||||
Color.rgb(233, 255, 83) // 浅黄色
|
||||
};
|
||||
|
||||
/**
|
||||
* 数据为空显示的文案
|
||||
*/
|
||||
private static final String NOT_DATA = "暂无统计数据";
|
||||
// 圆环颜色集合
|
||||
private int[] circle_colors = COLORS;
|
||||
|
||||
/**
|
||||
* 自定义属性
|
||||
*/
|
||||
private CircleCenterStyle mCenterStyle; // 图形中间样式
|
||||
private int mRadius;
|
||||
private Bitmap mCenterBitmap;
|
||||
private String mCenterStr;
|
||||
private float mCenterTxtSize;
|
||||
private int mCenterTxtColor;
|
||||
private float mCenterTxtMargin;
|
||||
|
||||
private Paint mPaint; // 圆环画笔
|
||||
private Paint mTxtPaint; // 文本画笔
|
||||
private int mBorderW; // 环的厚度
|
||||
private RectF mArcRectF;
|
||||
|
||||
public CircleView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CircleView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.progressBarStyle);
|
||||
}
|
||||
|
||||
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initCustomAttrs(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (mCenterBitmap != null && !mCenterBitmap.isRecycled()) {
|
||||
mCenterBitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void initCustomAttrs(Context context, AttributeSet attrs) {
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
|
||||
int ordinal = typedArray.getInt(R.styleable.CircleView_cv_center_style, CircleCenterStyle.Icon.ordinal());
|
||||
mCenterStyle = CircleCenterStyle.values()[ordinal];
|
||||
|
||||
mCenterBitmap = BitmapFactory.decodeResource(getResources(),
|
||||
typedArray.getResourceId(R.styleable.CircleView_cv_center_bmp, R.drawable.widget_icon_person));
|
||||
mCenterStr = typedArray.getString(R.styleable.CircleView_cv_center_txt);
|
||||
mCenterTxtSize = typedArray.getDimensionPixelSize(R.styleable.CircleView_cv_center_txt_size, DensityUtil.sp2px(context, 12));
|
||||
mCenterTxtColor = typedArray.getColor(R.styleable.CircleView_cv_center_txt_color, Color.rgb(42, 42, 42));
|
||||
mCenterTxtMargin = typedArray.getDimensionPixelOffset(R.styleable.CircleView_cv_cengter_txt_margin,DensityUtil.dp2px(4));
|
||||
mBorderW = typedArray.getDimensionPixelOffset(R.styleable.CircleView_cv_boderW, 6);
|
||||
mRadius = typedArray.getDimensionPixelOffset(R.styleable.CircleView_cv_radius, 16);
|
||||
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeWidth(mBorderW);
|
||||
|
||||
mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTxtPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
public void setBorderW(int borderW) {
|
||||
mBorderW = borderW;
|
||||
mPaint.setStrokeWidth(mBorderW);
|
||||
}
|
||||
|
||||
public int[] getCircleColos(){
|
||||
return circle_colors;
|
||||
}
|
||||
|
||||
public void setCircleColorsArr(int[] circle_colors) {
|
||||
this.circle_colors = circle_colors;
|
||||
}
|
||||
|
||||
public void setCircleColors(int... circle_colors) {
|
||||
this.circle_colors = circle_colors;
|
||||
}
|
||||
|
||||
public void setCenterTxtSize(float centerTxtSize) {
|
||||
mCenterTxtSize = centerTxtSize;
|
||||
}
|
||||
|
||||
public void setCenterTxtColor(int centerTxtColor) {
|
||||
mCenterTxtColor = centerTxtColor;
|
||||
}
|
||||
|
||||
public void setCenterStyle(CircleCenterStyle centerStyle) {
|
||||
mCenterStyle = centerStyle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int expectSize = mRadius * 2 + mBorderW + getPaddingLeft() + getPaddingRight();
|
||||
int width = resolveSize(expectSize, widthMeasureSpec);
|
||||
int height = resolveSize(expectSize, heightMeasureSpec);
|
||||
expectSize = Math.min(width, height);
|
||||
|
||||
LogUtil.logD("201810301418", "onMeasure-->expectSize = " + expectSize + "->width = " + width + "->height = " + height
|
||||
+ "->widthMeasureSpec = " + widthMeasureSpec + "->heightMeasureSpec = " + heightMeasureSpec);
|
||||
|
||||
mRadius = (expectSize - getPaddingTop() - getPaddingBottom() - mBorderW) / 2;
|
||||
if (mArcRectF == null) {
|
||||
mArcRectF = new RectF();
|
||||
}
|
||||
mArcRectF.set(0, 0, mRadius * 2, mRadius * 2);
|
||||
|
||||
setMeasuredDimension(width, expectSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if(EmptyUtils.isEmpty(mList)){
|
||||
drawEmptyData(canvas);
|
||||
}else{
|
||||
drawCircleRing(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public void setEmpty() {
|
||||
setData(null);
|
||||
}
|
||||
|
||||
private List<CircleBean> mList = new ArrayList<>();
|
||||
|
||||
public void setData(List<CircleBean> beans) {
|
||||
mList.clear();
|
||||
if (EmptyUtils.isNotEmpty(beans)) {
|
||||
mList.addAll(beans);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setCenterStr(String centerStr) {
|
||||
mCenterStr = centerStr;
|
||||
}
|
||||
|
||||
private void drawEmptyData(Canvas canvas) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setColor(Color.rgb(200, 200, 200));
|
||||
paint.setTextSize(DensityUtil.sp2px(getContext(), 12f));
|
||||
int canvasW = canvas.getWidth();
|
||||
int canvasH = canvas.getHeight();
|
||||
int[] wh = Util.getTextWH(NOT_DATA, paint);
|
||||
// 画文字的时候,y值是文字的底线
|
||||
canvas.drawText(NOT_DATA, canvasW / 2 - wh[0] / 2, canvasH / 2 + wh[1] / 2 - DensityUtil.dp2px(1.5f), paint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制环形中心视图
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawCenterView(Canvas canvas) {
|
||||
if (mCenterStyle == CircleCenterStyle.Icon) { // 绘制中间图标
|
||||
canvas.drawBitmap(mCenterBitmap, mRadius - mCenterBitmap.getWidth() / 2, mRadius - mCenterBitmap.getHeight() / 2, mPaint);
|
||||
} else if (mCenterStyle == CircleCenterStyle.Txt) { // 绘制中间文本
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
paint.setTextSize(mCenterTxtSize);
|
||||
paint.setColor(mCenterTxtColor);
|
||||
int[] wh = Util.getTextWH(mCenterStr, paint);
|
||||
canvas.drawText(mCenterStr, mRadius - wh[0] / 2, mRadius + wh[1] / 2, paint); // 字体开始绘制位置y的值是从字体底部(baseline)开始算的
|
||||
} else if (mCenterStyle == CircleCenterStyle.Double_Txt) { // 绘制中间文本,两行
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
paint.setTextSize(mCenterTxtSize);
|
||||
paint.setColor(mCenterTxtColor);
|
||||
|
||||
String[] centerTxt = mCenterStr.split(" ");
|
||||
int[] wh1 = Util.getTextWH(centerTxt[0], paint);
|
||||
int[] wh2 = Util.getTextWH(centerTxt[1], paint);
|
||||
|
||||
LogUtil.logD("201812201446", "源文本 = " + mCenterStr + "->文本1 = " + centerTxt[0] + "->文本2 = " + centerTxt[1]);
|
||||
canvas.drawText(centerTxt[0], mRadius - wh1[0] / 2, mRadius - mCenterTxtMargin, paint); // 字体开始绘制位置y的值是从字体底部开始算的
|
||||
canvas.drawText(centerTxt[1], mRadius - wh2[0] / 2, mRadius + wh2[1] + mCenterTxtMargin, paint); // 字体开始绘制位置y的值是从字体底部开始算的
|
||||
} else {
|
||||
throw new IllegalArgumentException("图形结合功能尚未实现");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制圆环
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawCircleRing(Canvas canvas) {
|
||||
if (EmptyUtils.isEmpty(mList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.移动画布保证居中对齐
|
||||
canvas.save();
|
||||
canvas.translate(getWidth() / 2 - mRadius - mBorderW / 2, getPaddingTop() + mBorderW / 2);
|
||||
|
||||
// 2.绘制圆环中间view
|
||||
drawCenterView(canvas);
|
||||
|
||||
// 3.逐一绘制圆环、横线、文案
|
||||
int size = mList.size();
|
||||
if (size > circle_colors.length) {
|
||||
size = circle_colors.length;
|
||||
}
|
||||
CircleBean bean;
|
||||
for (int i = 0; i < size; i++) {
|
||||
bean = mList.get(i);
|
||||
mPaint.setColor(circle_colors[i]);
|
||||
|
||||
LogUtil.logD("201812191556", "角度:start = " + bean.getStartPro() + "->end = " + bean.getEndPro());
|
||||
|
||||
canvas.drawArc(mArcRectF, bean.getStartPro(), bean.getEndPro(), false, mPaint);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private boolean mIsSHowDebug;
|
||||
public void showDebugView(boolean isSHowDebug){
|
||||
mIsSHowDebug = isSHowDebug;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.doyou.cv.widget.progress.circle;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 仿圆形分割渐变进度
|
||||
* @autor hongbing
|
||||
* @date 2019/2/14
|
||||
* 效果:http://tech.dianwoda.com/2016/11/01/androidzi-ding-yi-kong-jian-shi-xian-yan-se-jian-bian-shi-yuan-xing-jin-du/
|
||||
*/
|
||||
public class GradientProBar extends View {
|
||||
|
||||
// 圆弧线宽
|
||||
private float mCircleBorderWidth = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
|
||||
// 内边距
|
||||
private float mCirclePadding = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,20,getResources().getDisplayMetrics());
|
||||
// 字体大小
|
||||
private float mTextSize = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_SP,50,getResources().getDisplayMetrics());
|
||||
// 绘制圆周的画笔
|
||||
private Paint mBackCirclePaint;
|
||||
// 绘制圆周白色分割线的画笔
|
||||
private Paint mLinePaint;
|
||||
// 绘制文字的画笔
|
||||
private Paint mTextPaint;
|
||||
// 绘制渐变效果画笔
|
||||
private Paint mGradientCirclePaint;
|
||||
// 百分比
|
||||
private int mPercent = 0;
|
||||
// 渐变圆周颜色数组
|
||||
private int[] mGradientColorArray = new int[]{
|
||||
Color.GREEN,
|
||||
Color.parseColor("#fe751a"),
|
||||
Color.parseColor("#13be23"),
|
||||
Color.GREEN
|
||||
};
|
||||
|
||||
public GradientProBar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public GradientProBar(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public GradientProBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mBackCirclePaint = new Paint();
|
||||
mBackCirclePaint.setStyle(Paint.Style.STROKE);
|
||||
mBackCirclePaint.setAntiAlias(true);
|
||||
mBackCirclePaint.setColor(Color.LTGRAY);
|
||||
mBackCirclePaint.setStrokeWidth(mCircleBorderWidth);
|
||||
|
||||
mGradientCirclePaint = new Paint();
|
||||
mGradientCirclePaint.setStyle(Paint.Style.STROKE);
|
||||
mGradientCirclePaint.setAntiAlias(true);
|
||||
mGradientCirclePaint.setColor(Color.LTGRAY);
|
||||
mGradientCirclePaint.setStrokeWidth(mCircleBorderWidth);
|
||||
|
||||
mLinePaint = new Paint();
|
||||
mLinePaint.setColor(Color.WHITE);
|
||||
mLinePaint.setStrokeWidth(5);
|
||||
|
||||
mTextPaint = new Paint();
|
||||
mTextPaint.setAntiAlias(true);
|
||||
mTextPaint.setTextSize(mTextSize);
|
||||
mTextPaint.setColor(Color.BLACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
|
||||
setMeasuredDimension(Math.min(measureWidth, measureHeight), Math.min(measureWidth, measureHeight));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
// 1.绘制灰色背景圆环
|
||||
canvas.drawArc(
|
||||
new RectF(mCirclePadding * 2, mCirclePadding * 2,
|
||||
getMeasuredWidth() - mCirclePadding * 2, getMeasuredHeight() - mCirclePadding * 2)
|
||||
, -90
|
||||
, 360
|
||||
, false
|
||||
, mBackCirclePaint);
|
||||
// 2.绘制颜色渐变圆环
|
||||
LinearGradient linearGradient = new LinearGradient(
|
||||
mCirclePadding,mCirclePadding,getMeasuredWidth() - mCirclePadding,getMeasuredHeight() - mCirclePadding,
|
||||
mGradientColorArray,null, Shader.TileMode.MIRROR);
|
||||
mGradientCirclePaint.setShader(linearGradient);
|
||||
mGradientCirclePaint.setShadowLayer(10,10,10,Color.RED);
|
||||
canvas.drawArc(
|
||||
new RectF(mCirclePadding * 2, mCirclePadding * 2,
|
||||
getMeasuredWidth() - mCirclePadding * 2, getMeasuredHeight() - mCirclePadding * 2)
|
||||
, -90
|
||||
, (float) (mPercent / 100.0) * 360
|
||||
, false
|
||||
, mGradientCirclePaint);
|
||||
// 半径
|
||||
float radius = (getMeasuredWidth() - mCirclePadding * 3) / 2;
|
||||
// x轴中点坐标
|
||||
int centerX = getMeasuredHeight() / 2;
|
||||
// 3.绘制100份线段,切分空心圆弧
|
||||
for (int i = 0; i < 360; i++) {
|
||||
double rad = i * Math.PI / 180;
|
||||
float startX = (float) (centerX + (radius - mCircleBorderWidth) * Math.sin(rad));
|
||||
float startY = (float) (centerX + (radius - mCircleBorderWidth) * Math.cos(rad));
|
||||
float stopX = (float) (centerX + radius * Math.sin(rad) + 1);
|
||||
float stopY = (float) (centerX + radius * Math.cos(rad) + 1);
|
||||
canvas.drawLine(startX, startY, stopX, stopY, mLinePaint);
|
||||
}
|
||||
// 4.绘制文字
|
||||
float textWidth = mTextPaint.measureText(mPercent + "%");
|
||||
int textHeight = (int) (Math.ceil(mTextPaint.getFontMetrics().descent - mTextPaint.getFontMetrics().ascent) + 2);
|
||||
canvas.drawText(mPercent + "%", centerX - textWidth / 2, centerX + textHeight / 4, mTextPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置百分比
|
||||
* @param percent
|
||||
*/
|
||||
public void setPercent(int percent){
|
||||
if(percent < 0){
|
||||
percent = 0;
|
||||
}else if(percent > 100){
|
||||
percent = 100;
|
||||
}
|
||||
this.mPercent = percent;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,626 @@
|
||||
package com.doyou.cv.widget.progress.horbar;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/**
|
||||
* Created by Allen on 2017/5/14.
|
||||
* <p>
|
||||
* 自定义水平进度条
|
||||
*/
|
||||
public class HorProBar extends View {
|
||||
|
||||
private Paint bgPaint;
|
||||
private Paint progressPaint;
|
||||
|
||||
private Paint tipPaint;
|
||||
private Paint textPaint;
|
||||
/**
|
||||
* 起始,结束的数量
|
||||
*/
|
||||
private Paint numPaint;
|
||||
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mViewHeight;
|
||||
/**
|
||||
* 进度条的宽度
|
||||
*/
|
||||
private int mProgressWidth;
|
||||
/**
|
||||
* 进度
|
||||
*/
|
||||
private float mProgress;
|
||||
|
||||
/**
|
||||
* 当前进度
|
||||
*/
|
||||
private float currentProgress;
|
||||
|
||||
/**
|
||||
* 进度动画
|
||||
*/
|
||||
private ValueAnimator progressAnimator;
|
||||
|
||||
/**
|
||||
* 动画执行时间
|
||||
*/
|
||||
private int duration = 1000;
|
||||
/**
|
||||
* 动画延时启动时间
|
||||
*/
|
||||
private int startDelay = 500;
|
||||
|
||||
/**
|
||||
* 进度条弧度
|
||||
*/
|
||||
private int progressRound;
|
||||
|
||||
/**
|
||||
* 百分比提示框画笔的宽度
|
||||
*/
|
||||
private int tipPaintWidth;
|
||||
|
||||
/**
|
||||
* 百分比提示框的高度
|
||||
*/
|
||||
private int tipHeight;
|
||||
|
||||
/**
|
||||
* 百分比提示框的宽度
|
||||
*/
|
||||
private int tipWidth;
|
||||
|
||||
/**
|
||||
* 画三角形的path
|
||||
*/
|
||||
private Path path = new Path();
|
||||
/**
|
||||
* 三角形的高
|
||||
*/
|
||||
private int triangleHeight;
|
||||
/**
|
||||
* 进度条距离提示框的高度
|
||||
*/
|
||||
private int progressMarginTop;
|
||||
|
||||
/**
|
||||
* 进度移动的距离
|
||||
*/
|
||||
private float moveDis;
|
||||
|
||||
private Rect textRect = new Rect();
|
||||
private String textString = "0";
|
||||
private String targetString = "1500";
|
||||
/**
|
||||
* 百分比文字字体大小
|
||||
*/
|
||||
private int textPaintSize;
|
||||
|
||||
/**
|
||||
* 进度条背景颜色
|
||||
*/
|
||||
private int bgColor = 0xFFe1e5e8;
|
||||
/**
|
||||
* 进度条颜色
|
||||
*/
|
||||
private int progressColor = Color.rgb(88, 181, 250);
|
||||
|
||||
private int[] colors = {Color.rgb(101, 226, 175), Color.rgb(88, 181, 250)};
|
||||
|
||||
/**
|
||||
* 绘制提示框的矩形
|
||||
*/
|
||||
private RectF rectF = new RectF();
|
||||
private RectF bgRectF = new RectF();
|
||||
private RectF progressRectF = new RectF();
|
||||
|
||||
/**
|
||||
* 圆角矩形的圆角半径
|
||||
*/
|
||||
private int roundRectRadius;
|
||||
|
||||
/**
|
||||
* 进度监听回调
|
||||
*/
|
||||
private ProgressListener progressListener;
|
||||
|
||||
public HorProBar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HorProBar(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
initPaint();
|
||||
}
|
||||
|
||||
public void setProgressColor(int[] colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
public void setTipColor(int color) {
|
||||
tipPaint.setColor(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化画笔宽度及view大小
|
||||
*/
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
progressRound = dp2px(3);
|
||||
tipHeight = dp2px(15);
|
||||
// tipWidth = dp2px(30);
|
||||
tipPaintWidth = dp2px(1);
|
||||
triangleHeight = dp2px(3);
|
||||
roundRectRadius = dp2px(2);
|
||||
textPaintSize = sp2px(10);
|
||||
// progressMarginTop = dp2px(3);
|
||||
|
||||
//view真实的高度
|
||||
mViewHeight = tipHeight + tipPaintWidth + triangleHeight + progressRound + progressMarginTop;
|
||||
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.HorProBar);
|
||||
progressColor = ta.getColor(R.styleable.HorProBar_pro_color, Color.rgb(88, 181, 250));
|
||||
mProgressWidth = ta.getDimensionPixelOffset(R.styleable.HorProBar_pro_width, dp2px(80));
|
||||
progressMarginTop = ta.getDimensionPixelOffset(R.styleable.HorProBar_pro_height, dp2px(3));
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
public void setTargetString(String target) {
|
||||
LogUtil.logD("201810261453", target);
|
||||
targetString = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化画笔
|
||||
*/
|
||||
private void initPaint() {
|
||||
bgPaint = getPaint(progressRound, bgColor, Paint.Style.FILL);
|
||||
progressPaint = getPaint(progressRound, progressColor, Paint.Style.FILL);
|
||||
tipPaint = getPaint(tipPaintWidth, progressColor, Paint.Style.FILL);
|
||||
|
||||
initTextPaint();
|
||||
tipWidth = (int) textPaint.measureText(textString + "%") + dp2px(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化文字画笔
|
||||
*/
|
||||
private void initTextPaint() {
|
||||
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
textPaint.setTextSize(textPaintSize);
|
||||
textPaint.setColor(Color.WHITE);
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
textPaint.setAntiAlias(true);
|
||||
|
||||
numPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
numPaint.setTextSize(textPaintSize);
|
||||
numPaint.setColor(Color.BLACK);
|
||||
numPaint.setTextAlign(Paint.Align.CENTER);
|
||||
textPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一处理paint
|
||||
*
|
||||
* @param strokeWidth
|
||||
* @param color
|
||||
* @param style
|
||||
* @return
|
||||
*/
|
||||
private Paint getPaint(int strokeWidth, int color, Paint.Style style) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStrokeWidth(strokeWidth);
|
||||
paint.setColor(color);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStyle(style);
|
||||
return paint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(measureWidth(widthMode, width), measureHeight(heightMode, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测量宽度
|
||||
*
|
||||
* @param mode
|
||||
* @param width
|
||||
* @return
|
||||
*/
|
||||
private int measureWidth(int mode, int width) {
|
||||
switch (mode) {
|
||||
case MeasureSpec.UNSPECIFIED:
|
||||
case MeasureSpec.AT_MOST:
|
||||
break;
|
||||
case MeasureSpec.EXACTLY:
|
||||
mWidth = width;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测量高度
|
||||
*
|
||||
* @param mode
|
||||
* @param height
|
||||
* @return
|
||||
*/
|
||||
private int measureHeight(int mode, int height) {
|
||||
switch (mode) {
|
||||
case MeasureSpec.UNSPECIFIED:
|
||||
case MeasureSpec.AT_MOST:
|
||||
mHeight = mViewHeight + dp2px(10);
|
||||
break;
|
||||
case MeasureSpec.EXACTLY:
|
||||
mHeight = height;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public void setColors(int[] colors) {
|
||||
this.colors = colors;
|
||||
tipPaint.setColor(colors[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (moveDis < 0) {
|
||||
currentProgress = (mProgressWidth) * mProgress / 100;
|
||||
if (currentProgress < dp2px(2) && currentProgress != 0) {
|
||||
currentProgress = dp2px(2);
|
||||
}
|
||||
//移动百分比提示框,只有当前进度到提示框中间位置之后开始移动,
|
||||
//当进度框移动到最右边的时候停止移动,但是进度条还可以继续移动
|
||||
//moveDis是tip框移动的距离
|
||||
// if (currentProgress <= (mProgressWidth)) {
|
||||
// moveDis = currentProgress;
|
||||
// } else /*if (currentProgress > mWidth-tipWidth)*/ {
|
||||
// moveDis = mProgressWidth;
|
||||
// }
|
||||
if (currentProgress <= mProgressWidth) {
|
||||
moveDis = (mWidth - mProgressWidth - tipWidth) / 2 + currentProgress;
|
||||
} else /*if (currentProgress > mWidth-tipWidth)*/ {
|
||||
moveDis = (mWidth + mProgressWidth - tipWidth) / 2;
|
||||
Log.d("201904021445", "moveDis:" + moveDis + ";tipWidth" + tipWidth);
|
||||
}
|
||||
if (moveDis == 0) {
|
||||
moveDis = (mWidth - mProgressWidth - tipWidth) / 2;
|
||||
}
|
||||
}
|
||||
LinearGradient sweepGradient = new LinearGradient(tipWidth / 2, tipHeight, moveDis + tipWidth / 2, tipHeight, colors, null, Shader.TileMode.MIRROR);
|
||||
progressPaint.setShader(sweepGradient);
|
||||
drawBgProgress(canvas);
|
||||
drawProgress(canvas);
|
||||
drawTipView(canvas);
|
||||
drawText(canvas, textString);
|
||||
// drawTargetText(canvas);
|
||||
|
||||
}
|
||||
|
||||
private void drawBgProgress(Canvas canvas) {
|
||||
bgRectF.left = (mWidth - mProgressWidth) / 2;
|
||||
bgRectF.top = tipHeight + triangleHeight;
|
||||
bgRectF.right = (mWidth + mProgressWidth) / 2;
|
||||
bgRectF.bottom = tipHeight + triangleHeight + progressMarginTop;
|
||||
canvas.drawRoundRect(bgRectF, progressRound, progressRound, bgPaint);
|
||||
Log.d("201904021432", "drawBgProgress:" + mWidth + " /n");
|
||||
}
|
||||
|
||||
private void drawProgress(Canvas canvas) {
|
||||
progressRectF.left = (mWidth - mProgressWidth) / 2;
|
||||
progressRectF.top = tipHeight + triangleHeight;
|
||||
if (currentProgress <= mProgressWidth) {
|
||||
progressRectF.right = currentProgress + (mWidth - mProgressWidth) / 2;
|
||||
} else {
|
||||
progressRectF.right = (mWidth + mProgressWidth) / 2;
|
||||
}
|
||||
progressRectF.bottom = tipHeight + triangleHeight + progressMarginTop;
|
||||
canvas.drawRoundRect(progressRectF, progressRound, progressRound, progressPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制进度上边提示百分比的view
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawTipView(Canvas canvas) {
|
||||
drawRoundRect(canvas);
|
||||
drawTriangle(canvas);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绘制圆角矩形
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawRoundRect(Canvas canvas) {
|
||||
rectF.set(moveDis, 0, tipWidth + moveDis, tipHeight);
|
||||
canvas.drawRoundRect(rectF, roundRectRadius, roundRectRadius, tipPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制三角形
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawTriangle(Canvas canvas) {
|
||||
path.moveTo(tipWidth / 2 - triangleHeight + moveDis, tipHeight);
|
||||
path.lineTo(tipWidth / 2 + moveDis, tipHeight + triangleHeight);
|
||||
path.lineTo(tipWidth / 2 + triangleHeight + moveDis, tipHeight);
|
||||
canvas.drawPath(path, tipPaint);
|
||||
path.reset();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字
|
||||
*
|
||||
* @param canvas 画布
|
||||
*/
|
||||
private void drawText(Canvas canvas, String textString) {
|
||||
textRect.left = (int) moveDis;
|
||||
textRect.top = 0;
|
||||
textRect.right = (int) (tipWidth + moveDis);
|
||||
textRect.bottom = tipHeight;
|
||||
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
|
||||
int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
|
||||
//文字绘制到整个布局的中心位置
|
||||
canvas.drawText(textString + "%", textRect.centerX(), baseline, textPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字
|
||||
*
|
||||
* @param canvas 画布
|
||||
*/
|
||||
private void drawTargetText(Canvas canvas) {
|
||||
// canvas.drawText("0", getPaddingLeft() + tipWidth/2, mViewHeight + dp2px(8), numPaint);
|
||||
|
||||
// float width = numPaint.measureText(targetString);
|
||||
// if (mProgress>0) {
|
||||
canvas.drawText(targetString, tipWidth / 2 + moveDis, mViewHeight + dp2px(8), numPaint);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度移动动画 通过插值的方式改变移动的距离
|
||||
*/
|
||||
private void initAnimation() {
|
||||
progressAnimator = ValueAnimator.ofFloat(0, mProgress);
|
||||
progressAnimator.setDuration(duration);
|
||||
progressAnimator.setStartDelay(startDelay);
|
||||
progressAnimator.setInterpolator(new LinearInterpolator());
|
||||
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
float value = (float) valueAnimator.getAnimatedValue();
|
||||
//进度数值只显示整数,我们自己的需求,可以忽略
|
||||
textString = formatNum(/*format2Int(*/value);
|
||||
tipWidth = (int) (textPaint.measureText(textString + "%") + dp2px(10));
|
||||
//把当前百分比进度转化成view宽度对应的比例
|
||||
currentProgress = (mProgressWidth) * value / 100;
|
||||
if (currentProgress < dp2px(2) && currentProgress != 0) {
|
||||
currentProgress = dp2px(2);
|
||||
}
|
||||
//进度回调方法
|
||||
if (progressListener != null) {
|
||||
progressListener.currentProgressListener(value);
|
||||
}
|
||||
//移动百分比提示框,只有当前进度到提示框中间位置之后开始移动,
|
||||
//当进度框移动到最右边的时候停止移动,但是进度条还可以继续移动
|
||||
//moveDis是tip框移动的距离
|
||||
if (currentProgress <= (mProgressWidth)) {
|
||||
moveDis = (mWidth - mProgressWidth - tipWidth) / 2 + currentProgress;
|
||||
} else /*if (currentProgress > mWidth-tipWidth)*/ {
|
||||
moveDis = (mWidth + mProgressWidth - tipWidth) / 2;
|
||||
}
|
||||
if (moveDis == 0) {
|
||||
moveDis = (mWidth - mProgressWidth - tipWidth) / 2;
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
progressAnimator.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置进度条带动画效果
|
||||
*
|
||||
* @param progress
|
||||
* @return
|
||||
*/
|
||||
public HorProBar setProgressWithAnimation(float progress) {
|
||||
mProgress = progress;
|
||||
initAnimation();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实时显示进度
|
||||
*
|
||||
* @param progress
|
||||
* @return
|
||||
*/
|
||||
public HorProBar setCurrentProgress(float progress) {
|
||||
textString = formatNum(/*format2Int(*/progress);
|
||||
tipWidth = (int) (textPaint.measureText(textString + "%") + dp2px(10));
|
||||
mProgress = progress;
|
||||
currentProgress = (mProgressWidth) * progress / 100;
|
||||
if (currentProgress < dp2px(2) && currentProgress != 0) {
|
||||
currentProgress = dp2px(2);
|
||||
}
|
||||
if (mWidth == 0) {
|
||||
moveDis = -1;
|
||||
} else {
|
||||
if (currentProgress <= (mProgressWidth)) {
|
||||
moveDis = (mWidth - mProgressWidth - tipWidth) / 2 + currentProgress;
|
||||
} else /*if (currentProgress > mWidth-tipWidth)*/ {
|
||||
moveDis = (mWidth + mProgressWidth - tipWidth) / 2;
|
||||
}
|
||||
if (moveDis == 0) {
|
||||
moveDis = (mWidth - mProgressWidth - tipWidth) / 2;
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启动画
|
||||
*/
|
||||
public void startProgressAnimation() {
|
||||
if (progressAnimator != null &&
|
||||
!progressAnimator.isRunning() &&
|
||||
!progressAnimator.isStarted())
|
||||
progressAnimator.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停动画
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public void pauseProgressAnimation() {
|
||||
if (progressAnimator != null) {
|
||||
progressAnimator.pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复动画
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public void resumeProgressAnimation() {
|
||||
if (progressAnimator != null)
|
||||
progressAnimator.resume();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止动画
|
||||
*/
|
||||
public void stopProgressAnimation() {
|
||||
if (progressAnimator != null) {
|
||||
progressAnimator.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调接口
|
||||
*/
|
||||
public interface ProgressListener {
|
||||
void currentProgressListener(float currentProgress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调监听事件
|
||||
*
|
||||
* @param listener
|
||||
* @return
|
||||
*/
|
||||
public HorProBar setProgressListener(ProgressListener listener) {
|
||||
progressListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(保留两位小数)
|
||||
*
|
||||
* @param money
|
||||
* @return
|
||||
*/
|
||||
public static String formatNumTwo(double money) {
|
||||
DecimalFormat format = new DecimalFormat("0.00");
|
||||
return format.format(money);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(保留一位小数)
|
||||
*
|
||||
* @param money
|
||||
* @return
|
||||
*/
|
||||
public static String formatNum(int money) {
|
||||
DecimalFormat format = new DecimalFormat("0");
|
||||
return format.format(money);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(保留两位小数)
|
||||
*
|
||||
* @param money
|
||||
* @return
|
||||
*/
|
||||
public static String formatNum(float money) {
|
||||
if (((int) money * 1000) == (int) (money * 1000)) {
|
||||
//如果是一个整数
|
||||
return String.valueOf((int) money);
|
||||
}
|
||||
DecimalFormat format = new DecimalFormat("0.00");
|
||||
return format.format(money);
|
||||
}
|
||||
|
||||
/**
|
||||
* dp 2 px
|
||||
*
|
||||
* @param dpVal
|
||||
*/
|
||||
protected int dp2px(int dpVal) {
|
||||
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||
dpVal, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* sp 2 px
|
||||
*
|
||||
* @param spVal
|
||||
* @return
|
||||
*/
|
||||
protected int sp2px(int spVal) {
|
||||
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
|
||||
spVal, getResources().getDisplayMetrics());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package com.doyou.cv.widget.progress.horbar;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by Allen on 2018/1/31.
|
||||
* <p>
|
||||
* 线性加载动画
|
||||
*/
|
||||
|
||||
public class LoadingLineView extends View {
|
||||
|
||||
private Context mContext;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
|
||||
/**
|
||||
* 动画起点x坐标
|
||||
*/
|
||||
private int centerX;
|
||||
/**
|
||||
* 动画起点y坐标
|
||||
*/
|
||||
private int centerY;
|
||||
|
||||
/**
|
||||
* 偏移距离
|
||||
*/
|
||||
private float dis;
|
||||
|
||||
/**
|
||||
* view真实高度
|
||||
*/
|
||||
private int mViewHeight;
|
||||
|
||||
/**
|
||||
* 背景色画笔
|
||||
*/
|
||||
private Paint bgPaint;
|
||||
/**
|
||||
* loading画笔
|
||||
*/
|
||||
private Paint loadingPaint;
|
||||
|
||||
/**
|
||||
* 画笔宽度(等于vie高度)
|
||||
*/
|
||||
private int paintWidth;
|
||||
|
||||
/**
|
||||
* 底色
|
||||
*/
|
||||
private int bgColor = 0xFFe1e5e8;
|
||||
|
||||
/**
|
||||
* loading颜色
|
||||
*/
|
||||
private int loadingColor = 0xFF58B5FA;
|
||||
|
||||
|
||||
/**
|
||||
* 动画
|
||||
*/
|
||||
private ValueAnimator loadingAnimator;
|
||||
/**
|
||||
* 动画执行时间
|
||||
*/
|
||||
private int duration = 800;
|
||||
/**
|
||||
* 动画延时启动时间
|
||||
*/
|
||||
private int startDelay = 0;
|
||||
|
||||
/**
|
||||
* 是否停止动画(恢复初始状态)
|
||||
*/
|
||||
private boolean isStopAnimation = false;
|
||||
|
||||
public LoadingLineView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public LoadingLineView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public LoadingLineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
getAtt(attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 想实现颜色等参数可配置,在这里实现配置即可,笔者就不多写了
|
||||
*
|
||||
* @param attrs attrs
|
||||
*/
|
||||
private void getAtt(AttributeSet attrs) {
|
||||
|
||||
}
|
||||
|
||||
private void init() {
|
||||
paintWidth = DensityUtil.dp2px(2);
|
||||
mViewHeight = paintWidth;
|
||||
bgPaint = getPaint(paintWidth, bgColor, Paint.Style.FILL);
|
||||
loadingPaint = getPaint(paintWidth, loadingColor, Paint.Style.FILL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一处理paint
|
||||
*
|
||||
* @param strokeWidth 画笔宽度
|
||||
* @param color 颜色
|
||||
* @param style 风格
|
||||
* @return paint
|
||||
*/
|
||||
private Paint getPaint(int strokeWidth, int color, Paint.Style style) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStrokeWidth(strokeWidth);
|
||||
paint.setColor(color);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStyle(style);
|
||||
return paint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
|
||||
mWidth = w;
|
||||
mHeight = h;
|
||||
|
||||
centerX = w / 2;
|
||||
centerY = h / 2;
|
||||
|
||||
initLoadingAnimation();
|
||||
startLoading();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int height = MeasureSpec.getSize(heightMeasureSpec);
|
||||
|
||||
setMeasuredDimension(measureWidth(widthMode, width), measureHeight(heightMode, height));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测量宽度
|
||||
*
|
||||
* @param mode
|
||||
* @param width
|
||||
* @return
|
||||
*/
|
||||
private int measureWidth(int mode, int width) {
|
||||
switch (mode) {
|
||||
case MeasureSpec.UNSPECIFIED:
|
||||
case MeasureSpec.AT_MOST:
|
||||
break;
|
||||
case MeasureSpec.EXACTLY:
|
||||
mWidth = width;
|
||||
break;
|
||||
}
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测量高度
|
||||
*
|
||||
* @param mode
|
||||
* @param height
|
||||
* @return
|
||||
*/
|
||||
private int measureHeight(int mode, int height) {
|
||||
switch (mode) {
|
||||
case MeasureSpec.UNSPECIFIED:
|
||||
case MeasureSpec.AT_MOST:
|
||||
mHeight = mViewHeight;
|
||||
break;
|
||||
case MeasureSpec.EXACTLY:
|
||||
mHeight = height;
|
||||
break;
|
||||
}
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
/**
|
||||
* 画底色
|
||||
*/
|
||||
canvas.drawLine(0, centerY, mWidth, centerY, bgPaint);
|
||||
|
||||
if (!isStopAnimation) {
|
||||
/**
|
||||
* loading向左扩散
|
||||
*/
|
||||
canvas.drawLine(centerX, centerY, centerX - dis, centerY, loadingPaint);
|
||||
/**
|
||||
* loading向右扩散
|
||||
*/
|
||||
canvas.drawLine(centerX, centerY, centerX + dis, centerY, loadingPaint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化动画
|
||||
*/
|
||||
private void initLoadingAnimation() {
|
||||
final float loadingMoveDistance = mWidth / 2;
|
||||
loadingAnimator = ValueAnimator.ofFloat(0, loadingMoveDistance);
|
||||
loadingAnimator.setDuration(duration);
|
||||
loadingAnimator.setStartDelay(startDelay);
|
||||
loadingAnimator.setRepeatCount(ValueAnimator.INFINITE);
|
||||
loadingAnimator.setInterpolator(new LinearInterpolator());
|
||||
loadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
float value = (float) valueAnimator.getAnimatedValue();
|
||||
dis = value;
|
||||
if (value <= loadingMoveDistance / 2) {
|
||||
loadingPaint.setAlpha((int) ((255 * value) * 2 / loadingMoveDistance));
|
||||
} else {
|
||||
loadingPaint.setAlpha((int) (255 - (255 * value) * 2 / loadingMoveDistance));
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启动画
|
||||
*/
|
||||
public void startLoading() {
|
||||
if (loadingAnimator != null) {
|
||||
loadingAnimator.start();
|
||||
}
|
||||
isStopAnimation = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束动画
|
||||
*/
|
||||
public void stopLoading() {
|
||||
if (loadingAnimator != null) {
|
||||
loadingAnimator.cancel();
|
||||
}
|
||||
isStopAnimation = true;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.doyou.cv.widget.progress.horbar;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.bean.GradientColor;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
|
||||
/**
|
||||
* 自定义横向进度条,内含分段进度显示
|
||||
* @author hongbing
|
||||
* @since 20181029
|
||||
* 描述:用于数据页面显示
|
||||
* 借鉴:https://github.com/bingoogolapple/BGAProgressBar-Android
|
||||
*/
|
||||
public class SectionProBar extends ProgressBar {
|
||||
private static final String TAG = SectionProBar.class.getSimpleName();
|
||||
|
||||
private Paint mBgPaint;
|
||||
private Paint mProPaint;
|
||||
private int mReachedColor;
|
||||
private int mReachedHeight;
|
||||
private int mUnReachedColor;
|
||||
private int mUnReachedHeight;
|
||||
private int mMaxUnReachedEndX;
|
||||
private GradientColor mBgGradientColor; // 背景颜色渐变对象
|
||||
private GradientColor mProGradientColor; // 进度颜色渐变对象
|
||||
private float progress;
|
||||
|
||||
|
||||
public void setProgress(float progress) {
|
||||
this.progress = progress;
|
||||
LogUtil.logD("setProgress", "progress = " + progress);
|
||||
setProgress((int) progress);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public SectionProBar(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SectionProBar(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.progressBarStyle);
|
||||
}
|
||||
|
||||
public SectionProBar(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initDefaultAttrs();
|
||||
initCustomAttrs(context, attrs);
|
||||
}
|
||||
|
||||
private void initDefaultAttrs() {
|
||||
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mBgPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mBgPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
mProPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mProPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mProPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
mReachedColor = Color.rgb(112,168,0);
|
||||
mReachedHeight = DensityUtil.dp2px(2);
|
||||
mUnReachedColor = Color.rgb(204,204,204);
|
||||
mUnReachedHeight = DensityUtil.dp2px(1);
|
||||
}
|
||||
|
||||
private void initCustomAttrs(Context context, AttributeSet attrs) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SectionProBar);
|
||||
final int N = typedArray.getIndexCount();
|
||||
for (int i = 0; i < N; i++) {
|
||||
initAttr(typedArray.getIndex(i), typedArray);
|
||||
}
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
protected void initAttr(int attr, TypedArray typedArray) {
|
||||
if (attr == R.styleable.SectionProBar_hbv_reachedColor) {
|
||||
mReachedColor = typedArray.getColor(attr, mReachedColor);
|
||||
} else if (attr == R.styleable.SectionProBar_hbv_reachedHeight) {
|
||||
mReachedHeight = typedArray.getDimensionPixelOffset(attr, mReachedHeight);
|
||||
} else if (attr == R.styleable.SectionProBar_hbv_unReachedColor) {
|
||||
mUnReachedColor = typedArray.getColor(attr, mUnReachedColor);
|
||||
} else if (attr == R.styleable.SectionProBar_hbv_unReachedHeight) {
|
||||
mUnReachedHeight = typedArray.getDimensionPixelOffset(attr, mUnReachedHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int width = MeasureSpec.getSize(widthMeasureSpec);
|
||||
|
||||
int expectHeight = getPaddingTop() + getPaddingBottom();
|
||||
expectHeight += Math.max(mReachedHeight, mUnReachedHeight);
|
||||
int height = resolveSize(expectHeight, heightMeasureSpec);
|
||||
setMeasuredDimension(width, height);
|
||||
|
||||
mMaxUnReachedEndX = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
canvas.save();
|
||||
canvas.translate(getPaddingLeft(), getMeasuredHeight() / 2);
|
||||
|
||||
float reachedRatio = getProgress() * 1.0f / getMax();
|
||||
float reachedEndX = reachedRatio * mMaxUnReachedEndX;
|
||||
if (reachedEndX > mMaxUnReachedEndX) {
|
||||
reachedEndX = mMaxUnReachedEndX;
|
||||
}
|
||||
|
||||
LogUtil.logD("onDraw", "...onDraw....");
|
||||
|
||||
// 先绘制背景
|
||||
drawProgressBg(canvas, reachedEndX);
|
||||
// 再绘制进度
|
||||
drawProgress(canvas, reachedEndX);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制进度条背景
|
||||
*
|
||||
* @param canvas
|
||||
* @param reachedEndX
|
||||
*/
|
||||
private void drawProgressBg(Canvas canvas, float reachedEndX) {
|
||||
mBgPaint.setStrokeWidth(mUnReachedHeight);
|
||||
if (mBgGradientColor == null) { // 默认
|
||||
mBgPaint.setColor(mUnReachedColor);
|
||||
canvas.drawLine(0, 0, mMaxUnReachedEndX, 0, mBgPaint);
|
||||
} else { // 渐变
|
||||
mBgPaint.setShader(new LinearGradient(mMaxUnReachedEndX - reachedEndX, 0, mMaxUnReachedEndX, 0,
|
||||
mBgGradientColor.getStartColor(), mBgGradientColor.getEndColor(),
|
||||
Shader.TileMode.CLAMP));
|
||||
canvas.drawLine(0, 0, mMaxUnReachedEndX, 0, mBgPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制当前进度
|
||||
*
|
||||
* @param canvas
|
||||
* @param reachedEndX
|
||||
*/
|
||||
private void drawProgress(Canvas canvas, float reachedEndX) {
|
||||
if (reachedEndX > 0) {
|
||||
mProPaint.setStrokeWidth(mReachedHeight);
|
||||
if (mProGradientColor == null) {
|
||||
mProPaint.setColor(mReachedColor);
|
||||
canvas.drawLine(0, 0, reachedEndX, 0, mProPaint);
|
||||
} else {
|
||||
mProPaint.setShader(new LinearGradient(0, 0, reachedEndX, 0,
|
||||
mProGradientColor.getStartColor(), mProGradientColor.getEndColor(),
|
||||
Shader.TileMode.CLAMP));
|
||||
canvas.drawLine(0, 0, reachedEndX, 0, mProPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置渐变颜色
|
||||
*
|
||||
* @param startColor
|
||||
* @param endColor
|
||||
*/
|
||||
public void setGradientBgColor(int startColor, int endColor) {
|
||||
mBgGradientColor = new GradientColor(startColor, endColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置渐变颜色
|
||||
*
|
||||
* @param startColor
|
||||
* @param endColor
|
||||
*/
|
||||
public void setGradientProColor(int startColor, int endColor) {
|
||||
mProGradientColor = new GradientColor(startColor, endColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置动画开关
|
||||
*
|
||||
* @param progress
|
||||
*/
|
||||
public void setProgressAnim(float progress) {
|
||||
// 创建 ObjectAnimator 对象
|
||||
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "progress", 0, progress);
|
||||
animator.setDuration(1200);
|
||||
// 执行动画
|
||||
animator.start();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package com.doyou.cv.widget.progress.horbar;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.R;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 横向渐变进度条
|
||||
*/
|
||||
public class ShadowProBar extends View {
|
||||
|
||||
/**
|
||||
* 执行中
|
||||
*/
|
||||
private static final int[] COLORS_WAING = {Color.rgb(101, 226, 175), Color.rgb(88, 181, 250)};
|
||||
/**
|
||||
* 已完成
|
||||
*/
|
||||
private static final int[] COLORS_FINISH = {Color.rgb(255, 196, 0), Color.rgb(255, 110, 77)};
|
||||
|
||||
private Paint bgPaint;
|
||||
private Paint progressPaint;
|
||||
/**
|
||||
* 进度条弧度
|
||||
*/
|
||||
private int progressRound;
|
||||
/**
|
||||
* 进度条背景颜色
|
||||
*/
|
||||
private int bgColor = 0xFFe1e5e8;
|
||||
/**
|
||||
* 进度条颜色
|
||||
*/
|
||||
private int progressColor = Color.BLUE;
|
||||
|
||||
private int[] colors = COLORS_WAING;
|
||||
|
||||
|
||||
private RectF bgRectF = new RectF();
|
||||
|
||||
private RectF progressRectF = new RectF();
|
||||
private float mProgress;
|
||||
|
||||
/**
|
||||
* 动画执行时间
|
||||
*/
|
||||
private int duration = 1000;
|
||||
/**
|
||||
* 动画延时启动时间
|
||||
*/
|
||||
private int startDelay = 500;
|
||||
|
||||
/**
|
||||
* 进度动画
|
||||
*/
|
||||
private ValueAnimator progressAnimator;
|
||||
|
||||
/**
|
||||
* 当前进度
|
||||
*/
|
||||
private float currentProgress;
|
||||
|
||||
public ShadowProBar(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ShadowProBar(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
initPaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (progressAnimator != null) {
|
||||
progressAnimator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度条状态
|
||||
*
|
||||
* @param isFinish
|
||||
*/
|
||||
public void setProStatus(boolean isFinish) {
|
||||
if (isFinish) {
|
||||
this.colors = COLORS_FINISH;
|
||||
} else {
|
||||
this.colors = COLORS_WAING;
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgressColor(int[] colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化画笔宽度及view大小
|
||||
*/
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
progressRound = DensityUtil.dp2px(6);
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ShadowProBar);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化画笔
|
||||
*/
|
||||
private void initPaint() {
|
||||
bgPaint = getPaint(progressRound, bgColor, Paint.Style.FILL);
|
||||
progressPaint = getPaint(progressRound, progressColor, Paint.Style.FILL);
|
||||
}
|
||||
|
||||
private Paint getPaint(int strokeWidth, int color, Paint.Style style) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStrokeWidth(strokeWidth);
|
||||
paint.setColor(color);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeCap(Paint.Cap.ROUND);
|
||||
paint.setStyle(style);
|
||||
return paint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
drawBgProgress(canvas);
|
||||
drawProgress(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
bgRectF.left = 0f;
|
||||
bgRectF.right = w;
|
||||
bgRectF.top = 0;
|
||||
bgRectF.bottom = h;
|
||||
progressRectF.left = 0f;
|
||||
|
||||
progressRectF.top = 0f;
|
||||
progressRectF.bottom = h;
|
||||
}
|
||||
|
||||
private void drawBgProgress(Canvas canvas) {
|
||||
canvas.drawRoundRect(bgRectF, progressRound, progressRound, bgPaint);
|
||||
}
|
||||
|
||||
private void drawProgress(Canvas canvas) {
|
||||
LinearGradient sweepGradient = new LinearGradient(0f, getHeight(), currentProgress, getHeight(), colors, null, Shader.TileMode.MIRROR);
|
||||
progressPaint.setShader(sweepGradient);
|
||||
progressRectF.right = currentProgress;
|
||||
canvas.drawRoundRect(progressRectF, progressRound, progressRound, progressPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置进度条带动画效果
|
||||
*
|
||||
* @param progress
|
||||
* @return
|
||||
*/
|
||||
public ShadowProBar setProgressWithAnimation(float progress) {
|
||||
mProgress = progress;
|
||||
if (isCtrlAnim) {
|
||||
initAnimation();
|
||||
} else {
|
||||
invalidate();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// 是否控制进度动画
|
||||
private boolean isCtrlAnim = true;
|
||||
|
||||
public void isCtrlAnim(boolean isAnim) {
|
||||
isCtrlAnim = isAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度移动动画 通过插值的方式改变移动的距离
|
||||
*/
|
||||
private void initAnimation() {
|
||||
progressAnimator = ValueAnimator.ofFloat(0, mProgress);
|
||||
progressAnimator.setDuration(duration);
|
||||
progressAnimator.setStartDelay(startDelay);
|
||||
progressAnimator.setInterpolator(new LinearInterpolator());
|
||||
progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
float value = (float) valueAnimator.getAnimatedValue();
|
||||
//进度数值只显示整数,我们自己的需求,可以忽略
|
||||
//把当前百分比进度转化成view宽度对应的比例
|
||||
currentProgress = getWidth() * value / 100;
|
||||
|
||||
if (currentProgress < progressRound && currentProgress != 0) {
|
||||
currentProgress = progressRound;
|
||||
}
|
||||
|
||||
if (currentProgress > getWidth()) {
|
||||
currentProgress = getWidth();
|
||||
}
|
||||
//进度回调方法
|
||||
// if (progressListener != null) {
|
||||
// progressListener.currentProgressListener(value);
|
||||
// }
|
||||
invalidate();
|
||||
}
|
||||
});
|
||||
progressAnimator.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package com.doyou.cv.widget.progress.ring;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.dongni.tools.EmptyUtils;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.bean.CircleBean;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.widget.PointView;
|
||||
import com.doyou.cv.widget.base.CircleCenterStyle;
|
||||
import com.doyou.cv.widget.progress.circle.CircleView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
/**
|
||||
* 自定义分段圆环(内含图例)
|
||||
* @author hongbing
|
||||
* @since 20181219
|
||||
* 自定义属性:https://blog.csdn.net/android_cai_niao/article/details/43701747
|
||||
*/
|
||||
public class LegendRingView extends ConstraintLayout {
|
||||
|
||||
private static final String TAG = LegendRingView.class.getSimpleName();
|
||||
|
||||
private Context mContext;
|
||||
private CircleView mCircleView;
|
||||
private LinearLayout mLrvLayout;
|
||||
|
||||
// 图形中间样式
|
||||
private CircleCenterStyle mCircleStyle;
|
||||
// 圆环高度
|
||||
private int mCircleHeight;
|
||||
// 环的厚度
|
||||
private int mBorderW;
|
||||
// 圆环上下内边距
|
||||
private int mCirclePaddingTop,mCirclePaddingBottom;
|
||||
private String mCenterStr;
|
||||
private float mCenterTxtSize;
|
||||
private int mCenterTxtColor;
|
||||
// 图例的列宽
|
||||
private int mColumnWidth;
|
||||
// 图例行间距
|
||||
private int mLegendVerMargin;
|
||||
// 图例字体颜色
|
||||
private int mlabelColor;
|
||||
// 图例字体大小
|
||||
private float mLabelSize;
|
||||
// 图例文案和圆点的间距
|
||||
private int mLegendLabelAndPointMargin;
|
||||
// 图例每行第一个左侧偏移量
|
||||
private int mLegendOffsetLeft;
|
||||
// 动态计算出来的列数
|
||||
private int mColumn;
|
||||
// 圆环颜色集合
|
||||
private int[] circle_colors;
|
||||
|
||||
|
||||
public LegendRingView(Context context) {
|
||||
this(context,null);
|
||||
}
|
||||
|
||||
public LegendRingView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs,0);
|
||||
}
|
||||
|
||||
public LegendRingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LegendRingView);
|
||||
int ordinal = ta.getInt(R.styleable.LegendRingView_lrv_circle_center_style, CircleCenterStyle.Txt.ordinal());
|
||||
mCircleStyle = CircleCenterStyle.values()[ordinal];
|
||||
mCircleHeight = ta.getDimensionPixelSize(R.styleable.LegendRingView_lrv_circle_height, DensityUtil.dp2px(120));
|
||||
mBorderW = ta.getDimensionPixelOffset(R.styleable.LegendRingView_lrv_circle_boderW,DensityUtil.dp2px(7));
|
||||
mCirclePaddingTop = ta.getDimensionPixelSize(R.styleable.LegendRingView_lrv_circle_paddingTop,0);
|
||||
mCirclePaddingBottom = ta.getDimensionPixelSize(R.styleable.LegendRingView_lrv_circle_paddingBottom,0);
|
||||
mCenterStr = ta.getString(R.styleable.LegendRingView_lrv_circle_center_txt);
|
||||
mCenterTxtSize = ta.getDimensionPixelSize(R.styleable.LegendRingView_lrv_circle_center_txt_size,DensityUtil.sp2px(context, 12));
|
||||
mCenterTxtColor = ta.getColor(R.styleable.LegendRingView_lrv_circle_center_txt_color,Color.rgb(42, 42, 42));
|
||||
mlabelColor = ta.getColor(R.styleable.LegendRingView_lrv_legend_font_color,Color.rgb(42, 42, 42));
|
||||
mLabelSize = ta.getDimensionPixelSize(R.styleable.LegendRingView_lrv_legend_font_size,DensityUtil.sp2px(context,12));
|
||||
mColumnWidth = ta.getDimensionPixelOffset(R.styleable.LegendRingView_lrv_legend_columnW,DensityUtil.dp2px(42));
|
||||
mLegendVerMargin = ta.getDimensionPixelOffset(R.styleable.LegendRingView_lrv_legend_vertical_margin,DensityUtil.sp2px(context,8));
|
||||
mLegendLabelAndPointMargin = ta.getDimensionPixelOffset(R.styleable.LegendRingView_lrv_legend_labelAndPoint_margin,DensityUtil.dp2px(4));
|
||||
mLegendOffsetLeft = ta.getDimensionPixelOffset(R.styleable.LegendRingView_lrv_offset_left,0);
|
||||
ta.recycle();
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mContext = context;
|
||||
// 将自定义layout装载进来
|
||||
LayoutInflater.from(context).inflate(R.layout.legend_circleview_layout, this, true);
|
||||
mCircleView = findViewById(R.id.circle_view);
|
||||
mCircleView.setCenterStyle(mCircleStyle);
|
||||
mCircleView.setBorderW(mBorderW);
|
||||
// 设置圆环大小
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mCircleHeight);
|
||||
mCircleView.setLayoutParams(params);
|
||||
// 设置圆环上下内边距
|
||||
mCircleView.setPadding(0, mCirclePaddingTop, 0, mCirclePaddingBottom);
|
||||
circle_colors = mCircleView.getCircleColos();
|
||||
mCircleView.setCenterStr(mCenterStr);
|
||||
mCircleView.setCenterTxtSize(mCenterTxtSize);
|
||||
mCircleView.setCenterTxtColor(mCenterTxtColor);
|
||||
|
||||
// Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
// paint.setTextSize(12f);
|
||||
// mColumnWidth = getTextW("红宝石", paint);
|
||||
|
||||
mLrvLayout = findViewById(R.id.lrv_layout);
|
||||
}
|
||||
|
||||
private boolean mIsDebug;
|
||||
public void isOpenDebug(boolean isDebug){
|
||||
mIsDebug = isDebug;
|
||||
}
|
||||
|
||||
private int mMeasureCount;
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if(mIsDebug){
|
||||
mMeasureCount++;
|
||||
LogUtil.logD("201812211741","onMeasure...测量次数 = " + mMeasureCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
LogUtil.logD("201812201056","onFinishInflate...");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
LogUtil.logD("201812201056",
|
||||
"onLayout->MeasuredWidth = " + mLrvLayout.getMeasuredWidth() + "->changed = " + changed);
|
||||
autoCaclColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态计算列数
|
||||
*/
|
||||
private void autoCaclColumn(){
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mLrvLayout.getLayoutParams();
|
||||
int margin = params.leftMargin + params.rightMargin;
|
||||
// 计算每行显示列数
|
||||
mColumn = (mLrvLayout.getMeasuredWidth() - margin) / mColumnWidth;
|
||||
LogUtil.logD("201812201056", "动态计算出的列数 = " + mColumn +
|
||||
"->MeasuredWidth = " + mLrvLayout.getMeasuredWidth() + "->margin = " + margin);
|
||||
}
|
||||
|
||||
public int max() {
|
||||
return mCircleView.getMax();
|
||||
}
|
||||
|
||||
public void setCircleColors(int... circle_colors) {
|
||||
this.circle_colors = circle_colors;
|
||||
mCircleView.setCircleColors(circle_colors);
|
||||
}
|
||||
|
||||
public void setCircleColorsArr(int[] circle_colors) {
|
||||
this.circle_colors = circle_colors;
|
||||
mCircleView.setCircleColors(circle_colors);
|
||||
}
|
||||
|
||||
public CircleView getCircleView(){
|
||||
return mCircleView;
|
||||
}
|
||||
|
||||
public void setEmpty() {
|
||||
setData(null, null);
|
||||
}
|
||||
|
||||
public void setData(final List<CircleBean> list, final List<String> legends) {
|
||||
if (EmptyUtils.isEmpty(list) && EmptyUtils.isEmpty(legends)) {
|
||||
// 先清空之前的子view
|
||||
mLrvLayout.removeAllViews();
|
||||
mCircleView.setEmpty();
|
||||
return;
|
||||
}
|
||||
post(() -> {
|
||||
if (mColumn <= 0) { // view的大小未确定
|
||||
return;
|
||||
}
|
||||
if(list.size() != legends.size()){
|
||||
throw new IllegalArgumentException("标签和环形集合大小不一致...");
|
||||
}
|
||||
|
||||
LogUtil.logD("201812201056","setData...");
|
||||
mCircleView.setData(list);
|
||||
|
||||
int size = legends.size();
|
||||
|
||||
// 图例行数
|
||||
int circulation = size / mColumn + (size % mColumn > 0 ? 1 : 0);
|
||||
LogUtil.logD("201812201056", "图例行数 = " + circulation + "->集合总数size = " + size
|
||||
+ "->size / mColumn = " + (size / mColumn) + "->size % mColumn = " + (size % mColumn));
|
||||
if (mColumn > size) {
|
||||
mColumn = size;
|
||||
}
|
||||
|
||||
// 先清空之前的子view
|
||||
mLrvLayout.removeAllViews(); // 会导致onMeasure重新测量
|
||||
LinearLayout labelLayout;
|
||||
PointView pointView;
|
||||
TextView labelTv;
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
LinearLayout.LayoutParams sonParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
for (int i = 0; i < circulation; i++) {
|
||||
labelLayout = new LinearLayout(mContext);
|
||||
labelLayout.setLayoutParams(params);
|
||||
// linearLayout.setBackgroundColor(Color.rgb(123, 180, 248));
|
||||
if (circulation == 1) { // 只有一行,水平居中
|
||||
labelLayout.setGravity(Gravity.CENTER);
|
||||
} else { // 左对齐
|
||||
labelLayout.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
|
||||
if (i > 0) { // 大于一行,第一行不设置行间上间距
|
||||
params.topMargin = mLegendVerMargin;
|
||||
}
|
||||
params.leftMargin = mLegendOffsetLeft;
|
||||
}
|
||||
for (int j = 0; j < mColumn; j++) { // 创建label
|
||||
|
||||
if (i * mColumn + j > size - 1) { // 全取干净了
|
||||
break;
|
||||
}
|
||||
|
||||
pointView = new PointView(mContext);
|
||||
pointView.setColor(circle_colors[i * mColumn + j]);
|
||||
if (j > 0) {
|
||||
if (legends.get(i * mColumn + j - 1).length() > 2) { // 针对三个文字的间距设置
|
||||
sonParams.leftMargin = DensityUtil.dp2px(12);
|
||||
pointView.setLayoutParams(sonParams);
|
||||
}
|
||||
}
|
||||
labelLayout.addView(pointView);
|
||||
|
||||
labelTv = new TextView(mContext);
|
||||
// if (BuildConfig.DEBUG) {
|
||||
// labelTv.setBackgroundColor(Color.rgb(218, 112, 214));
|
||||
// }
|
||||
labelTv.setGravity(Gravity.CENTER_VERTICAL);
|
||||
labelTv.setPadding(mLegendLabelAndPointMargin, 0, 0, 0);
|
||||
labelTv.setWidth(mColumnWidth);
|
||||
labelTv.setText(legends.get(i * mColumn + j));
|
||||
labelTv.setTextColor(mlabelColor);
|
||||
labelTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLabelSize);
|
||||
labelLayout.addView(labelTv);
|
||||
}
|
||||
|
||||
mLrvLayout.addView(labelLayout); // 会导致onMeasure重新测量
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// public int getTextW(String text, Paint paint) {
|
||||
// Rect bounds = new Rect();
|
||||
// paint.getTextBounds(text, 0, text.length(), bounds);
|
||||
// int width = bounds.left + bounds.width();
|
||||
// return width;
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
package com.doyou.cv.widget.progress.ring;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.dongni.tools.EmptyUtils;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.bean.CircleBean;
|
||||
import com.doyou.cv.bean.RingVBean;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义分段圆环(内含横线和说明绘制)
|
||||
*
|
||||
* @author hongbing
|
||||
* @since 20181030
|
||||
* 描述:用于数据模块的统计显示
|
||||
* 借鉴:https://github.com/bingoogolapple/BGAProgressBar-Android
|
||||
*/
|
||||
public class RingView extends ProgressBar {
|
||||
private static final String TAG = RingView.class.getSimpleName();
|
||||
public final static int[] COLORS = {
|
||||
Color.rgb(88, 181, 250), // 蓝色
|
||||
Color.rgb(255, 196, 0), // 黄色
|
||||
Color.rgb(101, 226, 175),// 绿色
|
||||
Color.rgb(255, 105, 83), //红色
|
||||
Color.rgb(202, 129, 222), // 紫色
|
||||
Color.rgb(153, 153, 153), // 灰色
|
||||
Color.rgb(233, 255, 83) // 浅黄色
|
||||
};
|
||||
/**
|
||||
* 客群颜色专用
|
||||
*/
|
||||
public final static int[] COLORS_KQ = {
|
||||
// 男
|
||||
Color.rgb(88, 181, 250), // a:255
|
||||
Color.rgb(114, 192, 250), // a:215
|
||||
Color.rgb(140, 204, 251), // a:175
|
||||
Color.rgb(166, 215, 252), // a:135
|
||||
Color.rgb(192, 227, 253), // a:95
|
||||
Color.rgb(225, 241, 254), // a:45
|
||||
|
||||
// 女
|
||||
Color.rgb(255, 105, 83), // a:255
|
||||
Color.rgb(255, 129, 110), // a:215
|
||||
Color.rgb(255, 152, 137), // a:175
|
||||
Color.rgb(255, 175, 164), // a:135
|
||||
Color.rgb(255, 199, 191), // a:95
|
||||
Color.rgb(255, 228, 224) // a:45
|
||||
};
|
||||
private static final float DEGREE = 1.f; // 分割线度数
|
||||
|
||||
/**
|
||||
* 自定义属性
|
||||
*/
|
||||
private Mode mMode;
|
||||
private CenterStyle mCenterStyle; // 图形中间样式
|
||||
private boolean mOuterIsEnable; // 边线是否可点击
|
||||
private int mReachedColor;
|
||||
private int mReachedHeight;
|
||||
private int mUnReachedColor;
|
||||
private int mUnReachedHeight;
|
||||
private int mTextColor;
|
||||
private int mTextSize;
|
||||
private int mRadius;
|
||||
private Bitmap mCenterBitmap;
|
||||
private String mCenterTxt;
|
||||
private float mCenterTxtSize;
|
||||
private int mCenterTxtColor;
|
||||
private float mCenterImgTxtMargin;
|
||||
|
||||
private Paint mPaint; // 圆环画笔
|
||||
private Paint mLinePaint; // 横线画笔
|
||||
private Paint mTxtPaint; // 文本画笔
|
||||
private int mMaxStrokeWidth;
|
||||
private RectF mArcRectF;
|
||||
private List<RingVBean> mOuterRfList; // 边线矩阵集合
|
||||
private int[] mColors = COLORS;
|
||||
// private List<Integer> mColors = new ArrayList<>();
|
||||
private boolean mIsCut = false; // 是否切分割线,默认false
|
||||
private String mTopTxt, mBtmTxt;
|
||||
|
||||
public RingView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RingView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, android.R.attr.progressBarStyle);
|
||||
}
|
||||
|
||||
public RingView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mOuterRfList = new ArrayList<>(7);
|
||||
initDefaultAttrs(context);
|
||||
initCustomAttrs(context, attrs);
|
||||
mMaxStrokeWidth = Math.max(mReachedHeight, mUnReachedHeight);
|
||||
// 配置默认颜色值
|
||||
// for (int i = 0; i < COLORS.length; i++) {
|
||||
// mColors.add(COLORS[i]);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (mCenterBitmap != null && !mCenterBitmap.isRecycled()) {
|
||||
mCenterBitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private void initDefaultAttrs(Context context) {
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
|
||||
mTxtPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTxtPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mLinePaint.setStyle(Paint.Style.FILL);
|
||||
mLinePaint.setStrokeWidth(3);
|
||||
|
||||
mMode = Mode.Circle;
|
||||
mTextColor = Color.parseColor("#70A800");
|
||||
mTextSize = DensityUtil.sp2px(context, 10);
|
||||
mReachedColor = Color.parseColor("#70A800");
|
||||
mReachedHeight = DensityUtil.dp2px(2);
|
||||
mUnReachedColor = Color.parseColor("#CCCCCC");
|
||||
mUnReachedHeight = DensityUtil.dp2px(1);
|
||||
|
||||
mRadius = DensityUtil.dp2px(16);
|
||||
}
|
||||
|
||||
private void initCustomAttrs(Context context, AttributeSet attrs) {
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RingView);
|
||||
// final int N = typedArray.getIndexCount();
|
||||
// for (int i = 0; i < N; i++) {
|
||||
// initAttr(typedArray.getIndex(i), typedArray);
|
||||
// }
|
||||
|
||||
int ordinal = typedArray.getInt(R.styleable.RingView_rv_mode, Mode.Circle.ordinal());
|
||||
mMode = Mode.values()[ordinal];
|
||||
|
||||
ordinal = typedArray.getInt(R.styleable.RingView_rv_center_style, CenterStyle.Icon.ordinal());
|
||||
mCenterStyle = CenterStyle.values()[ordinal];
|
||||
|
||||
mCenterBitmap = BitmapFactory.decodeResource(getResources(), typedArray.getResourceId(R.styleable.RingView_rv_center_bmp, R.drawable.widget_icon_person));
|
||||
mCenterTxt = typedArray.getString(R.styleable.RingView_rv_center_txt);
|
||||
mCenterTxtSize = typedArray.getDimensionPixelSize(R.styleable.RingView_rv_center_txt_size, 12);
|
||||
mCenterTxtColor = typedArray.getColor(R.styleable.RingView_rv_center_txt_color, Color.GRAY);
|
||||
mCenterImgTxtMargin = typedArray.getDimension(R.styleable.RingView_rv_center_imgtxt_margin, DensityUtil.dp2px(6));
|
||||
mTextColor = typedArray.getColor(R.styleable.RingView_rv_textColor, mTextColor);
|
||||
mTextSize = typedArray.getDimensionPixelOffset(R.styleable.RingView_rv_textSize, mTextSize);
|
||||
mReachedColor = typedArray.getColor(R.styleable.RingView_rv_reachedColor, mReachedColor);
|
||||
mReachedHeight = typedArray.getDimensionPixelOffset(R.styleable.RingView_rv_reachedHeight, mReachedHeight);
|
||||
mUnReachedColor = typedArray.getColor(R.styleable.RingView_rv_unReachedColor, mUnReachedColor);
|
||||
mUnReachedHeight = typedArray.getDimensionPixelOffset(R.styleable.RingView_rv_unReachedHeight, mUnReachedHeight);
|
||||
mRadius = typedArray.getDimensionPixelOffset(R.styleable.RingView_rv_radius, mRadius);
|
||||
mOuterIsEnable = typedArray.getBoolean(R.styleable.RingView_rv_outer_enable, false);
|
||||
mIsCut = typedArray.getBoolean(R.styleable.RingView_rv_need_cut, false);
|
||||
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
// protected void initAttr(int attr, TypedArray typedArray) {
|
||||
// if (attr == R.styleable.RingView_rv_mode) {
|
||||
// int ordinal = typedArray.getInt(attr, Mode.Circle.ordinal());
|
||||
// mMode = Mode.values()[ordinal];
|
||||
// } else if (attr == R.styleable.RingView_rv_textColor) {
|
||||
// mTextColor = typedArray.getColor(attr, mTextColor);
|
||||
// } else if (attr == R.styleable.RingView_rv_textSize) {
|
||||
// mTextSize = typedArray.getDimensionPixelOffset(attr, mTextSize);
|
||||
// } else if (attr == R.styleable.RingView_rv_reachedColor) {
|
||||
// mReachedColor = typedArray.getColor(attr, mReachedColor);
|
||||
// } else if (attr == R.styleable.RingView_rv_reachedHeight) {
|
||||
// mReachedHeight = typedArray.getDimensionPixelOffset(attr, mReachedHeight);
|
||||
// } else if (attr == R.styleable.RingView_rv_unReachedColor) {
|
||||
// mUnReachedColor = typedArray.getColor(attr, mUnReachedColor);
|
||||
// } else if (attr == R.styleable.RingView_rv_unReachedHeight) {
|
||||
// mUnReachedHeight = typedArray.getDimensionPixelOffset(attr, mUnReachedHeight);
|
||||
// } else if (attr == R.styleable.RingView_rv_radius) {
|
||||
// mRadius = typedArray.getDimensionPixelOffset(attr, mRadius);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (mMode == Mode.Circle || mMode == Mode.MORE || mMode == Mode.GROUP) {
|
||||
int expectSize = mRadius * 2 + mMaxStrokeWidth + getPaddingLeft() + getPaddingRight();
|
||||
int width = resolveSize(expectSize, widthMeasureSpec);
|
||||
int height = resolveSize(expectSize, heightMeasureSpec);
|
||||
expectSize = Math.min(width, height);
|
||||
|
||||
LogUtil.logD("201810301418", "onMeasure-->expectSize = " + expectSize + "->width = " + width + "->height = " + height
|
||||
+ "->widthMeasureSpec = " + widthMeasureSpec + "->heightMeasureSpec = " + heightMeasureSpec);
|
||||
|
||||
mRadius = (expectSize - getPaddingTop() - getPaddingBottom() - mMaxStrokeWidth) / 2;
|
||||
if (mArcRectF == null) {
|
||||
mArcRectF = new RectF();
|
||||
}
|
||||
mArcRectF.set(0, 0, mRadius * 2, mRadius * 2);
|
||||
|
||||
setMeasuredDimension(width, expectSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mMode == Mode.Circle) {
|
||||
onDrawCircleByTwo(canvas);
|
||||
} else {
|
||||
onDrawCircleByMore(canvas);
|
||||
}
|
||||
drawRectToUseDebug(canvas);
|
||||
}
|
||||
|
||||
private float downX = 0f, downY = 0f;
|
||||
private long currentMS = 0l;
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!mOuterIsEnable) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
downX = event.getX();
|
||||
downY = event.getY();
|
||||
currentMS = System.currentTimeMillis();
|
||||
LogUtil.logD("201811161726", "MotionEvent.ACTION_DOWN->currentMS = " + currentMS);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
LogUtil.logD("201811161726", "MotionEvent.ACTION_MOVE");
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
long moveTime = System.currentTimeMillis() - currentMS;
|
||||
LogUtil.logD("201811161726", "moveTime = " + moveTime);
|
||||
if (moveTime < 120) { // 点击判定条件
|
||||
if (EmptyUtils.isNotEmpty(mOuterRfList)) {
|
||||
RingVBean bean;
|
||||
int size = mOuterRfList.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
bean = mOuterRfList.get(i);
|
||||
LogUtil.logD("201812121815", "矩阵 = " + bean.getRf().toString() + "->downX = " + downX +
|
||||
"->downY = " + downY + "->是否包含 = " + bean.isInChartArea(bean.getRf(), downX, downY) + "->size = " + size);
|
||||
if (bean.isInChartArea(bean.getRf(), downX, downY)) {
|
||||
if (mListener != null) {
|
||||
mListener.onOuterClick(bean.getLabel());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制圆形的背景和中心图标
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawCircleBg(Canvas canvas) {
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setColor(mUnReachedColor);
|
||||
mPaint.setStrokeWidth(mUnReachedHeight);
|
||||
canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据圆心、半径、角度获取圆上的任意点
|
||||
*
|
||||
* @param angle 角度
|
||||
* @return 任意点坐标
|
||||
* 数学公式:https://www.shuxuele.com/sine-cosine-tangent.html
|
||||
*/
|
||||
private float[] getPointByAngle(float angle) {
|
||||
float x, y;
|
||||
x = (float) (mRadius + mRadius * Math.cos(angle * Math.PI / 180));
|
||||
y = (float) (mRadius + mRadius * Math.sin(angle * Math.PI / 180));
|
||||
return new float[]{x, y};
|
||||
}
|
||||
|
||||
private String[] mStrs;
|
||||
|
||||
public void setData(float progress, String... strs) {
|
||||
if (EmptyUtils.isNotEmpty(mOuterRfList)) {
|
||||
mOuterRfList.clear();
|
||||
}
|
||||
if (EmptyUtils.isNotEmpty(mStrs)) {
|
||||
mStrs = null;
|
||||
}
|
||||
mStrs = strs;
|
||||
// 将百分制转换成360度
|
||||
progress = getMax() * progress / 100;
|
||||
setProgress((int) progress);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private List<CircleBean> mList = new ArrayList<>();
|
||||
|
||||
public void setData(List<CircleBean> beans) {
|
||||
if (EmptyUtils.isNotEmpty(mOuterRfList)) {
|
||||
mOuterRfList.clear();
|
||||
}
|
||||
if (EmptyUtils.isNotEmpty(mList)) {
|
||||
mList.clear();
|
||||
}
|
||||
mList = beans;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制圆环中心视图
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawCenterView(Canvas canvas) {
|
||||
if (mCenterStyle == CenterStyle.Icon) { // 绘制中间图标
|
||||
canvas.drawBitmap(mCenterBitmap, mRadius - mCenterBitmap.getWidth() / 2, mRadius - mCenterBitmap.getHeight() / 2, mPaint);
|
||||
} else if (mCenterStyle == CenterStyle.Txt) { // 绘制中间文字
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setDither(true);
|
||||
paint.setTextSize(mCenterTxtSize);
|
||||
paint.setColor(mCenterTxtColor);
|
||||
int[] wh = getTextWH(mCenterTxt, paint);
|
||||
canvas.drawText(mCenterTxt, mRadius - wh[0] / 2, mRadius + wh[1] / 2, paint); // 字体开始绘制位置y的值是从字体底部开始算的
|
||||
} else if (mCenterStyle == CenterStyle.DOUBLE_TXT) { // 两行文字
|
||||
if (!TextUtils.isEmpty(mTopTxt) && !TextUtils.isEmpty(mBtmTxt)) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setDither(true);
|
||||
paint.setTextSize(mCenterTxtSize);
|
||||
paint.setColor(mCenterTxtColor);
|
||||
|
||||
int[] whTop = getTextWH(mTopTxt, paint);
|
||||
int[] whBtm = getTextWH(mBtmTxt, paint);
|
||||
|
||||
float halfTxtTxtH = (whTop[1] + mCenterImgTxtMargin + whBtm[1]) / 2;
|
||||
float bmpTop = mRadius - halfTxtTxtH + whTop[1]; // 需要加上第一个字的高度
|
||||
|
||||
canvas.drawText(mTopTxt, mRadius - whTop[0] / 2, bmpTop, paint);
|
||||
canvas.drawText(mBtmTxt, mRadius - whBtm[0] / 2, bmpTop + mCenterImgTxtMargin + whBtm[1], paint);
|
||||
}
|
||||
} else {// 文字 + 图标组合
|
||||
if (mCenterBitmap == null) {
|
||||
throw new IllegalArgumentException("图形结合模式下rv_center_bmp需要配置");
|
||||
}
|
||||
if (EmptyUtils.isEmpty(mCenterTxt)) {
|
||||
throw new IllegalArgumentException("图形结合模式下rv_center_txt需要配置");
|
||||
}
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setDither(true);
|
||||
paint.setTextSize(mCenterTxtSize);
|
||||
paint.setColor(mCenterTxtColor);
|
||||
int[] wh = getTextWH(mCenterTxt, paint);
|
||||
|
||||
LogUtil.logD("20190312", "bmp H = " + mCenterBitmap.getHeight() + "->总高度 = " + (mCenterBitmap.getHeight() + mCenterImgTxtMargin + wh[1]) + "->半径 = " + mRadius);
|
||||
// 垂直居中对齐
|
||||
float halfBmpTxtH = (mCenterBitmap.getHeight() + mCenterImgTxtMargin + wh[1]) / 2;
|
||||
float bmpTop = mRadius - halfBmpTxtH;
|
||||
canvas.drawBitmap(mCenterBitmap, mRadius - mCenterBitmap.getWidth() / 2, bmpTop, null);
|
||||
canvas.drawText(mCenterTxt, mRadius - wh[0] / 2, bmpTop + mCenterBitmap.getHeight() + mCenterImgTxtMargin + wh[1], paint); // 字体开始绘制位置y的值是从字体底部开始算的
|
||||
if (mIsSHowDebug) {
|
||||
canvas.drawLine(0, mRadius, mRadius * 2, mRadius, mLinePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制两段圆弧
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void onDrawCircleByTwo(Canvas canvas) {
|
||||
if (EmptyUtils.isEmpty(mStrs)) {
|
||||
return;
|
||||
}
|
||||
// 1.移动画布保证居中对齐
|
||||
canvas.save();
|
||||
canvas.translate(getWidth() / 2 - mRadius - mMaxStrokeWidth / 2, getPaddingTop() + mMaxStrokeWidth / 2);
|
||||
|
||||
// 2.绘制图形背景圆和中间图
|
||||
drawCircleBg(canvas);
|
||||
drawCenterView(canvas);
|
||||
|
||||
// 3.绘制进度值和边线等操作
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setColor(mReachedColor);
|
||||
mPaint.setStrokeWidth(mReachedHeight);
|
||||
float sweepAngle = getProgress() * 1.0f / getMax() * 360;
|
||||
canvas.drawArc(mArcRectF, 0, sweepAngle, false, mPaint);
|
||||
|
||||
// 绘制两条腿和对应的文案
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
mPaint.setStrokeWidth(3);
|
||||
float[] xy; // 圆弧中心点
|
||||
int[] wh;
|
||||
String currTxt;
|
||||
mTxtPaint.setStyle(Paint.Style.FILL);
|
||||
mTxtPaint.setColor(mTextColor);
|
||||
mTxtPaint.setTextSize(mTextSize);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
xy = getPointByAngle(sweepAngle / 2);
|
||||
mPaint.setColor(mReachedColor); // 黄色
|
||||
break;
|
||||
default:
|
||||
xy = getPointByAngle(sweepAngle + (getMax() - sweepAngle) / 2);
|
||||
mPaint.setColor(mUnReachedColor); // 蓝色
|
||||
break;
|
||||
}
|
||||
|
||||
currTxt = mStrs[i];
|
||||
wh = getTextWH(currTxt, mTxtPaint);
|
||||
|
||||
// 判断横线是画在左边还是右边
|
||||
float endX;
|
||||
boolean isLeft = false;
|
||||
if (xy[0] >= mRadius) { // 右边
|
||||
endX = xy[0] + mRadius / 2;
|
||||
} else { // 左边
|
||||
endX = xy[0] - mRadius / 2;
|
||||
isLeft = true;
|
||||
}
|
||||
// 画腿咯
|
||||
canvas.drawLine(xy[0], xy[1], endX, xy[1], mPaint);
|
||||
// 画文案咯
|
||||
if (!TextUtils.isEmpty(currTxt)) {
|
||||
LogUtil.logD("201811191753", "currTxt = " + currTxt);
|
||||
if (isLeft) {
|
||||
float l_startX = endX - wh[0] - DensityUtil.dp2px(3);
|
||||
float l_startY = xy[1] + wh[1] / 2;
|
||||
canvas.drawText(currTxt, l_startX, l_startY, mTxtPaint);
|
||||
|
||||
// 图形起始点x
|
||||
float cir_x_yd_l = canvas.getWidth() / 2 - mRadius;
|
||||
addRingVBean(currTxt, cir_x_yd_l + l_startX, l_startY + getPaddingTop(), wh);
|
||||
} else {
|
||||
float r_startX = endX + DensityUtil.dp2px(3);
|
||||
float r_startY = xy[1] + wh[1] / 2;
|
||||
canvas.drawText(currTxt, r_startX, r_startY, mTxtPaint);
|
||||
|
||||
// 将图表坐标转换成屏幕坐标
|
||||
float cir_x_yd_r = canvas.getWidth() / 2 + mRadius;
|
||||
float result_x;
|
||||
if (r_startX < mRadius * 2) { // 圆内
|
||||
result_x = cir_x_yd_r - (mRadius * 2 - r_startX);
|
||||
} else if (r_startX > mRadius * 2) { // 圆外
|
||||
result_x = cir_x_yd_r + r_startX - mRadius * 2;
|
||||
} else { // 圆心
|
||||
result_x = cir_x_yd_r;
|
||||
}
|
||||
addRingVBean(currTxt, result_x, r_startY + getPaddingTop(), wh);
|
||||
}
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void onDrawCircleByMore(Canvas canvas) {
|
||||
if (EmptyUtils.isEmpty(mList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.移动画布保证居中对齐
|
||||
canvas.save();
|
||||
canvas.translate(getWidth() / 2 - mRadius - mMaxStrokeWidth / 2, getPaddingTop() + mMaxStrokeWidth / 2);
|
||||
|
||||
// 2.绘制中间图标
|
||||
drawCenterView(canvas);
|
||||
|
||||
// 3.相关画笔初始化
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
mPaint.setStrokeWidth(mReachedHeight);
|
||||
|
||||
mTxtPaint.setColor(mTextColor);
|
||||
mTxtPaint.setTextSize(mTextSize);
|
||||
|
||||
float[] xy;
|
||||
int[] wh;
|
||||
String currTxt;
|
||||
|
||||
|
||||
if (mMode == Mode.GROUP) { // 客群标配颜色值
|
||||
mColors = COLORS_KQ;
|
||||
}
|
||||
|
||||
// 4.逐一绘制圆环、横线、文案
|
||||
int size = mList.size();
|
||||
if (size > mColors.length) {
|
||||
size = mColors.length;
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
CircleBean bean = mList.get(i);
|
||||
mPaint.setColor(mColors[i]);
|
||||
if (mIsCut) {
|
||||
if (bean.getEndPro() > DEGREE) {
|
||||
canvas.drawArc(mArcRectF, bean.getStartPro(), bean.getEndPro() - DEGREE, false, mPaint);
|
||||
} else {
|
||||
if (bean.getEndPro() > 0f) { // 注意:当前对象没有进度值,不做绘制处理
|
||||
canvas.drawArc(mArcRectF, bean.getStartPro(), bean.getEndPro(), false, mPaint);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (bean.getEndPro() > 0f) { // 注意:当前对象没有进度值,不做绘制处理
|
||||
canvas.drawArc(mArcRectF, bean.getStartPro(), bean.getEndPro(), false, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
currTxt = bean.getDesc();
|
||||
if (TextUtils.isEmpty(currTxt)) { // 没配置描述就不绘制UI
|
||||
continue;
|
||||
}
|
||||
|
||||
mLinePaint.setColor(mColors[i]);
|
||||
xy = getPointByAngle(bean.getCenterPro());
|
||||
wh = getTextWH(bean.getDesc(), mTxtPaint);
|
||||
|
||||
|
||||
LogUtil.logD("度数", "中心点 = " + bean.getCenterPro() + "->对应值 = " + bean.getDesc());
|
||||
|
||||
// 判断横线是画在左边还是右边
|
||||
float endX;
|
||||
boolean isLeft = false;
|
||||
if (xy[0] >= mRadius) { // 右边,毛边长度针对0~45,0~-45(360~315)度角做缩短处理(避免小屏幕显示不全现象)
|
||||
if ((bean.getCenterPro() <= 45 && bean.getCenterPro() > 0)
|
||||
|| (bean.getCenterPro() >= 315 && bean.getCenterPro() < 360)) {
|
||||
endX = xy[0] + mRadius / 4;
|
||||
} else {
|
||||
endX = xy[0] + mRadius / 2;
|
||||
}
|
||||
} else { // 左边,毛边长度针对135~180,180~-225度角做缩短处理(避免小屏幕显示不全现象)
|
||||
if ((bean.getCenterPro() >= 135 && bean.getCenterPro() <= 180)
|
||||
|| (bean.getCenterPro() >= 180 && bean.getCenterPro() <= 225)) {
|
||||
endX = xy[0] - mRadius / 4;
|
||||
} else {
|
||||
endX = xy[0] - mRadius / 2;
|
||||
}
|
||||
isLeft = true;
|
||||
}
|
||||
// 画腿咯
|
||||
canvas.drawLine(xy[0], xy[1], endX, xy[1], mLinePaint);
|
||||
// 画文案咯
|
||||
if (isLeft) {
|
||||
float l_startX = endX - wh[0] - DensityUtil.dp2px(3);
|
||||
float l_startY = xy[1] + wh[1] / 2;
|
||||
canvas.drawText(currTxt, l_startX, l_startY, mTxtPaint);
|
||||
int cir_x_yd_l = canvas.getWidth() / 2 - mRadius;
|
||||
addRingVBean(currTxt, cir_x_yd_l + l_startX, l_startY + getPaddingTop(), wh);
|
||||
} else {
|
||||
float r_startX = endX + DensityUtil.dp2px(3);
|
||||
float r_startY = xy[1] + wh[1] / 2;
|
||||
canvas.drawText(currTxt, r_startX, r_startY, mTxtPaint);
|
||||
|
||||
float cir_x_yd_r = canvas.getWidth() / 2 + mRadius;
|
||||
float result_x;
|
||||
if (r_startX < mRadius * 2) {
|
||||
result_x = cir_x_yd_r - (mRadius * 2 - r_startX);
|
||||
} else if (r_startX > mRadius * 2) {
|
||||
result_x = cir_x_yd_r + r_startX - mRadius * 2;
|
||||
} else {
|
||||
result_x = cir_x_yd_r;
|
||||
}
|
||||
addRingVBean(currTxt, result_x, r_startY + getPaddingTop(), wh);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private boolean mIsSHowDebug;
|
||||
|
||||
public void showDebugView(boolean isSHowDebug) {
|
||||
mIsSHowDebug = isSHowDebug;
|
||||
}
|
||||
|
||||
private void drawRectToUseDebug(Canvas canvas) {
|
||||
if (mIsSHowDebug) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(2);
|
||||
paint.setDither(true); // 防抖动
|
||||
paint.setColor(mColors[0]);
|
||||
|
||||
RingVBean bean;
|
||||
for (int i = 0; i < mOuterRfList.size(); i++) {
|
||||
bean = mOuterRfList.get(i);
|
||||
canvas.drawRoundRect(bean.getRf(), 3, 3, paint);
|
||||
}
|
||||
|
||||
// LogUtil.logD("201812121815","绘制矩阵->" + rvb.getRf().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void addRingVBean(String label, float x, float y, int[] wh) {
|
||||
// 额外补加的间距值,目的是更容易点击到
|
||||
int bj_margin = DensityUtil.dp2px(4);
|
||||
RingVBean rvb = new RingVBean();
|
||||
rvb.setLabel(label);
|
||||
rvb.setRf(new RectF(x - mPaint.getStrokeWidth() - DensityUtil.dp2px(2) - bj_margin,
|
||||
y - wh[1] / 2 - bj_margin,
|
||||
x + wh[0] + bj_margin,
|
||||
y + wh[1] / 2 + bj_margin));
|
||||
mOuterRfList.add(rvb);
|
||||
}
|
||||
|
||||
private enum Mode {
|
||||
Circle,
|
||||
MORE,
|
||||
GROUP
|
||||
}
|
||||
|
||||
private enum CenterStyle {
|
||||
/**
|
||||
* 图片
|
||||
*/
|
||||
Icon,
|
||||
/**
|
||||
* 文字
|
||||
*/
|
||||
Txt,
|
||||
/**
|
||||
* 两行文字
|
||||
*/
|
||||
DOUBLE_TXT,
|
||||
/**
|
||||
* 文字和图片
|
||||
*/
|
||||
All
|
||||
}
|
||||
|
||||
public void setColorsArr(int[] colors) {
|
||||
// mColors.clear();
|
||||
// for (int i = 0; i < colors.length; i++) {
|
||||
// mColors.add(colors[i]);
|
||||
// }
|
||||
mColors = colors;
|
||||
}
|
||||
|
||||
public void setColors(int... colors) {
|
||||
// mColors.clear();
|
||||
// for (int i = 0; i < colors.length; i++) {
|
||||
// mColors.add(colors[i]);
|
||||
// }
|
||||
mColors = colors;
|
||||
}
|
||||
|
||||
public int[] getColors() {
|
||||
return mColors;
|
||||
}
|
||||
|
||||
public void setCenterTxt(String txt) {
|
||||
mCenterTxt = txt;
|
||||
}
|
||||
|
||||
public void setCenterTxtColor(int txtColor) {
|
||||
mCenterTxtColor = txtColor;
|
||||
}
|
||||
|
||||
public void setCenterTxtColor(String txt, int txtColor) {
|
||||
setCenterTxt(txt);
|
||||
mCenterTxtColor = txtColor;
|
||||
}
|
||||
|
||||
public void setCenterTxt(String topTxt, String btmTxt) {
|
||||
mTopTxt = topTxt;
|
||||
mBtmTxt = btmTxt;
|
||||
}
|
||||
|
||||
public void setCenterTxtColor(String topTxt, String btmTxt, int txtColor) {
|
||||
setCenterTxt(topTxt, btmTxt);
|
||||
mCenterTxtColor = txtColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文字的宽度和高度
|
||||
*
|
||||
* @param text 绘制的文字
|
||||
* @param paint 画笔
|
||||
* @return 文字的宽高
|
||||
*/
|
||||
public int[] getTextWH(String text, Paint paint) {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
return new int[]{0, 0};
|
||||
}
|
||||
Rect bounds = new Rect();
|
||||
paint.getTextBounds(text, 0, text.length(), bounds);
|
||||
int width = bounds.left + bounds.width();
|
||||
int height = bounds.bottom + bounds.height();
|
||||
return new int[]{width, height};
|
||||
}
|
||||
|
||||
private OuterListener mListener;
|
||||
|
||||
public void setListener(OuterListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
public interface OuterListener {
|
||||
void onOuterClick(String label);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
progress包下用来存放进度条控件
|
||||
|
||||
circle:用于存放圆形进度条
|
||||
|
||||
ring:用于存放环形进度条
|
||||
|
||||
horbar:用于存放水平进度条
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.doyou.cv.widget.sys;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.GridView;
|
||||
|
||||
/**
|
||||
* 解决ScrollView中嵌套gridview显示不正常的问题
|
||||
* @autor hongbing
|
||||
* @date 2018/8/31
|
||||
*/
|
||||
public class FixGridView extends GridView {
|
||||
public FixGridView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public FixGridView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public FixGridView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
|
||||
MeasureSpec.AT_MOST);
|
||||
super.onMeasure(widthMeasureSpec, expandSpec);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.doyou.cv.widget.sys;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.ListView;
|
||||
|
||||
/**
|
||||
* 解决ScrollView中嵌套listview显示不正常的问题
|
||||
* @autor hongbing
|
||||
* @date 2018/8/31
|
||||
*/
|
||||
public class FixListView extends ListView {
|
||||
public FixListView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public FixListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public FixListView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
|
||||
MeasureSpec.AT_MOST);
|
||||
super.onMeasure(widthMeasureSpec, expandSpec);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.doyou.cv.widget.sys;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
/**
|
||||
* 可以控制viewpager是否左右滑动
|
||||
*
|
||||
* @autor hongbing
|
||||
* @date 2018/6/22
|
||||
*/
|
||||
public class NoScrollViewPager extends ViewPager {
|
||||
|
||||
//是否可以滑动
|
||||
private boolean isCanScroll = true;
|
||||
|
||||
public NoScrollViewPager(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public NoScrollViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置 是否可以滑动
|
||||
*
|
||||
* @param isCanScroll
|
||||
*/
|
||||
public void setScrollble(boolean isCanScroll) {
|
||||
this.isCanScroll = isCanScroll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (isCanScroll) {
|
||||
return super.onTouchEvent(ev);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
if (isCanScroll) {
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解决显示不全问题
|
||||
* @param widthMeasureSpec
|
||||
* @param heightMeasureSpec
|
||||
* https://blog.csdn.net/q4878802/article/details/50820082
|
||||
*/
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
|
||||
getDefaultSize(0, heightMeasureSpec));
|
||||
|
||||
int height = getMeasuredHeight();
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
|
||||
int h = child.getMeasuredHeight();
|
||||
if (h > height) height = h;
|
||||
}
|
||||
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void scrollTo(int x, int y) { // 方法二
|
||||
// if(isCanScroll){
|
||||
// super.scrollTo(x, y);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.doyou.cv.widget.sys.viewpager.transformer;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.dongni.tools.Common;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
/**
|
||||
* 转换器:xy缩放+透明度改变
|
||||
* @autor hongbing
|
||||
* @date 2019-11-14
|
||||
*/
|
||||
public class ScaleAlphaTransformer implements ViewPager.PageTransformer {
|
||||
|
||||
private final float SCALE_MAX = 0.8f;
|
||||
private final float ALPHA_MAX = 0.5f;
|
||||
|
||||
@Override
|
||||
public void transformPage(@NonNull View page, float position) {
|
||||
Common.log_d("transformPage", "tag = " + page.getTag() + "->position = " + position);
|
||||
if ((int) position < -1 || (int) position > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = (position < 0)
|
||||
? ((1 - SCALE_MAX) * position + 1)
|
||||
: ((SCALE_MAX - 1) * position + 1);
|
||||
float alpha = (position < 0)
|
||||
? ((1 - ALPHA_MAX) * position + 1)
|
||||
: ((ALPHA_MAX - 1) * position + 1);
|
||||
|
||||
if (position < 0) { // 目标屏-左边一屏的中心点设置
|
||||
page.setPivotX(page.getWidth());
|
||||
page.setPivotY(page.getHeight() / 2);
|
||||
} else {
|
||||
page.setPivotX(0);
|
||||
page.setPivotY(page.getHeight() / 2);
|
||||
}
|
||||
|
||||
page.setScaleX(scale);
|
||||
page.setScaleY(scale);
|
||||
page.setAlpha(Math.abs(alpha));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
package com.doyou.cv.widget.taperchart;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.dongni.tools.EmptyUtils;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.bean.TaperChartBean;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.VelocityTrackerCompat;
|
||||
|
||||
/**
|
||||
* 峰值统计图,支持水平滑动
|
||||
*
|
||||
* @autor hongbing
|
||||
* @date 2019/03/25
|
||||
* 描述:用于数据模块的统计显示
|
||||
*/
|
||||
public class HorTaperChart extends View {
|
||||
|
||||
// 保留小数点后两位
|
||||
private static final DecimalFormat pointFormat = new DecimalFormat("#.##");
|
||||
public static final int[] COLORS = {
|
||||
Color.argb(153, 101, 226, 175), // 绿色
|
||||
Color.argb(153, 255, 196, 0), // 黄色
|
||||
Color.argb(153, 88, 181, 250), // 蓝色
|
||||
};
|
||||
private static final String ZERO = "0";
|
||||
/**
|
||||
* 最大值的偏移系数,能让y轴比最大值长点
|
||||
*/
|
||||
private static final float MAX_COF = 0.1f;
|
||||
|
||||
/**
|
||||
* 数据为空显示的文案
|
||||
*/
|
||||
private static final String NOT_DATA = "暂无统计数据";
|
||||
|
||||
/**
|
||||
* x轴贝塞尔曲线的控制点的y偏移量
|
||||
*/
|
||||
private static final int QUAD_Y_SPACE = 40;
|
||||
/**
|
||||
* 滑动方向判定阈值
|
||||
*/
|
||||
private static final int SCROLL_MIN_DISTANCE = 12;
|
||||
|
||||
private Context mContext;
|
||||
// 文字大小
|
||||
private int mTextSize;
|
||||
// x轴图形相交的距离
|
||||
private int mInterSpace = 30;
|
||||
|
||||
|
||||
// 画布的宽高
|
||||
private int mCanvasW, mCanvasH;
|
||||
private Paint mPaint;
|
||||
// 坐标系画笔
|
||||
private Paint mAxisPaint;
|
||||
// 文字画笔
|
||||
private Paint mTextPaint;
|
||||
// 数据为空的画笔
|
||||
private Paint mNullPaint;
|
||||
// 控制点画笔
|
||||
private Paint mPointPaint;
|
||||
// 测量线画笔
|
||||
private Paint mLinePaint;
|
||||
// 图形宽度
|
||||
private float mLabelWidth = 200;
|
||||
// x轴贝塞尔曲线的控制点的x偏移量
|
||||
private float mQuadXSpace = 68;
|
||||
// 控制点偏移量的误差值
|
||||
private int mQuadOffset;
|
||||
// 图形在坐标轴内的左右偏移量
|
||||
private float mOffLeft = 0;
|
||||
private float mOffBtm = 36;
|
||||
// y轴上的值和坐标轴之间的间距
|
||||
private int mLeftAxisLabelMargin;
|
||||
// 是否绘制峰值顶尖值,默认false
|
||||
private boolean mIsDrawTopValue;
|
||||
// 数据总值
|
||||
private float mTotalValue;
|
||||
// y轴最大值
|
||||
private float y_max = 5;
|
||||
// y轴刻度集合
|
||||
private List<String> mYAxisList;
|
||||
private List<String> mXValues;
|
||||
private List<Float> mYValues;
|
||||
// 统计点对象集合
|
||||
private List<TaperChartBean> mList;
|
||||
// y轴最长label
|
||||
private String mYMaxStr;
|
||||
// y轴最长label的长度值
|
||||
private float mYMaxStrW;
|
||||
// y轴最长label的高度值
|
||||
private float mYMaxStrH;
|
||||
// x轴标签总数
|
||||
private int mXAxisCount = 0;
|
||||
|
||||
// 手势相关
|
||||
/**
|
||||
* 手势状态:默认
|
||||
*/
|
||||
public static final int SCROLL_STATE_IDLE = 0;
|
||||
/**
|
||||
* 手势状态:滑动中
|
||||
*/
|
||||
public static final int SCROLL_STATE_DRAGGING = 1;
|
||||
/**
|
||||
* 手势状态:惯性滑动中
|
||||
*/
|
||||
public static final int SCROLL_STATE_SETTING = 2;
|
||||
private int mScrollState = SCROLL_STATE_IDLE;
|
||||
|
||||
// 滚动控制器
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private int mMinFlingVelocity;
|
||||
private int mMaxFlingVelocity;
|
||||
private OverScroller mScroller;
|
||||
private int mLastFlingX;
|
||||
|
||||
public HorTaperChart(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HorTaperChart(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public HorTaperChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HorTaperChart);
|
||||
mTextSize = typedArray.getDimensionPixelOffset(R.styleable.HorTaperChart_htc_axis_txtsize, mTextSize);
|
||||
mInterSpace = typedArray.getDimensionPixelOffset(R.styleable.HorTaperChart_htc_inner_space, mInterSpace);
|
||||
mIsDrawTopValue = typedArray.getBoolean(R.styleable.HorTaperChart_htc_is_draw_topValue, false);
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mContext = context;
|
||||
mOffBtm = DensityUtil.dp2px(18);
|
||||
mQuadOffset = DensityUtil.dp2px(5);
|
||||
mInterSpace = DensityUtil.dp2px(24);
|
||||
mLabelWidth = DensityUtil.dp2px(98);
|
||||
mLeftAxisLabelMargin = DensityUtil.dp2px(4f);
|
||||
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setARGB(153, 255, 196, 0);
|
||||
mPaint.setStrokeWidth(6);
|
||||
|
||||
mAxisPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mAxisPaint.setColor(Color.rgb(149, 199, 255));
|
||||
mAxisPaint.setStrokeWidth(DensityUtil.dp2px(0.5f));
|
||||
|
||||
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTextPaint.setColor(Color.rgb(153, 153, 153));
|
||||
mTextPaint.setTextSize(DensityUtil.sp2px(mContext, 10));
|
||||
// mTextPaint.setTextAlign(Paint.Align.LEFT);
|
||||
|
||||
mNullPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mNullPaint.setColor(Color.rgb(200, 200, 200));
|
||||
mNullPaint.setTextSize(DensityUtil.sp2px(mContext, 12f));
|
||||
|
||||
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPointPaint.setColor(Color.RED);
|
||||
mPointPaint.setStrokeWidth(10);
|
||||
mPointPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
|
||||
mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mLinePaint.setStyle(Paint.Style.FILL);
|
||||
mLinePaint.setStrokeWidth(DensityUtil.dp2px(0.5f));
|
||||
mLinePaint.setColor(Color.RED);
|
||||
|
||||
mYAxisList = new ArrayList<>(6);
|
||||
mList = new ArrayList<>(6);
|
||||
mXValues = new ArrayList<>(6);
|
||||
mYValues = new ArrayList<>(6);
|
||||
|
||||
final ViewConfiguration vc = ViewConfiguration.get(mContext);
|
||||
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
|
||||
|
||||
mScroller = new OverScroller(getContext(), interpolator);
|
||||
}
|
||||
|
||||
public void offSetXy(float xy) {
|
||||
mOffLeft = xy;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置空数据
|
||||
*/
|
||||
public void setEmpty() {
|
||||
setData(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空数据
|
||||
*/
|
||||
public void clearData() {
|
||||
setEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充图表数据
|
||||
*
|
||||
* @param xValues
|
||||
* @param yValues
|
||||
*/
|
||||
public void setData(List<String> xValues, List<Float> yValues) {
|
||||
mTotalValue = 0;
|
||||
y_max = 0;
|
||||
mXValues.clear();
|
||||
mYValues.clear();
|
||||
|
||||
if (EmptyUtils.isEmpty(xValues) || EmptyUtils.isEmpty(yValues)) { // 暂无数据
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
mXValues = xValues;
|
||||
mYValues = yValues;
|
||||
mXAxisCount = mYValues.size();
|
||||
for (int i = 0; i < mXAxisCount; i++) {
|
||||
float y = mYValues.get(i);
|
||||
y_max = Math.max(y_max, y);
|
||||
mTotalValue = mTotalValue + y;
|
||||
}
|
||||
autoCalcYAxisMax();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动计算y轴最大值(对实际的最大值做处理)
|
||||
*/
|
||||
private void autoCalcYAxisMax() {
|
||||
if (y_max < 10) {
|
||||
if (y_max <= 5) {
|
||||
y_max = 5;
|
||||
} else {
|
||||
y_max = 10;
|
||||
}
|
||||
} else {
|
||||
if (y_max % 10 != 0) {
|
||||
y_max = y_max + (10 - y_max % 10);
|
||||
}
|
||||
}
|
||||
y_max = y_max + y_max * MAX_COF;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据为空的时候,绘制文案
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawEmptyData(Canvas canvas) {
|
||||
int canvasW = canvas.getWidth();
|
||||
int canvasH = canvas.getHeight();
|
||||
int emptyTxtW = Util.getTextWidth(NOT_DATA, mNullPaint);
|
||||
int emptyTxtH = Util.getTextHeight(NOT_DATA, mNullPaint);
|
||||
// 画文字的时候,y值是文字的底线
|
||||
canvas.drawText(NOT_DATA, canvasW / 2 - emptyTxtW / 2, canvasH / 2 + emptyTxtH / 2 - DensityUtil.dp2px(1.5f), mNullPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制坐标系
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawAxis(Canvas canvas) {
|
||||
// 刻度间的间距
|
||||
int yCount = mYAxisList.size();
|
||||
// y坐标轴的底部值
|
||||
float yAxisBtm = mCanvasH - mOffBtm;
|
||||
float h = (yAxisBtm - mYMaxStrH * 2) / yCount;
|
||||
for (int i = 0; i < yCount; i++) {
|
||||
String yLabel = mYAxisList.get(i);
|
||||
float yLabelW = Util.getTextWidth(yLabel, mTextPaint);
|
||||
// 绘制y轴刻度值(刻度右对齐相对y轴)
|
||||
canvas.drawText(yLabel, mYMaxStrW - yLabelW, mYMaxStrH + h * i + mOffBtm, mTextPaint);
|
||||
}
|
||||
// 绘制0刻度(刻度右对齐相对y轴)
|
||||
float yZeroStrW = Util.getTextWidth(ZERO, mTextPaint);
|
||||
canvas.drawText(ZERO, mYMaxStrW - yZeroStrW, yAxisBtm, mTextPaint);
|
||||
// 还原默认长度
|
||||
float axisL = mYMaxStrW + mLeftAxisLabelMargin;
|
||||
// 绘制x轴
|
||||
canvas.drawLine(axisL, yAxisBtm, mCanvasW, yAxisBtm, mAxisPaint);
|
||||
// 绘制y轴
|
||||
canvas.drawLine(axisL, mOffBtm, axisL, yAxisBtm, mAxisPaint);
|
||||
}
|
||||
|
||||
private float mXAxisTxt_Y;
|
||||
|
||||
public void setXAxisTxt_Y(float xAxisTxt_Y) {
|
||||
mXAxisTxt_Y = xAxisTxt_Y;
|
||||
}
|
||||
|
||||
public float getXAxisTxt_Y() {
|
||||
return mXAxisTxt_Y;
|
||||
}
|
||||
|
||||
private boolean mIsShowDebug = false;
|
||||
|
||||
public void isShowDebugView(boolean isShowDebug) {
|
||||
mIsShowDebug = isShowDebug;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制x轴label
|
||||
*
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawLabel(Canvas canvas) {
|
||||
float yAxisBtm = mCanvasH - mOffBtm;
|
||||
// x轴label相对x轴的偏移量,间距
|
||||
int offset = DensityUtil.dp2px(1);
|
||||
int valueH = Util.getTextHeight(mXValues.get(0), mTextPaint);
|
||||
// 绘制x轴刻度值
|
||||
float xAxisTxt_Y = yAxisBtm + valueH + offset;
|
||||
setXAxisTxt_Y(xAxisTxt_Y);
|
||||
|
||||
String key;
|
||||
float xOffset;
|
||||
for (int i = 0; i < mXAxisCount; i++) {
|
||||
key = mXValues.get(i);
|
||||
int valueW = Util.getTextWidth(key, mTextPaint);
|
||||
if (i == 0) {
|
||||
xOffset = start_x + mLabelWidth / 2 - valueW / 2;
|
||||
} else {
|
||||
xOffset = start_x + mLabelWidth * i - mInterSpace * i // 算出下一个点的起始点
|
||||
+ (mLabelWidth / 2 - valueW / 2); // 文字居中
|
||||
}
|
||||
canvas.drawText(key, xOffset, xAxisTxt_Y, mTextPaint);
|
||||
}
|
||||
}
|
||||
|
||||
private void calcStartXOffset() {
|
||||
// 重新计算图形宽度
|
||||
int chartW = (int) (getWidth() - mYMaxStrW - mLeftAxisLabelMargin - mLinePaint.getStrokeWidth());
|
||||
mLabelWidth = (chartW / 2 + mInterSpace) / 2;
|
||||
start_x = (int) (mOffLeft + mYMaxStrW + mLeftAxisLabelMargin + mLinePaint.getStrokeWidth());
|
||||
mQuadXSpace = mLabelWidth / 2 - mQuadOffset;
|
||||
}
|
||||
|
||||
private int start_x = 0;
|
||||
|
||||
private void drawData(Canvas canvas) {
|
||||
// 单个数值对应的坐标高度
|
||||
float oneH = (mCanvasH - mOffBtm - mPaint.getStrokeWidth()) / y_max;
|
||||
PointF pf;
|
||||
|
||||
if (EmptyUtils.isNotEmpty(mList)) {
|
||||
mList.clear();
|
||||
}
|
||||
|
||||
String key;
|
||||
float value;
|
||||
float pf_y = mCanvasH - mOffBtm;
|
||||
|
||||
for (int i = 0; i < mXAxisCount; i++) {
|
||||
key = mXValues.get(i);
|
||||
value = mYValues.get(i);
|
||||
float dataPAxisH = mCanvasH - (value * oneH);
|
||||
pf = new PointF();
|
||||
pf.x = start_x + mLabelWidth * i - mInterSpace * i;
|
||||
pf.y = pf_y;
|
||||
drawGraph(canvas, pf, dataPAxisH, COLORS[2], key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制图形
|
||||
*
|
||||
* @param canvas
|
||||
* @param pf 开始绘制的点坐标
|
||||
* @param topY 顶部y值
|
||||
* @param paintColor 画笔颜色
|
||||
* @param xValue 对应的数据值 x
|
||||
* @param yValue 对应的数据值 y
|
||||
*/
|
||||
private void drawGraph(Canvas canvas, PointF pf, float topY, int paintColor, String xValue, float yValue) {
|
||||
// 设置画笔颜色
|
||||
mPaint.setColor(paintColor);
|
||||
// 直接参与画图的path
|
||||
Path path = new Path();
|
||||
// 图形中心点x轴值
|
||||
float centerX = pf.x + mLabelWidth / 2;
|
||||
|
||||
// 左侧控制点
|
||||
PointF pfL = new PointF();
|
||||
pfL.x = pf.x + mQuadXSpace;
|
||||
pfL.y = pf.y - QUAD_Y_SPACE;
|
||||
|
||||
if (topY >= pfL.y) { // 极端场景处理:数据值的y坐标>=控制点的y坐标
|
||||
if (topY >= mCanvasH - mOffBtm) { // 到底了,也就是数据为0的情况
|
||||
if (yValue > 0f) { // 总得有点东西显示吧
|
||||
// 控制点做调整
|
||||
pfL.y = mCanvasH - mOffBtm - QUAD_Y_SPACE / 5;
|
||||
topY = pfL.y - QUAD_Y_SPACE / 5;
|
||||
} else {
|
||||
topY = pfL.y = mCanvasH - mOffBtm;
|
||||
}
|
||||
} else {
|
||||
topY = topY - mOffBtm;
|
||||
pfL.y = topY + (mCanvasH - mOffBtm - pfL.y) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
TaperChartBean bean = new TaperChartBean();
|
||||
RectF rectF = new RectF(pf.x, topY, pf.x + mLabelWidth, pf.y);
|
||||
bean.setRectF(rectF);
|
||||
bean.setxValue(xValue);
|
||||
bean.setyValue(yValue);
|
||||
mList.add(bean);
|
||||
|
||||
|
||||
Path pathL = new Path();
|
||||
pathL.moveTo(pf.x, pf.y);
|
||||
pathL.quadTo(pfL.x, pfL.y, centerX, topY);
|
||||
pathL.lineTo(centerX, pf.y);
|
||||
|
||||
PointF pfR = new PointF();
|
||||
pfR.x = pf.x + mLabelWidth - mQuadXSpace;
|
||||
pfR.y = pfL.y;
|
||||
// 右边图形路径
|
||||
Path pathR = new Path();
|
||||
pathR.moveTo(pf.x + mLabelWidth, pf.y);
|
||||
pathR.quadTo(pfR.x, pfR.y, centerX, topY);
|
||||
pathR.lineTo(centerX, pf.y);
|
||||
|
||||
// path路径组合
|
||||
path.addPath(pathL);
|
||||
path.addPath(pathR);
|
||||
canvas.drawPath(path, mPaint);
|
||||
|
||||
|
||||
drawTopValue(canvas, yValue, topY, centerX);
|
||||
|
||||
|
||||
if (mIsShowDebug) {
|
||||
// 左 - 控制点
|
||||
canvas.drawPoint(pf.x + mQuadXSpace, pf.y - QUAD_Y_SPACE, mPointPaint);
|
||||
// 右 - 控制点
|
||||
canvas.drawPoint(pf.x + mLabelWidth - mQuadXSpace, pf.y - QUAD_Y_SPACE, mPointPaint);
|
||||
//绘制测量线,便于观察绘制的准确性
|
||||
canvas.drawLine(centerX, topY - DensityUtil.dp2px(5), centerX, pf.y, mLinePaint);
|
||||
// 绘制高度测量线,便于观察绘制的准确性
|
||||
mPaint.setStrokeWidth(1.5f);
|
||||
canvas.drawLine(mYMaxStrW + mLeftAxisLabelMargin + mPaint.getStrokeWidth(),
|
||||
topY, centerX, topY, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制峰值头部数值
|
||||
* @param canvas
|
||||
* @param yValue
|
||||
* @param topY
|
||||
* @param centerX
|
||||
*/
|
||||
private void drawTopValue(Canvas canvas, float yValue, float topY, float centerX) {
|
||||
if (mIsDrawTopValue) {
|
||||
String text = pointFormat.format(yValue);
|
||||
canvas.drawText(text, centerX - Util.getTextWidth(text, mTextPaint) / 2, topY - DensityUtil.dp2px(2), mTextPaint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算y轴刻度
|
||||
*/
|
||||
private void calcYAxisScale() {
|
||||
int count = 5;
|
||||
mYAxisList.clear();
|
||||
// y轴上刻度间的间距
|
||||
float yAxisSpace = y_max / count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
float yScale = y_max - i * yAxisSpace;
|
||||
mYAxisList.add((int) yScale + "");
|
||||
}
|
||||
mYMaxStr = mYAxisList.get(0);
|
||||
mYMaxStrW = Util.getTextWidth(mYMaxStr, mTextPaint);
|
||||
mYMaxStrH = Util.getTextHeight(mYMaxStr, mTextPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
calcYAxisScale();
|
||||
calcStartXOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (EmptyUtils.isEmpty(mXValues) || EmptyUtils.isEmpty(mYValues)) {
|
||||
drawEmptyData(canvas);
|
||||
return;
|
||||
}
|
||||
mCanvasW = canvas.getWidth();
|
||||
mCanvasH = canvas.getHeight();
|
||||
LogUtil.logD("view的宽度-->onDraw", "canvasW = " + mCanvasW + "->canvasH = " + mCanvasH + "->mOffsetX = " + mOffsetX);
|
||||
|
||||
// 1.绘制坐标轴
|
||||
drawAxis(canvas);
|
||||
// 2.裁剪图形显示区域
|
||||
float left = mYMaxStrW + mLeftAxisLabelMargin;
|
||||
canvas.clipRect(left, 0, mCanvasW, mCanvasH, Region.Op.INTERSECT);
|
||||
// 3.通过手势滑动图形
|
||||
canvas.translate(mOffsetX, 0);
|
||||
// 4.绘制x轴上的label
|
||||
drawLabel(canvas);
|
||||
// 5.绘制图形数据
|
||||
drawData(canvas);
|
||||
}
|
||||
|
||||
public float getOffsetX() {
|
||||
return mOffsetX;
|
||||
}
|
||||
|
||||
public void setOffsetX(float offsetX) {
|
||||
mOffsetX = offsetX;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 第一张图形的坐标值
|
||||
* @return float
|
||||
*/
|
||||
private float getFirstTaperW() {
|
||||
return mLabelWidth + mOffLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* 最后一张图形的坐标值
|
||||
* @return float
|
||||
*/
|
||||
private float getEndTaperW() {
|
||||
return start_x + (mLabelWidth * mXAxisCount) - (mInterSpace * (mXAxisCount - 1)) - getWidth();
|
||||
}
|
||||
|
||||
private float downX = 0f, downY = 0f;
|
||||
private float mOffsetX;
|
||||
private float mTaperOffsetX; // 图形的偏移量
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mVelocityTracker == null) {
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
boolean eventAddedToVelocityTracker = false;
|
||||
final MotionEvent vTev = MotionEvent.obtain(event);
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
downX = event.getX();
|
||||
downY = event.getY();
|
||||
mTaperOffsetX = mOffsetX;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
mOffsetX = mTaperOffsetX + event.getX() - downX;
|
||||
LogUtil.logD("move", "mOffsetX = " + mOffsetX);
|
||||
if (event.getX() - downX > SCROLL_MIN_DISTANCE) { // 向右滑动,禁止第一张图超出初始值
|
||||
if (mOffsetX > 0) {
|
||||
mOffsetX = 0;
|
||||
}
|
||||
} else if (event.getX() - downX < -SCROLL_MIN_DISTANCE) { // 向左滑动,禁止最后一张图超出初始值
|
||||
// 最后一张图形的坐标值
|
||||
float endTaperW = getEndTaperW();
|
||||
if (Math.abs(mOffsetX) > endTaperW) {
|
||||
mOffsetX = -endTaperW;
|
||||
}
|
||||
LogUtil.logD("201905071527", "mOffsetX = " + mOffsetX + "->endTaperW = " + endTaperW);
|
||||
}
|
||||
invalidate();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
mVelocityTracker.addMovement(vTev);
|
||||
eventAddedToVelocityTracker = true;
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
|
||||
float xVelocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker, event.getPointerId(0));
|
||||
if (Math.abs(xVelocity) < mMinFlingVelocity) {
|
||||
xVelocity = 0F;
|
||||
} else {
|
||||
xVelocity = Math.max(-mMaxFlingVelocity, Math.min(xVelocity, mMaxFlingVelocity));
|
||||
}
|
||||
if (xVelocity != 0) {
|
||||
// 将速度值反应到滚动器上
|
||||
onFling((int) xVelocity);
|
||||
} else {
|
||||
mScrollState = SCROLL_STATE_IDLE;
|
||||
}
|
||||
resetTouch();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
resetTouch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!eventAddedToVelocityTracker) {
|
||||
mVelocityTracker.addMovement(vTev);
|
||||
}
|
||||
vTev.recycle();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void resetTouch() {
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void onFling(int velocityX) {
|
||||
mLastFlingX = 0;
|
||||
mScrollState = SCROLL_STATE_SETTING;
|
||||
mScroller.fling(0, 0, velocityX, 0,
|
||||
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
super.computeScroll();
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
int x = mScroller.getCurrX();
|
||||
int dis = x - mLastFlingX;
|
||||
mLastFlingX = x;
|
||||
// 更新偏移量,达到移动效果
|
||||
mOffsetX += dis;
|
||||
LogUtil.logD("computeScroll", "dis = " + dis + "->mOffsetX = " + mOffsetX + "->mTaperOffsetX = " + mTaperOffsetX + "->currX = " + x);
|
||||
// 边界超出限制
|
||||
if (x < 0) { // 左滑
|
||||
float endTaperW = getEndTaperW();
|
||||
if (Math.abs(mOffsetX) > endTaperW) {
|
||||
mOffsetX = -endTaperW;
|
||||
}
|
||||
} else if (x > 0) { // 右滑
|
||||
if (mOffsetX > 0) {
|
||||
mOffsetX = 0;
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public List<TaperChartBean> getList() {
|
||||
return mList;
|
||||
}
|
||||
|
||||
// 自定义动画插值器 f(x) = (x-1)^5 + 1
|
||||
private static final Interpolator interpolator = input -> {
|
||||
input -= 1.0f;
|
||||
return input * input * input * input * input + 1.0f;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.doyou.cv.widget.taperchart;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.dongni.tools.EmptyUtils;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.bean.TaperChartBean;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 锥形图容器布局
|
||||
* @autor hongbing
|
||||
* @date 2018/11/21
|
||||
*/
|
||||
public class TaperChartLayout extends LinearLayout implements TaperChart.DrawDataListener{
|
||||
|
||||
/**
|
||||
* 数据为空显示的文案
|
||||
*/
|
||||
private static final String NOT_DATA = "暂无统计数据";
|
||||
|
||||
private Context mContext;
|
||||
private TaperChart mChart;
|
||||
private List<String> mLabels;
|
||||
private int DEFAULT_SIZE;
|
||||
// 文字画笔
|
||||
private Paint mTextPaint;
|
||||
// 测试点
|
||||
private Paint mPointPaint;
|
||||
// TaperChart设置的间距
|
||||
private float mTaperMargin;
|
||||
// 是否是对比峰值图
|
||||
private boolean mIsComparison;
|
||||
|
||||
public TaperChartLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TaperChartLayout(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TaperChartLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
setOrientation(VERTICAL);
|
||||
setWillNotDraw(false);
|
||||
View layout = LayoutInflater.from(context).inflate(R.layout.taperchart_layout, null);
|
||||
addView(layout);
|
||||
init(layout);
|
||||
}
|
||||
|
||||
private void init(View v) {
|
||||
mChart = v.findViewById(R.id.tchart);
|
||||
mChart.setListener(this);
|
||||
mChart.setNoticeLabel(true);
|
||||
|
||||
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mTextPaint.setColor(Color.rgb(153, 153, 153));
|
||||
mTextPaint.setTextSize(DensityUtil.sp2px(mContext, 10));
|
||||
|
||||
mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPointPaint.setStyle(Paint.Style.STROKE);
|
||||
mPointPaint.setStrokeWidth(12f);
|
||||
mPointPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mPointPaint.setColor(Color.RED);
|
||||
|
||||
DEFAULT_SIZE = (int) (getResources().getDimension(R.dimen.taper_chart_height) + DensityUtil.dp2px(20));
|
||||
mTaperMargin = getResources().getDimension(R.dimen.taper_chart_margin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置峰值图颜色值(优先setData之前调用)
|
||||
* @param colors
|
||||
*/
|
||||
public void setColors(int... colors){
|
||||
if(EmptyUtils.isEmpty(colors)){
|
||||
return;
|
||||
}
|
||||
if(EmptyUtils.isEmpty(mChart)){
|
||||
new IllegalArgumentException("TaperChart控件不能为空...");
|
||||
}
|
||||
mChart.setTaperColors(colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图形数据
|
||||
* @param xValues
|
||||
* @param yValues
|
||||
*/
|
||||
public void setData(List<String> xValues, List<Float> yValues) {
|
||||
mIsComparison = false;
|
||||
mChart.setData(xValues, yValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图形数据
|
||||
* @param xValues
|
||||
* @param yValues
|
||||
* @param labels
|
||||
*/
|
||||
public void setData(List<String> xValues, List<Float> yValues, List<String> labels) {
|
||||
mIsComparison = false;
|
||||
mChart.setData(xValues, yValues);
|
||||
if (EmptyUtils.isNotEmpty(mLabels)) {
|
||||
mLabels.clear();
|
||||
}
|
||||
mLabels = labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持点击查看单位
|
||||
* @param xValues
|
||||
* @param yValues
|
||||
* @param labels
|
||||
* @param dw
|
||||
*/
|
||||
public void setData(List<String> xValues, List<Float> yValues, List<String> labels,String dw) {
|
||||
mIsComparison = false;
|
||||
mChart.setData(xValues, yValues,dw);
|
||||
if (EmptyUtils.isNotEmpty(mLabels)) {
|
||||
mLabels.clear();
|
||||
}
|
||||
mLabels = labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置对比数据
|
||||
* @param xValues x轴标签
|
||||
* @param yValues y轴坐标
|
||||
* @param labels 对比的标签组
|
||||
* @param dw 单位
|
||||
* @param btmLabels 底部括号显示的label标签
|
||||
* @param isComparison
|
||||
*/
|
||||
public void setData(List<String> xValues, List<Float> yValues, String[] labels, String dw, List<String> btmLabels,boolean isComparison) {
|
||||
mIsComparison = isComparison;
|
||||
mChart.setData(xValues, yValues, labels, dw);
|
||||
if (EmptyUtils.isNotEmpty(mLabels)) {
|
||||
mLabels.clear();
|
||||
}
|
||||
mLabels = btmLabels;
|
||||
}
|
||||
|
||||
public void setEmpty() {
|
||||
if (EmptyUtils.isNotEmpty(mLabels)) {
|
||||
mLabels.clear();
|
||||
}
|
||||
mChart.setEmpty();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public TaperChart getChart() {
|
||||
return mChart;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据为空的时候,绘制文案
|
||||
* @param canvas
|
||||
*/
|
||||
private void drawEmptyData(Canvas canvas) {
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setColor(Color.rgb(200, 200, 200));
|
||||
paint.setTextSize(DensityUtil.sp2px(mContext,12f));
|
||||
|
||||
int canvasW = canvas.getWidth();
|
||||
int canvasH = canvas.getHeight();
|
||||
int emptyTxtL = Util.getTextWidth(NOT_DATA, paint);
|
||||
canvas.drawText(NOT_DATA, canvasW / 2 - emptyTxtL / 2, canvasH / 2 - paint.getStrokeWidth(), paint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
final int width = measureSize(widthMeasureSpec);
|
||||
final int height = measureSize(heightMeasureSpec);
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
private int measureSize(int measureSpec) {
|
||||
int specMode = MeasureSpec.getMode(measureSpec);
|
||||
int specSize = MeasureSpec.getSize(measureSpec);
|
||||
|
||||
int result;
|
||||
if (specMode == MeasureSpec.EXACTLY) { // 确切大小
|
||||
result = specSize + DensityUtil.dp2px(20);
|
||||
}
|
||||
// else if (specMode == MeasureSpec.AT_MOST) { // 不确定
|
||||
// result = Math.min(DEFAULT_SIZE, specSize);
|
||||
// }
|
||||
else {
|
||||
result = DEFAULT_SIZE;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (EmptyUtils.isEmpty(mChart.getList()) || EmptyUtils.isEmpty(mLabels)) {
|
||||
// drawEmptyData(canvas);
|
||||
LogUtil.logD("201812241158","清除画布-之前的文案...");
|
||||
canvas.drawText("",0,0,mTextPaint);
|
||||
return;
|
||||
}
|
||||
int size = mLabels.size();
|
||||
float halfW = mChart.getLabelW() / 2;
|
||||
TaperChartBean bean;
|
||||
|
||||
if(!mIsComparison){ // 绘制底部标签
|
||||
for (int i = 0; i < size; i++) {
|
||||
bean = mChart.getList().get(i);
|
||||
String label = mLabels.get(i);
|
||||
canvas.drawText(label, mTaperMargin + bean.getRectF().left + (halfW - Util.getTextWidth(label, mTextPaint) / 2),
|
||||
mChart.getXAxisTxt_Y() + Util.getTextHeight(label, mTextPaint) + DensityUtil.dp2px(4), mTextPaint);
|
||||
|
||||
// if(i == 0){
|
||||
// canvas.drawPoint(bean.getRectF().left + mTaperMargin,bean.getRectF().bottom,mPointPaint);
|
||||
// Log.d("201811221714","来这了x = " + bean.getRectF().left);
|
||||
// canvas.drawLine(bean.getRectF().left + mTaperMargin,bean.getRectF().bottom - DensityUtil.dp2px(12),bean.getRectF().right,
|
||||
// bean.getRectF().bottom - DensityUtil.dp2px(12),mPointPaint);
|
||||
// }
|
||||
}
|
||||
}else{ // 绘制对比形式的底部标签
|
||||
int innerSize = mChart.getList().size();
|
||||
TaperChartBean beanL,beanR;
|
||||
for (int i = 0; i < size; i++) {
|
||||
float left = 0f;
|
||||
float right;
|
||||
if (innerSize / 2 == size) {
|
||||
beanL = mChart.getList().get(i * 2);
|
||||
left = beanL.getRectF().left;
|
||||
beanR = mChart.getList().get(i * 2 + 1);
|
||||
right = beanR.getRectF().right;
|
||||
halfW = (right - left) / 2; // 图形的长度要取一组的长度
|
||||
}
|
||||
String label = mLabels.get(i);
|
||||
canvas.drawText(label, mTaperMargin + left + (halfW - Util.getTextWidth(label, mTextPaint) / 2),
|
||||
mChart.getXAxisTxt_Y() + Util.getTextHeight(label, mTextPaint) + DensityUtil.dp2px(4), mTextPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除画布
|
||||
* @param canvas
|
||||
*/
|
||||
private void clearCanvas(Canvas canvas){
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
// Paint paint = new Paint();
|
||||
// paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
// canvas.drawPaint(paint);
|
||||
// paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
package com.doyou.cv.widget.timeruler;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Handler;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.doyou.cv.R;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
/**
|
||||
* @author Amosnail
|
||||
* @date 2017/4/11
|
||||
* @desc 时间刻度尺view
|
||||
*/
|
||||
public class TimeRulerView extends View {
|
||||
|
||||
private static final int HOUR = 24;//1小时刻度
|
||||
private static final int HALFHOUR = 48;//半小时刻度
|
||||
private static final int FIFTEENMIN = 96;//15分钟刻度
|
||||
private static final int FIVEMIN = 288;//5分钟刻度
|
||||
private static final int MIDDLE = HOUR + (FIVEMIN - HOUR) / 2;//156
|
||||
private static final int TOPMIDDLE = MIDDLE + (FIVEMIN - MIDDLE) / 2;//222
|
||||
private static final int DOWNMIDDLE = HOUR + (MIDDLE - HOUR) / 2;//90
|
||||
private static final float TOUCH_SLOP = 1.0f;//避免用户误操作的阈值
|
||||
|
||||
private Context mContext;
|
||||
private SimpleDateFormat formatterMove = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
|
||||
/**
|
||||
* 24小时所分的总格数
|
||||
*/
|
||||
private int mTotalCellNum;
|
||||
/**
|
||||
* 文字的字体大小
|
||||
*/
|
||||
private float mTextFontSize;
|
||||
/**
|
||||
* 文字的颜色
|
||||
*/
|
||||
private int mTextColor;
|
||||
/**
|
||||
* 刻度的颜色
|
||||
*/
|
||||
private int mScaleColor;
|
||||
/**
|
||||
* 顶部的线的颜色
|
||||
*/
|
||||
private int mTopLineColor;
|
||||
/**
|
||||
* 底部的线的颜色
|
||||
*/
|
||||
private int mBottomLineColor;
|
||||
/**
|
||||
* 选择时间段背景颜色
|
||||
*/
|
||||
private int mSelectBackgroundColor;
|
||||
/**
|
||||
* 中间线的颜色
|
||||
*/
|
||||
private int mMiddleLineColor;
|
||||
/**
|
||||
* 底部线画笔
|
||||
*/
|
||||
private Paint mTopLinePaint;
|
||||
/**
|
||||
* 底部线画笔
|
||||
*/
|
||||
private Paint mBottomLinePaint;
|
||||
/**
|
||||
* 刻度线画笔
|
||||
*/
|
||||
private Paint mScaleLinePaint;
|
||||
/**
|
||||
* 中间线画笔
|
||||
*/
|
||||
private Paint mMiddleLinePaint;
|
||||
/**
|
||||
* 选择时间段的画笔
|
||||
*/
|
||||
private Paint mSelectPaint;
|
||||
/**
|
||||
* 文字画笔
|
||||
*/
|
||||
private TextPaint mTextPaint;
|
||||
/**
|
||||
* 文字信息
|
||||
*/
|
||||
private Paint.FontMetrics mFontMetrics;
|
||||
/**
|
||||
* 顶部线的粗度
|
||||
*/
|
||||
private float mTopLineStrokeWidth;
|
||||
/**
|
||||
* 底部线的粗度
|
||||
*/
|
||||
private float mBottomLineStrokeWidth;
|
||||
/**
|
||||
* 刻度线的粗度
|
||||
*/
|
||||
private float mScaleLineStrokeWidth;
|
||||
/**
|
||||
* 中间线的粗度
|
||||
*/
|
||||
private float mMiddleLineStrokeWidth;
|
||||
/**
|
||||
* 每一格在屏幕上显示的长度
|
||||
*/
|
||||
private float mWidthPerCell;
|
||||
/**
|
||||
* 每一格代表的毫秒数
|
||||
*/
|
||||
private long mMillisecondPerCell;
|
||||
|
||||
/**
|
||||
* 手指移动的距离
|
||||
*/
|
||||
private float mMoveDistance;
|
||||
/**
|
||||
* 手指最后停留的坐标
|
||||
*/
|
||||
private float mLastX;
|
||||
/**
|
||||
* 每一像素所占的毫秒数
|
||||
*/
|
||||
private float mMillisecondPerPixel;
|
||||
/**
|
||||
* 中间条绑定的日历对象
|
||||
*/
|
||||
private Calendar mCalendar;
|
||||
private long mTodayStart;
|
||||
private long mCurrentTime;
|
||||
/**
|
||||
* 中间条绑定的日历对象初始毫秒值
|
||||
*/
|
||||
private long mInitMillisecond;
|
||||
/**
|
||||
* 选择时间回调
|
||||
*/
|
||||
private OnChooseTimeListener onChooseTimeListener;
|
||||
/**
|
||||
* 需要播放的时间段列表
|
||||
*/
|
||||
private ArrayList<TimeInfo> mTimeInfos = new ArrayList<>();
|
||||
/**
|
||||
* 回放时间段最小时间点
|
||||
*/
|
||||
private long mMinTime = 0;
|
||||
/**
|
||||
* 回放时间段最大时间点
|
||||
*/
|
||||
private long mMaxTime = 0;
|
||||
/**
|
||||
* 是否移动中
|
||||
*/
|
||||
private boolean isMoving = false;
|
||||
/**
|
||||
* 时间显示高度
|
||||
*/
|
||||
private float mShowTimeHeight;
|
||||
/**
|
||||
* 三角形开始位置
|
||||
*/
|
||||
private float mArrowStartHeight;
|
||||
/**
|
||||
* 箭头高度
|
||||
*/
|
||||
private float mArrowHeight;
|
||||
/**
|
||||
* 箭头margin高度
|
||||
*/
|
||||
private float mArrowMarginHeight;
|
||||
/**
|
||||
* 时间轴高度
|
||||
*/
|
||||
private float mContentHeight;
|
||||
/**
|
||||
* 刻度线高度
|
||||
*/
|
||||
private float mScaleHeight;
|
||||
/**
|
||||
* 刻度开始高度
|
||||
*/
|
||||
private float mContentStartHeight;
|
||||
/**
|
||||
* 刻度结束高度
|
||||
*/
|
||||
private float mContentEndHeight;
|
||||
/**
|
||||
* 三角形路径
|
||||
*/
|
||||
private Path mPathTriangle;
|
||||
/**
|
||||
* 三角形路径
|
||||
*/
|
||||
private Path mPathTriangleBottom;
|
||||
/**
|
||||
* 时间显示宽度
|
||||
*/
|
||||
private float mShowTimeWidth;
|
||||
/**
|
||||
* 时间显示矩形范围
|
||||
*/
|
||||
private Rect mShowTimeRec;
|
||||
private RectF mShowTimeRecF;
|
||||
/**
|
||||
* 时间显示圆角半径
|
||||
*/
|
||||
private float mShowTimeCorner;
|
||||
/**
|
||||
* 时间显示矩形画笔
|
||||
*/
|
||||
private Paint mShowTimeRecFPaint;
|
||||
/**
|
||||
* 时间显示画笔
|
||||
*/
|
||||
private TextPaint mShowTimePaint;
|
||||
|
||||
private float mShowTimeMarginBottom;
|
||||
/**
|
||||
* 时间显示动画
|
||||
*/
|
||||
private ValueAnimator mValueAnimator;
|
||||
/**
|
||||
* 时间显示延迟消失
|
||||
*/
|
||||
private Handler mHandler;
|
||||
private Runnable mRunnable;
|
||||
|
||||
public TimeRulerView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TimeRulerView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TimeRulerView);
|
||||
initDefaultValue(typedArray);
|
||||
initPaint();
|
||||
initData();
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
mMillisecondPerCell = 24 * 3600 * 1000 / mTotalCellNum;
|
||||
mMillisecondPerPixel = mMillisecondPerCell / mWidthPerCell;
|
||||
mShowTimeWidth = mShowTimePaint.measureText(" 00:00:00 ");
|
||||
mShowTimeCorner = getResources().getDimension(R.dimen.timerulerview_showtime_corner);
|
||||
mScaleHeight = getResources().getDimension(R.dimen.timerulerview_scaleheight);
|
||||
mShowTimeMarginBottom = getResources().getDimension(R.dimen.timerulerview_showtime_marginbottom);
|
||||
mArrowStartHeight = mShowTimeHeight + mShowTimeMarginBottom;
|
||||
mArrowHeight = getResources().getDimension(R.dimen.timerulerview_arrowheight);
|
||||
mArrowMarginHeight = getResources().getDimension(R.dimen.timerulerview_arrowheight_margin);
|
||||
mScaleHeight = getResources().getDimension(R.dimen.timerulerview_scaleheight);
|
||||
mContentStartHeight = mArrowStartHeight + mArrowHeight + mArrowMarginHeight;
|
||||
mContentEndHeight = mContentStartHeight + mContentHeight;
|
||||
mPathTriangle = new Path();
|
||||
mPathTriangleBottom = new Path();
|
||||
|
||||
mCalendar = Calendar.getInstance();
|
||||
mCalendar.set(mCalendar.get(Calendar.YEAR), mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
|
||||
mInitMillisecond = mCalendar.getTimeInMillis();
|
||||
mCurrentTime = mInitMillisecond;
|
||||
mTodayStart = mInitMillisecond;
|
||||
|
||||
mHandler = new Handler();
|
||||
mRunnable = this::startAnimator;
|
||||
mValueAnimator = ValueAnimator.ofInt(255, 0);
|
||||
mValueAnimator.setDuration(300);
|
||||
mValueAnimator.addUpdateListener(animation -> {
|
||||
int currentValue = (int) animation.getAnimatedValue();
|
||||
mShowTimeRecFPaint.setAlpha(currentValue);
|
||||
mShowTimePaint.setAlpha(currentValue);
|
||||
if (currentValue == 0) {
|
||||
isMoving = false;
|
||||
}
|
||||
invalidate(mShowTimeRec);
|
||||
});
|
||||
}
|
||||
|
||||
public void setTotalCellNum(int mTotalCellNum) {
|
||||
//切换刻度,原来的移动距离得倍数处理
|
||||
if (mTotalCellNum > this.mTotalCellNum) {
|
||||
mMoveDistance *= mTotalCellNum * 1.0f / (this.mTotalCellNum * 1.0f);
|
||||
} else {
|
||||
mMoveDistance /= this.mTotalCellNum * 1.0f / (mTotalCellNum * 1.0f);
|
||||
}
|
||||
this.mTotalCellNum = mTotalCellNum;
|
||||
|
||||
mMillisecondPerCell = 24 * 3600 * 1000 / mTotalCellNum;
|
||||
mMillisecondPerPixel = mMillisecondPerCell / mWidthPerCell;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void initDefaultValue(@NonNull TypedArray typedArray) {
|
||||
mTotalCellNum = typedArray.getInt(R.styleable.TimeRulerView_totalTimePerCell, MIDDLE);
|
||||
mTextFontSize = typedArray.getDimension(R.styleable.TimeRulerView_textFontSize, dp2px(13));
|
||||
mTextColor = typedArray.getColor(R.styleable.TimeRulerView_textColors, Color.rgb(0, 0, 0));
|
||||
mScaleColor = typedArray.getColor(R.styleable.TimeRulerView_scaleColor, Color.rgb(0, 0, 0));
|
||||
mTopLineColor = typedArray.getColor(R.styleable.TimeRulerView_topLineColor, Color.rgb(0, 0, 0));
|
||||
mBottomLineColor = typedArray.getColor(R.styleable.TimeRulerView_bottomLineColor, Color.rgb(0, 0, 0));
|
||||
mMiddleLineColor = typedArray.getColor(R.styleable.TimeRulerView_middleLineColor, Color.rgb(0, 0, 0));
|
||||
mSelectBackgroundColor = typedArray.getColor(R.styleable.TimeRulerView_selectBackgroundColor, Color.rgb(255, 0, 0));
|
||||
mTopLineStrokeWidth = typedArray.getDimension(R.styleable.TimeRulerView_topLineStrokeWidth, dp2px(3));
|
||||
mBottomLineStrokeWidth = typedArray.getDimension(R.styleable.TimeRulerView_bottomLineStrokeWidth, dp2px(3));
|
||||
mScaleLineStrokeWidth = typedArray.getDimension(R.styleable.TimeRulerView_scaleLineStrokeWidth, dp2px(2));
|
||||
mMiddleLineStrokeWidth = typedArray.getDimension(R.styleable.TimeRulerView_middleLineStrokeWidth, dp2px(3));
|
||||
mWidthPerCell = typedArray.getDimension(R.styleable.TimeRulerView_widthPerScale, dp2px(50));
|
||||
mShowTimeHeight = typedArray.getDimension(R.styleable.TimeRulerView_showtimeHeight, getResources().getDimension(R.dimen.timerulerview_showtimeheight));
|
||||
mContentHeight = typedArray.getDimension(R.styleable.TimeRulerView_contentHeight, getResources().getDimension(R.dimen.timerulerview_contentheight));
|
||||
typedArray.recycle();
|
||||
}
|
||||
|
||||
private void initPaint() {
|
||||
|
||||
mTopLinePaint = new Paint();
|
||||
mTopLinePaint.setAntiAlias(true);
|
||||
mTopLinePaint.setColor(mTopLineColor);
|
||||
mTopLinePaint.setStyle(Paint.Style.STROKE);
|
||||
mTopLinePaint.setStrokeWidth(mTopLineStrokeWidth);
|
||||
|
||||
mBottomLinePaint = new Paint();
|
||||
mBottomLinePaint.setAntiAlias(true);
|
||||
mBottomLinePaint.setColor(mBottomLineColor);
|
||||
mBottomLinePaint.setStyle(Paint.Style.STROKE);
|
||||
mBottomLinePaint.setStrokeWidth(mBottomLineStrokeWidth);
|
||||
|
||||
mScaleLinePaint = new Paint();
|
||||
mScaleLinePaint.setAntiAlias(true);
|
||||
mScaleLinePaint.setColor(mScaleColor);
|
||||
mScaleLinePaint.setStyle(Paint.Style.STROKE);
|
||||
mScaleLinePaint.setStrokeWidth(mScaleLineStrokeWidth);
|
||||
|
||||
mMiddleLinePaint = new Paint();
|
||||
mMiddleLinePaint.setAntiAlias(true);
|
||||
mMiddleLinePaint.setColor(mMiddleLineColor);
|
||||
mMiddleLinePaint.setStyle(Paint.Style.STROKE);
|
||||
mMiddleLinePaint.setStrokeWidth(mMiddleLineStrokeWidth);
|
||||
|
||||
mSelectPaint = new Paint();
|
||||
mSelectPaint.setAntiAlias(true);
|
||||
mSelectPaint.setColor(mSelectBackgroundColor);
|
||||
mSelectPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mTextPaint = new TextPaint();
|
||||
mTextPaint.setAntiAlias(true);
|
||||
mTextPaint.setColor(mTextColor);
|
||||
mTextPaint.setStyle(Paint.Style.STROKE);
|
||||
mTextPaint.setTextAlign(Paint.Align.CENTER);
|
||||
mTextPaint.setTextSize(mTextFontSize);
|
||||
|
||||
mFontMetrics = mTextPaint.getFontMetrics();
|
||||
|
||||
mShowTimeRecFPaint = new Paint();
|
||||
mShowTimeRecFPaint.setAntiAlias(true);
|
||||
mShowTimeRecFPaint.setColor(getResources().getColor(R.color.timeruler_showtime_bg_color));
|
||||
mShowTimeRecFPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mShowTimePaint = new TextPaint();
|
||||
mShowTimePaint.setAntiAlias(true);
|
||||
mShowTimePaint.setColor(getResources().getColor(R.color.timeruler_showtime_text_color));
|
||||
mShowTimePaint.setStyle(Paint.Style.STROKE);
|
||||
mShowTimePaint.setTextAlign(Paint.Align.CENTER);
|
||||
mShowTimePaint.setTextSize(getResources().getDimension(R.dimen.timerulerview_showtime_textsize));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
float mMiddleLineX = getMeasuredWidth() / 2.0f;
|
||||
if (mMiddleLineX != 0 && mShowTimeRec == null) {
|
||||
mPathTriangle.reset();
|
||||
mPathTriangle.moveTo(mMiddleLineX - mArrowHeight / 2.0f, mArrowStartHeight);
|
||||
mPathTriangle.lineTo(mMiddleLineX + mArrowHeight / 2.0f, mArrowStartHeight);
|
||||
mPathTriangle.lineTo(mMiddleLineX, mArrowStartHeight + mArrowHeight);
|
||||
mPathTriangle.close();
|
||||
|
||||
mPathTriangleBottom.reset();
|
||||
mPathTriangleBottom.moveTo(mMiddleLineX, mContentEndHeight + mArrowMarginHeight);
|
||||
mPathTriangleBottom.lineTo(mMiddleLineX - mArrowHeight / 2.0f, mContentEndHeight + mArrowMarginHeight + mArrowHeight);
|
||||
mPathTriangleBottom.lineTo(mMiddleLineX + mArrowHeight / 2.0f, mContentEndHeight + mArrowMarginHeight + mArrowHeight);
|
||||
mPathTriangleBottom.close();
|
||||
|
||||
mShowTimeRec = new Rect((int) (mMiddleLineX - mShowTimeWidth / 2.0f), 0,
|
||||
(int) (mMiddleLineX + mShowTimeWidth / 2.0f), (int) mShowTimeHeight);
|
||||
mShowTimeRecF = new RectF(mMiddleLineX - mShowTimeWidth / 2.0f, 0,
|
||||
mMiddleLineX + mShowTimeWidth / 2.0f, mShowTimeHeight);
|
||||
}
|
||||
setMeasuredDimension(measureWidth(widthMeasureSpec),
|
||||
measureHeight(heightMeasureSpec));
|
||||
}
|
||||
|
||||
private int measureWidth(int measureSpec) {
|
||||
return MeasureSpec.getSize(measureSpec);
|
||||
}
|
||||
|
||||
private int measureHeight(int measureSpec) {
|
||||
int result = 0;
|
||||
int mode = MeasureSpec.getMode(measureSpec);
|
||||
int size = MeasureSpec.getSize(measureSpec);
|
||||
|
||||
if (mode == MeasureSpec.EXACTLY) {
|
||||
result = size;
|
||||
} else {
|
||||
result = (int) (mContentHeight + mArrowHeight * 2 + mArrowMarginHeight * 2 +
|
||||
mShowTimeMarginBottom + mShowTimeHeight);
|
||||
if (mode == MeasureSpec.AT_MOST) {
|
||||
result = Math.min(result, size);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
drawTimeInfos(canvas);
|
||||
drawTopLine(canvas);
|
||||
drawBottomLine(canvas);
|
||||
drawMiddleLine(canvas);
|
||||
drawScaleLineDynamic(canvas);
|
||||
}
|
||||
|
||||
private void drawTopLine(Canvas canvas) {
|
||||
canvas.drawLine(0, mContentStartHeight, getMeasuredWidth(), mContentStartHeight, mTopLinePaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制时间块
|
||||
*
|
||||
* @param canvas the canvas
|
||||
*/
|
||||
private void drawTimeInfos(Canvas canvas) {
|
||||
if (mTimeInfos != null) {
|
||||
for (TimeInfo timeInfo : mTimeInfos) {
|
||||
float start = getMeasuredWidth() / 2.0f - (mCurrentTime - timeInfo.startTime.getTimeInMillis()) / mMillisecondPerPixel;
|
||||
float end = getMeasuredWidth() / 2.0f + (timeInfo.endTime.getTimeInMillis() - mCurrentTime) / mMillisecondPerPixel;
|
||||
canvas.drawRect(start, mContentStartHeight, end, mContentEndHeight, mSelectPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 画中间线
|
||||
*
|
||||
* @param canvas 画笔
|
||||
*/
|
||||
private void drawMiddleLine(Canvas canvas) {
|
||||
mMiddleLinePaint.setStyle(Paint.Style.STROKE);
|
||||
canvas.drawLine(getMeasuredWidth() / 2.0f, mContentStartHeight, getMeasuredWidth() / 2.0f, mContentEndHeight, mMiddleLinePaint);
|
||||
mMiddleLinePaint.setStyle(Paint.Style.FILL);
|
||||
canvas.drawPath(mPathTriangle, mMiddleLinePaint);
|
||||
canvas.drawPath(mPathTriangleBottom, mMiddleLinePaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 画刻度线和文字
|
||||
*
|
||||
* @param canvas the canvas
|
||||
* @author huanghao6 Created on 2018-07-19
|
||||
*/
|
||||
private void drawScaleLineDynamic(@NonNull Canvas canvas) {
|
||||
if (isMoving) {
|
||||
//移动中,显示移动到的时间
|
||||
canvas.drawRoundRect(mShowTimeRecF, mShowTimeCorner, mShowTimeCorner, mShowTimeRecFPaint);
|
||||
float baseLineY = (mArrowStartHeight - mFontMetrics.top - mFontMetrics.bottom) / 2.0f;//基线中间点的y轴计算公式
|
||||
canvas.drawText(formatterMove.format(mCurrentTime), getWidth() / 2.0f, baseLineY, mShowTimePaint);
|
||||
}
|
||||
// 刻度线数量根据可见的刻度数量动态变化
|
||||
float totalWidth = getMeasuredWidth();
|
||||
|
||||
int showCell;
|
||||
if (mTotalCellNum >= TOPMIDDLE) {
|
||||
showCell = FIVEMIN;
|
||||
} else if (mTotalCellNum >= MIDDLE) {
|
||||
showCell = FIFTEENMIN;
|
||||
} else if (mTotalCellNum >= DOWNMIDDLE) {
|
||||
showCell = HALFHOUR;
|
||||
} else {
|
||||
showCell = HOUR;
|
||||
}
|
||||
int unitMinutes = 24 * 60 / showCell;
|
||||
int unitCount = 24 * 60 / unitMinutes;
|
||||
long unitTime;
|
||||
for (int i = 0; i <= unitCount; i++) {
|
||||
unitTime = mTodayStart + i * unitMinutes * 60 * 1000;
|
||||
float distance = (unitTime - mCurrentTime) / mMillisecondPerPixel;
|
||||
|
||||
float XFromMiddlePoint = totalWidth / 2.0f + distance;
|
||||
canvas.drawLine(XFromMiddlePoint, mContentEndHeight - mScaleHeight, XFromMiddlePoint, mContentEndHeight, mScaleLinePaint);
|
||||
canvas.drawLine(XFromMiddlePoint, mContentStartHeight, XFromMiddlePoint, mContentStartHeight + mScaleHeight, mScaleLinePaint);
|
||||
if (i % 2 == 0) {
|
||||
drawTextDynamic(i, unitMinutes, XFromMiddlePoint, canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void drawTextDynamic(int i, int unitMinutes, float moveX, Canvas canvas) {
|
||||
float baseLineY = mContentStartHeight + (mContentHeight - mFontMetrics.top - mFontMetrics.bottom) / 2.0f;//基线中间点的y轴计算公式
|
||||
int minuteOffset = i * unitMinutes;
|
||||
int hour = minuteOffset / 60;
|
||||
int minute = minuteOffset % 60;
|
||||
String time = String.format(Locale.CHINA, "%02d:%02d", hour, minute);
|
||||
canvas.drawText(time, moveX, baseLineY, mTextPaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* 画水平长线
|
||||
*
|
||||
* @param canvas 画笔
|
||||
*/
|
||||
private void drawBottomLine(Canvas canvas) {
|
||||
canvas.drawLine(0, mContentEndHeight, getMeasuredWidth(), mContentEndHeight, mBottomLinePaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (event.getY() >= mContentStartHeight) {
|
||||
//只有点击刻度范围内才有效
|
||||
if (mMaxTime > mMinTime) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mLastX = event.getX();
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
float mPointXoff = event.getX() - mLastX;
|
||||
mLastX = event.getX();
|
||||
if (Math.abs(mPointXoff) < TOUCH_SLOP) {
|
||||
return true;
|
||||
}
|
||||
isMoving = true;
|
||||
initAnimator();
|
||||
mMoveDistance += mPointXoff;
|
||||
//移动小于最小值、或大于最大值,进行校正,不让用户移过去
|
||||
long temp = mInitMillisecond - (long) (mMoveDistance * mMillisecondPerPixel);
|
||||
if (mMinTime != 0 && temp < mMinTime) {
|
||||
temp = mMinTime;
|
||||
mMoveDistance = (mInitMillisecond - mMinTime) / mMillisecondPerPixel;
|
||||
} else if (mMaxTime != 0 && temp > mMaxTime) {
|
||||
temp = mMaxTime;
|
||||
mMoveDistance = (mInitMillisecond - mMaxTime) / mMillisecondPerPixel;
|
||||
}
|
||||
mCurrentTime = temp;
|
||||
invalidate();
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (isMoving) {
|
||||
mHandler.removeCallbacks(mRunnable);
|
||||
mHandler.postDelayed(mRunnable, 500);
|
||||
if (onChooseTimeListener != null) {
|
||||
mCalendar.setTimeInMillis(mCurrentTime);
|
||||
onChooseTimeListener.onChooseTime(mCalendar);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (isMoving) {
|
||||
switch (event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
mHandler.removeCallbacks(mRunnable);
|
||||
mHandler.postDelayed(mRunnable, 500);
|
||||
if (onChooseTimeListener != null) {
|
||||
mCalendar.setTimeInMillis(mCurrentTime);
|
||||
onChooseTimeListener.onChooseTime(mCalendar);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原动画
|
||||
*/
|
||||
private void initAnimator() {
|
||||
mShowTimeRecFPaint.setAlpha(255);
|
||||
mShowTimePaint.setAlpha(255);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始动画
|
||||
*/
|
||||
private void startAnimator() {
|
||||
initAnimator();
|
||||
mValueAnimator.cancel();
|
||||
mValueAnimator.start();
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return isMoving;
|
||||
}
|
||||
|
||||
private int dp2px(float dpValue) {
|
||||
float scale = mContext.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
public interface OnChooseTimeListener {
|
||||
void onChooseTime(Calendar calendar);
|
||||
}
|
||||
|
||||
public void setOnChooseTimeListener(@NonNull OnChooseTimeListener onChooseTimeListener) {
|
||||
this.onChooseTimeListener = onChooseTimeListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日历切换开始的时候,复位时间轴
|
||||
*/
|
||||
public void reset() {
|
||||
mMinTime = mMaxTime = 0;
|
||||
mCurrentTime = mTodayStart;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setTime(Calendar calendar) {
|
||||
if (calendar != null) {
|
||||
mCurrentTime = calendar.getTimeInMillis();
|
||||
if (mCalendar.get(Calendar.YEAR) != calendar.get(Calendar.YEAR)
|
||||
|| (mCalendar.get(Calendar.YEAR) == calendar.get(Calendar.YEAR)
|
||||
&& mCalendar.get(Calendar.MONTH) != calendar.get(Calendar.MONTH))
|
||||
|| (mCalendar.get(Calendar.YEAR) == calendar.get(Calendar.YEAR)
|
||||
&& mCalendar.get(Calendar.MONTH) == calendar.get(Calendar.MONTH)
|
||||
&& mCalendar.get(Calendar.DAY_OF_MONTH) != calendar.get(Calendar.DAY_OF_MONTH))) {
|
||||
//不是同一天
|
||||
Calendar mCalendar = Calendar.getInstance();
|
||||
mCalendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
|
||||
mInitMillisecond = mCalendar.getTimeInMillis();
|
||||
mTodayStart = mInitMillisecond;
|
||||
}
|
||||
mCalendar.setTimeInMillis(calendar.getTimeInMillis());
|
||||
mMoveDistance = (mInitMillisecond - mCurrentTime) / mMillisecondPerPixel;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTimeInfos(ArrayList<TimeInfo> timeInfos) {
|
||||
this.mTimeInfos.clear();
|
||||
if (timeInfos != null) {
|
||||
this.mTimeInfos.addAll(timeInfos);
|
||||
}
|
||||
if (!this.mTimeInfos.isEmpty()) {
|
||||
long minTime = this.mTimeInfos.get(0).startTime.getTimeInMillis();
|
||||
long maxTime = this.mTimeInfos.get(0).endTime.getTimeInMillis();
|
||||
for (TimeInfo timeInfo : this.mTimeInfos) {
|
||||
if (timeInfo.startTime.getTimeInMillis() < minTime) {
|
||||
minTime = timeInfo.startTime.getTimeInMillis();
|
||||
}
|
||||
if (timeInfo.endTime.getTimeInMillis() > maxTime) {
|
||||
maxTime = timeInfo.endTime.getTimeInMillis();
|
||||
}
|
||||
}
|
||||
mMinTime = minTime;
|
||||
mMaxTime = maxTime;
|
||||
} else {
|
||||
mMinTime = mMaxTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TimeInfo {
|
||||
public TimeInfo(@NonNull Calendar startTime, @NonNull Calendar endTime) {
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Calendar startTime;
|
||||
@NonNull
|
||||
public Calendar endTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
package com.doyou.cv.widget.touch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.OverScroller;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
|
||||
import androidx.core.view.MotionEventCompat;
|
||||
import androidx.core.view.VelocityTrackerCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
/**
|
||||
* 自定义滑动控件(支持多指,支持惯性滑动,支持自定义滚动条)
|
||||
* @autor hongbing
|
||||
* @date 2019-10-12
|
||||
*/
|
||||
public class CustomScrollView extends ViewGroup {
|
||||
|
||||
public static final int MOCK_ITEM_COUNT = 20;
|
||||
|
||||
// 基础变量
|
||||
private Context mContext;
|
||||
// 屏幕宽高
|
||||
private int mScreenW, mScreenH;
|
||||
// 控件宽高
|
||||
private int mWidth, mHeight;
|
||||
|
||||
|
||||
// 手势相关
|
||||
public static final int INVAILD_POINTER = -1;
|
||||
/**
|
||||
* 手势状态:默认
|
||||
*/
|
||||
public static final int SCROLL_STATE_IDLE = 0;
|
||||
/**
|
||||
* 手势状态:滑动中
|
||||
*/
|
||||
public static final int SCROLL_STATE_DRAGGING = 1;
|
||||
/**
|
||||
* 手势状态:惯性滑动中
|
||||
*/
|
||||
public static final int SCROLL_STATE_SETTLING = 2;
|
||||
|
||||
// 手指状态
|
||||
private int mScrollState = SCROLL_STATE_IDLE;
|
||||
private int mLastTouchY;
|
||||
private int mTouchSlop;
|
||||
// 正在用的手指ID
|
||||
private int mScrollPointerId = INVAILD_POINTER;
|
||||
|
||||
|
||||
// 惯性滑动相关
|
||||
private VelocityTracker mVelocityTracker;
|
||||
private int mMinFlingVelocity;
|
||||
private int mMaxFlingVelocity;
|
||||
private ViewFlinger mFlinger = new ViewFlinger();
|
||||
|
||||
public CustomScrollView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CustomScrollView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.init(context);
|
||||
setWillNotDraw(false); // 进度条不能跟随滑动,需要配置这个方法
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mContext = context;
|
||||
int screen[] = Util.getScreenWH(mContext);
|
||||
mScreenW = screen[0];
|
||||
mScreenH = screen[1];
|
||||
|
||||
final ViewConfiguration vc = ViewConfiguration.get(mContext);
|
||||
mTouchSlop = vc.getScaledTouchSlop();
|
||||
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
|
||||
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
LogUtil.logD("init", "滑动阈值:" + mTouchSlop + "-->最小滑动速度 = " + mMinFlingVelocity + "-->最大滑动速度 = " + mMaxFlingVelocity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
boolean scrollbar = awakenScrollBars();
|
||||
LogUtil.logD("onTouchEvent","scrollbar = " + scrollbar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if(mFlinger != null){
|
||||
mFlinger.stop();
|
||||
}
|
||||
if(mVelocityTracker != null){
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int top = 0;
|
||||
for (int i = 0; i < MOCK_ITEM_COUNT; i++) {
|
||||
int width = mScreenW;
|
||||
int height = mScreenH / 2;
|
||||
int left = 0;
|
||||
int right = left + width;
|
||||
int bottom = top + height;
|
||||
|
||||
// 计算控件实际宽高
|
||||
if (bottom > mHeight) {
|
||||
mHeight = bottom;
|
||||
}
|
||||
if (right > mWidth) {
|
||||
mWidth = right;
|
||||
}
|
||||
|
||||
// 生成测试内容
|
||||
TextView tv = new TextView(mContext);
|
||||
if (i % 2 == 0) {
|
||||
tv.setBackgroundColor(Color.BLUE);
|
||||
} else {
|
||||
tv.setBackgroundColor(Color.GREEN);
|
||||
}
|
||||
tv.setTextColor(Color.WHITE);
|
||||
tv.setText("item:" + i);
|
||||
addView(tv);
|
||||
tv.layout(left, top, right, bottom);
|
||||
top += height;
|
||||
// 留20px的行间距
|
||||
top += MOCK_ITEM_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mVelocityTracker == null) {
|
||||
// 获取速度追踪器实例
|
||||
mVelocityTracker = VelocityTracker.obtain();
|
||||
}
|
||||
boolean eventAddedToVelocityTracker = false;
|
||||
// 包装event给新的对象,用于处理惯性滑动逻辑
|
||||
final MotionEvent vEvent = MotionEvent.obtain(event);
|
||||
|
||||
// 支持双指action
|
||||
final int action = MotionEventCompat.getActionMasked(event);
|
||||
// 手指在集合中的索引值
|
||||
final int actionIndex = MotionEventCompat.getActionIndex(event);
|
||||
LogUtil.logD("onTouchEvent", "action = " + action + "->actionIndex = " + actionIndex);
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
setScrollState(SCROLL_STATE_IDLE);
|
||||
mScrollPointerId = event.getPointerId(0);
|
||||
mLastTouchY = (int) (event.getY() + 0.5f);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
mScrollPointerId = event.getPointerId(actionIndex);
|
||||
mLastTouchY = (int) (event.getY(actionIndex) + 0.5f);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
// 获取最新手指的索引
|
||||
final int index = event.findPointerIndex(mScrollPointerId); /////
|
||||
if (index < 0) {
|
||||
LogUtil.logE("onTouchEvent", "检测不到手指...");
|
||||
return false;
|
||||
}
|
||||
|
||||
int y = (int) (event.getY(index) + 0.5f); //////
|
||||
int dy = mLastTouchY - y;
|
||||
if (mScrollState != SCROLL_STATE_DRAGGING) { // 判定不是滑动中,检测滑动距离是否满足滑动阈值及相关处理
|
||||
boolean startScroll = false;
|
||||
if (Math.abs(dy) > mTouchSlop) { // 滑动判定
|
||||
if (dy > 0) { // 上滑
|
||||
dy -= mTouchSlop;
|
||||
} else { // 下滑
|
||||
dy += mTouchSlop;
|
||||
}
|
||||
startScroll = true;
|
||||
}
|
||||
if (startScroll) {
|
||||
setScrollState(SCROLL_STATE_DRAGGING);
|
||||
}
|
||||
}
|
||||
if (mScrollState == SCROLL_STATE_DRAGGING) { // 滑动中
|
||||
mLastTouchY = y;
|
||||
// scrollBy(0, dy);
|
||||
constrainScrollBy(0, dy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
// 离开的是正在操作的手指,将操作值转移到新的手指上
|
||||
if (event.getPointerId(actionIndex) == mScrollPointerId) {
|
||||
// 接受新的手指
|
||||
final int newIndex = actionIndex == 0 ? 1 : 0;
|
||||
mScrollPointerId = event.getPointerId(newIndex);
|
||||
mLastTouchY = (int) (event.getY(newIndex) + 0.5f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
// 添加事件到追踪器
|
||||
mVelocityTracker.addMovement(vEvent);
|
||||
eventAddedToVelocityTracker = true;
|
||||
// 确定速度
|
||||
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
|
||||
float yVelocity = -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId);
|
||||
if (Math.abs(yVelocity) < mMinFlingVelocity) {
|
||||
yVelocity = 0F;
|
||||
} else {
|
||||
yVelocity = Math.max(-mMaxFlingVelocity, Math.min(yVelocity, mMaxFlingVelocity));
|
||||
}
|
||||
LogUtil.logD("onTouchEvent", "速度值:" + yVelocity);
|
||||
// 将速度反应到滑动上
|
||||
if (yVelocity != 0) {
|
||||
mFlinger.fling((int) yVelocity);
|
||||
} else {
|
||||
setScrollState(SCROLL_STATE_IDLE);
|
||||
}
|
||||
// 清理追踪器
|
||||
resetTouch();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL: { // 事件被不可抗拒因素打断,比如关机,来电等
|
||||
resetTouch();
|
||||
}
|
||||
}
|
||||
if (!eventAddedToVelocityTracker) {
|
||||
mVelocityTracker.addMovement(vEvent);
|
||||
}
|
||||
vEvent.recycle();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeVerticalScrollExtent() {
|
||||
LogUtil.logD("computeVerticalScrollExtent","computeVerticalScrollExtent");
|
||||
return super.computeVerticalScrollExtent();
|
||||
// return getScrollY();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeVerticalScrollOffset() {
|
||||
// return mHeight - getScrollY();
|
||||
// return mScreenH / 2;
|
||||
return super.computeVerticalScrollOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int computeVerticalScrollRange() {
|
||||
LogUtil.logD("computeVerticalScrollRange","mHeight = " + mHeight);
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
private void resetTouch() {
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void setScrollState(int state) {
|
||||
if (state == mScrollState) {
|
||||
return;
|
||||
}
|
||||
mScrollState = state;
|
||||
if (mScrollState != SCROLL_STATE_SETTLING) { // 状态不是快速滑动时,需要停止滑动
|
||||
mFlinger.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 边界告急检测及处理
|
||||
*
|
||||
* @param dx
|
||||
* @param dy
|
||||
*/
|
||||
private void constrainScrollBy(int dx, int dy) {
|
||||
LogUtil.logD("constrainScrollBy", "控件W = " + mWidth + "->控件H = " + mHeight);
|
||||
Rect viewport = new Rect();
|
||||
getGlobalVisibleRect(viewport);
|
||||
int height = viewport.height();
|
||||
int width = viewport.width();
|
||||
LogUtil.logD("constrainScrollBy", "viewport.height = " + height + "->viewport.width = " + width);
|
||||
|
||||
// 控件滚动后的x,y
|
||||
int scrollX = getScrollX();
|
||||
int scrollY = getScrollY();
|
||||
LogUtil.logD("constrainScrollBy", "scrollX = " + scrollX + "->scrollY = " + scrollY);
|
||||
|
||||
// 右边界
|
||||
if (mWidth - scrollX - dx < width) {
|
||||
dx = mWidth - scrollX - width;
|
||||
}
|
||||
// 左边界
|
||||
if (-scrollX - dx > 0) {
|
||||
dx = -scrollX;
|
||||
}
|
||||
/**
|
||||
* 下边界,看不懂的换个方式(等价平移):mHeight(控件总高度) = dy(按下到移动差值) + scrollY(滚动y值) + height(可见区域高度)
|
||||
* 是不是就能明白?关键搞懂scrollY就行
|
||||
* 举个例子:100px的容器,里面有个50px的item,那是不是只要滚动50px?so easy
|
||||
*/
|
||||
if (mHeight - scrollY - dy < height) {
|
||||
dy = mHeight - scrollY - height;
|
||||
}
|
||||
// 上边界
|
||||
if (scrollY + dy < 0) {
|
||||
dy = -scrollY;
|
||||
}
|
||||
scrollBy(dx, dy);
|
||||
}
|
||||
|
||||
// 自定义动画插值器 f(x) = (x-1)^5 + 1
|
||||
private static final Interpolator interpolator = input -> {
|
||||
input -= 1.0f;
|
||||
return input * input * input * input * input + 1.0f;
|
||||
};
|
||||
|
||||
private class ViewFlinger implements Runnable {
|
||||
|
||||
private int mLastFlingY = 0;
|
||||
private OverScroller mScroller;
|
||||
private boolean mEatRunOnAnimationRequest = false;
|
||||
private boolean mReSchedulePostAnimationCallback = false;
|
||||
|
||||
public ViewFlinger() {
|
||||
mScroller = new OverScroller(getContext(), interpolator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
disableRunOnAnimationRequests();
|
||||
final OverScroller scroller = mScroller;
|
||||
if (scroller.computeScrollOffset()) {
|
||||
final int y = scroller.getCurrY();
|
||||
int dy = y - mLastFlingY;
|
||||
mLastFlingY = y;
|
||||
// scrollBy(0, dy);
|
||||
constrainScrollBy(0, dy);
|
||||
postOnAnimation();
|
||||
}
|
||||
enableRunOnAnimationRequests();
|
||||
}
|
||||
|
||||
public void fling(int velocityY) {
|
||||
mLastFlingY = 0;
|
||||
setScrollState(SCROLL_STATE_SETTLING);
|
||||
mScroller.fling(0, 0, 0, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
postOnAnimation(); ///////
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
removeCallbacks(this);
|
||||
mScroller.abortAnimation();
|
||||
}
|
||||
|
||||
private void disableRunOnAnimationRequests() {
|
||||
mReSchedulePostAnimationCallback = false;
|
||||
mEatRunOnAnimationRequest = true;
|
||||
}
|
||||
|
||||
private void enableRunOnAnimationRequests() {
|
||||
mEatRunOnAnimationRequest = false;
|
||||
if (mReSchedulePostAnimationCallback) {
|
||||
postOnAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private void postOnAnimation() {
|
||||
if (mEatRunOnAnimationRequest) {
|
||||
mReSchedulePostAnimationCallback = true;
|
||||
} else {
|
||||
removeCallbacks(this);
|
||||
ViewCompat.postOnAnimation(CustomScrollView.this, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.doyou.cv.widget.touch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.OverScroller;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
|
||||
import androidx.core.view.MotionEventCompat;
|
||||
import androidx.core.view.VelocityTrackerCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
/**
|
||||
* @autor hongbing
|
||||
* @date 2019-10-15
|
||||
*/
|
||||
public class CustomScrollViewTest extends ViewGroup {
|
||||
public static final int MOCK_ITEM_COUNT = 20;
|
||||
|
||||
// 基础变量
|
||||
private Context mContext;
|
||||
// 屏幕宽高
|
||||
private int mScreenW, mScreenH;
|
||||
// 控件宽高
|
||||
private int mWidth, mHeight;
|
||||
|
||||
|
||||
public static final int SCROLL_STATE_IDLE = 0;
|
||||
public static final int SCROLL_STATE_DRAGING = 1;
|
||||
public static final int SCROLL_STATE_SETTING = 2;
|
||||
|
||||
private int mScrollState = SCROLL_STATE_IDLE;
|
||||
private int mLastTouchY;
|
||||
private int mTouchSlop;
|
||||
private int mScrollPointerId;
|
||||
|
||||
/////
|
||||
|
||||
private VelocityTracker mTracker;
|
||||
private int mMinVelocity, mMaxVelocity;
|
||||
private ViewFlinger mFlinger = new ViewFlinger();
|
||||
|
||||
public CustomScrollViewTest(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CustomScrollViewTest(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CustomScrollViewTest(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mContext = context;
|
||||
int screen[] = Util.getScreenWH(mContext);
|
||||
mScreenW = screen[0];
|
||||
mScreenH = screen[1];
|
||||
|
||||
ViewConfiguration vc = ViewConfiguration.get(mContext);
|
||||
mTouchSlop = vc.getScaledTouchSlop();
|
||||
mMinVelocity = vc.getScaledMinimumFlingVelocity();
|
||||
mMaxVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (mTracker != null) {
|
||||
mTracker.recycle();
|
||||
mTracker = null;
|
||||
}
|
||||
mFlinger.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int top = 0;
|
||||
for (int i = 0; i < MOCK_ITEM_COUNT; i++) {
|
||||
int width = mScreenW;
|
||||
int height = mScreenH / 2;
|
||||
int left = 0;
|
||||
int right = left + width;
|
||||
int bottom = top + height;
|
||||
|
||||
// 计算控件实际宽高
|
||||
if (bottom > mHeight) {
|
||||
mHeight = bottom;
|
||||
}
|
||||
if (right > mWidth) {
|
||||
mWidth = right;
|
||||
}
|
||||
|
||||
// 生成测试内容
|
||||
TextView tv = new TextView(mContext);
|
||||
if (i % 2 == 0) {
|
||||
tv.setBackgroundColor(Color.BLUE);
|
||||
} else {
|
||||
tv.setBackgroundColor(Color.GREEN);
|
||||
}
|
||||
tv.setTextColor(Color.WHITE);
|
||||
tv.setText("item:" + i);
|
||||
addView(tv);
|
||||
tv.layout(left, top, right, bottom);
|
||||
top += height;
|
||||
// 留20px的行间距
|
||||
top += MOCK_ITEM_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mTracker == null) {
|
||||
mTracker = VelocityTracker.obtain();
|
||||
}
|
||||
int action = MotionEventCompat.getActionMasked(event);
|
||||
int pointerIndex = MotionEventCompat.getActionIndex(event);
|
||||
MotionEvent vEvent = MotionEvent.obtain(event);
|
||||
boolean isAddEvent = false;
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mScrollState = SCROLL_STATE_IDLE;
|
||||
mScrollPointerId = event.getPointerId(0);
|
||||
mLastTouchY = (int) (event.getY() + 0.5f);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
mScrollPointerId = event.getPointerId(pointerIndex);
|
||||
mLastTouchY = (int) (event.getY(pointerIndex) + 0.5f);
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int index = event.findPointerIndex(mScrollPointerId);
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
int currY = (int) (event.getY(index) + 0.5f);
|
||||
int dy = mLastTouchY - currY;
|
||||
if (mScrollState != SCROLL_STATE_DRAGING) {
|
||||
if (Math.abs(dy) > mTouchSlop) {
|
||||
if (dy > 0) {
|
||||
dy -= mTouchSlop;
|
||||
} else {
|
||||
dy += mTouchSlop;
|
||||
}
|
||||
mScrollState = SCROLL_STATE_DRAGING;
|
||||
}
|
||||
}
|
||||
if (mScrollState == SCROLL_STATE_DRAGING) {
|
||||
mLastTouchY = currY;
|
||||
scrollBy(0, dy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
if (event.getPointerId(pointerIndex) == mScrollPointerId) {
|
||||
int newIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mScrollPointerId = event.getPointerId(newIndex);
|
||||
mLastTouchY = (int) (event.getY(newIndex) + 0.5f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
mTracker.addMovement(vEvent);
|
||||
isAddEvent = true;
|
||||
mTracker.computeCurrentVelocity(1000, mMaxVelocity);
|
||||
float velocityY = -VelocityTrackerCompat.getYVelocity(mTracker, mScrollPointerId);
|
||||
if (Math.abs(velocityY) < mMinVelocity) {
|
||||
velocityY = 0F;
|
||||
} else {
|
||||
velocityY = Math.max(-mMaxVelocity, Math.min(velocityY, mMaxVelocity));
|
||||
}
|
||||
if (velocityY != 0) {
|
||||
LogUtil.logD("velocityY", "velocityY = " + velocityY);
|
||||
// 将速度反应到滚动器上去
|
||||
mFlinger.onFling((int) velocityY);
|
||||
} else {
|
||||
mScrollState = SCROLL_STATE_IDLE;
|
||||
}
|
||||
resetTouch();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_CANCEL:{
|
||||
resetTouch();
|
||||
}
|
||||
}
|
||||
if (!isAddEvent) {
|
||||
mTracker.addMovement(vEvent);
|
||||
}
|
||||
vEvent.recycle();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void resetTouch() {
|
||||
if (mTracker != null) {
|
||||
mTracker.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义动画插值器 f(x) = (x-1)^5 + 1
|
||||
private static final Interpolator interpolator = input -> {
|
||||
input -= 1.0f;
|
||||
return input * input * input * input * input + 1.0f;
|
||||
};
|
||||
|
||||
private class ViewFlinger implements Runnable {
|
||||
|
||||
private int mLastFlingY;
|
||||
private OverScroller mScroller;
|
||||
|
||||
public ViewFlinger() {
|
||||
mScroller = new OverScroller(getContext(), interpolator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtil.logD("ViewFlinger", "run...");
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
int y = mScroller.getCurrY();
|
||||
int dis = y - mLastFlingY;
|
||||
mLastFlingY = y;
|
||||
|
||||
LogUtil.logD("ViewFlinger", "dis = " + dis);
|
||||
scrollBy(0, dis);
|
||||
postOnAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
public void onFling(int velocityY) {
|
||||
mLastFlingY = 0;
|
||||
mScrollState = SCROLL_STATE_SETTING;
|
||||
mScroller.fling(0, 0, 0, velocityY,
|
||||
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
postOnAnimation();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
removeCallbacks(this);
|
||||
mScroller.abortAnimation();
|
||||
}
|
||||
|
||||
private void postOnAnimation() {
|
||||
removeCallbacks(this);
|
||||
ViewCompat.postOnAnimation(CustomScrollViewTest.this, this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.doyou.cv.widget.touch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
/**
|
||||
* 自定义可拖拽的FloatingActionButton(悬浮按钮)
|
||||
* @autor hongbing
|
||||
* @date 2018/9/28
|
||||
*/
|
||||
public class DragFloatActionButton extends FloatingActionButton {
|
||||
|
||||
/**
|
||||
* 自定义点击接口
|
||||
*/
|
||||
private OnClickListener mListener;
|
||||
|
||||
public void setFabClickListener(OnClickListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private int _parentHeight, _parentWidth = 0;
|
||||
private int _xDown, _yDown; // 按下的点坐标
|
||||
private int _xDelta, _yDelta; // 随着移动不断变化的点坐标
|
||||
private boolean _isClick; // 是否判定为点击事件
|
||||
|
||||
public DragFloatActionButton(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public DragFloatActionButton(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public DragFloatActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean dispatchTouchEvent(MotionEvent event) {
|
||||
// Common.log_d("201809281348","view->dispatchTouchEvent -> action = " + event.getAction());
|
||||
// /**
|
||||
// * super.dispatchTouchEvent(event)不能放到log里面执行,会影响事件的正常传递,比如此处:return true会不起效果
|
||||
// */
|
||||
//// Common.log_d("201809281348","view->dispatchTouchEvent -> action = " + super.dispatchTouchEvent(event));
|
||||
// return super.dispatchTouchEvent(event);
|
||||
// }
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
LogUtil.logD("201809281348", "view->onTouchEvent ->action = " + event.getAction());
|
||||
final int rawX = (int) event.getRawX();
|
||||
final int rawY = (int) event.getRawY();
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
setPressed(true);
|
||||
_isClick = true;
|
||||
_xDown = rawX;
|
||||
_yDown = rawY;
|
||||
_xDelta = rawX;
|
||||
_yDelta = rawY;
|
||||
ViewGroup parent;
|
||||
if (getParent() != null) {
|
||||
parent = (ViewGroup) getParent();
|
||||
_parentHeight = parent.getHeight();
|
||||
_parentWidth = parent.getWidth();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
int dx = rawX - _xDelta;
|
||||
int dy = rawY - _yDelta;
|
||||
float x = getX() + dx;
|
||||
float y = getY() + dy;
|
||||
//检测是否到达边缘 左上右下
|
||||
x = x < 0 ? 0 : x > _parentWidth - getWidth() ? _parentWidth - getWidth() : x;
|
||||
y = getY() < 0 ? 0 : getY() + getHeight() > _parentHeight ? _parentHeight - getHeight() : y;
|
||||
setX(x);
|
||||
setY(y);
|
||||
_xDelta = rawX;
|
||||
_yDelta = rawY;
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
setPressed(false);
|
||||
int upX = rawX - _xDown;
|
||||
int upY = rawY - _yDown;
|
||||
if (Math.abs(upX) > 6 || Math.abs(upY) > 6) { // 移动了
|
||||
_isClick = false;
|
||||
}
|
||||
if (mListener != null && _isClick) {
|
||||
mListener.onClick(this);
|
||||
}
|
||||
_xDown = 0;
|
||||
_yDown = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true; // 自身消费 false不消费会导致移动无效果,super默认是消费的,但事件会继续往下传递,所以此处最佳是true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
//package com.doyou.cv.widget.touch;
|
||||
//
|
||||
//import android.content.Context;
|
||||
//import android.graphics.Bitmap;
|
||||
//import android.graphics.BitmapFactory;
|
||||
//import android.graphics.Matrix;
|
||||
//import android.graphics.PointF;
|
||||
//import android.support.v7.widget.AppCompatImageView;
|
||||
//import android.util.AttributeSet;
|
||||
//import android.view.MotionEvent;
|
||||
//
|
||||
//import com.doyou.cv.R;
|
||||
//import com.doyou.cv.utils.Utils;
|
||||
//
|
||||
//import java.util.Arrays;
|
||||
//
|
||||
///**
|
||||
// * @autor hongbing
|
||||
// * @date 2019/1/26
|
||||
// */
|
||||
//public class MatrixImageView extends AppCompatImageView {
|
||||
//
|
||||
// /**
|
||||
// * 默认的触摸模式
|
||||
// */
|
||||
// private static final int MODE_NONE = 0x00123;
|
||||
// /**
|
||||
// * 拖拽模式
|
||||
// */
|
||||
// private static final int MODE_DRAG = 0x00321;
|
||||
// /**
|
||||
// * 缩放or旋转模式
|
||||
// */
|
||||
// private static final int MODE_ZOOM = 0x00132;
|
||||
//
|
||||
// private Context mContext;
|
||||
// private int mode; // 当前的触摸模式
|
||||
// private float proMove = 1f; // 上一次手指移动的距离
|
||||
// private float saveRotate = 0f; // 保存了的角度值
|
||||
// private float rotate = 0f; // 旋转的角度
|
||||
//
|
||||
// private float[] preEventCoor; // 上一次各触摸点的坐标集合
|
||||
// private PointF start, mid; // 起点、中点对象
|
||||
// private Matrix currentMatrix, savedMatrix; // 当前和保存了的Matrix对象
|
||||
//
|
||||
// public MatrixImageView(Context context) {
|
||||
// this(context, null);
|
||||
// }
|
||||
//
|
||||
// public MatrixImageView(Context context, AttributeSet attrs) {
|
||||
// super(context, attrs);
|
||||
// mContext = context;
|
||||
// // 初始化
|
||||
// init();
|
||||
// }
|
||||
//
|
||||
// private void init() {
|
||||
// int screenW = getResources().getDisplayMetrics().widthPixels;
|
||||
// int screenH = getResources().getDisplayMetrics().heightPixels;
|
||||
//
|
||||
// /**
|
||||
// * 实例化对象
|
||||
// */
|
||||
// currentMatrix = new Matrix();
|
||||
// savedMatrix = new Matrix();
|
||||
// start = new PointF();
|
||||
// mid = new PointF();
|
||||
//
|
||||
// // 模式初始化
|
||||
// mode = MODE_NONE;
|
||||
//
|
||||
// /**
|
||||
// * 设置图片资源
|
||||
// */
|
||||
// Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.jingy);
|
||||
// bitmap = Bitmap.createScaledBitmap(bitmap, screenW, screenH, true);
|
||||
// setImageBitmap(bitmap);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean onTouchEvent(MotionEvent event) {
|
||||
// switch (event.getAction() & MotionEvent.ACTION_MASK) {
|
||||
// case MotionEvent.ACTION_DOWN: // 单点接触屏幕时
|
||||
// savedMatrix.set(currentMatrix);
|
||||
// start.set(event.getX(), event.getY());
|
||||
// mode = MODE_DRAG;
|
||||
// preEventCoor = null;
|
||||
// break;
|
||||
// case MotionEvent.ACTION_POINTER_DOWN: // 第二个点接触屏幕时
|
||||
// proMove = calSpacing(event);
|
||||
// if (proMove > 10f) {
|
||||
// savedMatrix.set(currentMatrix);
|
||||
// calMidPoint(mid, event);
|
||||
// mode = MODE_ZOOM;
|
||||
// }
|
||||
// preEventCoor = new float[4];
|
||||
// preEventCoor[0] = event.getX(0);
|
||||
// preEventCoor[1] = event.getX(1);
|
||||
// preEventCoor[2] = event.getY(0);
|
||||
// preEventCoor[3] = event.getY(1);
|
||||
// saveRotate = calRotation(event);
|
||||
// break;
|
||||
// case MotionEvent.ACTION_UP: // 单点离开屏幕时
|
||||
// Utils.logD("201801261717","ACTION_UP");
|
||||
// mode = MODE_NONE;
|
||||
// preEventCoor = null;
|
||||
// break;
|
||||
// case MotionEvent.ACTION_POINTER_UP: // 第二个点离开屏幕时
|
||||
// Utils.logD("201801261717", "ACTION_POINTER_UP");
|
||||
// mode = MODE_DRAG;
|
||||
// savedMatrix.set(currentMatrix);
|
||||
//// preEventCoor = null;
|
||||
// Utils.logD("201901261749", Arrays.toString(preEventCoor));
|
||||
// break;
|
||||
// case MotionEvent.ACTION_MOVE: // 触摸点移动时
|
||||
// /**
|
||||
// * 单点触摸拖拽平移
|
||||
// */
|
||||
// if (mode == MODE_DRAG) {
|
||||
// currentMatrix.set(savedMatrix);
|
||||
// float dx = event.getX() - start.x;
|
||||
// float dy = event.getY() - start.y;
|
||||
// currentMatrix.postTranslate(dx, dy);
|
||||
// }
|
||||
// /**
|
||||
// * 两点触控拖放旋转
|
||||
// */
|
||||
// else if (mode == MODE_ZOOM) {
|
||||
// if (event.getPointerCount() >= 2) {
|
||||
// float currentMove = calSpacing(event);
|
||||
// /**
|
||||
// * 指尖移动距离大于10f缩放
|
||||
// */
|
||||
// if (currentMove > 10f) {
|
||||
// float scale = currentMove / proMove;
|
||||
// currentMatrix.postScale(scale, scale, mid.x, mid.y);
|
||||
// }
|
||||
// /**
|
||||
// * 保持两点时旋转
|
||||
// */
|
||||
// if (preEventCoor != null) {
|
||||
// rotate = calRotation(event);
|
||||
// float r = rotate - saveRotate;
|
||||
// currentMatrix.postRotate(r, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
|
||||
// }
|
||||
// } else {
|
||||
// mode = MODE_DRAG;
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// setImageMatrix(currentMatrix);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 计算两个触摸点间的距离
|
||||
// * @param event
|
||||
// * @return
|
||||
// */
|
||||
// private float calSpacing(MotionEvent event) {
|
||||
// float x = event.getX(0) - event.getX(1);
|
||||
// float y = event.getY(0) - event.getY(1);
|
||||
// return (float) Math.sqrt(x * x + y * y);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 计算两个触摸点的中点坐标
|
||||
// *
|
||||
// * @param pointF
|
||||
// * @param event
|
||||
// */
|
||||
// private void calMidPoint(PointF pointF, MotionEvent event) {
|
||||
// float x = event.getX(0) + event.getX(1);
|
||||
// float y = event.getY(0) + event.getY(1);
|
||||
// pointF.set(x / 2, y / 2);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 计算旋转角度
|
||||
// *
|
||||
// * @param event
|
||||
// */
|
||||
// private float calRotation(MotionEvent event) {
|
||||
// double deltaX = event.getX(0) - event.getX(1);
|
||||
// double deltaY = event.getY(0) - event.getY(1);
|
||||
// double radius = Math.atan2(deltaY, deltaX);
|
||||
// return (float) Math.toDegrees(radius);
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,275 @@
|
||||
package com.doyou.cv.widget.touch;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.OverScroller;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
/**
|
||||
* 手势操作图片,具备功能
|
||||
* 1.外部图片导入
|
||||
* 2.拖拽
|
||||
* 3.双击放大 / 缩小
|
||||
* 4.手势缩放
|
||||
* 5.边缘检测
|
||||
*
|
||||
* @autor hongbing
|
||||
* @date 2019/5/5
|
||||
*/
|
||||
public class MatrixView extends View implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, Runnable {
|
||||
|
||||
/**
|
||||
* 放大系数
|
||||
*/
|
||||
public static final float OVER_SCALE = 1.5f;
|
||||
|
||||
private Bitmap mBitmap;
|
||||
private Paint mPaint;
|
||||
// 屏幕宽高
|
||||
private int mScreenW, mScreenH;
|
||||
// 偏移量
|
||||
private float mOffsetX, mOffsetY;
|
||||
// 图片偏移量
|
||||
private float mOriginalOffsetX, mOriginalOffsetY;
|
||||
// 手势按下
|
||||
private float mDownX, mDownY;
|
||||
// 图片的偏移量
|
||||
private float mBmpOffsetX, mBmpOffsetY;
|
||||
// 手势
|
||||
private GestureDetectorCompat mDetector;
|
||||
// 最大、最小缩放值
|
||||
private float mBigScale, mSmallScale;
|
||||
// 是否最大
|
||||
private boolean mIsBig;
|
||||
// 缩放系数渐变值
|
||||
private float mScaleFraction;
|
||||
// 动画
|
||||
private ObjectAnimator mAnimator;
|
||||
// 惯性滑动器
|
||||
private OverScroller mOverScroller;
|
||||
|
||||
public float getScaleFraction() {
|
||||
return mScaleFraction;
|
||||
}
|
||||
|
||||
public void setScaleFraction(float scaleFraction) {
|
||||
mScaleFraction = scaleFraction;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public MatrixView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mBitmap = Util.getAvatar(getResources(), DensityUtil.dp2px(220));
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mScreenW = getResources().getDisplayMetrics().widthPixels;
|
||||
mScreenH = getResources().getDisplayMetrics().heightPixels;
|
||||
LogUtil.logD("初始化", "mBitmap.H = " + mBitmap
|
||||
.getHeight() + "->屏幕宽高 = " + mScreenW + "-" + mScreenH);
|
||||
|
||||
mDetector = new GestureDetectorCompat(context, this);
|
||||
mOverScroller = new OverScroller(context);
|
||||
}
|
||||
|
||||
public ObjectAnimator getAnimator() {
|
||||
if (mAnimator == null) {
|
||||
mAnimator = ObjectAnimator.ofFloat(this, "scaleFraction", 0f, 1f);
|
||||
}
|
||||
return mAnimator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修复图片超出边界的处理
|
||||
*/
|
||||
private void fixOverOffset(){
|
||||
float overX = (mBigScale * mBitmap.getWidth() - getWidth()) / 2;
|
||||
float overY = (mBigScale * mBitmap.getHeight() - getHeight()) / 2;
|
||||
LogUtil.logD("onScroll", "mOffsetX = " + mOffsetX + "->mOffsetY = " + mOffsetY + "->overX = " + overX + "->overY = " + overY);
|
||||
if (mOffsetX < -overX) {
|
||||
mOffsetX = -overX;
|
||||
}
|
||||
if (mOffsetX > overX) {
|
||||
mOffsetX = overX;
|
||||
}
|
||||
if (mOffsetY < -overY) {
|
||||
mOffsetY = -overY;
|
||||
}
|
||||
if (mOffsetY > overY) {
|
||||
mOffsetY = overY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mOriginalOffsetX = (getWidth() - mBitmap.getWidth()) / 2;
|
||||
mOriginalOffsetY = (getHeight() - mBitmap.getHeight()) / 2;
|
||||
if ((float) mBitmap.getWidth() / mBitmap.getHeight() > (float) getWidth() / getHeight()) { // 图片宽高比 > view的宽高比
|
||||
mSmallScale = (float) getWidth() / mBitmap.getWidth();
|
||||
mBigScale = (float) getHeight() / mBitmap.getHeight() + OVER_SCALE;
|
||||
} else {
|
||||
mSmallScale = (float) getHeight() / mBitmap.getHeight();
|
||||
mBigScale = (float) getWidth() / mBitmap.getWidth() + OVER_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.translate(mOffsetX * mScaleFraction, mOffsetY * mScaleFraction);
|
||||
LogUtil.logD("缩放系数", "mSmallScale = " + mSmallScale + "->mBigScale = " + mBigScale);
|
||||
// float scale = mIsBig ? mBigScale : mSmallScale;
|
||||
float scale = mSmallScale + (mBigScale - mSmallScale) * mScaleFraction;
|
||||
canvas.scale(scale, scale, getWidth() / 2, getHeight() / 2);
|
||||
canvas.drawBitmap(mBitmap, mOriginalOffsetX, mOriginalOffsetY, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// if(!mIsBig){
|
||||
// switch (event.getActionMasked()) {
|
||||
// case MotionEvent.ACTION_DOWN:
|
||||
// mDownX = event.getX();
|
||||
// mDownY = event.getY();
|
||||
//
|
||||
// mBmpOffsetX = mOriginalOffsetX;
|
||||
// mBmpOffsetY = mOriginalOffsetY;
|
||||
// break;
|
||||
// case MotionEvent.ACTION_MOVE:
|
||||
// mOriginalOffsetX = mBmpOffsetX + event.getX() - mDownX;
|
||||
// mOriginalOffsetY = mBmpOffsetY + event.getY() - mDownY;
|
||||
// Common.log_d("ACTION_MOVE", "mOriginalOffsetX = " + mOriginalOffsetX + "->mOriginalOffsetY = " + mOriginalOffsetY);
|
||||
// // 临界点处理,避免图片滑出屏幕可视区域
|
||||
// if (mOriginalOffsetX < 0f) {
|
||||
// mOriginalOffsetX = 0f;
|
||||
// }
|
||||
// if (mOriginalOffsetX > (getWidth() - mBitmap.getWidth())) {
|
||||
// mOriginalOffsetX = getWidth() - mBitmap.getWidth();
|
||||
// }
|
||||
// if (mOriginalOffsetY < 0f) {
|
||||
// mOriginalOffsetY = 0f;
|
||||
// }
|
||||
// if (mOriginalOffsetY > (getHeight() - mBitmap.getHeight())) {
|
||||
// mOriginalOffsetY = getHeight() - mBitmap.getHeight();
|
||||
// }
|
||||
// invalidate();
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
return mDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
LogUtil.logD("DoubleTap", "onSingleTapConfirmed...");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
LogUtil.logD("DoubleTap", "onDoubleTap...");
|
||||
mIsBig = !mIsBig;
|
||||
if (mIsBig) {
|
||||
// e.getX() - getWidth() / 2 // 放大前的x位置
|
||||
// (e.getX() - getWidth() / 2) * mBigScale / mSmallScale; // 放大后的x位置
|
||||
// 计算放到后坐标回到点击位置
|
||||
mOffsetX = (e.getX() - getWidth() / 2f) * (1 - mBigScale / mSmallScale);
|
||||
mOffsetY = (e.getY() - getHeight() / 2f) * (1 - mBigScale / mSmallScale);
|
||||
fixOverOffset();
|
||||
getAnimator().start();
|
||||
} else {
|
||||
getAnimator().reverse();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
LogUtil.logD("DoubleTap", "onDoubleTapEvent...");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 滑动
|
||||
*
|
||||
* @param e1
|
||||
* @param e2
|
||||
* @param distanceX :旧点 - 新点
|
||||
* @param distanceY
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (mIsBig) { // 最大模式下才能移动
|
||||
mOffsetX = mOffsetX + (-distanceX);
|
||||
mOffsetY = mOffsetY + (-distanceY);
|
||||
fixOverOffset();
|
||||
invalidate();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 惯性滑动
|
||||
*
|
||||
* @param e1
|
||||
* @param e2
|
||||
* @param velocityX
|
||||
* @param velocityY
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
if (mIsBig) {
|
||||
mOverScroller.fling((int) mOffsetX, (int) mOffsetY, (int) velocityX, (int) velocityY,
|
||||
-(int) ((mBitmap.getWidth() * mBigScale - getWidth()) / 2), (int) ((mBitmap.getWidth() * mBigScale - getWidth()) / 2),
|
||||
-(int) ((mBitmap.getHeight() * mBigScale - getHeight()) / 2), (int) ((mBitmap.getHeight() * mBigScale - getHeight()) / 2),
|
||||
120, 120); // 120像素的回弹效果
|
||||
ViewCompat.postOnAnimation(this, this);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (mOverScroller.computeScrollOffset()) {
|
||||
mOffsetX = mOverScroller.getCurrX();
|
||||
mOffsetY = mOverScroller.getCurrY();
|
||||
invalidate();
|
||||
ViewCompat.postOnAnimation(this, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
package com.doyou.cv.widget.touch;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.drawable.AnimationDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Animation.AnimationListener;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Scroller;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.R;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
public class RefreshView extends FrameLayout {
|
||||
|
||||
private Context mContext;
|
||||
private RelativeLayout mTouchView;
|
||||
private ImageView mAnimImg;
|
||||
private ImageView mAnimList;
|
||||
private int mScreenW; // 屏幕分辨率
|
||||
private int mHeadW, mHeadH;// 头部笑脸的宽度,高度
|
||||
private int mDownY;
|
||||
private int mMoveY;
|
||||
private int mUpY; // 抬手时已滑动的高度值
|
||||
private int mRadius = 10;// 小圆点半径,默认为10
|
||||
private int mScrDistance = DensityUtil.dp2px(180); // 下滑最大高度
|
||||
private int mInitLeftPX, mInitRightPX;
|
||||
private int mBmpL, mBmpT; // 头部位图左上值
|
||||
private Scroller mScroller; // 图片控件滚动器
|
||||
private Scroller mScroller1; // 笑脸控件滚动器
|
||||
private Paint mPaint;
|
||||
private Bitmap mHeadBmp;
|
||||
private boolean isGonePoint;// 是否隐藏圆点,默认false
|
||||
private Animation mTranslateAnim;
|
||||
private AnimationDrawable mAniDraw;
|
||||
private boolean mAnimIsIng; // 动画是否进行中...
|
||||
|
||||
public RefreshView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public RefreshView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public RefreshView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setWillNotDraw(false);
|
||||
View root = LayoutInflater.from(context).inflate(
|
||||
R.layout.pullrefesh_layout, this, false);
|
||||
mTouchView = root.findViewById(R.id.id_touchImg);
|
||||
mAnimImg = root.findViewById(R.id.id_animImg);
|
||||
mAnimList = root.findViewById(R.id.id_animList);
|
||||
addView(root);
|
||||
mAnimImg.setVisibility(GONE);
|
||||
mAnimList.setVisibility(GONE);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
mContext = context;
|
||||
|
||||
DisplayMetrics dm = getResources().getDisplayMetrics();
|
||||
mScreenW = dm.widthPixels;
|
||||
mHeadBmp = BitmapFactory.decodeResource(getResources(), R.drawable.load_more_1);
|
||||
|
||||
// mScrDistance = mScreenH / 3 - 46;
|
||||
mHeadW = mHeadBmp.getWidth();
|
||||
mHeadH = mHeadBmp.getHeight();
|
||||
mInitLeftPX = mScreenW / 2 - mHeadW;
|
||||
mInitRightPX = mScreenW / 2 + mHeadW;
|
||||
mBmpL = mScreenW / 2 - mHeadBmp.getWidth() / 2;
|
||||
mBmpT = 0;
|
||||
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mPaint.setColor(Color.RED);
|
||||
mPaint.setStyle(Style.FILL);//设置非填充
|
||||
mScroller = new Scroller(mContext, new DecelerateInterpolator());
|
||||
mScroller1 = new Scroller(mContext, new DecelerateInterpolator());
|
||||
|
||||
mAniDraw = (AnimationDrawable) mAnimList.getDrawable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始由上到下的动画
|
||||
*/
|
||||
private void startUpToDownAnim() {
|
||||
mAnimIsIng = true;
|
||||
mAnimImg.postDelayed(() -> {
|
||||
mAnimImg.setVisibility(View.VISIBLE);
|
||||
LogUtil.logD("startUpToDownAnim", "头部动画view的右坐标:" + mAnimImg.getRight());
|
||||
mTranslateAnim = new TranslateAnimation(0, 0, -mUpY, mUpY);
|
||||
mTranslateAnim.setDuration(200);
|
||||
mTranslateAnim.setFillAfter(false);
|
||||
// // 越来越快
|
||||
mTranslateAnim.setInterpolator(new AccelerateInterpolator());
|
||||
LogUtil.logD("startUpToDownAnim", "动画滑动的距离:" + mUpY);
|
||||
mAnimImg.setAnimation(mTranslateAnim);
|
||||
mTranslateAnim.setAnimationListener(new AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
LogUtil.logD("startUpToDownAnim","动画开始...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
LogUtil.logD("startUpToDownAnim","动画结束...");
|
||||
|
||||
mAnimImg.setVisibility(GONE);
|
||||
mAnimList.setVisibility(VISIBLE);
|
||||
mAnimList.setY(mUpY);
|
||||
mAniDraw.start();
|
||||
|
||||
stopRefresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
});
|
||||
}, 250);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消由上到下的动画
|
||||
*/
|
||||
private void cancelUpToDownAnim() {
|
||||
mAnimImg.clearAnimation();
|
||||
mTranslateAnim = new TranslateAnimation(0, 0, mUpY, -mUpY);
|
||||
mTranslateAnim.setDuration(500);
|
||||
mTranslateAnim.setFillAfter(false);
|
||||
// 越来越慢
|
||||
mTranslateAnim.setInterpolator(new DecelerateInterpolator());
|
||||
LogUtil.logD("cancelUpToDownAnim","动画滑动的距离:" + mUpY);
|
||||
mAnimImg.setAnimation(mTranslateAnim);
|
||||
mTranslateAnim.setAnimationListener(new AnimationListener() {
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
mAnimImg.setVisibility(GONE);
|
||||
mAnimList.setY(mUpY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
// 绘制顶部两个圆点
|
||||
if (isGonePoint) {
|
||||
// 隐藏
|
||||
canvas.drawCircle(mInitLeftPX, 0, 0, mPaint);
|
||||
canvas.drawCircle(mInitRightPX, 0, 0, mPaint);
|
||||
} else {
|
||||
// 显示
|
||||
canvas.drawCircle(mInitLeftPX, 0, mRadius, mPaint);
|
||||
canvas.drawCircle(mInitRightPX, 0, mRadius, mPaint);
|
||||
}
|
||||
|
||||
// 绘制头部图片
|
||||
canvas.drawBitmap(mHeadBmp, mBmpL, mBmpT, mPaint);
|
||||
// 绘制斜线
|
||||
canvas.drawLine(mInitLeftPX, 0, mBmpL, mBmpT + mHeadH / 2, mPaint);
|
||||
canvas.drawLine(mInitRightPX, 0, mBmpL + mHeadW, mBmpT + mHeadH / 2, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mAnimIsIng) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
isGonePoint = false;
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDownY = (int) event.getY();
|
||||
return true;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mMoveY = (int) event.getY() - mDownY;
|
||||
|
||||
if (mMoveY > mHeadH * 2) { // 如果下拉的距离大于2倍的控件高度,则让控件悬在外部控件的顶上
|
||||
mBmpT = mMoveY - mHeadH;
|
||||
// 限制下拉距离
|
||||
if (mMoveY > mScrDistance) {
|
||||
mBmpT = mScrDistance - mHeadH;
|
||||
}
|
||||
} else {
|
||||
mBmpT = mMoveY - mMoveY / 2;
|
||||
}
|
||||
|
||||
// 同样外部控件也需要做下拉距离限制
|
||||
if (mMoveY > mScrDistance) {
|
||||
mTouchView.setY(mScrDistance);
|
||||
} else {
|
||||
mTouchView.setY(mMoveY);
|
||||
}
|
||||
|
||||
postInvalidate();
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
mUpY = mBmpT;
|
||||
mRadius = 0;
|
||||
mScroller1.startScroll(0, mBmpT, 0, -mBmpT - mHeadBmp.getHeight(), 250);
|
||||
invalidate();
|
||||
|
||||
startUpToDownAnim();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止刷新动画
|
||||
*/
|
||||
public void stopRefresh() {
|
||||
int time = 0;
|
||||
for (int i = 0; i < mAniDraw.getNumberOfFrames(); i++) {
|
||||
time += mAniDraw.getDuration(i); // 计算动画播放的时间
|
||||
}
|
||||
|
||||
postDelayed(new Runnable() {
|
||||
/**
|
||||
* 逐帧动画结束
|
||||
*/
|
||||
public void run() {
|
||||
// 隐藏逐帧控件
|
||||
mAnimList.setVisibility(GONE);
|
||||
// 注:结束后需要调用stop不然不能重复实现动画效果
|
||||
mAniDraw.stop();
|
||||
// 动画全部结束
|
||||
mAnimIsIng = false;
|
||||
mScroller.startScroll(0, (int) mTouchView.getY(), 0, -(int) mTouchView.getY(),
|
||||
300);
|
||||
isGonePoint = true;
|
||||
mUpY = 0;
|
||||
mDownY = 0;
|
||||
mMoveY = 0;
|
||||
mRadius = 10;
|
||||
mBmpT = -mHeadBmp.getHeight();
|
||||
invalidate();
|
||||
}
|
||||
}, time + 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
super.computeScroll();
|
||||
// 先判断mScroller滚动是否完成
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
mTouchView.setY(mScroller.getCurrY());
|
||||
// 必须调用该方法,否则不一定能看到滚动效果
|
||||
postInvalidate();
|
||||
}
|
||||
if (mScroller1.computeScrollOffset()) {
|
||||
mBmpT = mScroller1.getCurrY();
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package com.doyou.cv.widget.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import com.doyou.cv.callback.Rotatable;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatImageButton;
|
||||
|
||||
/**
|
||||
* 跟随设备旋转改变显示位置的图片按钮
|
||||
* @autor hongbing
|
||||
* @date 2019-10-08
|
||||
*/
|
||||
public class CircleButton extends AppCompatImageButton implements Rotatable {
|
||||
|
||||
private static final String TAG = "CircleButton";
|
||||
private GradientDrawable mNormal;
|
||||
private GradientDrawable mPressed;
|
||||
private GradientDrawable mSelected;
|
||||
private GradientDrawable mDisabled;
|
||||
|
||||
private static int[] STATE_SET_DISABLED = new int[]{android.R.attr.state_enabled};
|
||||
private static int[] STATE_SET_PRESSED = new int[]{android.R.attr.state_pressed};
|
||||
private static int[] STATE_SET_SELECTED = new int[android.R.attr.state_selected];
|
||||
private static int[] STATE_SET_NONE = new int[]{};
|
||||
|
||||
private boolean mEnableAnimation = true;
|
||||
private boolean mClockwise = false;
|
||||
|
||||
private static final int ANIMATION_SPEED = 270; // 270 deg/sec
|
||||
|
||||
private int mCurrentDegree = 0; // [0, 359]
|
||||
private int mStartDegree = 0;
|
||||
private int mTargetDegree = 0;
|
||||
|
||||
private long mAnimationStartTime = 0;
|
||||
private long mAnimationEndTime = 0;
|
||||
|
||||
public CircleButton(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CircleButton(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
|
||||
}
|
||||
|
||||
public CircleButton(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setStroke(int width, int color) {
|
||||
mNormal.setStroke(width, color);
|
||||
mPressed.setStroke(width, color);
|
||||
mSelected.setStroke(width, color);
|
||||
}
|
||||
|
||||
public void setBackgroundNormalColor(int color){
|
||||
mNormal.setColor(color);
|
||||
}
|
||||
|
||||
public void setBackgroundPressedColor(int color){
|
||||
mPressed.setColor(color);
|
||||
}
|
||||
|
||||
public void setBackgroundSelectedColor(int color){
|
||||
mSelected.setColor(color);
|
||||
}
|
||||
|
||||
public void setBackgroundDisabledColor(int color){
|
||||
mDisabled.setColor(color);
|
||||
}
|
||||
|
||||
public void setDisabledStroke(int width,int color){
|
||||
mDisabled.setStroke(width, color);
|
||||
}
|
||||
|
||||
public int getDegree(){
|
||||
return mTargetDegree;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mNormal = new GradientDrawable();
|
||||
mNormal.setShape(GradientDrawable.OVAL);
|
||||
|
||||
mPressed = new GradientDrawable();
|
||||
mPressed.setShape(GradientDrawable.OVAL);
|
||||
|
||||
mSelected = new GradientDrawable();
|
||||
mSelected.setShape(GradientDrawable.OVAL);
|
||||
|
||||
mDisabled = new GradientDrawable();
|
||||
mDisabled.setShape(GradientDrawable.OVAL);
|
||||
|
||||
StateListDrawable background = new StateListDrawable();
|
||||
background.addState(STATE_SET_DISABLED, mDisabled);
|
||||
background.addState(STATE_SET_PRESSED, mPressed);
|
||||
background.addState(STATE_SET_SELECTED, mSelected);
|
||||
background.addState(STATE_SET_NONE, mNormal);
|
||||
setBackgroundDrawable(background);
|
||||
|
||||
LogUtil.logD(TAG,"init....");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrientation(int degree, boolean animation) {
|
||||
LogUtil.logD(TAG,"setOrientation degree...." + degree);
|
||||
mEnableAnimation = animation;
|
||||
// make sure in the range of [0, 359]
|
||||
degree = degree >= 0 ? degree % 360 : degree % 360 + 360;
|
||||
if (degree == mTargetDegree) {
|
||||
return;
|
||||
}
|
||||
mTargetDegree = degree;
|
||||
if (mEnableAnimation) {
|
||||
mStartDegree = mCurrentDegree;
|
||||
mAnimationStartTime = AnimationUtils.currentAnimationTimeMillis();
|
||||
|
||||
int diff = mTargetDegree - mCurrentDegree;
|
||||
boolean clockwise = diff == 180;
|
||||
diff = diff >= 0 ? diff : 360 + diff; // make it in range [0, 359]
|
||||
|
||||
// Make it in range [-179, 180]. That's the shorted distance between the two angles
|
||||
diff = diff > 180 ? diff - 360 : diff;
|
||||
|
||||
mClockwise = diff >= 0 && diff < 180 || clockwise;
|
||||
mAnimationEndTime = mAnimationStartTime + Math.abs(diff) * 1000 / ANIMATION_SPEED;
|
||||
|
||||
} else {
|
||||
mCurrentDegree = mTargetDegree;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
LogUtil.logD(TAG,"onDraw start....");
|
||||
|
||||
Drawable drawable = getDrawable();
|
||||
if (drawable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect bounds = drawable.getBounds();
|
||||
int w = bounds.right - bounds.left;
|
||||
int h = bounds.bottom - bounds.top;
|
||||
|
||||
if (w == 0 || h == 0) {
|
||||
return; // nothing to draw
|
||||
}
|
||||
|
||||
if (mCurrentDegree != mTargetDegree) {
|
||||
long time = AnimationUtils.currentAnimationTimeMillis();
|
||||
if (time < mAnimationEndTime) {
|
||||
int deltaTime = (int) (time - mAnimationStartTime);
|
||||
int degree = mStartDegree + ANIMATION_SPEED * (mClockwise ? deltaTime : -deltaTime) / 1000;
|
||||
degree = degree >= 0 ? degree % 360 : degree % 360 + 360;
|
||||
mCurrentDegree = degree;
|
||||
invalidate();
|
||||
} else {
|
||||
mCurrentDegree = mTargetDegree;
|
||||
}
|
||||
}
|
||||
|
||||
int left = getPaddingLeft();
|
||||
int top = getPaddingTop();
|
||||
int right = getPaddingRight();
|
||||
int bottom = getPaddingBottom();
|
||||
int width = getWidth() - left - right;
|
||||
int height = getHeight() - top - bottom;
|
||||
|
||||
int saveCount = canvas.getSaveCount();
|
||||
|
||||
// Scale down the image first if required.
|
||||
if ((getScaleType() == ScaleType.FIT_CENTER) && ((width < w) || (height < h))) {
|
||||
float ratio = Math.min((float) width / w, (float) height / h);
|
||||
canvas.scale(ratio,ratio,width / 2.f,height / 2.f);
|
||||
}
|
||||
canvas.translate(left + width / 2,top + height / 2);
|
||||
canvas.rotate(-mCurrentDegree);
|
||||
canvas.translate(-w / 2,-h / 2);
|
||||
drawable.draw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
|
||||
LogUtil.logD(TAG,"onDraw end....");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.doyou.cv.widget.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.text.TextPaint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.utils.Util;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 自定义图文混排控件
|
||||
* @autor hongbing
|
||||
* @date 2019/4/26
|
||||
*/
|
||||
public class ImgTxtMixtureView extends View {
|
||||
|
||||
private static final int IMAGE_WIDTH = (int) DensityUtil.dp2px(140);
|
||||
public static final int IMAGE_PADDING = (int) DensityUtil.dp2px(44);
|
||||
private String text = "Save the date! Android Dev Summit is coming to Sunnyvale, CA on Oct 23-24, 2019.The Android App Bundle speaks Duolingos language, reducing its app size by 56%\n" +
|
||||
"Since 2011, Duolingo has made language learning fun for millions of people worldwide. Offering free King is a leading interactive entertainment company, with popular mobile games " +
|
||||
"such as Candy Crush Saga, Farm Heroes Saga and Bubble Witch 3 Saga. In March 2018, King implemented Google Play Instant and was excited to see the potential impact on removing user " +
|
||||
"acquisition friction, targeting audiences more efficiently, and increasing the effectiveness of game cross-promotion.Saga and Bubble Witch 3 Saga. In March 2018, King implemented Google Play Instant and was excited to see the potential impact on removing user" +
|
||||
"acquisition friction, targeting audiences more efficiently, and increasing the effectiveness of game cross-promotion";
|
||||
|
||||
TextPaint mTextPaint = new TextPaint();
|
||||
Paint mPaint = new Paint();
|
||||
float[] measureWidth = new float[1];
|
||||
Paint.FontMetrics mMetrics = new Paint.FontMetrics();
|
||||
Bitmap mBitmap;
|
||||
|
||||
public ImgTxtMixtureView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
{
|
||||
mTextPaint.setTextSize(DensityUtil.dp2px(15));
|
||||
mPaint.setTextSize(DensityUtil.dp2px(16));
|
||||
mBitmap = Util.getAvatar(getResources(),IMAGE_WIDTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
canvas.drawBitmap(mBitmap,getWidth() - IMAGE_WIDTH,IMAGE_PADDING,mPaint);
|
||||
int length = text.length();
|
||||
float yOffset = mPaint.getFontSpacing();
|
||||
int usableWidth;
|
||||
for (int start = 0, count; start < length; start += count,yOffset += mPaint.getFontSpacing()) {
|
||||
float textTop = yOffset + mMetrics.top;
|
||||
float textBottom = yOffset + mMetrics.bottom;
|
||||
if (textTop > IMAGE_PADDING && textTop < IMAGE_PADDING + IMAGE_WIDTH + DensityUtil.dp2px(12) ||
|
||||
textBottom > IMAGE_PADDING && textBottom < IMAGE_PADDING + IMAGE_WIDTH + DensityUtil.dp2px(12)) {
|
||||
usableWidth = getWidth() - IMAGE_WIDTH;
|
||||
}else{
|
||||
usableWidth = getWidth();
|
||||
}
|
||||
count = mPaint.breakText(text, start, length, true, usableWidth, measureWidth);
|
||||
canvas.drawText(text, start, start + count, 0, yOffset, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.doyou.cv.widget.wave;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.dongni.tools.DensityUtil;
|
||||
import com.doyou.cv.utils.LogUtil;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* 圆形背景水波纹进度效果
|
||||
* <p>
|
||||
* 1、setXfermode必须要设置上层图形的画笔中
|
||||
* 2、用onDraw方法中的canvas设置setXfermode没有效果
|
||||
* 3、setXfermode必须要在画笔用到之前设置
|
||||
*
|
||||
* @autor hongbing
|
||||
* @date 2019-07-22
|
||||
*/
|
||||
public class CircleWaveView extends View {
|
||||
|
||||
|
||||
/**
|
||||
* PorterDuff的16种模式
|
||||
* <p>
|
||||
* 源像素:当前设置xfermode的
|
||||
* 目标像素:固定不变的
|
||||
*
|
||||
* <p>
|
||||
* 1、CLEAR:清除画笔所绘制的内容
|
||||
* 2、SRC_ATOP:丢弃未覆盖目标像素的源像素。
|
||||
* 3、SRC_IN:原始图 & 操作图相交的部分
|
||||
* 4、SRC_OUT:原始图 & 操作图除了相交的部分
|
||||
* 5、6、SRC,SRC_OVER:原始图 & 操作图覆盖在上面
|
||||
* 7、XOR:原始图 & 操作图除了相交的部分
|
||||
* 8、SCREEN:添加源像素和目标像素,然后减去源像素乘以目标
|
||||
* 9、OVERLAY:根据目标颜色将源和目标相乘或屏幕显示
|
||||
* 10、MULTIPLY:将源像素和目标像素相乘。
|
||||
* 11、LIGHTEN:保留源像素和目标像素的最大分量。
|
||||
* 12、DST:丢弃操作图,是原始图保持不变
|
||||
* 13、DST_OUT:保留源像素未覆盖的目标像素
|
||||
* 14、DST_OVER:源像素在目标像素的后面
|
||||
* 15、DST_IN:
|
||||
* 16、DST_ATOP:
|
||||
*/
|
||||
|
||||
/**
|
||||
* 弧长高度
|
||||
*/
|
||||
private static final int ARC_H = DensityUtil.dp2px(32);
|
||||
|
||||
private static final String TAG = "CircleWaveView";
|
||||
private Paint mCirclePaint;
|
||||
private Paint mWavePaint;
|
||||
private PorterDuffXfermode mDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
|
||||
|
||||
private Canvas mCanvas;
|
||||
private Bitmap mBitmap;
|
||||
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mX, mY;
|
||||
private int mPercent;
|
||||
private boolean isLft;
|
||||
private Path mPath, mPath2;
|
||||
|
||||
public CircleWaveView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CircleWaveView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mCirclePaint.setColor(Color.parseColor("#990000FF"));
|
||||
mCirclePaint.setStrokeWidth(10);
|
||||
mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mWavePaint.setColor(Color.parseColor("#7F0000FF"));
|
||||
|
||||
mPath = new Path();
|
||||
mPath2 = new Path();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
|
||||
if (widthMode == MeasureSpec.EXACTLY) {
|
||||
mWidth = widthSize;
|
||||
}
|
||||
|
||||
if (heightMode == MeasureSpec.EXACTLY) {
|
||||
mHeight = heightSize;
|
||||
}
|
||||
|
||||
// 设置画布大小
|
||||
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
|
||||
mCanvas = new Canvas(mBitmap);
|
||||
|
||||
mY = mHeight;
|
||||
setMeasuredDimension(mWidth, mHeight);
|
||||
LogUtil.logD(TAG, "mWidth = " + mWidth + "->mHeight = " + mHeight);
|
||||
|
||||
}
|
||||
|
||||
private int mStart;
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
if (mPercent <= 0) {
|
||||
mBitmap.eraseColor(Color.parseColor("#00000000"));
|
||||
mCanvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mCirclePaint);
|
||||
} else {
|
||||
float target;
|
||||
if (mPercent < 50) {
|
||||
// 计算起始点
|
||||
// (mWidth * mPercent / 50f) 计算进度横向长度
|
||||
mStart = (int) (mWidth / 2 - (mWidth * mPercent / 50f) / 2);
|
||||
target = mWidth * mPercent / 50f;
|
||||
if (mX > target + ARC_H * 2 + 4) { // ARC_H * 2 弧长,mX在经度横切面不断的更新控制点x坐标,起到动画效果
|
||||
isLft = true;
|
||||
} else if (mX < -ARC_H * 2 - 4) {
|
||||
isLft = false;
|
||||
}
|
||||
} else if (mPercent == 50) {
|
||||
mStart = 0;
|
||||
target = mWidth;
|
||||
if (mX > target + 4) { // ARC_H * 2 弧长,mX在经度横切面不断的更新控制点x坐标,起到动画效果
|
||||
isLft = true;
|
||||
} else if (mX < -4) {
|
||||
isLft = false;
|
||||
}
|
||||
} else {
|
||||
mStart = (int) (mWidth / 2 - (mWidth - mWidth * mPercent / 100f) / 2);
|
||||
target = mWidth - mWidth * mPercent / 100f;
|
||||
if (mX > target + ARC_H * 2 + 4) { // ARC_H * 2 弧长,mX在经度横切面不断的更新控制点x坐标,起到动画效果
|
||||
isLft = true;
|
||||
} else if (mX < -ARC_H * 2 - 4) {
|
||||
isLft = false;
|
||||
}
|
||||
}
|
||||
LogUtil.logD(TAG, "mWidth = " + mWidth + "->百分比 = " + mPercent + "->target = " + target + "->mStart = " + mStart + "->mX = " + mX);
|
||||
|
||||
|
||||
if (isLft) {
|
||||
mX = mX - 8;
|
||||
} else {
|
||||
mX = mX + 8;
|
||||
}
|
||||
mY = (int) ((1 - mPercent / 100f) * mHeight);
|
||||
|
||||
mPath.reset();
|
||||
mPath.moveTo(0, mY);
|
||||
mPath.cubicTo(mStart + mX, mY + ARC_H, mStart + mX, mY - ARC_H, mWidth, mY);
|
||||
mPath.lineTo(mWidth, mHeight);
|
||||
mPath.lineTo(0, mHeight);
|
||||
mPath.close();
|
||||
|
||||
mBitmap.eraseColor(Color.parseColor("#00000000"));
|
||||
mCanvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, mCirclePaint);
|
||||
|
||||
// mWavePaint.setColor(Color.parseColor("#B20000FF"));
|
||||
mWavePaint.setColor(Color.parseColor("#990000FF"));
|
||||
mWavePaint.setXfermode(mDuffXfermode);
|
||||
mCanvas.drawPath(mPath, mWavePaint);
|
||||
mWavePaint.setXfermode(null);
|
||||
|
||||
// mPath2.reset();
|
||||
// mPath2.moveTo(0, mY);
|
||||
// mPath2.cubicTo(mStart + mX, mY + ARC_H * 2, mStart + mX, mY - ARC_H * 2, mWidth, mY);
|
||||
// mPath2.lineTo(mWidth, mHeight);
|
||||
// mPath2.lineTo(0, mHeight);
|
||||
// mPath2.close();
|
||||
//
|
||||
// mWavePaint.setColor(Color.parseColor("#990000FF"));
|
||||
// mWavePaint.setXfermode(mXfermode);
|
||||
// mCanvas.drawPath(mPath2, mWavePaint);
|
||||
// mWavePaint.setXfermode(null);
|
||||
}
|
||||
canvas.drawBitmap(mBitmap, 0, 0, null);
|
||||
postInvalidateDelayed(10);
|
||||
}
|
||||
|
||||
public void setPercent(int percent) {
|
||||
mPercent = percent;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 155 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 958 B |
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:oneshot="false" >
|
||||
|
||||
<item
|
||||
android:drawable="@drawable/load_more_32"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_31"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_30"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_29"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_28"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_27"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_26"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_25"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_24"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_23"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_22"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_21"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_20"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_19"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_18"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_17"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_16"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_15"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_14"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_13"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_12"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_11"
|
||||
android:duration="20"/>
|
||||
<item
|
||||
android:drawable="@drawable/load_more_10"
|
||||
android:duration="20"/>
|
||||
|
||||
</animation-list>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="#55ffffff"/>
|
||||
<size
|
||||
android:width="5dp"
|
||||
android:height="5dp"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!--android:background="#FF4081"-->
|
||||
<com.doyou.cv.widget.progress.circle.CircleView
|
||||
android:id="@+id/circle_view"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:max="360"
|
||||
app:cv_boderW="7dp"
|
||||
app:cv_center_style="txt"
|
||||
app:cv_center_txt="风格"
|
||||
app:cv_center_txt_color="#2a2a2a"
|
||||
app:cv_center_txt_size="14sp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/lrv_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/legend_txt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#2a2a2a"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="哈哈"/>
|
||||
|
||||
|
||||
</androidx.constraintlayout.ConstraintLayout>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!--android:background="#FF4081"-->
|
||||
<com.doyou.cv.widget.RingView
|
||||
android:id="@+id/ring_view"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:max="360"
|
||||
app:cv_boderW="7dp"
|
||||
app:cv_center_style="txt"
|
||||
app:cv_center_txt="风格"
|
||||
app:cv_center_txt_color="#2a2a2a"
|
||||
app:cv_center_txt_size="14sp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/lrv_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/id_animImg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/load_more_10"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/id_animList"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:src="@drawable/anim_list"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/id_touchImg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="这是一个仿网易一元夺宝的下拉刷新效果"
|
||||
android:textColor="#999999"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="下拉看看"
|
||||
android:textColor="#333333"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/centerImg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/widget_icon_buy"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/centerTxt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="#2a2a2a"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="客群分布"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!--android:background="#303F9F"-->
|
||||
<com.doyou.cv.widget.taperchart.TaperChart
|
||||
android:id="@+id/tchart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/taper_chart_height"
|
||||
android:layout_marginLeft="@dimen/taper_chart_margin"
|
||||
android:layout_marginRight="@dimen/taper_chart_margin"
|
||||
app:tc_inner_space="28dp"
|
||||
app:tc_quad_x_offset="42dp"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,363 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!--RingView控件样式-->
|
||||
<declare-styleable name="RingView">
|
||||
<!-- 进度条样式,默认值为 system -->
|
||||
<attr name="rv_mode" format="enum">
|
||||
<!-- 圆形进度条 -->
|
||||
<enum name="circle" value="0"/>
|
||||
<!-- 圆形进度条-多段 -->
|
||||
<enum name="more" value="1"/>
|
||||
<!-- 圆形进度条-客群 -->
|
||||
<enum name="group" value="2"/>
|
||||
</attr>
|
||||
<!--中间内容类型(文字|图标|二者都有)-->
|
||||
<attr name="rv_center_style" format="enum">
|
||||
<!--图标-->
|
||||
<enum name="icon" value="0"/>
|
||||
<!--文案-->
|
||||
<enum name="txt" value="1"/>
|
||||
<!--双行文字-->
|
||||
<enum name="double_txt" value="2" />
|
||||
<!--二者都有-->
|
||||
<enum name="all" value="3"/>
|
||||
</attr>
|
||||
<!--中间图资源文件-->
|
||||
<attr name="rv_center_bmp" format="reference"/>
|
||||
<!--中间图文案-->
|
||||
<attr name="rv_center_txt" format="string"/>
|
||||
<!--中间图文案字体大小-->
|
||||
<attr name="rv_center_txt_size" format="dimension"/>
|
||||
<!--中间图文案字体颜色-->
|
||||
<attr name="rv_center_txt_color" format="color" />
|
||||
<!--中间图文结合,两者之间的间距-->
|
||||
<attr name="rv_center_imgtxt_margin" format="dimension" />
|
||||
<!-- 文字颜色,默认值为 #70A800 -->
|
||||
<attr name="rv_textColor" format="reference|color"/>
|
||||
<!-- 文字大小,默认值为 10sp -->
|
||||
<attr name="rv_textSize" format="dimension"/>
|
||||
<!-- 文字和进度条之间的间距,默认值为 4dp -->
|
||||
<attr name="rv_textMargin" format="dimension"/>
|
||||
<!-- 已完成进度的颜色,默认值为 #70A800 -->
|
||||
<attr name="rv_reachedColor" format="reference|color"/>
|
||||
<!-- 已完成进度的高度,默认值为 2dp -->
|
||||
<attr name="rv_reachedHeight" format="dimension"/>
|
||||
<!-- 未完成进度的颜色,默认值为 #CCCCCC -->
|
||||
<attr name="rv_unReachedColor" format="reference|color"/>
|
||||
<!-- 未完成进度的高度,默认值为 1dp -->
|
||||
<attr name="rv_unReachedHeight" format="dimension"/>
|
||||
<!-- 圆形进度条半径,默认值为 16dp -->
|
||||
<attr name="rv_radius" format="dimension"/>
|
||||
<!--边线是否可点击-->
|
||||
<attr name="rv_outer_enable" format="boolean"/>
|
||||
<!--是否需要分割-->
|
||||
<attr name="rv_need_cut" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--CircleView控件样式-->
|
||||
<declare-styleable name="CircleView">
|
||||
<!--中间内容类型(文字|图标|二者都有)-->
|
||||
<attr name="cv_center_style" format="enum">
|
||||
<!--图标-->
|
||||
<enum name="icon" value="0"/>
|
||||
<!--文案-->
|
||||
<enum name="txt" value="1"/>
|
||||
<!--两行文案-->
|
||||
<enum name="double_txt" value="2"/>
|
||||
<!--二者都有-->
|
||||
<enum name="all" value="3"/>
|
||||
</attr>
|
||||
<!--中间图资源文件-->
|
||||
<attr name="cv_center_bmp" format="reference"/>
|
||||
<!--中间图文案-->
|
||||
<attr name="cv_center_txt" format="string"/>
|
||||
<!--中间图文案字体大小-->
|
||||
<attr name="cv_center_txt_size" format="dimension"/>
|
||||
<!--中间图文案字体颜色-->
|
||||
<attr name="cv_center_txt_color" format="color" />
|
||||
<!--中间图文案字体两行文本间的间距-->
|
||||
<attr name="cv_cengter_txt_margin" format="dimension"/>
|
||||
<!-- 圆环宽度,默认值为 2dp -->
|
||||
<attr name="cv_boderW" format="dimension"/>
|
||||
<!-- 圆形进度条半径,默认值为 16dp -->
|
||||
<attr name="cv_radius" format="dimension"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--图例样式-->
|
||||
<declare-styleable name="LegendView">
|
||||
<!--图例列宽-->
|
||||
<attr name="lv_legend_columnW" format="dimension"/>
|
||||
<!--图例行间距-->
|
||||
<attr name="lv_legend_vertical_margin" format="dimension" />
|
||||
<!--图例和圆环的间距,默认为0-->
|
||||
<attr name="lv_legend_circle_margin" format="dimension"/>
|
||||
<!--图例文字大小-->
|
||||
<attr name="lv_legend_font_size" format="dimension"/>
|
||||
<!--图例文字颜色-->
|
||||
<attr name="lv_legend_font_color" format="color"/>
|
||||
<!--图例文案和圆点的间距-->
|
||||
<attr name="lv_legend_labelAndPoint_margin" format="dimension"/>
|
||||
<!--图例每行第一个左侧偏移量-->
|
||||
<attr name="lv_legend_offset_left" format="dimension"/>
|
||||
<!--图例样式-->
|
||||
<attr name="lv_legend_style" format="enum">
|
||||
<!--圆点-->
|
||||
<enum name="circle" value="0"/>
|
||||
<!--方点-->
|
||||
<enum name="square" value="1"/>
|
||||
<!--横线-->
|
||||
<enum name="line" value="2"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<!--自带图例的圆环控件样式-->
|
||||
<declare-styleable name="LegendRingView">
|
||||
<!--中间内容类型(文字|图标|二者都有)-->
|
||||
<attr name="lrv_circle_center_style" format="enum">
|
||||
<!--图标-->
|
||||
<enum name="icon" value="0"/>
|
||||
<!--文案-->
|
||||
<enum name="txt" value="1"/>
|
||||
<!--两行文案-->
|
||||
<enum name="double_txt" value="2"/>
|
||||
<!--二者都有-->
|
||||
<enum name="all" value="3"/>
|
||||
</attr>
|
||||
<!--圆环视图高度-->
|
||||
<attr name="lrv_circle_height" format="dimension"/>
|
||||
<!-- 圆环宽度,默认值为 2dp -->
|
||||
<attr name="lrv_circle_boderW" format="dimension"/>
|
||||
<!--圆环内间距-上,默认为0-->
|
||||
<attr name="lrv_circle_paddingTop" format="dimension"/>
|
||||
<!--圆环内间距-下,默认为0-->
|
||||
<attr name="lrv_circle_paddingBottom" format="dimension"/>
|
||||
<!--圆环中间图文案-->
|
||||
<attr name="lrv_circle_center_txt" format="string"/>
|
||||
<!--中间图文案字体大小-->
|
||||
<attr name="lrv_circle_center_txt_size" format="dimension"/>
|
||||
<!--中间图文案字体颜色-->
|
||||
<attr name="lrv_circle_center_txt_color" format="color" />
|
||||
<!--图例列宽-->
|
||||
<attr name="lrv_legend_columnW" format="dimension"/>
|
||||
<!--图例行间距-->
|
||||
<attr name="lrv_legend_vertical_margin" format="dimension" />
|
||||
<!--图例和圆环的间距,默认为0-->
|
||||
<attr name="lrv_legend_circle_margin" format="dimension"/>
|
||||
<!--图例文字大小-->
|
||||
<attr name="lrv_legend_font_size" format="dimension"/>
|
||||
<!--图例文字颜色-->
|
||||
<attr name="lrv_legend_font_color" format="color"/>
|
||||
<!--图例文案和圆点的间距-->
|
||||
<attr name="lrv_legend_labelAndPoint_margin" format="dimension"/>
|
||||
<!--图例每行第一个左侧偏移量-->
|
||||
<attr name="lrv_offset_left" format="dimension"/>
|
||||
<!--图例样式-->
|
||||
<attr name="lrv_legend_style" format="enum">
|
||||
<!--圆点-->
|
||||
<enum name="circle" value="0"/>
|
||||
<!--方点-->
|
||||
<enum name="square" value="1"/>
|
||||
<!--横线-->
|
||||
<enum name="line" value="2"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<!--HorBarView控件样式-->
|
||||
<declare-styleable name="SectionProBar">
|
||||
<!-- 已完成进度的颜色,默认值为 #70A800 -->
|
||||
<attr name="hbv_reachedColor" format="reference|color"/>
|
||||
<!-- 已完成进度的高度,默认值为 2dp -->
|
||||
<attr name="hbv_reachedHeight" format="dimension"/>
|
||||
<!-- 未完成进度的颜色,默认值为 #CCCCCC -->
|
||||
<attr name="hbv_unReachedColor" format="reference|color"/>
|
||||
<!-- 未完成进度的高度,默认值为 1dp -->
|
||||
<attr name="hbv_unReachedHeight" format="dimension"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--TaperChart控件样式-->
|
||||
<declare-styleable name="TaperChart">
|
||||
<!-- 图形样式,默认值为 system -->
|
||||
<attr name="tc_mode" format="enum">
|
||||
<!-- 2张图,2个label -->
|
||||
<enum name="first" value="0"/>
|
||||
<!-- 3张图,3个label -->
|
||||
<enum name="second" value="1"/>
|
||||
<!-- 4张图,4个label -->
|
||||
<enum name="third" value="2"/>
|
||||
<!-- 4张图,2个label -->
|
||||
<enum name="fourth" value="3"/>
|
||||
<!-- 6张图,3个label -->
|
||||
<enum name="fifth" value="4"/>
|
||||
</attr>
|
||||
<!--y轴是否是百分制-->
|
||||
<attr name="tc_yaxis_percent" format="boolean"/>
|
||||
<!--文字大小-->
|
||||
<attr name="tc_axis_txtsize" format="dimension"/>
|
||||
<!--图形相交长度-->
|
||||
<attr name="tc_inner_space" format="dimension"/>
|
||||
<!--图表控制点的偏移量-->
|
||||
<attr name="tc_quad_x_offset" format="dimension"/>
|
||||
<!--是否绘制峰值顶尖值-->
|
||||
<attr name="is_draw_topvalue" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--TaperChart控件样式-->
|
||||
<declare-styleable name="HorTaperChart">
|
||||
<!--y轴是否是百分制-->
|
||||
<attr name="htc_yaxis_percent" format="boolean"/>
|
||||
<!--文字大小-->
|
||||
<attr name="htc_axis_txtsize" format="dimension"/>
|
||||
<!--图形相交长度-->
|
||||
<attr name="htc_inner_space" format="dimension"/>
|
||||
<!--图表控制点的偏移量-->
|
||||
<attr name="htc_quad_x_offset" format="dimension"/>
|
||||
<!--是否绘制峰值顶尖值-->
|
||||
<attr name="htc_is_draw_topValue" format="boolean"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--GradientLine控件样式-->
|
||||
<declare-styleable name="GradientLine">
|
||||
<!-- 渐变曲线样式,默认值为 flat -->
|
||||
<attr name="gl_mode" format="enum">
|
||||
<enum name="flat" value="0"/>
|
||||
<enum name="up" value="1"/>
|
||||
<enum name="down" value="2"/>
|
||||
</attr>
|
||||
<!--曲线宽度-->
|
||||
<attr name="gl_line_width" format="dimension"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--CircleProgressBarView控件样式-->
|
||||
<declare-styleable name="CircleProgressBarView">
|
||||
|
||||
<attr name="circleBgStrokeWidth" format="dimension" />
|
||||
<attr name="progressStrokeWidth" format="dimension" />
|
||||
|
||||
<attr name="circleBgColor" format="color" />
|
||||
<attr name="progressColor" format="color" />
|
||||
|
||||
<attr name="circleAnimationDuration" format="integer" />
|
||||
|
||||
<attr name="isDrawCenterProgressText" format="boolean" />
|
||||
|
||||
<attr name="centerProgressTextColor" format="color" />
|
||||
<attr name="centerProgressTextSize" format="dimension" />
|
||||
<attr name="targetNumColor" format="color" />
|
||||
<attr name="targetNumSize" format="dimension" />
|
||||
<attr name="targetTextColor" format="color" />
|
||||
<attr name="targetTextSize" format="integer" />
|
||||
<attr name="lineWidth" format="integer" />
|
||||
<attr name="lineColor" format="color" />
|
||||
<attr name="target_text" format="string" />
|
||||
|
||||
<attr name="round_color" format="color" />
|
||||
<attr name="round_progress_color" format="color" />
|
||||
<attr name="round_width" format="dimension" />
|
||||
<attr name="max_" format="integer" />
|
||||
<attr name="text_isDisplayable" format="boolean" />
|
||||
<attr name="style_">
|
||||
<enum name="STROKE" value="0" />
|
||||
<enum name="FILL" value="1" />
|
||||
</attr>
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
<!--横向渐变进度条控件样式-->
|
||||
<declare-styleable name="ShadowProBar">
|
||||
<attr name="progress_color" format="color|reference"/>
|
||||
</declare-styleable>
|
||||
|
||||
<!--仪表盘控件样式-->
|
||||
<declare-styleable name="HalfCircleProView">
|
||||
<attr name="strokeWidth" format="dimension" />
|
||||
<attr name="txtColor" format="color" />
|
||||
<attr name="txtSize" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<!--横向进度条颜色-->
|
||||
<declare-styleable name="HorProBar">
|
||||
<attr name="pro_color" format="color|reference"/>
|
||||
<attr name="pro_width" format="dimension" />
|
||||
<attr name="pro_height" format="dimension" />
|
||||
</declare-styleable>
|
||||
|
||||
<!--环形带刻度的颜色渐变进度条-->
|
||||
<declare-styleable name="CircleProgress">
|
||||
<!--是否开启抗锯齿-->
|
||||
<attr name="antiAlias" format="boolean" />
|
||||
<!--圆弧起始角度,3点钟方向为0,小于0或大于360进行取余-->
|
||||
<attr name="startAngle" format="float" />
|
||||
<!--圆弧度数-->
|
||||
<attr name="sweepAngle" format="float" />
|
||||
<!--设置动画时间-->
|
||||
<attr name="animTime" format="integer" />
|
||||
<!--设置内容的数值-->
|
||||
<attr name="maxValue" format="float" />
|
||||
<attr name="value" format="float" />
|
||||
<!--设置内容的单位-->
|
||||
<attr name="unit" format="string|reference" />
|
||||
<attr name="unitSize" format="dimension" />
|
||||
<attr name="unitColor" format="color|reference" />
|
||||
<!--绘制内容相应的提示语-->
|
||||
<attr name="hint" format="string|reference" />
|
||||
<attr name="hintSize" format="dimension" />
|
||||
<attr name="hintColor" format="color|reference" />
|
||||
<!--精度,默认为0-->
|
||||
<attr name="precision" format="integer" />
|
||||
<attr name="valueSize" format="dimension" />
|
||||
<attr name="valueColor" format="color|reference" />
|
||||
<!--背景圆弧颜色,默认白色-->
|
||||
<attr name="bgArcColor" format="color|reference" />
|
||||
<!--圆弧宽度-->
|
||||
<attr name="arcWidth" format="dimension" />
|
||||
<!--圆弧颜色-->
|
||||
<attr name="arcColors" format="color|reference" />
|
||||
<!--文字的偏移量 相对于圆半径而言,默认三分之一 -->
|
||||
<attr name="textOffsetPercentInRadius" format="float" />
|
||||
<!--背景圆弧宽度-->
|
||||
<attr name="bgArcWidth" format="dimension"/>
|
||||
<!--线条数-->
|
||||
<attr name="dottedLineCount" format="integer"/>
|
||||
<!--圆弧跟虚线之间的距离-->
|
||||
<attr name="lineDistance" format="integer" />
|
||||
<!--线条宽度-->
|
||||
<attr name="dottedLineWidth" format="dimension" />
|
||||
<!--进度条的前景色 起始颜色-->
|
||||
<attr name="foreStartColor" format="color" />
|
||||
<!--进度条的前景色 结束颜色-->
|
||||
<attr name="foreEndColor" format="color" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="HorScrollSelectedView">
|
||||
<!--可见数目-->
|
||||
<attr name="seeSize" format="integer" />
|
||||
<!--被选择文字的大小和颜色-->
|
||||
<attr name="selectedTextSize" format="float" />
|
||||
<attr name="selectedTextColor" format="color" />
|
||||
<!--未被选择文字的大小和颜色-->
|
||||
<attr name="textSize" format="float"/>
|
||||
<attr name="textColor" format="color|reference" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="TimeRulerView">
|
||||
<attr name="textFontSize" format="dimension|reference"/>
|
||||
<attr name="totalTimePerCell" >
|
||||
<enum name="hour" value="24" />
|
||||
<enum name="halfHour" value="48" />
|
||||
<enum name="fifteenmin" value="96" />
|
||||
<enum name="fivemin" value="288" />
|
||||
</attr>
|
||||
<attr name="textColors" format="color|reference"/>
|
||||
<attr name="scaleColor" format="color|reference"/>
|
||||
<attr name="topLineColor" format="color|reference"/>
|
||||
<attr name="bottomLineColor" format="color|reference"/>
|
||||
<attr name="middleLineColor" format="color|reference"/>
|
||||
<attr name="selectBackgroundColor" format="color|reference"/>
|
||||
<attr name="topLineStrokeWidth" format="dimension|reference"/>
|
||||
<attr name="bottomLineStrokeWidth" format="dimension|reference"/>
|
||||
<attr name="middleLineStrokeWidth" format="dimension|reference"/>
|
||||
<attr name="scaleLineStrokeWidth" format="dimension|reference"/>
|
||||
<attr name="widthPerScale" format="dimension|reference"/>
|
||||
<attr name="showtimeHeight" format="dimension|reference"/>
|
||||
<attr name="contentHeight" format="dimension|reference"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="txt_black">#2A2A2A</color>
|
||||
|
||||
<color name="ruler_line_color">#9E9E9E</color>
|
||||
<color name="ruler_text_color">#909090</color>
|
||||
<color name="ruler_indicator_color">#E64D14</color>
|
||||
|
||||
|
||||
<color name="timeruler_showtime_bg_color">#000000</color>
|
||||
<color name="timeruler_showtime_text_color">#E6FFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="taper_chart_margin">12dp</dimen>
|
||||
<dimen name="taper_chart_height">240dp</dimen>
|
||||
<!--峰值图字体大小-->
|
||||
<dimen name="taper_text_size">10sp</dimen>
|
||||
|
||||
<!--时间刻度选择器-->
|
||||
<dimen name="timerulerview_height">60dp</dimen>
|
||||
<dimen name="timerulerview_showtimewidth">48dp</dimen>
|
||||
<dimen name="timerulerview_showtimeheight">28dp</dimen>
|
||||
<dimen name="timerulerview_showtime_marginbottom">1dp</dimen>
|
||||
<dimen name="timerulerview_showtime_corner">2dp</dimen>
|
||||
<dimen name="timerulerview_showtime_textsize">14sp</dimen>
|
||||
<dimen name="timerulerview_contentheight">36dp</dimen>
|
||||
<dimen name="timerulerview_arrowheight">6dp</dimen>
|
||||
<dimen name="timerulerview_arrowheight_margin">1dp</dimen>
|
||||
<dimen name="timerulerview_scaleheight">5dp</dimen>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">CustomView</string>
|
||||
</resources>
|
||||