This commit is contained in:
coco
2026-07-03 15:56:07 +08:00
commit caef23209c
5767 changed files with 1004268 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+48
View File
@@ -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}"
}
+136
View File
@@ -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,其他moveup不执行
}
@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);
// 设置画笔的样式,为FILLFILL_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);
// 控件滚动后的xy
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那是不是只要滚动50pxso 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>
* 1setXfermode必须要设置上层图形的画笔中
* 2用onDraw方法中的canvas设置setXfermode没有效果
* 3setXfermode必须要在画笔用到之前设置
*
* @autor hongbing
* @date 2019-07-22
*/
public class CircleWaveView extends View {
/**
* PorterDuff的16种模式
* <p>
* 源像素当前设置xfermode的
* 目标像素固定不变的
*
* <p>
* 1CLEAR清除画笔所绘制的内容
* 2SRC_ATOP丢弃未覆盖目标像素的源像素
* 3SRC_IN原始图 & 操作图相交的部分
* 4SRC_OUT原始图 & 操作图除了相交的部分
* 56SRCSRC_OVER原始图 & 操作图覆盖在上面
* 7XOR原始图 & 操作图除了相交的部分
* 8SCREEN添加源像素和目标像素然后减去源像素乘以目标
* 9OVERLAY根据目标颜色将源和目标相乘或屏幕显示
* 10MULTIPLY将源像素和目标像素相乘
* 11LIGHTEN保留源像素和目标像素的最大分量
* 12DST丢弃操作图是原始图保持不变
* 13DST_OUT保留源像素未覆盖的目标像素
* 14DST_OVER源像素在目标像素的后面
* 15DST_IN
* 16DST_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;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

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>