Android自定義View實現簡單的折線圖、柱狀圖
阿新 • • 發佈:2019-01-28
首先說第一個柱狀圖,實現很簡單。一個自定義View,重現裡面的OnDraw方法。然後利用paint,canvas繪製帶填充的長方形即可。每個長方形的X軸平方View的x軸即可,長方形的高度通過簡單的計算即可得到。下面上柱狀圖程式碼
package com.hrules.charter.demo.widget; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.animation.BounceInterpolator; import com.hrules.charter.demo.R; import java.util.Random; /** * 柱狀圖 * Created by 黃海 on 2017/4/17. */ public class Histogram extends View implements View.OnClickListener { int width, height; Paint paintBar, paintText; float[] values = new float[15]; float[] valuesTemp = new float[15]; int colorBackground = R.color.default_barBackgroundColor;//柱狀背景 int[] colorBar = new int[]{R.color.lightBlue500, R.color.lightBlue400, R.color.lightBlue300};//柱狀顏色 float maxY; int barMarginLeft = 7; int tagHeight = 45;//x和Y軸的數字 boolean anim; boolean showLineXNums = true, showLineYNums = true;//展示X、Y軸的數字 int lineYNums = 5;//Y軸展示的格數 ValueAnimator valueAnimator; public Histogram(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public Histogram(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); paintText = paintBar = new Paint(); paintBar.setAntiAlias(true); paintText.setAntiAlias(true); paintText.setTextSize(25); paintText.setColor(getResources().getColor(colorBar[0])); initValuesAndMaxY(); setOnClickListener(this); } private void initValuesAndMaxY() { Random random = new Random(); for (int i = 0; i < values.length; i++) { values[i] = random.nextFloat() * 100; } for (float i : values) { maxY = maxY < i ? i : maxY; } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); width = getMeasuredWidth(); height = getMeasuredHeight(); int barWidth = (width - tagHeight) / values.length; int j = 0; if (!anim) valuesTemp = values.clone(); for (int i = 0; i < valuesTemp.length; i++) { RectF rect = new RectF(); rect.left = tagHeight + i * barWidth + barMarginLeft; rect.top = (height - tagHeight) * (1 - 1.0f * valuesTemp[i] / maxY); rect.right = rect.left + barWidth - barMarginLeft; rect.bottom = height - tagHeight; //draw the barBackground paintBar.setColor(getResources().getColor(colorBackground)); canvas.drawRect(rect.left, 0, rect.right, rect.bottom, paintBar); //paint the bar j = j > colorBar.length - 1 ? 0 : j; paintBar.setColor(getResources().getColor(colorBar[j++])); canvas.drawRect(rect, paintBar); if (showLineXNums) { //draw x-coordinate num float textWidth = paintText.measureText(String.valueOf(i)); float textLeft = rect.left + rect.width() / 2 - textWidth / 2; canvas.drawText(String.valueOf(i), textLeft, height, paintText); } } //draw y-coordinate num if (showLineYNums) { int avgHeight = (height - tagHeight) / lineYNums; for (int i = 0; i < lineYNums; i++) { float x = 0; float y = (height - tagHeight) - avgHeight * (i + 1); int valueY = (int) (maxY * (i + 1) / lineYNums); // canvas寫字是從x、y軸往右上寫的 canvas.drawText(String.valueOf(valueY), x, y + 30, paintText); } } } @Override public void onClick(View v) { anim = true; valueAnimator = ValueAnimator.ofFloat(0f, 1f); valuesTemp = values.clone(); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { for (int i = 0; i < valuesTemp.length; i++) { //update valuesTemps float animatedValue = (float) animation.getAnimatedValue(); valuesTemp[i] = maxY * animatedValue < values[i] ? maxY * animatedValue : values[i]; } invalidate(); } }); // valueAnimator.setInterpolator(new LinearInterpolator()); //落地回撥效果 valueAnimator.setInterpolator(new BounceInterpolator()); valueAnimator.setDuration(2000l); valueAnimator.start(); } }
實現比較簡單,註釋都有。最後說下onClick方法的作用:valuesTemp是原有柱狀資料的副本,view點選後讓valuesTemp的每個資料從maxY的最小百分比開始不斷的增長,從而實現一個動態改變的動畫效果,如下:
折線圖運用的paint,canvas與path完成,計算每個點的位置和柱狀圖類似,折線圖形成一個填充效果也簡單就是path close一下,然後繪製close後的path就是。下面看原始碼
package com.hrules.charter.demo.widget; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import com.hrules.charter.demo.R; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * 折線圖 * Created by 黃海 on 2017/4/18. */ public class LineChart extends View { Path path = new Path(); float[] values = new float[15]; float maxY; Paint paintXy, paintLine; int width, height;//view 寬高 public LineChart(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public LineChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initValuesAndMaxY(); paintXy = new Paint(Paint.ANTI_ALIAS_FLAG); paintXy.setStyle(Paint.Style.STROKE); paintXy.setStrokeWidth(6); paintXy.setColor(getResources().getColor(R.color.lightBlue500)); paintLine = new Paint(Paint.ANTI_ALIAS_FLAG); paintLine.setStyle(Paint.Style.FILL); paintLine.setColor(getResources().getColor(R.color.colorPrimary)); } private void initValuesAndMaxY() { Random random = new Random(); for (int i = 0; i < values.length; i++) { values[i] = random.nextFloat() * 100; maxY = maxY < values[i] ? values[i] : maxY; } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); width = getMeasuredWidth(); height = getMeasuredHeight(); //每格寬度 int widthAvg = width / values.length; //畫x軸y軸 path.moveTo(0, 0); path.lineTo(0, height); path.lineTo(width, height); canvas.drawPath(path, paintXy); List<PointF> list = new ArrayList<>(); for (int i = 0; i < values.length; i++) {//收集座標資訊 PointF point = new PointF(widthAvg / 2 + i * widthAvg, 0); point.set(widthAvg / 2 + i * widthAvg, (1 - values[i] / maxY) * height); list.add(point); } //畫線 path.reset();//清除 畫x軸y軸而產生的閉合影響 path.moveTo(list.get(0).x, list.get(0).y); for (int i = 1; i < values.length; i++) { path.lineTo(list.get(i).x, list.get(i).y); // path.cubicTo(ps.x, ps.y,(ps.x+pe.x)/2,(ps.y+pe.y)/2, pe.x, pe.y);//畫三次貝塞爾曲線 } canvas.drawPath(path, paintXy); //折線圖閉合 path.lineTo(list.get(values.length - 1).x, height); path.lineTo(list.get(0).x, height); path.close(); canvas.drawPath(path, paintLine); //畫點 for (int i = 0; i < values.length; i++) { canvas.drawCircle(list.get(i).x, list.get(i).y, 9, paintLine); } } }