年底了,各種失業(yè)潮,尤其是互聯(lián)網(wǎng)裁員信息不斷,多學(xué)習(xí)總是有用的。
做了一個(gè)金融產(chǎn)品經(jīng)常用到的View。
主要涉及到的知識(shí):
- 三角函數(shù)貫穿始終,各種轉(zhuǎn)化,畫刻度和圓都會(huì)用到。
- 一些小技巧(設(shè)置貨幣符號(hào)和金額的距離)
- 動(dòng)畫的執(zhí)行
- View的繪制流程(重點(diǎn))
先看圖后上代碼,繪制流程就不廢話了,因?yàn)榇a已經(jīng)注釋的很清晰了。

image.gif
public class ArcProgressView extends View {
/**
* 不足之處:
* 1.文字和整體圖像的大小牽連起來
* 2.開口的大小動(dòng)態(tài)做出來
* 3.漸變的顏色默認(rèn)是三個(gè),如果用兩個(gè),怎么去除第三個(gè)默認(rèn)顏色
*/
private Paint mPaint;
/**
* 中心點(diǎn)的坐標(biāo)
*/
private int mX, mY;
/**
* 中間圓弧的畫筆寬度
*/
private float strokeWith = 30f;
/**
* 外圓弧的畫筆寬度
*/
private float outStrokeWith = 4f;
/**
* 內(nèi)圓弧的畫筆寬度
*/
private float innerStrokeWith = 2f;
/**
* 中心圓的半徑
*/
float mR = 200f;
float mROut = mR + mR * 0.1f;
float mRInner = mR - mR * 0.1f;
/**
* 兩邊耳朵的長度
*/
float bothEarLength = mR * 0.1f;
/**
* 大刻度線的個(gè)數(shù)
*/
private int mMainCalibration = 8;
/**
* 小刻度線的個(gè)數(shù)
*/
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;
/**
* 整個(gè)圓的大小
*/
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();
}
}
/**
* 掃過的動(dòng)畫
*/
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);
//畫三個(gè)圓弧
drawArcMiddleBackground(canvas);
drawArcMiddle(canvas, currentSweep);
// drawArcMiddle(canvas, sweepAngle);
drawArcOut(canvas);
drawArcInner(canvas);
//畫主刻度
drawMainCalibration(canvas);
//畫分刻度
drawSecondaryCalibration(canvas);
//畫可用額度的文字
drawAvailableLimitAlias(canvas, availableLimitAlias);
DecimalFormat decimalFormat = new DecimalFormat(",###");
//畫可用額度的數(shù)字大小
drawTextAvailableLimit(canvas, decimalFormat.format(availableLimit));
//畫總額度的text
drawTotalLimitAlias(canvas, totalLimitAlias);
//畫總額度的數(shù)字大小
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是三點(diǎn)鐘方向的角度0,逆時(shí)針到起始的角度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);
//最外圈的兩個(gè)邊
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);
//測(cè)量"錢"所占的像素
int width = (int) mPaint.measureText(text);
mPaint.setFakeBoldText(false);
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.setColor(currencySymbolColor);
mPaint.setTextSize(26);
//$符號(hào)的寬度,中心點(diǎn)的x坐標(biāo)減去上一步畫筆畫字的寬度 再減去一個(gè)$的占位符,預(yù)留一點(diǎn)點(diǎn)位置
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);
//測(cè)量"錢"所占的像素
int width = (int) mPaint.measureText(text);
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.setColor(currencySymbolColor);
mPaint.setTextSize(18);
//$符號(hào)的寬度
int placeholderWidth = (int) mPaint.measureText("$");
canvas.drawText("$", mX - width * 0.5f - placeholderWidth, mY + mR / 4, mPaint);
}
}
下面是自定義的一些屬性:
<attr name="middle_Arc_Color_Bg" format="color"/>
<attr name="middle_Arc_Color_One" format="color"/>
<attr name="middle_Arc_Color_Two" format="color"/>
<attr name="middle_Arc_Color_Three" format="color"/>
<attr name="inner_Arc_Color" format="color"/>
<attr name="outer_Arc_Color" format="color"/>
<attr name="available_Limit_Alias" format="string"/>
<attr name="available_Limit_Color" format="color"/>
<attr name="available_Limit_Text_Color" format="color"/>
<attr name="total_Limit_Alias" format="string"/>
<attr name="total_Limit_Color" format="color"/>
<attr name="total_Limit_Text_Color" format="color"/>
<attr name="available_Limit" format="float"/>
<attr name="total_Limit" format="float"/>
<attr name="currency_Symbol_Color" format="color"/>