使用
ColumnChart lineChart = findViewById(R.id.lineChart);
//設定20組數據
int[][] data = new int[20][5];
data[0][1] = R.color.blue_rgba_24_261_255;
data[0][2] = R.color.orange;
data[0][3] = R.color.red_2;
data[0][4] = R.color.main_green;
//設置隨機數
Random random = new Random();
for (int i = 0; i < 20; i++) {
data[i][0] =random.nextInt(19)+1;
}
lineChart.setData(data);//添加數據
lineChart.start();//開啓動畫
佈局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<com.example.ocean.charts.column.ColumnChart
android:id="@+id/lineChart"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="400dp"
app:graphTitle = "騎行記錄"
app:xAxisNmae = "第幾天"
app:yAxisNmae = "騎行公里數"
app:axisTextSize = "4sp"
app:axisDevidedSizeX = "20"
app:axisDevidedSizeY = "20"
android:layout_marginBottom="30dp"
/>
<!-- app:axisTextColor = "000000"-->
</RelativeLayout>
實現步驟
drawTitle(canvas,basePaint);//繪製標題
drawAxisArrowX(canvas,basePaint);//繪製X軸箭頭
drawAxisArrowY(canvas,basePaint);//繪製Y軸箭頭
drawAxisX(canvas,basePaint);//繪製X軸
drawAxisY(canvas,basePaint);//繪製Y軸
drawAxisScaleX(canvas,basePaint);//繪製X軸刻度
drawAxisScaleY(canvas,basePaint);//繪製Y軸刻度
drawAxisScaleValuveX(canvas,basePaint);//繪製X軸刻度值
drawAxisScaleXValuveY(canvas,basePaint);//繪製Y軸刻度值
drawColumn(canvas,basePaint);//繪製柱體
圖表類 ColumnChart.java
package com.example.ocean.charts.column;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import com.example.ocean.R;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
public class ColumnChart extends BaseColumnChart {
private float moveX;//當前手指在屏幕上X軸的座標
private float moveY;//當前手指在屏幕上Y軸的座標
private Paint touchPaint;//觸摸時候的畫筆
int[][] data;//傳入的數據
public void setData(int[][] data) {
this.data = data;
}
public ColumnChart(Context context) {
this(context,null);
}
public ColumnChart(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public ColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public ColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initPaint();
}
// 初始化畫筆
private void initPaint() {
touchPaint = new Paint(basePaint);//觸摸時候的畫筆,觸摸效果
touchPaint.setStyle(Paint.Style.FILL);
}
/** Explain : 返回當前需要設定的Y軸的長度
* @author LiXiang */
@Override
protected int getSpaceY() {
return getDefaultSpaceY();
}
/** Explain : 返回當前需要設定的X軸的長度
* @author LiXiang */
@Override
protected int getSpaceX() {
return getDefaultSpaceX();
}
/** Explain : 繪製柱體
* @author LiXiang */
@Override
protected void drawColumn(Canvas canvas, Paint basePaint) {
Paint rectPaint = new Paint(basePaint);
if (data != null) {
for (int i = 0; i < data.length; i++) {
getColumnColor(rectPaint, data[i]);
canvas.drawRect(originalX+segmentX*i,originalY-segmentY*data[i][0]*mAnimatedValue,originalX+segmentX*(i+1)-dip2px(2),originalY,rectPaint);
}
}
if ( onTouch && mAnimatedValue == mAnimatedValueMax) {//曲線繪製完成之後才能進行觸摸繪製
drawOnTouch(canvas);
}
}
/** Explain : 設置柱體顏色,依據當前不同高度設定不同顏色
* @author LiXiang */
private int getColumnColor(Paint rectPaint, int[] datum) {
int color = 0;
if (datum[0]<5){//高度<5的顏色
color = ContextCompat.getColor(mContext, data[0][1]);
}else if (datum[0]<10 && datum[0]>4){//4<高度<10的顏色
color = ContextCompat.getColor(mContext, data[0][2]);
}else if (datum[0]<15 && datum[0]>9){//9<高度<15的顏色
color = ContextCompat.getColor(mContext, data[0][3]);
}else {//14<高度 的顏色
color = ContextCompat.getColor(mContext, data[0][4]);
}
rectPaint.setColor(color);
return color;
}
/** Explain : 繪製X軸
* @author LiXiang */
@Override
protected void drawAxisX(Canvas canvas, Paint basePaint) {
canvas.drawLine(originalX,originalY,endX,originalY,basePaint);
}
/** Explain : 繪製Y軸
* @author LiXiang */
@Override
protected void drawAxisY(Canvas canvas, Paint basePaint) {
canvas.drawLine(originalX,endY,originalX,originalY,basePaint);
}
/** Explain : 繪製X軸刻度線
* @author LiXiang */
@Override
protected void drawAxisScaleX(Canvas canvas, Paint basePaint) {
for (int i = 0; i < axisDevidedSizeX-1; i++) {
canvas.drawLine(originalX+i*segmentX,originalY,originalX+i*segmentX,originalY-dip2px(2),basePaint);
}
}
/** Explain : 繪製Y軸刻度線
* @author LiXiang */
@Override
protected void drawAxisScaleY(Canvas canvas, Paint basePaint) {
for (int i = 0; i < axisDevidedSizeY-1; i++) {
canvas.drawLine(originalX,originalY-i*segmentY,originalX+dip2px(2),originalY-i*segmentY,basePaint);
}
}
/** Explain : 繪製X軸刻度值
* @author LiXiang */
@Override
protected void drawAxisScaleValuveX(Canvas canvas, Paint basePaint) {
for (int i = 1; i < axisDevidedSizeX+1; i++) {
canvas.drawText(i+"",originalX+i*segmentX-segmentX/2,originalY+dip2px(15),basePaint);
}
}
/** Explain : 繪製Y軸刻度值
* @author LiXiang */
@Override
protected void drawAxisScaleXValuveY(Canvas canvas, Paint basePaint) {
for (int i = 1; i < axisDevidedSizeY+1; i++) {
canvas.drawText(i+"",originalX-dip2px(10),originalY-i*segmentY+segmentX/2,basePaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//當前動畫沒有加載完成則不進行觸摸監聽
if (mAnimatedValue != mAnimatedValueMax) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mAnimatedValue == mAnimatedValueMax) {//當動畫效果展示完成以後纔可以進行觸摸效果
onTouch = true;
moveX = event.getX();
moveY = event.getY();
}
break;
case MotionEvent.ACTION_MOVE:
moveX = event.getX();
moveY = event.getY();
//觸摸效果只能在當前自定義視圖之內
if (moveX >= getLeft() && moveX <= getRight() && moveY >= getTop() && moveY <= getBottom()) {//設定範圍邊界
getParent().requestDisallowInterceptTouchEvent(true);//繪製區域內 允許子view響應觸摸事件
invalidate();
} else {//當滑出範圍邊界就對視圖延遲一秒後刷新
postDelayedInvalidate();
}
break;
case MotionEvent.ACTION_UP:
moveX = event.getX();
moveY = event.getY();
postDelayedInvalidate();//手指擡起後就對視圖延遲一秒後刷新
onTouch = false;
break;
}
// 返回值由當前是否正在滑動決定
return onTouch == true ? true : super.onTouchEvent(event);
}
private void drawOnTouch(Canvas canvas) {
//這裏獲取int整型數值 ,剛好與數據源的索引吻合 ,如果數據長度過短,可能會索引越界,可以對index進行判斷
int index = (int) ((moveX - originalX) / segmentX);
if (index >= data.length) index = data.length - 1;
float x0 = originalX + index * segmentX;
float y = data[index][0];
float y0 = originalY - y * segmentY;
//每次繪製之前都需重新設置漸變屬性
touchPaint.setColor(ContextCompat.getColor(getContext(), R.color.alpha));
//畫矩形
canvas.drawRect(x0, y0, x0 + segmentX -dip2px(2), originalY, touchPaint);
Paint p = new Paint(touchPaint);
p.setTextSize(dip2px(15));
p.setColor(getColumnColor(touchPaint,data[index]));
float x1 = x0 + 0.5f * segmentX;
canvas.drawText(y + "公里", x1, y0 - defaultPadding / 2, p);
}
}
基礎類 BaseColumnChart.java
package com.example.ocean.charts.column;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import com.example.ocean.R;
import com.example.ocean.charts.BaseChart;
import androidx.annotation.Nullable;
public abstract class BaseColumnChart extends BaseChart {
protected int axisDevidedSizeX;//軸間距數量
protected int axisDevidedSizeY;//軸間距數量
protected int axisTextColor;//刻度值顏色
protected int axisTextSize;//刻度值字體大小
protected String xAxisNmae;//X軸名字
protected String yAxisNmae;//Y軸名字
protected String graphTitle;//圖表名字
protected int segmentX;//軸間距
protected int segmentY;//軸間距
public BaseColumnChart(Context context) {
this(context,null);
}
public BaseColumnChart(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public BaseColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public BaseColumnChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
/** Explain : 獲取樣式
* @author LiXiang */
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.GraphStyle);
graphTitle = typedArray.getString(R.styleable.GraphStyle_graphTitle);
xAxisNmae = typedArray.getString(R.styleable.GraphStyle_xAxisNmae);
yAxisNmae = typedArray.getString(R.styleable.GraphStyle_yAxisNmae);
axisDevidedSizeX = typedArray.getInteger(R.styleable.GraphStyle_axisDevidedSizeX,10);
axisDevidedSizeY = typedArray.getInteger(R.styleable.GraphStyle_axisDevidedSizeY,20);
axisTextColor = typedArray.getColor(R.styleable.GraphStyle_axisTextColor,Color.GRAY);
axisTextSize= (int) typedArray.getDimension(R.styleable.GraphStyle_axisTextSize,4);
axisTextSize = dip2px(axisTextSize);
if (typedArray != null) {
typedArray.recycle();
}
//初始化基礎畫筆
basePaint.setTextSize(dip2px(12));//設置標題大小
basePaint.setTextAlign(Paint.Align.CENTER);//標題對其格式
basePaint.setStrokeWidth(dip2px(0.5f));//畫筆粗細
basePaint.setStrokeCap(Paint.Cap.ROUND);//設置圓角
basePaint.setColor(Color.GRAY);//設置畫筆顏色
Typeface font0 = Typeface.create(Typeface.SANS_SERIF, Typeface.DEFAULT_BOLD.getStyle());
basePaint.setTypeface(font0);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//獲取刻度的寬度
segmentX = (endX - originalX)/axisDevidedSizeX ;
segmentY = (originalY - endY )/axisDevidedSizeY;
drawTitle(canvas,basePaint);//繪製標題
drawAxisArrowX(canvas,basePaint);//繪製X軸箭頭
drawAxisArrowY(canvas,basePaint);//繪製Y軸箭頭
//以下類交給子類處理
drawAxisX(canvas,basePaint);//繪製X軸
drawAxisY(canvas,basePaint);//繪製Y軸
drawAxisScaleX(canvas,basePaint);//繪製X軸刻度
drawAxisScaleY(canvas,basePaint);//繪製Y軸刻度
drawAxisScaleValuveX(canvas,basePaint);//繪製X軸刻度值
drawAxisScaleXValuveY(canvas,basePaint);//繪製Y軸刻度值
drawColumn(canvas,basePaint);
}
/** Explain : 繪製X軸箭頭
* @author LiXiang */
private void drawAxisArrowX(Canvas canvas, Paint basePaint) {
Path path = new Path();
path.moveTo(endX+dip2px(2),originalY);
path.lineTo(endX,originalY + dip2px(2));
path.lineTo(endX,originalY - dip2px(2));
path.close();
canvas.drawPath(path,basePaint);
canvas.drawText(xAxisNmae,endX+dip2px(3),originalY+dip2px(15),basePaint);
}
/** Explain : 繪製Y軸箭頭
* @author LiXiang */
private void drawAxisArrowY(Canvas canvas, Paint basePaint) {
Path path = new Path();
path.moveTo(originalX,endY - dip2px(2));
path.lineTo(originalX+ dip2px(2),endY );
path.lineTo(originalX- dip2px(2),endY );
path.close();
canvas.drawPath(path,basePaint);
canvas.drawText(yAxisNmae,originalX+defaultPadding/3,endY - dip2px(3),basePaint);
}
/** Explain : 設置標題
* @author LiXiang
* @param canvas
* @param basePaint */
private void drawTitle(Canvas canvas, Paint basePaint) {
if (!TextUtils.isEmpty(graphTitle)) {
Paint titlePaint = new Paint(basePaint);
titlePaint.setFakeBoldText(true);//設置字體加粗
titlePaint.setTextSize(axisTextSize);
titlePaint.setColor(axisTextColor);
canvas.drawText(graphTitle,getWidth() / 2,originalY+dip2px(35),titlePaint);
}
}
protected abstract void drawColumn(Canvas canvas, Paint basePaint);
protected abstract void drawAxisScaleXValuveY(Canvas canvas, Paint basePaint);
protected abstract void drawAxisScaleValuveX(Canvas canvas, Paint basePaint);
protected abstract void drawAxisScaleY(Canvas canvas, Paint basePaint);
protected abstract void drawAxisScaleX(Canvas canvas, Paint basePaint);
protected abstract void drawAxisY(Canvas canvas, Paint basePaint);
protected abstract void drawAxisX(Canvas canvas, Paint basePaint);
}
基礎類 BaseChart.java
package com.example.ocean.charts;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import com.example.ocean.R;
import androidx.annotation.Nullable;
public abstract class BaseChart extends View {
protected final Context mContext;
protected Paint basePaint;//基礎畫筆
protected int originalX ;//X軸起點
protected int originalY ;//Y軸起點
protected int endX ;//X軸終點
protected int endY ;//Y軸終點
protected int defaultPadding = dip2px(30);//默認內邊距
protected ValueAnimator valueAnimator;//動畫
protected boolean starting = false;//是否正在執行動畫
protected float mAnimatedValueMax = 1;//動畫最大值
protected float mAnimatedValue = 0;//動畫當前值
protected boolean onTouch = false;//是否正在觸摸
public BaseChart(Context context) {
this(context,null);
}
public BaseChart(Context context, @Nullable AttributeSet attrs) {
this(context,attrs,0);
}
public BaseChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public BaseChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
initBasePaint();
}
/** Explain : 初始化基礎畫筆
* @author LiXiang */
private void initBasePaint() {
if (basePaint == null) {
basePaint = new Paint();
basePaint.setAntiAlias(true);//抗鋸齒
basePaint.setDither(true);//防抖動
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
originalX = getPaddingLeft() + defaultPadding;
originalY = getMeasuredHeight() - getPaddingBottom() - 2*defaultPadding;
endX = getSpaceX()>getDefaultSpaceX()?getSpaceX():getDefaultSpaceX();
endY = getSpaceY()>getDefaultSpaceY()?getSpaceY():getDefaultSpaceY();
}
protected int getDefaultSpaceY() { return getPaddingTop() + defaultPadding; }
protected int getDefaultSpaceX() { return getMeasuredWidth() - getPaddingRight() - defaultPadding; }
protected abstract int getSpaceY();
abstract protected int getSpaceX();
protected int dip2px(float dipValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
public void start() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
startAnimator();
} else {
this.post(new Runnable() {//可以避免頁面未初始化完成造成的 空白
@Override
public void run() {
startAnimator();
}
});
}
}
private void startAnimator() {
if ( starting) {//只能繪製一次 或者正在繪製過程中的話不能再次繪製
return;
}
starting = true;
valueAnimator = ValueAnimator.ofFloat(0, mAnimatedValueMax).setDuration(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatedValue = (float) valueAnimator.getAnimatedValue();
if (starting) {
System.out.println("mAnimatedValue:" + mAnimatedValue);
invalidateOtherData();
invalidate();
}
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
starting = false;
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.start();
}
protected void invalidateOtherData(){};
public void postDelayedInvalidate() {
onTouch = false;//置爲響應觸摸操作的繪製
getParent().requestDisallowInterceptTouchEvent(false);//離開繪製區域,攔截觸摸事件
handler.postDelayed(new Runnable() {
@Override
public void run() {
invalidate();
}
}, 1000);
}
@SuppressLint("HandlerLeak")
private Handler handler = new Handler();
}
Color
<color name="blue_rgba_24_261_255">#17A1FF</color>
<color name="orange">#FFBB4D</color>
<color name="red_2">#F76567</color>
<color name="main_green">#7EC501</color>
style樣式屬性
<!-- 柱狀圖圖表樣式-->
<declare-styleable name="GraphStyle">
<attr name="graphTitle" format="string"/>
<attr name="xAxisNmae" format="string"/>
<attr name="yAxisNmae" format="string"/>
<attr name="axisTextSize" format="dimension|integer"/>
<attr name="axisTextColor" format="color|integer"/>
<attr name="axisDevidedSizeX" format="integer"/>
<attr name="axisDevidedSizeY" format="integer"/>
</declare-styleable>