本文代碼:https://github.com/AndroidKid19/MPAndroidChartProject
MPAndroidChart是一款基于Android的開(kāi)源圖表庫(kù)刻获,MPAndroidChart不僅可以在Android設(shè)備上繪制各種統(tǒng)計(jì)圖表筐带,而且可以對(duì)圖表進(jìn)行拖動(dòng)和縮放操作絮爷,應(yīng)用起來(lái)非常靈活冀偶。MPAndroidChart顯得更為輕巧和簡(jiǎn)單,擁有常用的圖表類型:線型圖盏筐、餅圖座泳、柱狀圖和散點(diǎn)圖诞仓。
Github 地址:https://github.com/PhilJay/MPAndroidChart
依賴:
Project 的build.gradle文件中添加
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
然后在 module中的build,gradle 中添加
implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3'
不滿足可簡(jiǎn)單粗暴下載下載源碼并將MPChartLib引入自己的項(xiàng)目中蛇损。
自定義MarkerView
1 MarkerView 概念
Android里面只要用過(guò)圖表的應(yīng)該都知道MPAndroidChart這個(gè)庫(kù)。這個(gè)庫(kù)在iOS里面也有對(duì)應(yīng)Charts坛怪,所以一般移動(dòng)端做圖表淤齐,Android和iOS兩端都要實(shí)現(xiàn)同樣的效果,他們是不錯(cuò)的一個(gè)選擇袜匿。但是更啄,對(duì)于圖表這種包含的情況非常復(fù)雜的東西,很難滿足大家各種各樣的需求居灯,所以很多都需要自定義祭务。下面就是給大家分享一下自己寫(xiě)的自適應(yīng)MarkerView。
圖效果圖
這里的邏輯步驟分為:
1怪嫌、創(chuàng)建新類繼承自MarkerView
2、inflate layout進(jìn)去
3、重寫(xiě)getOffsetForDrawingAtPoint 僧家,分別處理各種邊界情況的偏移
4箱季、重寫(xiě)draw,繪制底色噪径,根據(jù)不同的情況柱恤,繪制帶箭頭的對(duì)話框
沒(méi)啥好說(shuō)的來(lái)干貨吧。
1.搭建MarkView的布局文件layout_markview
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape_square"
android:orientation="vertical">
<TextView
android:id="@+id/tv_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white" />
<TextView
android:id="@+id/tv_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textColor="@android:color/white" />
</LinearLayout>
其中背景 fade_blue.xml 是圓角橙色背景
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="ResourceName">
<gradient
android:startColor="#58FACD89"
android:endColor="#58ffffff"
android:angle="270"
/>
</shape>
選中點(diǎn) ic_brightness_curve_point.xml是圓形白底
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
>
<solid android:color="@color/colorWhite" />
<stroke
android:width="2dp"
android:color="@color/colorPrimary" />
<size
android:width="8dp"
android:height="8dp" />
</shape>
2.定義常用變量
private final int DEFAULT_INDICATOR_COLOR = 0xffFF584f;//指示器默認(rèn)的顏色
private final int ARROW_HEIGHT = dp2px(10); // 箭頭的高度
private final int ARROW_WIDTH = dp2px(15); // 箭頭的寬度
private final float ARROW_OFFSET = dp2px(2);//箭頭偏移量
private final float BG_CORNER = dp2px(2);//背景圓角
private Bitmap bitmapForDot;//選中點(diǎn)圖片
private int bitmapWidth;//點(diǎn)寬
private int bitmapHeight;//點(diǎn)高
3.定義畫(huà)筆
//指示器背景畫(huà)筆
Paint bgPaint = new Paint();
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setAntiAlias(true);
bgPaint.setColor(DEFAULT_INDICATOR_COLOR);
//箭頭畫(huà)筆
Paint arrowPaint = new Paint();
arrowPaint.setStyle(Paint.Style.FILL);
arrowPaint.setAntiAlias(true);
arrowPaint.setColor(DEFAULT_INDICATOR_COLOR);
4.動(dòng)態(tài)繪制三角行及view
注意:繪制的時(shí)候大概有四種特殊情況
markerView超過(guò)上邊界找爱、超過(guò)下邊界梗顺、超過(guò)左邊界、超過(guò)右邊界车摄、以及正常顯示
//畫(huà)指示器
Path path = new Path();
RectF bRectF;
if (posY < height + ARROW_HEIGHT + bitmapHeight / 2f) {//處理超過(guò)上邊界
//移動(dòng)畫(huà)布并繪制三角形和背景
canvas.translate(0, height + ARROW_HEIGHT + bitmapHeight / 2f);
if (posX > chart.getWidth() - (width/2f)) {//超過(guò)右邊界
canvas.translate( -(width/2 - (chart.getWidth() - posX)), 0);
/*************繪制三角形 超過(guò)上邊界 / 超過(guò)右邊界**/
path.moveTo(width/2 - (chart.getWidth() - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
path.lineTo(ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(- ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.moveTo(width/2 - (chart.getWidth() - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
}else{
if (posX > width / 2f) {//在圖表中間
path.moveTo(0, -(height + ARROW_HEIGHT));
path.lineTo(ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(- ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(0, -(height + ARROW_HEIGHT));
} else {//超過(guò)左邊界
canvas.translate(width/2f - posX, 0);
/*************繪制三角形 超過(guò)上邊界 / 超過(guò)左邊界**/
path.moveTo(-(width/2f - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
path.lineTo(ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(- ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.moveTo(-(width/2f - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
}
}
bRectF=new RectF(-width / 2, -height, width / 2, 0);
canvas.drawPath(path, arrowPaint);
canvas.drawRoundRect(bRectF, BG_CORNER, BG_CORNER, bgPaint);
canvas.translate(-width / 2f, -height);
} else {//沒(méi)有超過(guò)上邊界
//移動(dòng)畫(huà)布并繪制三角形和背景
canvas.translate(0, -height - ARROW_HEIGHT - bitmapHeight / 2f);
if (posX < width/2f) {//超過(guò)左邊界 平移view
canvas.translate( width/2f - posX, 0);
/*************繪制三角形 超過(guò)下邊界 / 超過(guò)左邊界**/
path.moveTo(-(width/2f - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
path.lineTo(ARROW_WIDTH / 2f, height - BG_CORNER);
path.lineTo(- ARROW_WIDTH / 2f, height - BG_CORNER);
path.moveTo(-(width/2f - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
}else{
if (posX > chart.getWidth() - (width/2f)) {//超過(guò)右邊界 繪制三角
/*************繪制三角形 超過(guò)下邊界 / 超過(guò)右邊界**/
canvas.translate( -(width/2 - (chart.getWidth() - posX)), 0);
path.moveTo(width/2 - (chart.getWidth() - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
path.lineTo(ARROW_WIDTH / 2f, height - BG_CORNER);
path.lineTo(- ARROW_WIDTH / 2f, height - BG_CORNER);
path.moveTo(width/2 - (chart.getWidth() - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
}else{
path.moveTo(0, height + ARROW_HEIGHT);
path.lineTo(ARROW_WIDTH / 2f, height - BG_CORNER);
path.lineTo(- ARROW_WIDTH / 2f, height - BG_CORNER);
path.moveTo(0, height + ARROW_HEIGHT);
}
}
bRectF=new RectF(-width / 2, 0, width / 2, height);
canvas.drawPath(path, arrowPaint);
canvas.drawRoundRect(bRectF, BG_CORNER, BG_CORNER, bgPaint);
canvas.translate(-width / 2f, 0);
}
draw(canvas);
canvas.restoreToCount(saveId);
5.自定義MarkerView全套代碼
public class LineChartMarkView extends MarkerView {
private final int DEFAULT_INDICATOR_COLOR = 0xffFF584f;//指示器默認(rèn)的顏色
private final int ARROW_HEIGHT = dp2px(10); // 箭頭的高度
private final int ARROW_WIDTH = dp2px(15); // 箭頭的寬度
private final float ARROW_OFFSET = dp2px(2);//箭頭偏移量
private final float BG_CORNER = dp2px(2);//背景圓角
private Bitmap bitmapForDot;//選中點(diǎn)圖片
private int bitmapWidth;//點(diǎn)寬
private int bitmapHeight;//點(diǎn)高
private TextView tvDate;
private TextView tvValue0;
private TextView tvValue1;
private ValueFormatter valueFormatter;
DecimalFormat df = new DecimalFormat("0.00");
public LineChartMarkView(Context context, ValueFormatter valueFormatter) {
super(context, R.layout.layout_markview);
this.valueFormatter = valueFormatter;
tvDate = findViewById(R.id.tv_date);
tvValue0 = findViewById(R.id.tv_value0);
tvValue1 = findViewById(R.id.tv_value1);
//圖片自行替換
bitmapForDot = getBitmap(context, R.drawable.ic_brightness_curve_point);
bitmapWidth = bitmapForDot.getWidth();
bitmapHeight = bitmapForDot.getHeight();
}
private static Bitmap getBitmap(Context context,int vectorDrawableId) {
Bitmap bitmap=null;
if (Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP){
Drawable vectorDrawable = context.getDrawable(vectorDrawableId);
bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
vectorDrawable.draw(canvas);
}else {
bitmap = BitmapFactory.decodeResource(context.getResources(), vectorDrawableId);
}
return bitmap;
}
@SuppressLint("SetTextI18n")
@Override
public void refreshContent(Entry e, Highlight highlight) {
Chart chart = getChartView();
if (chart instanceof LineChart) {
LineData lineData = ((LineChart) chart).getLineData();
//獲取到圖表中的所有曲線
List<ILineDataSet> dataSetList = lineData.getDataSets();
for (int i = 0; i < dataSetList.size(); i++) {
LineDataSet dataSet = (LineDataSet) dataSetList.get(i);
//獲取到曲線的所有在Y軸的數(shù)據(jù)集合寺谤,根據(jù)當(dāng)前X軸的位置 來(lái)獲取對(duì)應(yīng)的Y軸值
float y = dataSet.getValues().get((int) e.getX()).getY();
if (i == 0) {
tvValue0.setText(dataSet.getLabel() + ":" + df.format(y * 100) + "%");
}
if (i == 1) {
tvValue1.setText(dataSet.getLabel() + ":" + df.format(y * 100) + "%");
}
}
tvDate.setText(valueFormatter.getFormattedValue(e.getX()));
}
super.refreshContent(e, highlight);
}
@Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
@Override
public void draw(Canvas canvas, float posX, float posY) {
Chart chart = getChartView();
if (chart == null) {
super.draw(canvas, posX, posY);
return;
}
//指示器背景畫(huà)筆
Paint bgPaint = new Paint();
bgPaint.setStyle(Paint.Style.FILL);
bgPaint.setAntiAlias(true);
bgPaint.setColor(DEFAULT_INDICATOR_COLOR);
//剪頭畫(huà)筆
Paint arrowPaint = new Paint();
arrowPaint.setStyle(Paint.Style.FILL);
arrowPaint.setAntiAlias(true);
arrowPaint.setColor(DEFAULT_INDICATOR_COLOR);
float width = getWidth();
float height = getHeight();
int saveId = canvas.save();
//移動(dòng)畫(huà)布到點(diǎn)并繪制點(diǎn)
canvas.translate(posX, posY);
canvas.drawBitmap(bitmapForDot, -bitmapWidth / 2f , -bitmapHeight / 2f ,null);
//畫(huà)指示器
Path path = new Path();
RectF bRectF;
if (posY < height + ARROW_HEIGHT + bitmapHeight / 2f) {//處理超過(guò)上邊界
//移動(dòng)畫(huà)布并繪制三角形和背景
canvas.translate(0, height + ARROW_HEIGHT + bitmapHeight / 2f);
if (posX > chart.getWidth() - (width/2f)) {//超過(guò)右邊界
canvas.translate( -(width/2 - (chart.getWidth() - posX)), 0);
/*************繪制三角形 超過(guò)上邊界 / 超過(guò)右邊界**/
path.moveTo(width/2 - (chart.getWidth() - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
path.lineTo(ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(- ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.moveTo(width/2 - (chart.getWidth() - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
}else{
if (posX > width / 2f) {//在圖表中間
path.moveTo(0, -(height + ARROW_HEIGHT));
path.lineTo(ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(- ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(0, -(height + ARROW_HEIGHT));
} else {//超過(guò)左邊界
canvas.translate(width/2f - posX, 0);
/*************繪制三角形 超過(guò)上邊界 / 超過(guò)左邊界**/
path.moveTo(-(width/2f - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
path.lineTo(ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.lineTo(- ARROW_WIDTH / 2f, -(height - BG_CORNER));
path.moveTo(-(width/2f - posX) - ARROW_OFFSET, -(height + ARROW_HEIGHT + ARROW_OFFSET));
}
}
bRectF=new RectF(-width / 2, -height, width / 2, 0);
canvas.drawPath(path, arrowPaint);
canvas.drawRoundRect(bRectF, BG_CORNER, BG_CORNER, bgPaint);
canvas.translate(-width / 2f, -height);
} else {//沒(méi)有超過(guò)上邊界
//移動(dòng)畫(huà)布并繪制三角形和背景
canvas.translate(0, -height - ARROW_HEIGHT - bitmapHeight / 2f);
if (posX < width/2f) {//超過(guò)左邊界 平移view
canvas.translate( width/2f - posX, 0);
/*************繪制三角形 超過(guò)下邊界 / 超過(guò)左邊界**/
path.moveTo(-(width/2f - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
path.lineTo(ARROW_WIDTH / 2f, height - BG_CORNER);
path.lineTo(- ARROW_WIDTH / 2f, height - BG_CORNER);
path.moveTo(-(width/2f - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
}else{
if (posX > chart.getWidth() - (width/2f)) {//超過(guò)右邊界 繪制三角
/*************繪制三角形 超過(guò)下邊界 / 超過(guò)右邊界**/
canvas.translate( -(width/2 - (chart.getWidth() - posX)), 0);
path.moveTo(width/2 - (chart.getWidth() - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
path.lineTo(ARROW_WIDTH / 2f, height - BG_CORNER);
path.lineTo(- ARROW_WIDTH / 2f, height - BG_CORNER);
path.moveTo(width/2 - (chart.getWidth() - posX) + ARROW_OFFSET, height + ARROW_HEIGHT + ARROW_OFFSET);
}else{
path.moveTo(0, height + ARROW_HEIGHT);
path.lineTo(ARROW_WIDTH / 2f, height - BG_CORNER);
path.lineTo(- ARROW_WIDTH / 2f, height - BG_CORNER);
path.moveTo(0, height + ARROW_HEIGHT);
}
}
bRectF=new RectF(-width / 2, 0, width / 2, height);
canvas.drawPath(path, arrowPaint);
canvas.drawRoundRect(bRectF, BG_CORNER, BG_CORNER, bgPaint);
canvas.translate(-width / 2f, 0);
}
draw(canvas);
canvas.restoreToCount(saveId);
}
private int dp2px(int dpValues) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dpValues,
getResources().getDisplayMetrics());
}
}
“如果我比別人看得更遠(yuǎn)些仑鸥,那是因?yàn)槲艺驹诰奕说募绨蛏稀薄V劣诰奕耸钦l(shuí)矗漾,我也不知道