推薦個音樂下載app【佳語音樂下載】
https://gitlab.com/gaopinqiang/checkversion/raw/master/Music_Download.apk
開發有時會用到儀表盤(例如測速,類似汽車儀表盤),這裏我們自定義個View來實現這個功能,還支持指針動畫,避免過於生硬。
先給個效果圖:
下面是具體的實現代碼(在真機上測試通過):
DashboardView.java
package com.example.dashboardView;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import com.example.testgesturedetector.R;
/**
* 自定義測速儀表盤
* 直接繼承View需要處理wrap_content 時onMeasure方法的寬高
*/
public class DashboardView extends View {
/** 定義一些缺省值 */
private int mRadius; // 扇形半徑
private int mStartAngle = 150; // 起始角度,180-30度(左邊平行夾角30度)
private int mSweepAngle = 240; // 繪製角度,360+30度 = 150+240度
private int mMin = 0; // 最小值
private int mMax = 100; // 最大值
private int mSection = 8; // 值域(mMax-mMin)等分份數
private int mPortion = 5; // 一個mSection等分份數
private String mHeaderText = ""; // 表頭 Mbps
private int mVelocity = mMin; // 實時速度
private int mStrokeWidth; // 畫筆寬度
private int mLength1; // 長刻度的相對圓弧的長度
private int mLength2; // 刻度讀數頂部的相對圓弧的長度
private int mPLRadius; // 指針長半徑
private int mPSRadius; // 指針短半徑
private int mPadding;
private float mCenterX, mCenterY; // 圓心座標
private Paint mPaint;
private RectF mRectFArc;
private Path mPath;
private RectF mRectFInnerArc;
private Rect mRectText;
private String[] mTexts;
private int[] mColors;
private Context mContext;
public DashboardView(Context context) {
this(context, null);
mContext = context;
}
public DashboardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
mContext = context;
}
public DashboardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init();
}
@TargetApi(Build.VERSION_CODES.M)
private void init() {
mStrokeWidth = dp2px(3);
mLength1 = dp2px(8) + mStrokeWidth;
mLength2 = mLength1 + dp2px(4);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mRectFArc = new RectF();
mPath = new Path();
mRectFInnerArc = new RectF();
mRectText = new Rect();
mTexts = new String[mSection + 1]; // 需要顯示mSection + 1個刻度讀數
for (int i = 0; i < mTexts.length; i++) {
int n = (mMax - mMin) / mSection;
mTexts[i] = String.valueOf(mMin + i * n);
}
// TODO: 2020/4/20 重置刻度盤
mTexts[0] = "0M";
mTexts[1] = "1M";
mTexts[2] = "2M";
mTexts[3] = "5M";
mTexts[4] = "10M";
mTexts[5] = "20M";
mTexts[6] = "50M";
mTexts[7] = "80M";
mTexts[8] = "100M";
mColors = new int[]{
mContext.getColor(R.color.color_green),
mContext.getColor(R.color.color_yellow),
mContext.getColor(R.color.color_red)
};
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mPadding = Math.max(
Math.max(getPaddingLeft(), getPaddingTop()),
Math.max(getPaddingRight(), getPaddingBottom())
);
setPadding(mPadding, mPadding, mPadding, mPadding);
int width = resolveSize(dp2px(260), widthMeasureSpec);
mRadius = (width - mPadding * 2 - mStrokeWidth * 2) / 2;
// 由起始角度確定的高度
float[] point1 = getCoordinatePoint(mRadius, mStartAngle);
// 由結束角度確定的高度
float[] point2 = getCoordinatePoint(mRadius, mStartAngle + mSweepAngle);
int height = (int) Math.max(point1[1] + mRadius + mStrokeWidth * 2,
point2[1] + mRadius + mStrokeWidth * 2);
// TODO: 2020/4/20 需要處理高度
// setMeasuredDimension(width, height + getPaddingTop() + getPaddingBottom());
setMeasuredDimension(width, width);
mCenterX = mCenterY = getMeasuredWidth() / 2f;
mRectFArc.set(
getPaddingLeft() + mStrokeWidth,
getPaddingTop() + mStrokeWidth,
getMeasuredWidth() - getPaddingRight() - mStrokeWidth,
getMeasuredWidth() - getPaddingBottom() - mStrokeWidth
);
mPaint.setTextSize(sp2px(16));
mPaint.getTextBounds("0", 0, "0".length(), mRectText);
mRectFInnerArc.set(
getPaddingLeft() + mLength2 + mRectText.height() + dp2px(30),
getPaddingTop() + mLength2 + mRectText.height() + dp2px(30),
getMeasuredWidth() - getPaddingRight() - mLength2 - mRectText.height() - dp2px(30),
getMeasuredWidth() - getPaddingBottom() - mLength2 - mRectText.height() - dp2px(30)
);
mPLRadius = mRadius - dp2px(30);
mPSRadius = dp2px(25);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/** 畫測速儀背景色 */
// canvas.drawColor(mContext.getColor(R.color.color_dark));
/** 畫圓弧 最外層 */
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setColor(mContext.getColor(R.color.appBlue));
canvas.drawArc(mRectFArc, mStartAngle, mSweepAngle, false, mPaint);
/**
* 畫長刻度
* 畫好起始角度的一條刻度後通過canvas繞着原點旋轉來畫剩下的長刻度
*/
double cos = Math.cos(Math.toRadians(mStartAngle - 180));
double sin = Math.sin(Math.toRadians(mStartAngle - 180));
float x0 = (float) (mPadding + mStrokeWidth + mRadius * (1 - cos));
float y0 = (float) (mPadding + mStrokeWidth + mRadius * (1 - sin));
float x1 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - mLength1) * cos);
float y1 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - mLength1) * sin);
canvas.save();
canvas.drawLine(x0, y0, x1, y1, mPaint);
float angle = mSweepAngle * 1f / mSection;
for (int i = 0; i < mSection; i++) {
canvas.rotate(angle, mCenterX, mCenterY);
canvas.drawLine(x0, y0, x1, y1, mPaint);
}
canvas.restore();
/**
* 畫短刻度
* 同樣採用canvas的旋轉原理
*/
canvas.save();
mPaint.setStrokeWidth(mStrokeWidth / 2f);
float x2 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - 2 * mLength1 / 3f) * cos);
float y2 = (float) (mPadding + mStrokeWidth + mRadius - (mRadius - 2 * mLength1 / 3f) * sin);
canvas.drawLine(x0, y0, x2, y2, mPaint);
angle = mSweepAngle * 1f / (mSection * mPortion);
for (int i = 1; i < mSection * mPortion; i++) {
canvas.rotate(angle, mCenterX, mCenterY);
if (i % mPortion == 0) { // 避免與長刻度畫重合
continue;
}
canvas.drawLine(x0, y0, x2, y2, mPaint);
}
canvas.restore();
/**
* 畫長刻度讀數
*/
mPaint.setTextSize(sp2px(16));
mPaint.setStyle(Paint.Style.FILL);
float α;
float[] p;
angle = mSweepAngle * 1f / mSection;
for (int i = 0; i <= mSection; i++) {
α = mStartAngle + angle * i;
p = getCoordinatePoint(mRadius - mLength2, α);
if (α % 360 > 135 && α % 360 < 225) {
mPaint.setTextAlign(Paint.Align.LEFT);
} else if ((α % 360 >= 0 && α % 360 < 45) || (α % 360 > 315 && α % 360 <= 360)) {
mPaint.setTextAlign(Paint.Align.RIGHT);
} else {
mPaint.setTextAlign(Paint.Align.CENTER);
}
if (!TextUtils.isEmpty(mHeaderText)) {
mPaint.getTextBounds(mHeaderText, 0, mTexts[i].length(), mRectText);
}
int txtH = mRectText.height();
if (i <= 1 || i >= mSection - 1) {
canvas.drawText(mTexts[i], p[0], p[1] + txtH / 2, mPaint);
} else if (i == 3) {
canvas.drawText(mTexts[i], p[0] + txtH / 2, p[1] + txtH, mPaint);
} else if (i == mSection - 3) {
canvas.drawText(mTexts[i], p[0] - txtH / 2, p[1] + txtH, mPaint);
} else {
canvas.drawText(mTexts[i], p[0], p[1] + txtH, mPaint);
}
}
/** 畫內層圓弧 漸變色*/
/**
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dp2px(10));
mPaint.setShader(generateSweepGradient());
canvas.drawArc(mRectFInnerArc, mStartAngle + 1, mSweepAngle - 2, false, mPaint);
*/
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setShader(null);
/** 畫表頭 沒有表頭就不畫 */
if (!TextUtils.isEmpty(mHeaderText)) {
mPaint.setTextSize(sp2px(16));
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.getTextBounds(mHeaderText, 0, mHeaderText.length(), mRectText);
canvas.drawText(mHeaderText, mCenterX, mCenterY - mRectText.height() * 3, mPaint);
}
/** 畫指針 */
float a = mStartAngle + mSweepAngle * (mVelocity - mMin) / (mMax - mMin); // 指針與水平線夾角
mPaint.setColor(mContext.getColor(R.color.appBlue));
int r = mRadius / 10;
canvas.drawCircle(mCenterX, mCenterY, dp2px(9), mPaint);//中心圓
mPaint.setColor(mContext.getColor(R.color.color_light));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dp2px(3));
canvas.drawCircle(mCenterX, mCenterY, dp2px(12), mPaint);//空心圓
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(dp2px(2));
mPaint.setColor(mContext.getColor(R.color.appBlue));
// float[] p1 = getCoordinatePoint(mPLRadius, a);
// Path path = new Path();
// path.moveTo(p1[0], p1[1]);// 此點爲多邊形的起點
// path.lineTo(mCenterX-10, mCenterY+10);
// path.lineTo(mCenterX+10, mCenterY-10);
// path.close(); // 使這些點構成封閉的多邊形
// canvas.drawPath(path, mPaint);
mPaint.setColor(mContext.getColor(R.color.appBlue));
int d = dp2px(5); // 指針由兩個等腰三角形構成,d爲共底邊長的一半
mPath.reset();
float[] p1 = getCoordinatePoint(mPLRadius, a);
mPath.moveTo(p1[0], p1[1]);
float[] p2 = getCoordinatePoint(d, a-90);
mPath.lineTo(p2[0], p2[1]);
float[] p3 = getCoordinatePoint(d, a + 90);
mPath.lineTo(p3[0], p3[1]);
// float[] p4 = getCoordinatePoint(d, a + 180);
// mPath.lineTo(p4[0], p4[1]);
mPath.close();
canvas.drawPath(mPath, mPaint);
// canvas.drawLine(p1[0], p1[1], mCenterX, mCenterY, mPaint);
// float[] p2 = getCoordinatePoint(mPSRadius, θ + 180);
// canvas.drawLine(mCenterX, mCenterY, p2[0], p2[1], mPaint);
/** 畫實時度數值 */
/**
mPaint.setColor(mContext.getColor(R.color.colorPrimary));
mPaint.setStrokeWidth(dp2px(2));
int xOffset = dp2px(22);
if (mVelocity >= 100) {
drawDigitalTube(canvas, mVelocity / 100, -xOffset);
drawDigitalTube(canvas, (mVelocity - 100) / 10, 0);
drawDigitalTube(canvas, mVelocity % 100 % 10, xOffset);
} else if (mVelocity >= 10) {
drawDigitalTube(canvas, -1, -xOffset);
drawDigitalTube(canvas, mVelocity / 10, 0);
drawDigitalTube(canvas, mVelocity % 10, xOffset);
} else {
drawDigitalTube(canvas, -1, -xOffset);
drawDigitalTube(canvas, -1, 0);
drawDigitalTube(canvas, mVelocity, xOffset);
}
*/
}
/** 數碼管樣式 */
private void drawDigitalTube(Canvas canvas, int num, int xOffset) {
float x = mCenterX + xOffset;
float y = mCenterY + dp2px(40);
int lx = dp2px(5);
int ly = dp2px(10);
int gap = dp2px(2);
// 1
mPaint.setAlpha(num == -1 || num == 1 || num == 4 ? 25 : 255);
canvas.drawLine(x - lx, y, x + lx, y, mPaint);
// 2
mPaint.setAlpha(num == -1 || num == 1 || num == 2 || num == 3 || num == 7 ? 25 : 255);
canvas.drawLine(x - lx - gap, y + gap, x - lx - gap, y + gap + ly, mPaint);
// 3
mPaint.setAlpha(num == -1 || num == 5 || num == 6 ? 25 : 255);
canvas.drawLine(x + lx + gap, y + gap, x + lx + gap, y + gap + ly, mPaint);
// 4
mPaint.setAlpha(num == -1 || num == 0 || num == 1 || num == 7 ? 25 : 255);
canvas.drawLine(x - lx, y + gap * 2 + ly, x + lx, y + gap * 2 + ly, mPaint);
// 5
mPaint.setAlpha(num == -1 || num == 1 || num == 3 || num == 4 || num == 5 || num == 7
|| num == 9 ? 25 : 255);
canvas.drawLine(x - lx - gap, y + gap * 3 + ly,
x - lx - gap, y + gap * 3 + ly * 2, mPaint);
// 6
mPaint.setAlpha(num == -1 || num == 2 ? 25 : 255);
canvas.drawLine(x + lx + gap, y + gap * 3 + ly,
x + lx + gap, y + gap * 3 + ly * 2, mPaint);
// 7
mPaint.setAlpha(num == -1 || num == 1 || num == 4 || num == 7 ? 25 : 255);
canvas.drawLine(x - lx, y + gap * 4 + ly * 2, x + lx, y + gap * 4 + ly * 2, mPaint);
}
private int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
Resources.getSystem().getDisplayMetrics());
}
private int sp2px(int sp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp,
Resources.getSystem().getDisplayMetrics());
}
public float[] getCoordinatePoint(int radius, float angle) {
float[] point = new float[2];
double arcAngle = Math.toRadians(angle); //將角度轉換爲弧度
if (angle < 90) {
point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
} else if (angle == 90) {
point[0] = mCenterX;
point[1] = mCenterY + radius;
} else if (angle > 90 && angle < 180) {
arcAngle = Math.PI * (180 - angle) / 180.0;
point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
} else if (angle == 180) {
point[0] = mCenterX - radius;
point[1] = mCenterY;
} else if (angle > 180 && angle < 270) {
arcAngle = Math.PI * (angle - 180) / 180.0;
point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
} else if (angle == 270) {
point[0] = mCenterX;
point[1] = mCenterY - radius;
} else {
arcAngle = Math.PI * (360 - angle) / 180.0;
point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
}
return point;
}
private SweepGradient generateSweepGradient() {
SweepGradient sweepGradient = new SweepGradient(mCenterX, mCenterY,
mColors,
new float[]{0, 140 / 360f, mSweepAngle / 360f}
);
Matrix matrix = new Matrix();
matrix.setRotate(mStartAngle - 3, mCenterX, mCenterY);
sweepGradient.setLocalMatrix(matrix);
return sweepGradient;
}
public int getVelocity() {
return mVelocity;
}
public void setVelocity(int velocity) {
if (mVelocity == velocity || velocity < mMin || velocity > mMax) {
return;
}
mVelocity = velocity;
postInvalidate();
}
public int getmMax() {
return mMax;
}
public int getmSection() {
return mSection;
}
public int getmPortion() {
return mPortion;
}
}
xml佈局文件:
<?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="match_parent"
android:layout_height="match_parent"
tools:context="com.example.dashboardView.TestDashViewActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_marginLeft="48dp"
android:layout_marginRight="48dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:layout_marginTop="32dp"
>
<com.example.dashboardView.DashboardView
android:id="@+id/dbv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</LinearLayout>
color.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="appBlue">#008577</color>
<color name="color_light">#f4f4f4</color>
<color name="color_green">#00ff00</color>
<color name="color_yellow">#ffff00</color>
<color name="color_red">#ff0000</color>
</resources>
使用:
package com.example.dashboardView;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.animation.LinearInterpolator;
import com.example.testgesturedetector.R;
public class TestDashViewActivity extends Activity {
private static final String TAG = "TestDashViewActivity";
private DashboardView dbv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_dash_view);
dbv = findViewById(R.id.dbv);
startShowSpeedAnimator("50Mbps");
}
private boolean isAnimFinished = true;//播放動畫控制
private void startShowSpeedAnimator(String speedStr){
float speed ;
try {
speed = Float.parseFloat(speedStr.replaceAll("[a-zA-Z\u4e00-\u9fa5]", "").replaceAll(" ",""));
} catch (NumberFormatException e) {
e.printStackTrace();
Log.e(TAG,"startShowSpeedAnimator error = " + e.getMessage());
return;
}
if(speed == 0){
Log.d(TAG,"測試過程中統計的速率爲0,不顯示");
return;
}
int velocity = getRealVelocity(speed);
if (isAnimFinished) {
Log.i(TAG,"velocity = " + velocity);
ObjectAnimator animator = ObjectAnimator.ofInt(dbv, "mRealTimeValue", dbv.getVelocity() ,velocity);
animator.setDuration(500).setInterpolator(new LinearInterpolator());
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
isAnimFinished = false;
Log.i(TAG,"onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.i(TAG,"onAnimationEnd");
isAnimFinished = true;
}
@Override
public void onAnimationCancel(Animator animation) {
isAnimFinished = true;
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
dbv.setVelocity(value);
//Log.v(TAG,"onAnimationUpdate value = " + value);
}
});
animator.start();
}
}
private int getRealVelocity(float speed){
float velocity = 0;
if(dbv == null){
return 0;
}
int max = dbv.getmMax();
int section = dbv.getmSection();
int portion = dbv.getmPortion();
float bigScale = (float) (max*1.0/section);
float smallScale = (float) (bigScale*1.0/portion);
if(speed >= 100){
velocity = 100;
}else if(speed > 80){
velocity = (float) (((speed-80)/4.0)*smallScale + 7*bigScale);
}else if(speed > 50){
velocity = (float) (((speed-50)/6.0)* smallScale+ 6*bigScale);
}else if(speed > 20){
velocity = (float) (((speed-20)/6.0)*smallScale + 5*bigScale);
}else if(speed > 10){
velocity = (float) (((speed-10)/2.0)*smallScale + 4*bigScale);
}else if(speed > 5){
velocity = (float) (((speed-5.0))*smallScale + 3*bigScale);
}else if(speed > 2){
velocity = (float) (((speed-2)/0.6)*smallScale + 2*bigScale);
}else if(speed > 1){
velocity = (float) (((speed-1)/0.2)*smallScale + 1*bigScale);
}else if(speed > 0){
velocity = (float) (((speed)/0.2)*smallScale);
}
Log.d(TAG,"speed = " + speed + " || velocity = " + velocity + " || (int)velocity = " + (int)velocity);
return (int)velocity;
}
}
功能已實現,但是並沒有做很好的封裝,有興趣的同學可以自行修改。可以根據自己的業務需要來定製自己的儀表盤。