年底了,各种失业潮,尤其是互联网裁员信息不断,多学习总是有用的。
做了一个金融产品经常用到的View。
主要涉及到的知识:
- 三角函数贯穿始终,各种转化,画刻度和圆都会用到。
- 一些小技巧(设置货币符号和金额的距离)
- 动画的执行
- View的绘制流程(重点)
先看图后上代码,绘制流程就不废话了,因为代码已经注释的很清晰了。
public class ArcProgressView extends View {
/**
* 不足之处:
* 1.文字和整体图像的大小牵连起来
* 2.开口的大小动态做出来
* 3.渐变的颜色默认是三个,如果用两个,怎么去除第三个默认颜色
*/
private Paint mPaint;
/**
* 中心点的坐标
*/
private int mX, mY;
/**
* 中间圆弧的画笔宽度
*/
private float strokeWith = 30f;
/**
* 外圆弧的画笔宽度
*/
private float outStrokeWith = 4f;
/**
* 内圆弧的画笔宽度
*/
private float innerStrokeWith = 2f;
/**
* 中心圆的半径
*/
float mR = 200f;
float mROut = mR + mR * 0.1f;
float mRInner = mR - mR * 0.1f;
/**
* 两边耳朵的长度
*/
float bothEarLength = mR * 0.1f;
/**
* 大刻度线的个数
*/
private int mMainCalibration = 8;
/**
* 小刻度线的个数
*/
private int mSecondaryCalibration = 22;
/**
* 大刻度线长度
*/
private int mMainCalibrationLength = 30;
/**
* 小刻度线长度
*/
private int mSecondaryCalibrationLength = 8;
/*
从-15的地方开始画刻度
*/
private double mStartAngle = -15;
private double mSecondaryStartAngle = -15;
/**
* 开口大小
*/
float openRadian = 150;
/**
* 画弧开始的角度位置
*/
float startAngle = openRadian + (180 - openRadian) / 2;
/**
* 整个圆的大小
*/
float arcAngle = 360;
/**
* 背景画弧扫过的角度
*/
float middleBgSweepAngle = arcAngle - openRadian;
float sweepAngle = 0;
private Context mContext;
public ArcProgressView(Context context) {
this(context, null);
}
public ArcProgressView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ArcProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ArcProgress, defStyleAttr, 0);
initByAttributes(attributes);
attributes.recycle();
initPaint();
}
private final int defaultMiddleArcColorBg = Color.argb(255, 232, 242, 252);
private final int defaultMiddleArcColorOne = Color.parseColor("#D9465E");
private final int defaultMiddleArcColorTwo = Color.parseColor("#2DA9F8");
private final int defaultMiddleArcColorThree = Color.argb(255, 108, 132, 191);
private final int defaultInnerArcColor = Color.parseColor("#DEF1FD");
private final int defaultOuterArcColor = Color.parseColor("#DEF1FD");
private final int defaultAvailableLimitColor = Color.argb(255, 29, 86, 166);
private final int defaultTotalLimitColor = Color.argb(255, 29, 86, 166);
private final int defaultCurrencySymbolColor = Color.argb(255, 58, 164, 196);
private final int defaultAvailableLimitTextColor = Color.argb(255, 58, 164, 196);
private final int defaultTotalLimitTextColor = Color.argb(255, 201, 201, 201);
private int[] middleArcColor;
private int middleArcColorBg;
private int middleArcColorOne;
private int middleArcColorTwo;
private int middleArcColorThree;
private int availableLimitColor;
private int availableLimitTextColor;
private int totalLimitColor;
private int totalLimitTextColor;
private int currencySymbolColor;
private int innerArcColor;
private int outerArcColor;
private float availableLimit;
private float totalLimit;
private String availableLimitAlias;
private String totalLimitAlias;
protected void initByAttributes(TypedArray attributes) {
middleArcColorBg = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_Bg, defaultMiddleArcColorBg);
middleArcColorOne = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_One, defaultMiddleArcColorOne);
middleArcColorTwo = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_Two, defaultMiddleArcColorTwo);
middleArcColorThree = attributes.getColor(R.styleable.ArcProgress_middle_Arc_Color_Three, defaultMiddleArcColorThree);
innerArcColor = attributes.getColor(R.styleable.ArcProgress_inner_Arc_Color, defaultInnerArcColor);
outerArcColor = attributes.getColor(R.styleable.ArcProgress_outer_Arc_Color, defaultOuterArcColor);
availableLimitColor = attributes.getColor(R.styleable.ArcProgress_available_Limit_Color, defaultAvailableLimitColor);
totalLimitColor = attributes.getColor(R.styleable.ArcProgress_total_Limit_Color, defaultTotalLimitColor);
availableLimitTextColor = attributes.getColor(R.styleable.ArcProgress_available_Limit_Text_Color, defaultAvailableLimitTextColor);
totalLimitTextColor = attributes.getColor(R.styleable.ArcProgress_total_Limit_Text_Color, defaultTotalLimitTextColor);
currencySymbolColor = attributes.getColor(R.styleable.ArcProgress_currency_Symbol_Color, defaultCurrencySymbolColor);
if (!TextUtils.isEmpty(attributes.getString(R.styleable.ArcProgress_available_Limit_Alias))) {
setAvailableLimitAlias(attributes.getString(R.styleable.ArcProgress_available_Limit_Alias));
} else {
setAvailableLimitAlias("Available Limit");
}
if (!TextUtils.isEmpty(attributes.getString(R.styleable.ArcProgress_total_Limit_Alias))) {
setTotalLimitAlias(attributes.getString(R.styleable.ArcProgress_total_Limit_Alias));
} else {
setTotalLimitAlias("Total limit");
}
setAvailableLimit(attributes.getFloat(R.styleable.ArcProgress_available_Limit, 0));
setTotalLimit(attributes.getFloat(R.styleable.ArcProgress_total_Limit, 0));
}
private void setTotalLimitAlias(String string) {
this.totalLimitAlias = string;
this.invalidate();
}
public String getTotalLimitAlias() {
return totalLimitAlias;
}
public void setAvailableLimitAlias(String string) {
this.availableLimitAlias = string;
this.invalidate();
}
public String getAvailableLimitAlias() {
return availableLimitAlias;
}
public void setTotalLimit(float totalLimit) {
this.totalLimit = totalLimit;
if (totalLimit > 0) {
startAnimator();
invalidate();
}
}
public float getTotalLimit() {
return totalLimit;
}
public void setAvailableLimit(float availableLimit) {
this.availableLimit = availableLimit;
if (availableLimit > 0) {
startAnimator();
invalidate();
}
}
/**
* 扫过的动画
*/
private void startAnimator() {
if (availableLimit > 0 && totalLimit > 0) {
if (availableLimit > totalLimit) {
availableLimit = totalLimit;
}
sweepAngle = ((arcAngle - openRadian) * availableLimit) / totalLimit;
ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentSweep = (int) animation.getAnimatedValue();
invalidate();
}
};
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, (int) sweepAngle);
valueAnimator.addUpdateListener(animatorUpdateListener);
valueAnimator.setDuration(500);
valueAnimator.setInterpolator(new DecelerateInterpolator(0.6f));
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.start();
}
}
public float getAvailableLimit() {
return availableLimit;
}
@Override
public void invalidate() {
initPaint();
super.invalidate();
}
private void initPaint() {
middleArcColor = new int[]{defaultMiddleArcColorOne, defaultMiddleArcColorTwo, defaultMiddleArcColorThree};
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(strokeWith);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//获取所在父控件或者 的位置
mX = w / 2;
mY = h / 2;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
float currentSweep = 0.0f;
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画三个圆弧
drawArcMiddleBackground(canvas);
drawArcMiddle(canvas, currentSweep);
// drawArcMiddle(canvas, sweepAngle);
drawArcOut(canvas);
drawArcInner(canvas);
//画主刻度
drawMainCalibration(canvas);
//画分刻度
drawSecondaryCalibration(canvas);
//画可用额度的文字
drawAvailableLimitAlias(canvas, availableLimitAlias);
DecimalFormat decimalFormat = new DecimalFormat(",###");
//画可用额度的数字大小
drawTextAvailableLimit(canvas, decimalFormat.format(availableLimit));
//画总额度的text
drawTotalLimitAlias(canvas, totalLimitAlias);
//画总额度的数字大小
drawTextTotalLimit(canvas, decimalFormat.format(totalLimit));
}
public float getArcAngle() {
return currentSweep;
}
public void setArcAngle(float arcAngle) {
this.currentSweep = arcAngle;
}
/**
* 画可用额度
*
* @param canvas
* @param sweepAngle
*/
private void drawArcMiddle(Canvas canvas, float sweepAngle) {
LinearGradient linearGradient = new LinearGradient(
(float) (mX - Math.sqrt((mR * mR - (mR / 2) * (mR / 2)))), mY + mR / 2,
(float) (mX + Math.sqrt((mR * mR - (mR / 2) * (mR / 2)))), mY + mR / 2,
middleArcColor, null, Shader.TileMode.MIRROR);
mPaint.setShader(linearGradient);
RectF oval = new RectF(mX - mR, mY - mR, mX + mR, mY + mR);
canvas.drawArc(oval, startAngle, sweepAngle, false, mPaint);
mPaint.setShader(null);
}
/**
* 画总额度 底色
*
* @param canvas
*/
private void drawArcMiddleBackground(Canvas canvas) {
//笔的圆角
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setColor(middleArcColorBg);
RectF oval = new RectF(mX - mR, mY - mR, mX + mR, mY + mR);
canvas.drawArc(oval, startAngle, middleBgSweepAngle, false, mPaint);
}
private void drawArcInner(Canvas canvas) {
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setColor(innerArcColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(innerStrokeWith);
RectF ovalInner = new RectF(mX - mRInner, mY - mRInner, mX + mRInner, mY + mRInner);
//startAngle是三点钟方向的角度0,逆时针到起始的角度120
canvas.drawArc(ovalInner, startAngle - 3, middleBgSweepAngle + 6, false, mPaint);
}
private void drawArcOut(Canvas canvas) {
mPaint.reset();
mPaint.setAntiAlias(true);
mPaint.setColor(outerArcColor);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(outStrokeWith);
RectF ovalOut = new RectF(mX - mROut, mY - mROut, mX + mROut, mY + mROut);
canvas.drawArc(ovalOut, startAngle, middleBgSweepAngle, false, mPaint);
//最外圈的两个边
drawTwoBothSide(canvas);
}
/**
* 画两边的耳朵
*
* @param canvas
*/
private void drawTwoBothSide(Canvas canvas) {
canvas.drawLine((float) (mX - mROut * Math.sin(Math.toRadians(openRadian / 2)))
, (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2)))
, (float) (mX - mROut * Math.sin(Math.toRadians(openRadian / 2)) - bothEarLength)
, (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2))), mPaint);
canvas.drawLine((float) (mX + mROut * Math.sin(Math.toRadians(openRadian / 2)))
, (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2)))
, (float) (mX + mROut * Math.sin(Math.toRadians(openRadian / 2)) + bothEarLength)
, (float) (mY + mROut * Math.cos(Math.toRadians(openRadian / 2))), mPaint);
}
private void drawMainCalibration(Canvas canvas) {
float startAngele = (float) Math.toRadians(mStartAngle);
for (int i = 0; i < mMainCalibration; i++) {
float startX = (float) (mX + ((mR - mMainCalibrationLength - strokeWith / 2) * Math.cos(startAngele)));
float startY = (float) (mY - ((mR - mMainCalibrationLength - strokeWith / 2) * Math.sin(startAngele)));
float stopX = (float) (mX + (mR - strokeWith / 2) * Math.cos(startAngele));
float stopY = (float) (mY - (mR - strokeWith / 2) * Math.sin(startAngele));
canvas.drawLine(startX, startY, stopX, stopY, mPaint);
startAngele = (float) (startAngele + Math.toRadians(30));
}
}
private void drawSecondaryCalibration(Canvas canvas) {
float startAngele = (float) Math.toRadians(mSecondaryStartAngle);
for (int i = 0; i < mSecondaryCalibration; i++) {
float startX = (float) (mX + ((mR - mSecondaryCalibrationLength - strokeWith) * Math.cos(startAngele)));
float startY = (float) (mY - ((mR - mSecondaryCalibrationLength - strokeWith) * Math.sin(startAngele)));
float stopX = (float) (mX + (mR - strokeWith) * Math.cos(startAngele));
float stopY = (float) (mY - (mR - strokeWith) * Math.sin(startAngele));
canvas.drawLine(startX, startY, stopX, stopY, mPaint);
startAngele = (float) (startAngele + Math.toRadians(10));
}
}
private void drawAvailableLimitAlias(Canvas canvas, String text) {
mPaint.reset();
mPaint.setColor(availableLimitTextColor);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(2);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize(20f);
mPaint.setTextScaleX(1.4f);
canvas.drawText(text, mX, mY - mR / 2, mPaint);
}
/**
* 画可用额度
*
* @param canvas
* @param text
*/
private void drawTextAvailableLimit(Canvas canvas, String text) {
mPaint.setFakeBoldText(true);
mPaint.setTextSize(40);
mPaint.setColor(defaultAvailableLimitColor);
canvas.drawText(text, mX, mY - mR / 6, mPaint);
//测量"钱"所占的像素
int width = (int) mPaint.measureText(text);
mPaint.setFakeBoldText(false);
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.setColor(currencySymbolColor);
mPaint.setTextSize(26);
//$符号的宽度,中心点的x坐标减去上一步画笔画字的宽度 再减去一个$的占位符,预留一点点位置
int placeholderWidth = (int) mPaint.measureText("$");
canvas.drawText("$", mX - width * 0.5f - placeholderWidth, mY - mR / 6, mPaint);
}
private void drawTotalLimitAlias(Canvas canvas, String text) {
mPaint.setTextSize(16f);
mPaint.setColor(totalLimitTextColor);
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(text, mX, mY + mR / 20, mPaint);
}
/**
* 画总额度
*
* @param canvas
* @param text
*/
private void drawTextTotalLimit(Canvas canvas, String text) {
mPaint.setFakeBoldText(false);
mPaint.setTextSize(24);
mPaint.setColor(totalLimitColor);
canvas.drawText(text, mX, mY + mR / 4, mPaint);
//测量"钱"所占的像素
int width = (int) mPaint.measureText(text);
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.setColor(currencySymbolColor);
mPaint.setTextSize(18);
//$符号的宽度
int placeholderWidth = (int) mPaint.measureText("$");
canvas.drawText("$", mX - width * 0.5f - placeholderWidth, mY + mR / 4, mPaint);
}
}
下面是自定义的一些属性:
源码地址: