接到一个需求需要折线图显示数据,权衡利弊后没有自己手绘哦,毕竟怕耽搁时间(或许也是怕写一半写不出来 哈哈哈),所以首选当然是之前接触过的MPAndroidChart,毕竟它很强很强很强。。。
添加依赖,之前用还是在eclipse时代的2.0:
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
GitHub网址:https://github.com/PhilJay/MPAndroidChart
首先上一下效果图,有相同需求的小伙伴可以借鉴的,那就往下看
需求如下:显示一个平滑的曲线,并且点击的时候要显示底部的一个小标标,选中的值还要改变选中的圆球颜色,并且蛋疼的还要第一次数据加载好就要显示出来,每次点击根据圆球位置显示marker三角形和球球。
我这里采用的是欺骗的手法,想着有marker可以显示数据,是不是也可以改动呢。小球球和三角形下标是静态不变大小的,而中间一根灰色竖立的小线是随球球坐标动态改变的长度,线根据需求可以设置颜色,骗过用户不就ok吗,透明的,红的,白的,绿的都行。下面看一下我的item_marker.xml
布局就是这么简单,三角形本想用drawable画的,没成功,劳烦善良的美工切了一个。
HistoryMarkerView第一版如下:
public class MyMarkerView extends MarkerView
{
private TextView tvContent;
private DecimalFormat format = new DecimalFormat("##0");
public MyMarkerView(Context context) {
super(context, R.layout.item_marker);//这个布局自己定义
tvContent = (TextView) findViewById(R.id.tvContent);
}
//显示的内容
@Override
public void refreshContent(Entry e, Highlight highlight) {
tvContent.setText(format(e.getX())+"\n"+format.format(e.getY())+"辆");
super.refreshContent(e, highlight);
}
//标记相对于折线图的偏移量
@Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
//时间格式化(显示今日往前30天的每一天日期)
public String format(float x)
{
CharSequence format = DateFormat.format("MM月dd日",
System.currentTimeMillis()-(long) (30-(int)x)*24*60*60*1000);
return format.toString();
}
}
第一版效果图,作为解说参考:
很明显这段是我度娘来的,也是在这基础改进现在。
最终第二版:
/**
* Author:jianbo
*
* Create Time:2020/6/18 11:11
*
* Email:[email protected]
*
* Describe:图表的指示器
*/
public class HistoryMarkerView extends MarkerView {
private TextView tvContent;
private DecimalFormat format = new DecimalFormat("##0");
private float lineheight;
private View lineH;
private Context mContext;
private ImageView ivTopCircle;
public HistoryMarkerView(Context context, float lineheight) {
super(context, R.layout.item_marker);//这个布局自己定义
this.lineheight = lineheight;
this.mContext = context;
tvContent = (TextView) findViewById(R.id.tv_marker_Content);
lineH = (View) findViewById(R.id.line_marker_h);
ivTopCircle = (ImageView) findViewById(R.id.iv_marker_top);
}
//显示的内容
@Override
public void refreshContent(Entry e, Highlight highlight) {
LogUtils.e("-----lineX:" + lineheight + "===>", highlight.getX() + "\n" + highlight.getXPx() + "\n" + highlight.getDrawX());
LogUtils.e("-----lineY:" + lineheight + "===>", highlight.getY() + "\n" + highlight.getYPx() + "\n" + highlight.getDrawY());
LogUtils.e("-----lineHighlight:" + lineheight + "===>", "getYPx:" + highlight.getYPx() + "getDrawY:" + highlight.getDrawY() + "getHeight:" + getHeight());
//实际细线高度
float lineHight = lineheight - highlight.getYPx() - tvContent.getHeight() - ivTopCircle.getHeight() / 2;
//为了效果,让细线高一点点
LinearLayout.LayoutParams lineparams = new LinearLayout.LayoutParams(lineH.getWidth(), (int) lineHight + 2);
lineH.setLayoutParams(lineparams);
super.refreshContent(e, highlight);
}
//标记相对于折线图的偏移量
@Override
public MPPointF getOffset() {
//偏移量(x,y),y的话又看到我xml布局中圆球球是10dp的,这里就网上偏移5dp也就是半径
return new MPPointF(-(getWidth() / 2), -DisplayUtils.dip2px(mContext, 5));
}
private MPPointF mOffset2 = new MPPointF();
@Override
public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) {
MPPointF offset = getOffset();
mOffset2.x = offset.x;
mOffset2.y = offset.y;
Chart chart = getChartView();
float width = getWidth();
float height = getHeight();
if (posX + mOffset2.x < 0) {
//第一个数据的时候,超出屏幕了,实现父类方法改一改
mOffset2.x = offset.x;
} else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) {
mOffset2.x = chart.getWidth() - posX - width;
}
if (posY + mOffset2.y < 0) {
mOffset2.y = -posY;
} else if (chart != null && posY + height + mOffset2.y > chart.getHeight()) {
mOffset2.y = chart.getHeight() - posY - height;
}
return mOffset2;
}
}
刚开始也不知道refreshContent(Entry e, Highlight highlight) ,getOffset() ,getOffsetForDrawingAtPoint(float posX, float posY) 这几个方法的用处那就打印,百度呗。
refreshContent:回调显示的时候会调用
getOffset:如方法名的意思就是偏移量
getOffsetForDrawingAtPoint:绘制的时候回调用,不是很清楚,之所以会改写它,也是应为在我点击第一个数据的时候,marker被他强制的往右偏了,无论我getOffset返回多少,所以就跟着getOffset进了源码,看看我的x偏移值在哪被人改了,就是在getOffsetForDrawingAtPoint判断后修改的原句是
if (posX + mOffset2.x < 0) {
mOffset2.x = - posX;------------------->>改动这里
} else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) {
mOffset2.x = chart.getWidth() - posX - width;
}
看到没,内部判断后没有用我传递给他的值,现在改回来,ok.
好了接下来看看LineChart完整的java控制代码和xml
布局
折线图实现
//悬浮窗
private HistoryMarkerView myMarkerView;
//上一次的高亮线
private Highlight[] highlightsOld = new Highlight[1];
private void initLineChart() {
List listY = new ArrayList<>();
for (int i = 0; i < 7; i++) {
if (i % 2 == 0)
listY.add(i);
else
listY.add((int) (i * 1.6));
}
//显示边界
mLineChartStudy.setDrawBorders(false);
mLineChartStudy.setDragEnabled(false);//:启用/禁用拖动(平移)图表。
mLineChartStudy.setScaleEnabled(false);//:启用/禁用缩放图表上的两个轴。
//设置数据
List yEntries = new ArrayList<>();
for (int i = 0; i < listY.size(); i++) {
yEntries.add(new Entry(i, (float) listY.get(i)));
}
//---------线条设置》》一个LineDataSet对象就是一条线
LineDataSet lineDataSet = new LineDataSet(yEntries, "YYY");
//线宽度
lineDataSet.setLineWidth(1.5f);
// 是否显示高亮的线
lineDataSet.setHighlightEnabled(true);
lineDataSet.setHighlightLineWidth(0.1f);
lineDataSet.setHighLightColor(Color.parseColor("#00000000"));
//线颜色
lineDataSet.setColor(Color.parseColor("#ffffff"));
//外圈半径
lineDataSet.setCircleRadius(3f);
//外圈的颜色
lineDataSet.setCircleColor(Color.parseColor("#ffffff"));
// 内圈的颜色
lineDataSet.setCircleHoleColor(Color.parseColor("#ffffff"));
//内圈半径
lineDataSet.setCircleHoleRadius(1.5f);
//不显示圆点
// lineDataSet.setDrawCircles(false);
//线条平滑,曲线
lineDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);
//设置折线图填充
lineDataSet.setDrawFilled(true);
lineDataSet.setFillDrawable(getResources().getDrawable(R.drawable.shape_chart_filled_history_bg));
LineData linedata = new LineData(lineDataSet);
//无数据时显示的文字
mLineChartStudy.setNoDataText("暂无数据");
//折线图是否显示数值
linedata.setDrawValues(true);
//-----------------得到X轴----------------
XAxis xAxis = mLineChartStudy.getXAxis();
//设置X轴颜色
xAxis.setAxisLineColor(Color.parseColor("#00000000"));
//设置X轴的位置(默认在上方)
xAxis.setPosition(XAxis.XAxisPosition.TOP);
//设置X轴坐标之间的最小间隔
xAxis.setGranularity(1f);
//设置X轴的刻度数量,第二个参数为true,将会画出明确数量(带有小数点),但是可能值导致不均匀,默认(6,false)
xAxis.setLabelCount(listY.size(), false);
//设置X轴的值(最小值、最大值、然后会根据设置的刻度数量自动分配刻度显示)
//除非你的x轴显示不全还是别动它
// xAxis.setAxisMinimum(1f);
//// //x轴刻度值
// xAxis.setAxisMaximum((float) listY.size() - 1);
//不显示网格线
xAxis.setDrawGridLines(true);
//x轴网格线颜色
xAxis.setGridColor(Color.parseColor("#33FFFFFF"));
//网格线宽
xAxis.setGridLineWidth(0.4f);
// 标签倾斜
// xAxis.setLabelRotationAngle(45);
xAxis.setTextColor(Color.parseColor("#88FFFFFF"));
// xAxis.setTextSize(12f);
//设置X轴值为字符串
xAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float value) {
int IValue = (int) value;
CharSequence format = DateFormat.format("M.dd",
System.currentTimeMillis() - (long) (listY.size() - 1 - IValue) * 24 * 60 * 60 * 1000);
return format.toString();
}
});
//-----------------得到Y轴-----------------
YAxis yAxis = mLineChartStudy.getAxisLeft();
YAxis rightYAxis = mLineChartStudy.getAxisRight();
//设置Y轴是否显示
//右侧Y轴不显示
rightYAxis.setEnabled(false);
//左侧Y轴不显示
YAxis leftYAxis = mLineChartStudy.getAxisLeft();
//设置左侧Y轴是否显示
leftYAxis.setEnabled(false);
//设置y轴坐标之间的最小间隔
//不显示网格线
yAxis.setDrawGridLines(false);
//设置Y轴坐标之间的最小间隔
yAxis.setGranularity(1);
//图例:得到Lengend
Legend legend = mLineChartStudy.getLegend();
//隐藏Lengend,数据图标如:正方形,圆形等
legend.setEnabled(false);
//隐藏描述
Description description = new Description();
description.setEnabled(false);
mLineChartStudy.setDescription(description);
//设置数据
mLineChartStudy.setData(linedata);
// mLineChartStudy.setOnTouchListener(new ChartTouchListener(mLineChartStudy) {
// @Override
// public boolean onTouch(View v, MotionEvent event) {
// return true;
// }
// });
//开启值转高亮线
mLineChartStudy.valuesToHighlight();
mLineChartStudy.post(new Runnable() {
@Override
public void run() {
//折线图点的标记
myMarkerView = new HistoryMarkerView(MyStudyHistoryActivity.this, mLineChartStudy.getHeight());
mLineChartStudy.setMarker(myMarkerView);
//通过触摸生成高亮线
// Highlight h = mLineChartStudy.getHighlightByTouchPoint(mLineChartStudy.getCenter().x,mLineChartStudy.getCenter().y);
Highlight h = mLineChartStudy.getHighlightByTouchPoint(mLineChartStudy.getRight(), mLineChartStudy.getTop());
mLineChartStudy.highlightValue(h, true);
}
});
//图标刷新
mLineChartStudy.invalidate();
mLineChartStudy.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
CharSequence dateformat = DateFormat.format("MM月dd日 学习时长",
System.currentTimeMillis() - (long) (listY.size() - 1 - (int) e.getX()) * 24 * 60 * 60 * 1000);
mTvStudyHistoryDate.setText(dateformat);
mTvStudyHistoryTime.setText(e.getY() + "");
highlightsOld[0] = h;
}
@Override
public void onNothingSelected() {
//高亮线非选中状态
mLineChartStudy.highlightValues(highlightsOld);
}
});
}
难得是蛋疼的数据加载完好就显示marker,因为他没有这个api方法。那我只能一点点跟源码喽。
从mLineChartStudy.highlightValues(highlightsOld);开始跟,知道要先显示出来,除非要先给他一条高亮线,不然会显示的。
没办法既然如此那我就创建一条,那就要知道高亮线需要哪些属性值,找到这个实体类里面有很多x,y,mDrawX,mDrawY。。。
这些我从哪里知道,晕了。然后想到marker每次点击都能在setOnChartValueSelectedListener(new OnChartValueSelectedListener() )与refreshContent(Entry e, Highlight highlight) 拿到一个Highlight,那这个Highlight哪里来的呢。接着就跟着 OnChartValueSelectedListener的回调方法找到了
最后找到了下面两个方法,踏实了
最后
是不是完全踏实下来了,通过触摸拿到一个点,将点的x,y给他生成一个高亮的点。
于是想了一下,我点击折线图空白处的时候也是能够判断我点的最近的高亮点的,来显示高亮线(当然具体里面怎么实现判断的,我不深究了,已经够条件实现我要的需求了),我要显示最后一个数据点,也就是当天数据,那我把折线图view最右边的一个角的坐标值给他就完了,当然你也可以写最左边,中间值等。
收工!!!
忘提了,之后看到它还可以设置每个圆圈圈的颜色,后面一想其实marker的圆圈圈也可以通过选择高亮后的回调监听动态重新给他们赋值不一样的颜色,来实现。
附上一些不错的借鉴文章:他的实现方式相对较优雅赶脚。。。
MPAndroidChart之LineChart(2)MarkerView