车队管理完善

This commit is contained in:
2024-02-07 17:12:21 +08:00
parent 4c51daa245
commit 8dcf3b77c7
15 changed files with 1005 additions and 61 deletions
@@ -0,0 +1,323 @@
package com.dahe.mylibrary.weight;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.TextView;
import com.dahe.mylibrary.R;
/**
* @ClassName CircleBarView
* @Author john
* @Date 2024/2/7 16:08
* @Description TODO
*/
public class CircleBarView extends View {
private Paint rPaint;//绘制矩形的画笔
private Paint progressPaint;//绘制圆弧的画笔
private Paint anchorPaint, anchorBgPaint;//锚点
private float anchorRadius, anchorBgRadius;
private int anchorColor, anchorBgColor;
private CircleBarAnim anim;
private Paint bgPaint;//绘制背景圆弧的画笔
private float progress;//可以更新的进度条数值
private float maxProgress;//进度条最大值
private float progressSweepAngle;//进度条圆弧扫过的角度
private int startAngle;//背景圆弧的起始角度
private float sweepAngle;//背景圆弧扫过的角度
private RectF mRectF;//绘制圆弧的矩形区域
private float barWidth;//圆弧进度条宽度
private int defaultSize;//自定义View默认的宽高
private int[] progressColors;//进度条圆弧颜色
private int bgColor;//背景圆弧颜色
private SweepGradient sweepGradient;//进度条颜色使用渐变色
private LinearGradient linearGradient;
private float circleWidth, circleHeight;
private float xDiff;
public CircleBarView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleBarView);
int color1 = typedArray.getColor(R.styleable.CircleBarView_progress_color1, Color.RED);
int color2 = typedArray.getColor(R.styleable.CircleBarView_progress_color2, color1);
int color3 = typedArray.getColor(R.styleable.CircleBarView_progress_color3, color2);//写法巧妙
progressColors = new int[]{color1, color1, color2, color3};//必须得4个,
bgColor = typedArray.getColor(R.styleable.CircleBarView_bg_color, Color.GRAY);//默认为灰色
circleWidth = typedArray.getDimension(R.styleable.CircleBarView_circle_width, dip2px(context, 200));//默认为0
circleHeight = typedArray.getDimension(R.styleable.CircleBarView_circle_height, dip2px(context, 80));//默认为360
barWidth = typedArray.getDimension(R.styleable.CircleBarView_bar_width, dip2px(context, 10));//默认为10dp
anchorRadius = typedArray.getDimension(R.styleable.CircleBarView_anchor_radius, dip2px(context, 5));
anchorBgRadius = typedArray.getDimension(R.styleable.CircleBarView_anchor_bg_radius, dip2px(context, 8.5f));
anchorColor = typedArray.getColor(R.styleable.CircleBarView_anchor_color, Color.RED);
anchorBgColor = typedArray.getColor(R.styleable.CircleBarView_anchor_bg_color, Color.GREEN);
maxProgress = typedArray.getInt(R.styleable.CircleBarView_progress_max, 100);
typedArray.recycle();//typedArray用完之后需要回收,防止内存泄漏
rPaint = new Paint();
rPaint.setStyle(Paint.Style.STROKE);//只描边,不填充
rPaint.setColor(Color.RED);
progressPaint = new Paint();
progressPaint.setStyle(Paint.Style.STROKE);//只描边,不填充
progressPaint.setStrokeWidth(barWidth);
progressPaint.setAntiAlias(true);//设置抗锯齿
progressPaint.setStrokeCap(Paint.Cap.ROUND);
//锚点
anchorPaint = new Paint();
anchorPaint.setAntiAlias(true);//设置抗锯齿
anchorPaint.setColor(anchorColor);
//锚点背景
anchorBgPaint = new Paint();
anchorBgPaint.setAntiAlias(true);//设置抗锯齿
int a = Color.alpha(anchorBgColor);
int r = Color.red(anchorBgColor);
int g = Color.green(anchorBgColor);
int b = Color.blue(anchorBgColor);
// LogSuperUtil.i("circle_bar", "a=" + a + ",r=" + r + ",g=" + g + ",b=" + b);
anchorBgPaint.setColor(Color.rgb(r, g, b));
anchorBgPaint.setAlpha(a);
/*raw
anchorBgPaint.setColor(anchorBgColor);
anchorBgPaint.setAlpha(185);*/
anim = new CircleBarAnim();
bgPaint = new Paint();
bgPaint.setStyle(Paint.Style.STROKE);
bgPaint.setColor(bgColor);
bgPaint.setStrokeWidth(barWidth);
bgPaint.setAntiAlias(true);
bgPaint.setStrokeCap(Paint.Cap.ROUND);
mRectF = new RectF();
//
defaultSize = dip2px(context, 200);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//进度条的总轮廓
canvas.drawArc(mRectF, startAngle, sweepAngle, false, bgPaint);
float outCircleRadius = mRectF.width() / 2;//带宽度的进度条的中线半径
if (sweepGradient == null) {
float centerX = (mRectF.left + mRectF.right) / 2;
float centerY = (mRectF.top + mRectF.bottom) / 2;
//
int rotateDiff = 30;//为了解决圆弧开头出的圆角颜色不正常的问题,30度够用了吧
int rotateDegree = startAngle - rotateDiff;
float sweepEndPosition = (rotateDiff + sweepAngle * progress / maxProgress * 1.0f) / 360;
// LogSuperUtil.i("circle_bar", "startAngle=" + startAngle + ",sweepEndPosition=" + sweepEndPosition);
float[] positions = {0, rotateDiff * 1.0f / 360, sweepEndPosition, 1};
sweepGradient = new SweepGradient(centerX, centerY, progressColors, positions);
Matrix rotateMatrix = new Matrix();
//设置渐变色
rotateMatrix.setRotate(rotateDegree, centerX, centerY);//180表示从左侧开始往上渐变
sweepGradient.setLocalMatrix(rotateMatrix);//sweepGradient默认是从0度方向开始渐变的,所以要逆转一下。
progressPaint.setShader(sweepGradient);
}
/*
if(linearGradient==null) {
linearGradient = new LinearGradient(0, 0, outCircleRadius*2, 0, progressColors,
null, LinearGradient.TileMode.CLAMP);
progressPaint.setShader(linearGradient);
}*/
//进度条
canvas.drawArc(mRectF, startAngle, progressSweepAngle, false, progressPaint);
float anchorAngle = progressSweepAngle + (startAngle - 180);
float anchorCoordRedius = outCircleRadius - barWidth / 2;
double cosValue = Math.cos(Math.toRadians(anchorAngle));
double sinValue = Math.sin(Math.toRadians(anchorAngle));
float diffByAnchor = anchorBgRadius - barWidth / 2;//x和y的偏移正好都是这个
//float cx=(float)(outCircleRadius-cosValue*anchorCoordRedius+diffByAnchor);
float cx = (float) (outCircleRadius - cosValue * outCircleRadius + anchorBgRadius);
//float cy=(float)(outCircleRadius-sinValue*anchorCoordRedius+diffByAnchor);
float cy = (float) (outCircleRadius - sinValue * outCircleRadius + anchorBgRadius);
canvas.drawCircle(cx - xDiff, cy, anchorRadius, anchorPaint);//锚点小圆
canvas.drawCircle(cx - xDiff, cy, anchorBgRadius, anchorBgPaint);//锚点背景大圆
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//int height=measureSize(defaultSize,heightMeasureSpec);
//int width=measureSize(defaultSize,widthMeasureSpec);
//startAngle是计算出来的,因为弦的这种,没法通过观察直接得出圆弧的起始角度
//通过数学计算得知x
//x是圆心(圆弧所在的圆的圆心)到弦(也就是圆弧的底部)的垂直距离
float x = (circleWidth * circleWidth - 4 * circleHeight * circleHeight) / (8 * circleHeight);
//画圆弧用到的圆的半径
float r = x + circleHeight;
//int r=(int)(circleWidth/2/Math.cos(Math.toRadians(startAngle-180)));
//弦长/2/半径=cos(startAngle-180)
//(startAngle-180)
double asin = Math.asin(circleWidth / 2 / r);
//LogSuperUtil.i("circle_bar","asin="+asin);
int a = (int) Math.toDegrees(asin);
//LogSuperUtil.i("circle_bar","a="+a);
startAngle = 180 + (90 - a);
sweepAngle = 2 * a;
//int min=Math.max(width,height);//获取View最短边的长度
int width = (int) (Math.sqrt(Math.pow(r + anchorBgRadius, 2) - Math.pow(x, 2)) * 2 + 0.5f + anchorBgRadius);//圆弧的宽度
width = (int) (2 * (circleWidth / 2 + anchorBgRadius) + 0.5f);
int height = (int) (circleHeight + anchorBgRadius + 0.5f + anchorBgRadius);
//height=width;
xDiff = r - circleWidth / 2;//画布是从最左边开始画圆且显示的,不偏移的话,显示的效果不对。
// LogSuperUtil.i("circle_bar", "xDiff=" + xDiff);
setMeasuredDimension(width, height);
if (Math.min(width, height) >= barWidth * 2) {//这里简单限制了圆弧的最大宽度
//1.mRectF决定着圆画在哪个框框里面
//2.mRectF在当前View中,但不是完全占据当前View
//3.弧度的粗细画的时候是均分到圆半径线的两侧的
//4.之所以矩形设置的四个点有偏移,是因为有弧度宽度。
//因为锚点也有宽度,点再次做调整.
//mRectF.set(barWidth/2,barWidth/2,min-barWidth/2,min-barWidth/2);
float diffByAnchor = anchorBgRadius - barWidth / 2;//受锚点影响需要修正的偏移
//是整个圆的矩形,不是弧形区域所在的矩形
mRectF.set(barWidth / 2 + diffByAnchor - xDiff, barWidth / 2 + diffByAnchor, barWidth / 2 + diffByAnchor + 2 * r - xDiff, barWidth / 2 + diffByAnchor + 2 * r);
}
}
private int measureSize(int defaultSize, int measureSpec) {
int result = defaultSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
// LogSuperUtil.i("circle_bar", "=EXACTLY");
result = specSize;
} else if (specMode == MeasureSpec.AT_MOST) {
// LogSuperUtil.i("circle_bar", "=AT_MOST");
result = Math.min(result, specSize);
} else {
// LogSuperUtil.i("circle_bar", "=defaultSize");
}
return result;
}
private TextView textView;
private OnAnimationListener onAnimationListener;
public class CircleBarAnim extends Animation {
public CircleBarAnim() {
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
progressSweepAngle = interpolatedTime * sweepAngle * progress / maxProgress;//这里计算进度条的比例
postInvalidate();
if (textView != null && onAnimationListener != null) {
textView.setText(onAnimationListener.howToChangeText(interpolatedTime, progress, maxProgress));
}
}
}
/**
* 设置显示文字的TextView
*
* @param textView
*/
public void setTextView(TextView textView) {
this.textView = textView;
}
/*
* circleBarView.setOnAnimationListener(new CircleBarView.OnAnimationListener() {
@Override
public String howToChangeText(float interpolatedTime, float progressNum, float maxNum) {
DecimalFormat decimalFormat=new DecimalFormat("0.00");
String s = decimalFormat.format(interpolatedTime * progressNum / maxNum * 100) + "%";
return s;
}
});*/
public interface OnAnimationListener {
/**
* 如何处理要显示的文字内容
*
* @param interpolatedTime 从0渐变成1,到1时结束动画
* @param progressNum 进度条数值
* @param maxNum 进度条最大值
* @return
*/
String howToChangeText(float interpolatedTime, float progressNum, float maxNum);
}
//写个方法给外部调用,用来设置动画时间
public void setProgress(float progress, int time) {
anim.setDuration(time);
this.startAnimation(anim);
this.progress = progress;
}
public static int dip2px(Context context, float dpValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
public static int px2dip(Context context, float pxValue) {
float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* //extraDistance比较小时,TextView离圆弧很近,是正常,因为TextView是从其自身的左上角开始渲染。
*
* @param sweepAnglePercent
* @param extraDistance
* @return
*/
public float getAngleX(float sweepAnglePercent, int extraDistance) {
float outCircleRadius = mRectF.width() / 2 + barWidth / 2;//带宽度的进度条的外圆半径
float diffByAnchor = anchorBgRadius - barWidth / 2;//x和y的偏移正好都是这个
float x = (float) (outCircleRadius + diffByAnchor - Math.cos(Math.toRadians(startAngle - 180 + sweepAnglePercent * sweepAngle)) * (outCircleRadius + dip2px(getContext(), extraDistance)) - xDiff);
return x;
}
private static final String TAG = "circle_bar";
/**
* 在当前View中的y坐标
* (注意,TextView渲染的时候,这个坐标是作为TextView控件的左上角进行渲染。
* 所以,extraDistance小的话,显示的结果TextView可能还在当前View的内部呢,但TextView的左上角是在圆外部的。
*
* @param sweepAnglePercent
* @param extraDistance 从外圆算的往外扩展的距离,不是从锚点背景边缘往外扩展。
* @return
*/
public float getAngleY(float sweepAnglePercent, int extraDistance) {
float outCircleRadius = mRectF.width() / 2 + barWidth / 2;//带宽度的进度条的外圆半径
float diffByAnchor = anchorBgRadius - barWidth / 2;//x和y的偏移正好都是这个
float coordA = startAngle - 180 + sweepAnglePercent * sweepAngle;
// LogSuperUtil.i("circle_bar", "coordA=" + coordA);
float coordY = (float) Math.sin(Math.toRadians(coordA)) * (outCircleRadius + dip2px(getContext(), extraDistance));
// LogSuperUtil.i("circle_bar", "coordY=" + coordY);
float tempR = outCircleRadius + diffByAnchor;
// LogSuperUtil.i("circle_bar", "tempR=" + tempR + ",=" + circleWidth);//CircleWidth是圆弧宽度
// LogSuperUtil.i("circle_bar", "mRectF.width()/2=" + mRectF.width() / 2);
float y = (float) (tempR - coordY);
return y;
}
}
@@ -0,0 +1,259 @@
package com.dahe.mylibrary.weight;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.dahe.mylibrary.R;
/**
* @ClassName CircleBarView2
* @Author john
* @Date 2024/2/7 16:28
* @Description TODO
*/
public class CircleBarView2 extends View {
private Paint outPaint, innerPaint;
private Paint mTextPaint, mValueTextPaint, mUnitTextPaint;
private final RectF oval = new RectF();
//最大进度
private int max = 100;
//当前进度
private int progress = 0;
//文本内容
private String text = "";
//数值内容
private String valueText = "";
//单位内容
private String unitText = "";
// 圆弧颜色
private int roundColor = getContext().getColor(R.color.color_f25);
// 设置数值颜色
private int valueColor = getContext().getColor(R.color.color_f25);
//用于动画
private float nowPro = 0;
private ValueAnimator animator;
// 文字间距
private int textMargin;
// 圆弧宽度、半径和数值、小间距
private int roundWidth, radius, smallMargin;
// view的高度
private int height;
// 中心点
private final Point centerPoint = new Point();
// 测量文字的范围
private final Rect textRect = new Rect();
public CircleBarView2(Context context) {
this(context, null);
}
public CircleBarView2(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleBarView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context);
}
public int getMax() {
return max;
}
public void setMax(int max) {
this.max = max;
}
public int getProgress() {
return progress;
}
public void setProgress(int progress) {
if (this.progress == progress) {
return;
}
if (animator != null && animator.isRunning()) {
animator.cancel();
}
animator = ValueAnimator.ofFloat(nowPro, progress);
animator.setDuration(1000);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(animation -> {
nowPro = (float) animation.getAnimatedValue();
CircleBarView2.this.progress = (int) nowPro;
valueText = String.valueOf(CircleBarView2.this.progress);
invalidate();
});
animator.start();
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getValueText() {
return valueText;
}
public void setValueText(String valueText) {
this.valueText = valueText;
}
public String getUnitText() {
return unitText;
}
public void setUnitText(String unitText) {
this.unitText = unitText;
}
public void setRoundColor(int roundColor) {
this.roundColor = roundColor;
invalidate();
}
public void setValueColor(int valueColor) {
this.valueColor = valueColor;
invalidate();
}
private void initAttrs(Context context) {
// 文字间距
textMargin = dp2px(context, 5);
// 圆弧宽度
roundWidth = dp2px(context, 5);
// 半径
radius = dp2px(context, 72);
height = radius;
// 小间距
smallMargin = roundWidth / 2;
outPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
outPaint.setColor(context.getColor(R.color.color_f6));
outPaint.setStyle(Paint.Style.STROKE);
outPaint.setStrokeCap(Paint.Cap.ROUND);
outPaint.setStrokeWidth(roundWidth);
innerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
innerPaint.setColor(context.getColor(R.color.color_f15));
innerPaint.setStyle(Paint.Style.STROKE);
innerPaint.setStrokeCap(Paint.Cap.ROUND);
innerPaint.setStrokeWidth(roundWidth);
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(context.getColor(R.color.color_ff3));
mTextPaint.setTextSize(sp2px(getContext(), 24));
mValueTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mValueTextPaint.setColor(valueColor);
mValueTextPaint.setTextSize(sp2px(getContext(), 24));
mUnitTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mUnitTextPaint.setColor(context.getColor(R.color.color_ff3));
mUnitTextPaint.setTextSize(sp2px(getContext(), 12));
//动画
animator = ValueAnimator.ofFloat(0, progress);
animator.setDuration(1000);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(animation -> {
nowPro = (float) animation.getAnimatedValue();
postInvalidate();
});
animator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, (int) height);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, heightSpecSize);
height = heightSpecSize;
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, (int) height);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 中心点
centerPoint.set(getWidth() / 2, height / 2);
// 计算圆弧显示的范围,height * 2 是因为圆弧的高度是根据园来算的,所以双倍才是半个圆
oval.set(getWidth() / 2f - radius, (float) roundWidth, getWidth() / 2f + radius, height * 2 - roundWidth*2);
}
@Override
protected void onDraw(Canvas canvas) {
// 最外层圆弧
canvas.drawArc(oval, 180, 180, false, outPaint); //绘制外层圆弧
// 进度圆弧
if (nowPro > 0) {
innerPaint.setColor(roundColor);
} else {
innerPaint.setColor(getContext().getColor(R.color.color_f6));
}
canvas.drawArc(oval, 180, 180f * nowPro / max, false, innerPaint); //绘制圆弧
// 文字从底部从下往上绘制。
int valueY = height - smallMargin;
// 值
float valueTextWidth = mValueTextPaint.measureText(valueText);
float unitTextWidth = mUnitTextPaint.measureText(unitText);
canvas.drawText(valueText, centerPoint.x - (valueTextWidth + unitTextWidth) / 2f, valueY, mValueTextPaint);
// 单位
canvas.drawText(unitText, centerPoint.x + valueTextWidth / 2f - unitTextWidth / 2f + smallMargin, valueY, mUnitTextPaint);
// 测量值的高度
mValueTextPaint.getTextBounds(text, 0, text.length(), textRect);
// 提示文字("高血压")
float textWidth = mTextPaint.measureText(text);
// 文字的高度 = 值的高度起点 - 值的高度 - 间距
float textY = valueY - textRect.height() - textMargin;
canvas.drawText(text, centerPoint.x - textWidth / 2f, textY, mTextPaint);
}
/**
* dp 转 px
*/
private int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* sp 转 px
*/
private int sp2px(Context context, float spValue) {
final float scale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * scale + 0.5f);
}
}
+5
View File
@@ -29,5 +29,10 @@
<color name="color_f6">#F6F6F6</color>
<color name="color_f15">#F15F15</color>
<color name="color_ff3">#FF3FF3</color>
<color name="color_f25">#f25f25</color>
</resources>
+24
View File
@@ -52,4 +52,28 @@
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<declare-styleable name="CircleBarView">
<attr name="progress_color1" format="reference|color"></attr>
<attr name="progress_color2" format="reference|color"></attr>
<attr name="progress_color3" format="reference|color"></attr>
<attr name="bg_color" format="color"></attr>
<attr name="bar_width" format="dimension"></attr>
<attr name="circle_width" format="dimension"></attr><!--背景圆弧宽度 -->
<attr name="circle_height" format="dimension"></attr><!--背景圆弧高度 -->
<attr name="anchor_radius" format="dimension"></attr>
<attr name="anchor_bg_radius" format="dimension"></attr>
<attr name="anchor_color" format="reference|color"></attr>
<attr name="anchor_bg_color" format="reference|color"></attr>
<attr name="progress_max" format="integer"></attr>
</declare-styleable>
<declare-styleable name="CircleTextView">
<attr name="paddingRing2Bg" format="dimension"></attr>
<attr name="ringStrokeWidth" format="dimension"></attr>
<attr name="ringColor1" format="reference|color"></attr>
<attr name="ringColor2" format="reference|color"></attr>
<attr name="bgColor1" format="reference|color"></attr>
<attr name="bgColor2" format="reference|color"></attr>
</declare-styleable>
</resources>