Android 曲線圖的繪制示例代碼
本文介紹了Android 曲線圖的繪制示例代碼,分享給大家,具體如下:
效果展示
效果展示.gif
使用方式
// 初始化數(shù)據(jù)表格相關(guān) with(mTableView) { // 配置坐標(biāo)系 setupCoordinator("日", "人", /*這里是橫坐標(biāo)的值*/0f, 5f, 10f, 15f, 20f, 25f, 30f) // 添加曲線, 確??v坐標(biāo)的數(shù)值位數(shù)相等 addWave(ContextCompat.getColor(this@MainActivity, R.color.colorYellow), false, 0f, 10f, 30f, 54f, 30f, 100f, 10f) addWave(ContextCompat.getColor(this@MainActivity, R.color.colorGreen), false, 0f, 30f, 20f, 20f, 46f, 25f, 5f) addWave(ContextCompat.getColor(this@MainActivity, R.color.colorPink), false, 0f, 30f, 20f, 50f, 46f, 30f, 30f) addWave(Color.parseColor("#8596dee9"), true, 0f, 15f, 10f, 10f, 40f, 20f, 5f) }
實現(xiàn)思路
- 橫坐標(biāo)是固定的, 縱坐標(biāo)需要跟隨曲線傳入的數(shù)值去動態(tài)的調(diào)整
- 繪制坐標(biāo)軸: 縱橫交錯的網(wǎng)格
- 根據(jù)用戶傳入坐標(biāo)數(shù)值去繪制坐標(biāo)軸上的數(shù)值
- 給X軸和Y軸添加單位信息
- 根據(jù)用戶傳入的具體的數(shù)值繪制曲線(這里不采用Bezier, 不容易精確的控制頂點的位置)
- 繪制填充效果
- 添加屬性動畫
代碼實現(xiàn)
/** * Created by FrankChoo on 2017/12/29. * Email: frankchoochina@gmail.com * Version: 1.0 * Description: 表格自定義View */ public class TableView extends View { private List<WaveConfigData> mWaves;// 數(shù)值集合 // 坐標(biāo)軸的數(shù)值 private int mCoordinateYCount = 8; private float[] mCoordinateXValues;// 外界傳入 private float[] mCoordinateYValues;// 動態(tài)計算 // 坐標(biāo)的單位 private String mXUnit; private String mYUnit; // 所有曲線中所有數(shù)據(jù)中的最大值 private float mGlobalMaxValue;// 用于確認(rèn)是否需要調(diào)整坐標(biāo)系 private Paint mCoordinatorPaint; private Paint mTextPaint; private Paint mWrapPaint; // 坐標(biāo)軸上描述性文字的空間大小 private int mTopUnitHeight;// 頂部Y軸單位高度 private int mBottomTextHeight; private int mLeftTextWidth; // 網(wǎng)格尺寸 private int mGridWidth, mGridHeight; private float mAnimProgress; public TableView(Context context) { this(context, null); } public TableView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public TableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); post(new Runnable() { @Override public void run() { showAnimator(); } }); } private void init() { // 初始化數(shù)據(jù)集合的容器 mWaves = new ArrayList<>(); // 坐標(biāo)系的單位 mBottomTextHeight = dp2px(40);// X軸底部字體的高度 mLeftTextWidth = mBottomTextHeight;// Y軸左邊字體的寬度 mTopUnitHeight = dp2px(30);// 頂部Y軸的單位 // 初始化坐標(biāo)軸Paint mCoordinatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mCoordinatorPaint.setColor(Color.LTGRAY); // 初始化文本Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mTextPaint.setColor(Color.GRAY); mTextPaint.setTextSize(sp2px(12)); // 初始化曲線Paint mWrapPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mWrapPaint.setPathEffect(new CornerPathEffect(200f)); } /** * 配置坐標(biāo)軸信息 * * @param xUnit X 軸的單位 * @param yUnit Y 軸的單位 * @param coordinateXValues X 坐標(biāo)軸上的數(shù)值 */ public void setupCoordinator(String xUnit, String yUnit, float... coordinateXValues) { mXUnit = xUnit; mYUnit = yUnit; mCoordinateXValues = coordinateXValues; } /** * 添加一條曲線, 確保與橫坐標(biāo)的數(shù)值對應(yīng) * * @param color * @param isCoverRegion * @param values */ public void addWave(int color, boolean isCoverRegion, float... values) { mWaves.add(new WaveConfigData(color, isCoverRegion, values)); // 根據(jù)value的值去計算縱坐標(biāo)的數(shù)值 float maxValue = 0; for (float value : values) { maxValue = Math.max(maxValue, value); } if (maxValue < mGlobalMaxValue) return; mGlobalMaxValue = maxValue; // 保證網(wǎng)格的數(shù)值都為 5 的倍數(shù) float gridValue = mGlobalMaxValue / (mCoordinateYCount - 1); if (gridValue % 5 != 0) { gridValue += 5 - (gridValue % 5); } // 給縱坐標(biāo)的數(shù)值賦值 mCoordinateYValues = new float[mCoordinateYCount]; for (int i = 0; i < mCoordinateYCount; i++) { mCoordinateYValues[i] = i * gridValue; } invalidate(); } @Override protected void onDraw(Canvas canvas) { drawCoordinate(canvas); drawWrap(canvas); } public void showAnimator() { ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(1000); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mAnimProgress = (float) animation.getAnimatedValue(); invalidate(); } }); animator.start(); } /** * 繪制坐標(biāo)系 */ private void drawCoordinate(Canvas canvas) { Point start = new Point(); Point stop = new Point(); // 1. 繪制橫軸線和縱坐標(biāo)單位 int xLineCount = mCoordinateYValues.length; mGridHeight = (getHeight() - getPaddingTop() - getPaddingBottom() - mBottomTextHeight - mTopUnitHeight) / (xLineCount - 1); for (int i = 0; i < xLineCount; i++) { start.x = getPaddingLeft() + mLeftTextWidth; start.y = getHeight() - getPaddingBottom() - mBottomTextHeight - mGridHeight * i; stop.x = getRight() - getPaddingRight(); stop.y = start.y; // 繪制橫軸線 canvas.drawLine(start.x, start.y, stop.x, stop.y, mCoordinatorPaint); // 繪制縱坐標(biāo)單位 if (i == 0) continue; String drawText = String.valueOf((int) mCoordinateYValues[i]); Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt(); float offsetY = ((fontMetrics.bottom - fontMetrics.top) / 2 + fontMetrics.bottom) / 2; float baseLine = start.y + offsetY; float left = getPaddingLeft() + mLeftTextWidth / 2 - mTextPaint.measureText(drawText) / 2; canvas.drawText(drawText, left, baseLine, mTextPaint); // 繪制Y軸單位 if (i == xLineCount - 1) { drawText = mYUnit; baseLine = getPaddingTop() + mTopUnitHeight / 2; canvas.drawText(drawText, left, baseLine, mTextPaint); } } // 2. 繪制縱軸線和橫坐標(biāo)單位 int yLineCount = mCoordinateXValues.length; mGridWidth = (getWidth() - getPaddingLeft() - getPaddingRight() - mLeftTextWidth) / (yLineCount - 1); for (int i = 0; i < yLineCount; i++) { start.x = getPaddingTop() + mLeftTextWidth + mGridWidth * i; start.y = getPaddingTop() + mTopUnitHeight; stop.x = start.x; stop.y = getHeight() - mBottomTextHeight - getPaddingBottom(); // 繪制縱軸線 canvas.drawLine(start.x, start.y, stop.x, stop.y, mCoordinatorPaint); // 繪制橫坐標(biāo)單位 String drawText = String.valueOf((int) mCoordinateXValues[i]); Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt(); float offsetY = ((fontMetrics.bottom - fontMetrics.top) / 2 + fontMetrics.bottom) / 2; float baseLine = getHeight() - getPaddingBottom() - mBottomTextHeight / 2 + offsetY; float left = start.x - mTextPaint.measureText(drawText) / 2; // 繪制X軸單位 if (i == 0) { drawText = mXUnit; left = getPaddingLeft() + mLeftTextWidth / 2 - mTextPaint.measureText(drawText) / 2; } canvas.drawText(drawText, left, baseLine, mTextPaint); } } /** * 繪制曲線 */ private void drawWrap(Canvas canvas) { canvas.clipRect(new RectF( mLeftTextWidth, getPaddingTop() + mTopUnitHeight, (getRight() - getPaddingRight()) * mAnimProgress, getHeight() - getPaddingBottom() - mBottomTextHeight) ); float yHeight = mGridHeight * (mCoordinateYCount - 1); for (WaveConfigData wave : mWaves) { Path path = new Path(); path.moveTo(0, getHeight()); float maxY = mCoordinateYValues[mCoordinateYCount - 1];// Y軸坐標(biāo)的最大值 for (int index = 1; index < wave.values.length; index++) { path.lineTo( mLeftTextWidth + mGridWidth * index, getHeight() - getPaddingBottom() - mBottomTextHeight - yHeight * (wave.values[index] / maxY) ); } if (wave.isCoverRegion) { mWrapPaint.setStyle(Paint.Style.FILL); path.lineTo(getRight() - getPaddingRight(), getHeight()); path.close(); } else { mWrapPaint.setStyle(Paint.Style.STROKE); mWrapPaint.setStrokeWidth(10); } mWrapPaint.setColor(wave.color); canvas.drawPath(path, mWrapPaint); } } private int dp2px(float dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); } private int sp2px(float sp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics()); } public static class WaveConfigData { int color; boolean isCoverRegion; float values[]; public WaveConfigData(int color, boolean isCoverRegion, float[] values) { this.color = color; this.isCoverRegion = isCoverRegion; this.values = values; } } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Flutter桌面應(yīng)用如何進(jìn)行多分辨率適配
這篇文章主要為大家介紹了Flutter桌面應(yīng)用如何進(jìn)行多分辨率適配的方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Android自定義控件案例匯總2(自定義開關(guān)、下拉刷新、側(cè)滑菜單)
這篇文章主要介紹了Android自定義控件案例匯總,自定義開關(guān)、Listview實現(xiàn)下拉刷新、側(cè)滑菜單,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12android activity設(shè)置無標(biāo)題實現(xiàn)全屏
本文將詳細(xì)介紹Android如何設(shè)置Activity全屏和無標(biāo)題的實現(xiàn)方法,需要的朋友可以參考下2012-12-12Android 實現(xiàn)徹底退出自己APP 并殺掉所有相關(guān)的進(jìn)程
這篇文章主要介紹了Android 實現(xiàn)徹底退出自己APP 并殺掉所有相關(guān)的進(jìn)程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android實現(xiàn)回彈ScrollView的原理
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)回彈ScrollView的原理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04Android TextView實現(xiàn)點擊顯示全文與隱藏功能(附源碼)
TextView用法很多,用到的地方更是普遍,所以學(xué)好TextView的使用很重要很重要很重要。下面這篇文章主要介紹了Android中TextView實現(xiàn)顯示全文與隱藏功能的相關(guān)資料,文中給出了詳細(xì)的示例代碼和源碼下載,需要的朋友可以參考下。2017-03-03