餅狀圖效果圖:
紅色代表核心
佈局XMl :
<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/big_tv_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10" android:orientation="vertical"> <TextView android:id="@+id/Big_bigdata" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|center" android:layout_marginLeft="@dimen/dp_10" android:clickable="true" android:drawable="@drawable/shape_textview" android:text="按項目屬性" android:textSize="15sp" /> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="wrap_content" /> <com.dunqi.gpm.chaotian.develop.widget.LinePieView android:id="@+id/Big_linePieView" android:layout_width="match_parent" android:layout_height="300dp" app:centerTextColor="#000000" app:centerTextSize="24sp" app:circleWidth="20dp" app:dataTextColor="#ff00ff" app:dataTextSize="12sp" /> <include layout="@layout/divider_1" /> <TextView android:id="@+id/industry_bigdata" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|center" android:layout_marginLeft="@dimen/dp_10" android:clickable="false" android:drawable="@drawable/shape_textview" android:gravity="center" android:text="按行業大類" android:textSize="15sp" /> <android.support.v7.widget.RecyclerView android:id="@+id/industry_recycler" android:layout_width="match_parent" android:layout_height="wrap_content" /> <com.dunqi.gpm.chaotian.develop.widget.LinePieView android:id="@+id/industry_linePieView" android:layout_width="match_parent" android:layout_height="300dp" app:centerTextColor="#000000" app:centerTextSize="24sp" app:circleWidth="20dp" app:dataTextColor="#ff00ff" app:dataTextSize="12sp" /> </LinearLayout> </RelativeLayout>
餅狀圖代碼:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import java.util.Random;
/**
* <p>
* Description:折線數據類型餅狀統計圖
* </p>
*
* @author tangzhijie
*/
public class LinePieView extends View {
//使用wrap_content時默認的尺寸
public static final int DEFAULT_WIDTH = 600;
public static final int DEFAULT_HEIGHT = 600;
/**
* 斜線長度
*/
private static final int SlASH_LINE_OFFSET = 50;
/**
* 橫線長度
*/
private static final int HOR_LINE_LENGTH = 100;
/**
* 橫線上文字的橫向偏移量
*/
private static final int X_OFFSET = 20;
/**
* 橫線上文字的縱向偏移量
*/
private static final int Y_OFFSET = 10;
/**
* 中心座標
*/
private int centerX;
private int centerY;
/**
* 半徑
*/
private float radius;
/**
* 弧形外接矩形
*/
private RectF rectF;
/**
* 中間文本的大小
*/
private Rect centerTextBound = new Rect();
/**
* 數據文本的大小
*/
private Rect dataTextBound = new Rect();
/**
* 扇形畫筆
*/
private Paint mArcPaint;
/**
* 中心文本畫筆
*/
private Paint centerTextPaint;
/**
* 數據畫筆
*/
private Paint dataPaint;
/**
* 數據源數字數組
*/
private int[] numbers;
/**
* 數據源名稱數組
*/
private String[] names;
/**
* 數據源總和
*/
private int sum;
/**
* 顏色數組
*/
private int[] colors;
private Random random = new Random();
//自定義屬性 Start
/**
* 中間字體大小
*/
private float centerTextSize = 50;
/**
* 數據字體大小
*/
private float dataTextSize = 20;
/**
* 中間字體顏色
*/
private int centerTextColor = Color.BLACK;
/**
* 數據字體顏色
*/
private int dataTextColor = Color.BLACK;
/**
* 圓圈的寬度
*/
private float circleWidth = 50;
//自定義屬性 End
public LinePieView(Context context) {
super(context);
init();
}
public LinePieView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LinePieView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, com.don.pieviewlibrary.R.styleable.PieView);
centerTextSize = typedArray.getDimension(com.don.pieviewlibrary.R.styleable.PieView_centerTextSize, centerTextSize);
dataTextSize = typedArray.getDimension(com.don.pieviewlibrary.R.styleable.PieView_dataTextSize, dataTextSize);
circleWidth = typedArray.getDimension(com.don.pieviewlibrary.R.styleable.PieView_circleWidth, circleWidth);
centerTextColor = typedArray.getColor(com.don.pieviewlibrary.R.styleable.PieView_centerTextColor, centerTextColor);
dataTextColor = typedArray.getColor(com.don.pieviewlibrary.R.styleable.PieView_dataTextColor, dataTextColor);
typedArray.recycle();
init();
}
/**
* 初始化
*/
private void init() {
mArcPaint = new Paint();
mArcPaint.setStrokeWidth(circleWidth);
mArcPaint.setAntiAlias(true);
mArcPaint.setStyle(Paint.Style.STROKE);
centerTextPaint = new Paint();
centerTextPaint.setTextSize(centerTextSize);
centerTextPaint.setAntiAlias(true);
centerTextPaint.setColor(centerTextColor);
dataPaint = new Paint();
dataPaint.setStrokeWidth(2);
dataPaint.setTextSize(dataTextSize);
dataPaint.setAntiAlias(true);
dataPaint.setColor(dataTextColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measureWidthSize = MeasureSpec.getSize(widthMeasureSpec);
int measureHeightSize = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
if (measureWidthMode == MeasureSpec.AT_MOST && measureHeightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
} else if (measureWidthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(DEFAULT_WIDTH, measureHeightSize);
} else if (measureHeightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(measureWidthSize, DEFAULT_HEIGHT);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = getMeasuredWidth() / 2;
centerY = getMeasuredHeight() / 2;
//設置半徑爲寬高最小值的1/4
radius = Math.min(getMeasuredWidth(), getMeasuredHeight()) / 4;
//設置扇形外接矩形
rectF = new RectF(centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
calculateAndDraw(canvas);
}
/**
* 計算比例並且繪製扇形和數據
*/
private void calculateAndDraw(Canvas canvas) {
if (numbers == null || numbers.length == 0) {
return;
}
//扇形開始度數
int startAngle = 0;
//所佔百分比
float percent;
//所佔度數
float angle;
for (int i = 0; i < numbers.length; i++) {
percent = numbers[i] / (float) sum;
//獲取百分比在360中所佔度數
if (i == numbers.length - 1) {//保證所有度數加起來等於360
angle = 360 - startAngle;
} else {
angle = (float) Math.ceil(percent * 360);
}
//繪製第i段扇形
drawArc(canvas, startAngle, angle, colors[i]);
startAngle += angle;
//繪製數據
if (numbers[i] <= 0) {
continue;
}
//當前扇形弧線相對於縱軸的中心點度數,由於扇形的繪製是從三點鐘方向開始,所以加90
float arcCenterDegree = 90 + startAngle - angle / 2;
drawData(canvas, arcCenterDegree, i, percent);
}
//繪製中心數字總和
canvas.drawText(sum + "", centerX - centerTextBound.width() / 2, centerY + centerTextBound.height() / 2, centerTextPaint);
}
/**
* 計算每段弧度的中心座標
*
* @param degree 當前扇形中心度數
*/
private float[] calculatePosition(float degree) {
//由於Math.sin(double a)中參數a不是度數而是弧度,所以需要將度數轉化爲弧度
//而Math.toRadians(degree)的作用就是將度數轉化爲弧度
//sin 一二正,三四負 sin(180-a)=sin(a)
//扇形弧線中心點距離圓心的x座標
float x = (float) (Math.sin(Math.toRadians(degree)) * radius);
//cos 一四正,二三負
//扇形弧線中心點距離圓心的y座標
float y = (float) (Math.cos(Math.toRadians(degree)) * radius);
//每段弧度的中心座標(扇形弧線中心點相對於view的座標)
float startX = centerX + x;
float startY = centerY - y;
float[] position = new float[2];
position[0] = startX;
position[1] = startY;
return position;
}
/**
* 繪製數據
*
* @param canvas 畫布
* @param degree 第i段弧線中心點相對於縱軸的夾角度數
* @param i 第i段弧線
* @param percent 數據百分比
*/
private void drawData(Canvas canvas, float degree, int i, float percent) {
//弧度中心座標
float startX = calculatePosition(degree)[0];
float startY = calculatePosition(degree)[1];
//斜線結束座標
float endX = 0;
float endY = 0;
//橫線結束座標
float horEndX = 0;
float horEndY = 0;
//數字開始座標
float numberStartX = 0;
float numberStartY = 0;
//文本開始座標
float textStartX = 0;
float textStartY = 0;
//根據每個弧度的中心點座標繪製數據
dataPaint.getTextBounds(names[i], 0, names[i].length(), dataTextBound);
//根據角度判斷象限,並且計算各個座標點
if (degree > 90 && degree < 180) {//二象限
endX = startX + SlASH_LINE_OFFSET;
endY = startY + SlASH_LINE_OFFSET;
horEndX = endX + HOR_LINE_LENGTH;
horEndY = endY;
numberStartX = endX + X_OFFSET;
numberStartY = endY - Y_OFFSET;
textStartX = endX + X_OFFSET;
textStartY = endY + dataTextBound.height() + Y_OFFSET / 2;
} else if (degree == 180) {
startX = centerX;
startY = centerY + radius;
endX = startX + SlASH_LINE_OFFSET;
endY = startY + SlASH_LINE_OFFSET;
horEndX = endX + HOR_LINE_LENGTH;
horEndY = endY;
numberStartX = endX + X_OFFSET;
numberStartY = endY - Y_OFFSET;
textStartX = endX + X_OFFSET;
textStartY = endY + dataTextBound.height() + Y_OFFSET / 2;
} else if (degree > 180 && degree < 270) {//三象限
endX = startX - SlASH_LINE_OFFSET;
endY = startY + SlASH_LINE_OFFSET;
horEndX = endX - HOR_LINE_LENGTH;
horEndY = endY;
numberStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
numberStartY = endY - Y_OFFSET;
textStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
textStartY = endY + dataTextBound.height() + Y_OFFSET / 2;
} else if (degree == 270) {
startX = centerX - radius;
startY = centerY;
endX = startX - SlASH_LINE_OFFSET;
endY = startY - SlASH_LINE_OFFSET;
horEndX = endX - HOR_LINE_LENGTH;
horEndY = endY;
numberStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
numberStartY = endY - Y_OFFSET;
textStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
textStartY = endY + dataTextBound.height() + Y_OFFSET / 2;
} else if (degree > 270 && degree < 360) {//四象限
endX = startX - SlASH_LINE_OFFSET;
endY = startY - SlASH_LINE_OFFSET;
horEndX = endX - HOR_LINE_LENGTH;
horEndY = endY;
numberStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
numberStartY = endY - Y_OFFSET;
textStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
textStartY = endY + dataTextBound.height() + Y_OFFSET / 2;
} else if (degree == 360) {
startX = centerX;
startY = centerY - radius;
endX = startX - SlASH_LINE_OFFSET;
endY = startY - SlASH_LINE_OFFSET;
horEndX = endX - HOR_LINE_LENGTH;
horEndY = endY;
numberStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
numberStartY = endY - Y_OFFSET;
textStartX = endX - HOR_LINE_LENGTH + X_OFFSET;
textStartY = endY + dataTextBound.height() + Y_OFFSET / 2;
} else if (degree > 360) {//一象限
endX = startX + SlASH_LINE_OFFSET;
endY = startY - SlASH_LINE_OFFSET;
horEndX = endX + HOR_LINE_LENGTH;
horEndY = endY;
numberStartX = endX + X_OFFSET;
numberStartY = endY - Y_OFFSET;
textStartX = endX + X_OFFSET;
textStartY = endY + dataTextBound.height() + Y_OFFSET / 2;
}
//繪製折線
canvas.drawLine(startX, startY, endX, endY, dataPaint);
//繪製橫線
canvas.drawLine(endX, endY, horEndX, horEndY, dataPaint);
//繪製數字
canvas.drawText(numbers[i] + "", numberStartX, numberStartY, dataPaint);
//繪製文字
canvas.drawText(names[i] + "", textStartX, textStartY, dataPaint);
}
/**
* 繪製扇形
*
* @param canvas 畫布
* @param startAngle 開始度數
* @param angle 扇形的度數
* @param color 顏色
*/
private void drawArc(Canvas canvas, float startAngle, float angle, int color) {
mArcPaint.setColor(color);
//+0.5是爲了讓每個扇形之間沒有間隙
if (angle != 0) {
angle += 0.5f;
}
canvas.drawArc(rectF, startAngle, angle, false, mArcPaint);
}
/**
* 生成隨機顏色
*/
private int randomColor() {
int red = random.nextInt(256);
int green = random.nextInt(256);
int blue = random.nextInt(256);
return Color.rgb(red, green, blue);
}
/**
* 設置數據(使用隨機顏色)
*
* @param numbers 數字數組
* @param names 名稱數組
*/
public void setData(int[] numbers, String[] names) {
if (numbers == null || numbers.length == 0 || names == null || names.length == 0) {
return;
}
if (numbers.length != names.length) {
return;
}
this.numbers = numbers;
this.names = names;
colors = new int[numbers.length];
sum = 0;
for (int i = 0; i < this.numbers.length; i++) {
//計算總和
sum += numbers[i];
//隨機顏色
colors[i] = randomColor();
}
//計算總和數字的寬高
centerTextPaint.getTextBounds(sum + "", 0, (sum + "").length(), centerTextBound);
invalidate();
}
/**
* 設置數據(自定義顏色)
*
* @param numbers 數字數組
* @param names 名稱數組
* @param colors 顏色數組
*/
public void setData(int[] numbers, String[] names, int[] colors) {
if (numbers == null || numbers.length == 0
|| names == null || names.length == 0
|| colors == null || colors.length == 0) {
return;
}
if (numbers.length != names.length || numbers.length != colors.length) {
return;
}
this.numbers = numbers;
this.names = names;
this.colors = colors;
sum = 0;
for (int i = 0; i < this.numbers.length; i++) {
//計算總和
sum += numbers[i];
}
//計算總和數字的寬高
centerTextPaint.getTextBounds(sum + "", 0, (sum + "").length(), centerTextBound);
invalidate();
}
}
效果圖:
柱狀圖代碼:
柱狀圖XML:
<RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/big_tv_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10" android:orientation="vertical"> <TextView android:id="@+id/Big_bigdata" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|center" android:layout_margin="@dimen/dp_10" android:clickable="true" android:drawable="@drawable/shape_textview" android:text="按項目屬性" android:textSize="15sp" /> <android.support.v7.widget.RecyclerView android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" /><com.dunqi.gpm.chaotian.develop.widget.BarChartNew android:id="@+id/Big_barChart" android:layout_width="match_parent" android:layout_marginLeft="10dp" android:layout_marginRight="@dimen/dp_10" android:layout_height="300dp" /> <include layout="@layout/divider_1" /> <TextView android:id="@+id/industry_bigdata" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="left|center" android:layout_margin="@dimen/dp_10" android:clickable="false" android:drawable="@drawable/shape_textview" android:gravity="center" android:text="按行業大類" android:textSize="15sp" /> <android.support.v7.widget.RecyclerView android:id="@+id/industry_recycler" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" /> <com.dunqi.gpm.chaotian.develop.widget.BarChartNew android:id="@+id/industry_barChart" android:layout_width="match_parent" android:layout_marginLeft="10dp" android:layout_marginRight="@dimen/dp_10" android:layout_height="300dp" /> </LinearLayout> </RelativeLayout>
紅色代表核心
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
import com.dunqi.gpm.chaotian.R;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* _oo0oo_
* o8888888o
* 88" . "88
* (| -_- |)
* 0\ = /0
* ___/`---'\___
* .' \\| |// '.
* / \\||| : |||// \
* / _||||| -卍-|||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' |_/ |
* \ .-\__ '-' ___/-. /
* ___'. .' /--.--\ `. .'___
* ."" '< `.___\_<|>_/___.' >' "".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `_. \_ __\ /__ _/ .-` / /
* =====`-.____`.___ \_____/___.-`___.-'=====
* `=---='
* 佛祖保佑 永無BUG
* 佛曰:
* 程序園裏程序天,程序天里程序員;
* 程序猿人寫程序,又拿程序換肉錢。
* 肉飽繼續桌前坐,飽暖還是桌前眠;
* 半迷半醒日復日,碼上碼下年復年。
* 但願叱吒互聯世,不願搬磚碼當前;
* 諸葛周瑜算世事,我來算出得加錢。
* 別人笑我忒直男,我笑自己太像猿;
* 但見重慶府國內,處處地地程序員。
* Created by HCJ
* ${DATA}
* 柱狀圖
*/
public class BarChartNew extends View {
private Context mContext;
/**
* 視圖的寬和高 刻度區域的最大值
*/
private int mTotalWidth, mTotalHeight, maxHeight;
private int paddingRight, paddingBottom, paddingTop;
//柱形圖的顏色集合
private int barColors[];
//距離底部的多少 用來顯示底部的文字
private int bottomMargin;
//距離頂部的多少 用來顯示頂部的文字
private int topMargin;
private int rightMargin;
private int leftMargin;
/**
* 畫筆 軸 刻度 柱子 點擊後的柱子 單位
*/
private Paint axisPaint, textPaint, barPaint, borderPaint, unitPaint;
private List<BarChartEntity> mData;//數據集合
/**
* item中的Y軸最大值
*/
private float maxYValue;
/**
* Y軸最大的刻度值
*/
private float maxYDivisionValue;
/**
* 柱子的矩形
*/
private Rect mBarRect, mBarRectClick;
/**
* 繪製的區域
*/
private RectF mDrawArea;
/**
* 每一個bar的寬度
*/
private int barWidth;
/**
* 每個bar之間的距離
*/
private int barSpace;
/**
* 向右邊滑動的距離
*/
private float leftMoving;
/**
* 左後一次的x座標
*/
private float lastPointX;
/**
* 當前移動的距離
*/
private float movingThisTime = 0.0f;
/**
* 右邊的最大和最小值
*/
private int maxRight, minRight;
/**
* 下面兩個相當於圖表的原點
*/
private float mStartX;
private int mStartY;
/**
* 柱形圖左邊的x軸座標 和右邊的x軸座標
*/
private List<Integer> mBarLeftXPoints = new ArrayList<>();
private List<Integer> mBarRightXPoints = new ArrayList<>();
/* 用戶點擊到了無效位置 */
public static final int INVALID_POSITION = -1;
private OnItemBarClickListener mOnItemBarClickListener;
private GestureDetector mGestureListener;
/**
* 是否繪製點擊效果
*/
private boolean isDrawBorder;
/**
* 點擊的地方
*/
private int mClickPosition;
//滑動速度相關
private VelocityTracker velocityTracker;
private Scroller scroller;
/**
* fling最大速度
*/
private int maxVelocity;
//x軸 y軸的單位
private String unitX;
private String unitY;
public void setOnItemBarClickListener(OnItemBarClickListener onRangeBarClickListener) {
this.mOnItemBarClickListener = onRangeBarClickListener;
}
public interface OnItemBarClickListener {
void onClick(int position);
}
public BarChartNew(Context context) {
super(context);
init(context);
}
public BarChartNew(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BarChartNew(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mContext = context;
barWidth = DensityUtil.dip2px(getContext(), 40);
barSpace = DensityUtil.dip2px(getContext(), 40);
topMargin = DensityUtil.dip2px(getContext(), 20);
bottomMargin = DensityUtil.dip2px(getContext(), 30);
rightMargin = DensityUtil.dip2px(getContext(), 40);
leftMargin = DensityUtil.dip2px(getContext(), 10);
scroller = new Scroller(context);
maxVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
mGestureListener = new GestureDetector(context, new RangeBarOnGestureListener());
axisPaint = new Paint();
axisPaint.setColor(ContextCompat.getColor(mContext, R.color.color_1f74c9));
axisPaint.setStrokeWidth(1);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setTextSize(DensityUtil.dip2px(getContext(), 10));
unitPaint = new Paint();
unitPaint.setAntiAlias(true);
Typeface typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD);
unitPaint.setTypeface(typeface);
unitPaint.setTextSize(DensityUtil.dip2px(getContext(), 10));
barPaint = new Paint();
barPaint.setColor(barColors != null && barColors.length > 0 ? barColors[0] : Color.parseColor("#6FC5F4"));
borderPaint = new Paint();
borderPaint.setAntiAlias(true);
borderPaint.setStyle(Paint.Style.FILL);
borderPaint.setColor(Color.rgb(0, 0, 0));
borderPaint.setAlpha(120);
mBarRect = new Rect(0, 0, 0, 0);
mBarRectClick = new Rect(0, 0, 0, 0);
mDrawArea = new RectF(0, 0, 0, 0);
}
public void setData(List<BarChartEntity> list, int colors[], String unitX, String unitY) {
this.mData = list;
this.barColors = colors;
this.unitX = unitX;
this.unitY = unitY;
if (list != null && list.size() > 0) {
maxYValue = calculateMax(list);
getRange(maxYValue);
}
}
/**
* 計算出Y軸最大值
*
* @return
*/
private float calculateMax(List<BarChartEntity> list) {
float start = list.get(0).getSum();
for (BarChartEntity entity : list) {
if (entity.getSum() > start) {
start = entity.getSum();
}
}
return start;
}
/**
* 得到柱狀圖的最大和最小的分度值
*/
private void getRange(float maxYValue) {
// maxYValue = 12.50f;
int scale = CalculateUtil.getScale(maxYValue);//獲取這個最大數 數總共有幾位
float unScaleValue = (float) (maxYValue / Math.pow(10, scale));//最大值除以位數之後剩下的值 比如1200/1000 後剩下1.2
maxYDivisionValue = (float) (CalculateUtil.getRangeTop(unScaleValue) * Math.pow(10, scale));//獲取Y軸的最大的分度值
mStartX = CalculateUtil.getDivisionTextMaxWidth(maxYDivisionValue, mContext) + 20;
}
@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);
mTotalWidth = w;
mTotalHeight = h;
maxHeight = h - getPaddingTop() - getPaddingBottom() - bottomMargin - topMargin;
paddingBottom = getPaddingBottom();
paddingTop = getPaddingTop();
int paddingLeft = getPaddingLeft();
paddingRight = getPaddingRight();
}
//獲取滑動範圍和指定區域
private void getArea() {
maxRight = (int) (mStartX + (barSpace + barWidth) * mData.size());
minRight = mTotalWidth - leftMargin - rightMargin;
mStartY = mTotalHeight - bottomMargin - paddingBottom;
mDrawArea = new RectF(mStartX, paddingTop, mTotalWidth - paddingRight - rightMargin, mTotalHeight - paddingBottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mData == null || mData.isEmpty()) return;
getArea();
checkTheLeftMoving();
//繪製刻度線 和 刻度
drawScaleLine(canvas);
//繪製單位
drawUnit(canvas);
//調用clipRect()方法後,只會顯示被裁剪的區域
canvas.clipRect(mDrawArea.left, mDrawArea.top, mDrawArea.right, mDrawArea.bottom + mDrawArea.height());
//繪製柱子
drawBar(canvas);
//繪製X軸的text
drawXAxisText(canvas);
}
private void drawUnit(Canvas canvas) {
String textLength = maxYDivisionValue % 5 == 0 ? String.valueOf((int) maxYDivisionValue) : String.valueOf(maxYDivisionValue);
canvas.drawText(unitY, mStartX - textPaint.measureText(textLength), topMargin / 2, unitPaint);
canvas.drawText(unitX, mTotalWidth - rightMargin - paddingRight + 10, mTotalHeight - bottomMargin / 2, unitPaint);
}
/**
* 檢查向左滑動的距離 確保沒有畫出屏幕
*/
private void checkTheLeftMoving() {
if (leftMoving > (maxRight - minRight)) {
leftMoving = maxRight - minRight;
}
if (leftMoving < 0) {
leftMoving = 0;
}
}
private void drawXAxisText(Canvas canvas) {
//這裏設置 x 軸的字一條最多顯示5個,大於三個就換行
for (int i = 0; i < mData.size(); i++) {
String text = mData.get(i).getxLabel();
if (text.length() <= 5) {
canvas.drawText(text, mBarLeftXPoints.get(i) - (textPaint.measureText(text) - barWidth) / 2, mTotalHeight - bottomMargin * 2 / 3, textPaint);
} else {
String text1 = text.substring(0, 3);
String text2 = text.substring(3, text.length());
canvas.drawText(text1, mBarLeftXPoints.get(i) - (textPaint.measureText(text1) - barWidth) / 2, mTotalHeight - bottomMargin * 2 / 3, textPaint);
canvas.drawText(text2, mBarLeftXPoints.get(i) - (textPaint.measureText(text2) - barWidth) / 2, mTotalHeight - bottomMargin / 3, textPaint);
}
}
}
private float percent = 1f;
private TimeInterpolator pointInterpolator = new DecelerateInterpolator();
public void startAnimation() {
ValueAnimator mAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setDuration(2000);
mAnimator.setInterpolator(pointInterpolator);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
percent = (float) animation.getAnimatedValue();
invalidate();
}
});
mAnimator.start();
}
private void drawBar(Canvas canvas) {
mBarLeftXPoints.clear();
mBarRightXPoints.clear();
mBarRect.bottom = mStartY;
for (int i = 0; i < mData.size(); i++) {
if (barColors.length == 1) {
mBarRect.left = (int) (mStartX + barWidth * i + barSpace * (i + 1) - leftMoving);
mBarRect.top = mStartY - (int) ((maxHeight * (mData.get(i).getyValue() / maxYDivisionValue)) * percent);
mBarRect.right = mBarRect.left + barWidth;
canvas.drawRect(mBarRect, barPaint);
} else {
int eachHeight = 0;//每一塊的高度
mBarRect.left = (int) (mStartX + barWidth * i + barSpace * (i + 1) - leftMoving);
mBarRect.right = mBarRect.left + barWidth;
for (int j = 0; j < barColors.length; j++) {
barPaint.setColor(barColors[j]);
mBarRect.bottom = (int) (mStartY - eachHeight * percent);
eachHeight += (int) ((maxHeight * (mData.get(i).getyValue() / maxYDivisionValue)));
mBarRect.top = (int) (mBarRect.bottom - ((maxHeight * (mData.get(i).getyValue() / maxYDivisionValue))) * percent);
canvas.drawRect(mBarRect, barPaint);
}
}
mBarLeftXPoints.add(mBarRect.left);
mBarRightXPoints.add(mBarRect.right);
}
if (isDrawBorder) {
drawBorder(mClickPosition);
canvas.drawRect(mBarRectClick, borderPaint);
}
}
private void drawBorder(int position) {
mBarRectClick.left = (int) (mStartX + barWidth * position + barSpace * (position + 1) - leftMoving);
mBarRectClick.right = mBarRectClick.left + barWidth;
mBarRectClick.bottom = mStartY;
mBarRectClick.top = mStartY - (int) (maxHeight * (mData.get(position).getSum() / maxYDivisionValue));
}
/**
* Y軸上的text (1)當最大值大於1 的時候 將其分成5份 計算每個部分的高度 分成幾份可以自己定
* (2)當最大值大於0小於1的時候 也是將最大值分成5份
* (3)當爲0的時候使用默認的值
*/
private void drawScaleLine(Canvas canvas) {
float eachHeight = (maxHeight / 5f);
float textValue = 0;
if (maxYValue > 1) {
for (int i = 0; i <= 5; i++) {
float startY = mStartY - eachHeight * i;
BigDecimal maxValue = new BigDecimal(maxYDivisionValue);
BigDecimal fen = new BigDecimal(0.2 * i);
String text = null;
//因爲圖表分了5條線,如果能除不進,需要顯示小數點不然數據不準確
if (maxYDivisionValue % 5 != 0) {
text = String.valueOf(maxValue.multiply(fen).floatValue());
} else {
text = String.valueOf(maxValue.multiply(fen).longValue());
}
canvas.drawText(text, mStartX - textPaint.measureText(text) - 5, startY + textPaint.measureText("0") / 2, textPaint);
canvas.drawLine(mStartX, startY, mTotalWidth - paddingRight - rightMargin, startY, axisPaint);
}
} else if (maxYValue > 0 && maxYValue <= 1) {
for (int i = 0; i <= 5; i++) {
float startY = mStartY - eachHeight * i;
textValue = CalculateUtil.numMathMul(maxYDivisionValue, (float) (0.2 * i));
String text = String.valueOf(textValue);
canvas.drawText(text, mStartX - textPaint.measureText(text) - 5, startY + textPaint.measureText("0") / 2, textPaint);
canvas.drawLine(mStartX, startY, mTotalWidth - paddingRight - rightMargin, startY, axisPaint);
}
} else {
for (int i = 0; i <= 5; i++) {
float startY = mStartY - eachHeight * i;
String text = String.valueOf(10 * i);
canvas.drawText(text, mStartX - textPaint.measureText(text) - 5, startY + textPaint.measureText("0") / 2, textPaint);
canvas.drawLine(mStartX, startY, mTotalWidth - paddingRight - rightMargin, startY, axisPaint);
}
}
}
private void initOrResetVelocityTracker() {
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
} else {
velocityTracker.clear();
}
}
private void recycleVelocityTracker() {
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
movingThisTime = (scroller.getCurrX() - lastPointX);
leftMoving = leftMoving + movingThisTime;
lastPointX = scroller.getCurrX();
postInvalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastPointX = event.getX();
scroller.abortAnimation();//終止動畫
initOrResetVelocityTracker();
velocityTracker.addMovement(event);//將用戶的移動添加到跟蹤器中。
break;
case MotionEvent.ACTION_MOVE:
float movex = event.getX();
movingThisTime = lastPointX - movex;
leftMoving = leftMoving + movingThisTime;
lastPointX = movex;
invalidate();
velocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_UP:
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000, maxVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity();
velocityTracker.clear();
scroller.fling((int) event.getX(), (int) event.getY(), -initialVelocity / 2,
0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
invalidate();
lastPointX = event.getX();
break;
case MotionEvent.ACTION_CANCEL:
recycleVelocityTracker();
break;
default:
return super.onTouchEvent(event);
}
if (mGestureListener != null) {
mGestureListener.onTouchEvent(event);
}
return true;
}
/**
* 點擊
*/
private class RangeBarOnGestureListener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
int position = identifyWhichItemClick(e.getX(), e.getY());
if (position != INVALID_POSITION && mOnItemBarClickListener != null) {
mOnItemBarClickListener.onClick(position);
setClicked(position);
invalidate();
}
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}
/**
* 設置選中的位置
*
* @param position
*/
public void setClicked(int position) {
isDrawBorder = true;
mClickPosition = position;
}
/**
* 根據點擊的手勢位置識別是第幾個柱圖被點擊
*
* @param x
* @param y
* @return -1時表示點擊的是無效位置
*/
private int identifyWhichItemClick(float x, float y) {
float leftx = 0;
float rightx = 0;
if (mData != null) {
for (int i = 0; i < mData.size(); i++) {
leftx = mBarLeftXPoints.get(i);
rightx = mBarRightXPoints.get(i);
if (x < leftx) {
break;
}
if (leftx <= x && x <= rightx) {
return i;
}
}
}
return INVALID_POSITION;
}
}