前言
今天来给大家介绍HelloChart折线图的简单使用,因为参加中软杯做视图展示的时候刚好要使用折线图,于是在网上通过一些博主的文章学习了一下如何使用HelloChart(可能代码内有一些借鉴了,希望不要介意,主要是学习如何简单使用)。
但是我们都知道凡是看起来简单,使用的时候就会遇到一些奇奇怪怪的问题,这些对我这个经验不足的小菜鸡实在是太常见了。我在项目中使用折线图的目地是显示从外部通过网络获取当前人流量的数据,然后快速动态地设置展示在这些图中。
所以这个折线图需要做到能够快速地绘制新的数据折线图,而只要涉及到快就会逃不了性能优化问题0.0,我在本次实践中就面临了以下问题
- 折线图绘制到60+条的时候开始卡得不动
- 嵌套Fragment的无法通信问题
- HelloChart折线图没有时间坐标
接下来我会在下文中提到如何解决这些问题。
目录
1、效果展示
2、添加依赖
HelloChart的github地址为https://github.com/lecho/hellocharts-android
项目中添加下面的依赖
dependencies{ compile 'com.github.lecho:hellocharts-library:1.5.8@aar' }
3、属性的初始化
因为是面向对象编程,所以一个完整的折线图,应该设置的属性有坐标系、折线和顶点。注意这三种属性都由LineChartData类管辖。而LineChartView就是折线图的视图对象,它可以设置折线图的一些交互属性。
- 设置坐标系属性
这里先初始化了Y轴,其中字体大小是指坐标数字的字体大小,注意太大的话会和“当前人数”的坐标名重叠
/** 初始化Y轴 */
axisY = new Axis();
axisY.setName("当前人数");//添加Y轴的名称
axisY.setHasLines(true);//Y轴分割线
axisY.setTextSize(10);//设置字体大小
axisY.setMaxLabelChars(1);//设置坐标轴间隔
axisY.setLineColor(Color.CYAN);
axisY.setTextColor(Color.CYAN);//设置Y轴颜色,默认浅灰色
//这里我给Y轴填充了0-5的坐标
List<AxisValue> mAxisYValues = new ArrayList<AxisValue>();
for (int i = 0; i < 6; i++) {
mAxisYValues.add(new AxisValue(i).setLabel(i+""));
}
axisY.setValues(mAxisYValues);//填充Y轴的坐标名称
data = new LineChartData(linesList);
data.setAxisYLeft(axisY);//设置Y轴在左边
这里应该注意的一点是,HelloChart好像没有可以设置时间轴的方法,所以这里只能通过设置足够多的X轴坐标,模拟时间轴
/** 初始化X轴 */
axisX = new Axis();
axisX.setHasTiltedLabels(false);//X坐标轴字体是斜的显示还是直的,true是斜的显示
axisX.setTextColor(Color.CYAN);//设置X轴颜色
axisX.setName("时间(单位:s)");//X轴名称
axisX.setHasLines(true);//X轴分割线
axisX.setLineColor(Color.CYAN);
axisX.setTextSize(10);//设置字体大小
axisX.setMaxLabelChars(0);//设置0的话X轴坐标值就间隔为1
//给x轴设置时间轴
mAxisXValues = new ArrayList<AxisValue>();
long time = System.currentTimeMillis();
for (int x = 0; x < 1000; ++x) {
timeList.add(simpleDateFormat.format(new Date(time+1000*x)));
mAxisXValues.add(new AxisValue(x).setLabel(timeList.get(x)));
}
axisX.setValues(mAxisXValues);
data.setAxisXBottom(axisX);//X轴在底部
这里Viewport类很关键,它是用来设置当前折线图的具体意义的间隔值还有平移的位置,当我们的LineChartView类使用方法setCurrentViewport设置它的时候,我们的折线图坐标就会根据Viewport的设置的上下左右的值去动态更新。我们的折线图的动态呈现的就是根据这个类。
/**Viewport设置值*/
private Viewport initViewPort(float left,float right) {
Viewport port = new Viewport();
port.top = 5;//Y轴上限,固定(不固定上下限的话,Y轴坐标值可自适应变化)
port.bottom = 0;//Y轴下限,固定
port.left = left;//X轴左边界,变化
port.right = right;//X轴右边界,变化
return port;
}
- 设置顶点属性
PointValue pointValue = new PointValue();
pointValue.setLabel("xx");//设置顶点显示的标签
pointValueList.add(pointValue);//实时添加新的点
- 设置折线属性
//根据新的点的集合画出新的线
Line line = new Line(pointValueList);
line.setColor(Color.parseColor("#ff33b5e5"));//设置折线颜色
line.setShape(ValueShape.CIRCLE);//设置折线图上数据点形状为 圆形 (共有三种 :ValueShape.SQUARE ValueShape.CIRCLE ValueShape.DIAMOND)
line.setCubic(isCubic);//曲线是否平滑,true是平滑曲线,false是折线
line.setHasLabels(hasLabels);//数据是否有标注
line.setHasLines(hasLines);//是否用线显示,如果为false则没有曲线只有点显示
line.setHasPoints(hasPoints);//是否显示圆点 ,如果为false则没有原点只有点显示(每个数据点都是个大圆点)
line.setFilled(true);
linesList.add(line);
- 设置LineChartView属性
chart.setLineChartData(data);
Viewport port = initViewPort(0,xRegionMax);//初始化X轴5个间隔坐标
chart.setCurrentViewportWithAnimation(port);
chart.setInteractive(false);//设置不可交互
chart.setScrollEnabled(true);//设置可以chart可以滚动
chart.setValueTouchEnabled(false);
chart.setFocusableInTouchMode(false);
chart.setViewportCalculationEnabled(false);
chart.setContainerScrollEnabled(true, ContainerScrollType.HORIZONTAL);//设置横向滚动
chart.startDataAnimation();
4、解决动态刷新卡顿问题
动态刷新越来越卡的原因是Link类的属性很多,我们却要不断的添加新的线和点,而我们发现,每次展示在折线图上的线和点都是有限的,这样就造成了大量的资源浪费。
解决的方法就是对List类的进行操作,当已经加载过在折线图不可见的直线和点,我们就通过remove将在最前面的线和点去掉,而新加进来的线和点就添加到后面,这样就可以保证,线和点的数量一直维持在与当前可见横坐标的数量。
if (numberOfPoints > xRegionMax + 1) {
pointValueList.remove(0);
}
if (numberOfPoints > xRegionMax) {
linesList.remove(0);
}
5、解决Fragment嵌套通信问题
我发现当我在Fragment中通过LineChartView对象进行设置点的值的时候,总是没有效果,采用了Activity作为中间交互者也是没有用,于是尝试一下利用广播来设置值,终于成功了。使用的时候发送带值的广播就可以了。
其中需要注意的问题是,如果在Fragment中注册广播但是没有在OnDestroy中取消注册的话,就会发生莫名其妙的错误。而且在Activity中忘记取消注册的话却不会报错0.0
6、完整代码
/**
* author:John on 2019/06/02 16:24
* email:871493721@qq.com
* 当前人流量的折线图
*/
public class CurrentChartFragment extends Fragment {
private LineChartView chart;
private LineChartData data;
private int numberOfPoints = 0;//用来记录当前折线图有多少个点,同时可以作为判断的下标
private int xRegionMax = 5;
private boolean hasLines = true; //
private boolean hasPoints = true;
private boolean hasLabels = true;//是否显示点的数据
private boolean isCubic = false;
private Axis axisX;
private Axis axisY;
private List<PointValue> pointValueList;
private List<Line> linesList;
private List<AxisValue> mAxisXValues;
private List<String> timeList;
private SimpleDateFormat simpleDateFormat;
private int value;
private boolean isChange = false;
Timer timer;
TimerTask timerTask;
private boolean isRun = true;
BroadcastReceiver receiver;
BroadcastReceiver receiver1;
BroadcastReceiver receiver2;
public CurrentChartFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
View rootView = inflater.inflate(R.layout.fragment_linechart, container, false);
initView(rootView);
initViewport();
chart.setViewportCalculationEnabled(false);
// startAClock();
createBroadcast();
return rootView;
}
private void startAClock() {
timer = new Timer();
timerTask = new TimerTask() {
@Override
public void run() {
if (isRun) {
showMovingLineChart();
}
}
};
timer.scheduleAtFixedRate(timerTask, 0, 1000);
}
//用来初始化视图
private void initViewport() {
/** 初始化Y轴 */
axisY = new Axis();
axisY.setName("当前人数");//添加Y轴的名称
axisY.setHasLines(true);//Y轴分割线
axisY.setTextSize(10);//设置字体大小
axisY.setMaxLabelChars(1);
axisY.setLineColor(Color.CYAN);
axisY.setTextColor(Color.CYAN);//设置Y轴颜色,默认浅灰色
List<AxisValue> mAxisYValues = new ArrayList<AxisValue>();
for (int i = 0; i < 6; i++) {
mAxisYValues.add(new AxisValue(i).setLabel(i+""));
}
axisY.setValues(mAxisYValues);//填充Y轴的坐标名称
data = new LineChartData(linesList);
data.setAxisYLeft(axisY);//设置Y轴在左边
/** 初始化X轴 */
axisX = new Axis();
axisX.setHasTiltedLabels(false);//X坐标轴字体是斜的显示还是直的,true是斜的显示
axisX.setTextColor(Color.CYAN);//设置X轴颜色
axisX.setName("时间(单位:s)");//X轴名称
axisX.setHasLines(true);//X轴分割线
axisX.setLineColor(Color.CYAN);
axisX.setTextSize(10);//设置字体大小
axisX.setMaxLabelChars(0);//设置0的话X轴坐标值就间隔为1
//给x轴设置时间轴
mAxisXValues = new ArrayList<AxisValue>();
long time = System.currentTimeMillis();
for (int x = 0; x < 1000; ++x) {
timeList.add(simpleDateFormat.format(new Date(time+1000*x)));
mAxisXValues.add(new AxisValue(x).setLabel(timeList.get(x)));
}
axisX.setValues(mAxisXValues);
data.setAxisXBottom(axisX);//X轴在底部
chart.setLineChartData(data);
Viewport port = initViewPort(0,xRegionMax);//初始化X轴10个间隔坐标
chart.setCurrentViewportWithAnimation(port);
chart.setInteractive(false);//设置不可交互
chart.setScrollEnabled(true);
chart.setValueTouchEnabled(false);
chart.setFocusableInTouchMode(false);
chart.setViewportCalculationEnabled(false);
chart.setContainerScrollEnabled(true, ContainerScrollType.HORIZONTAL);
chart.startDataAnimation();
}
private void initView(View rootView) {
chart = (LineChartView) rootView.findViewById(R.id.linechartview);
chart.setOnValueTouchListener(new ValueTouchListener());
initData();
}
private void initData() {
pointValueList = new ArrayList<PointValue>();
linesList = new ArrayList<Line>();
mAxisXValues = new ArrayList<>();
timeList = new ArrayList<>();
simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
}
private Viewport initViewPort(float left,float right) {
Viewport port = new Viewport();
port.top = 5;//Y轴上限,固定(不固定上下限的话,Y轴坐标值可自适应变化)
port.bottom = 0;//Y轴下限,固定
port.left = left;//X轴左边界,变化
port.right = right;//X轴右边界,变化
return port;
}
private PointValue generateValues() {
if (!isChange){
this.value = 0;
}
isChange = false;//赋值后置为false
return new PointValue(numberOfPoints++, (float) (this.value));
}
public void setValue(int x){
this.isChange = true;
this.value = x;
}
private void reset() {
numberOfPoints = 0;
initData();
initViewport();
}
private class ValueTouchListener implements LineChartOnValueSelectListener {
@Override
public void onValueSelected(int lineIndex, int pointIndex, PointValue value) {
Toast.makeText(getActivity(), "Selected: " + value, Toast.LENGTH_SHORT).show();
}
@Override
public void onValueDeselected() {
// TODO Auto-generated method stub
}
}
private void showMovingLineChart() {
PointValue pointValue = generateValues();
pointValueList.add(pointValue);//实时添加新的点
if (numberOfPoints > xRegionMax + 1) {
pointValueList.remove(0);
}
//根据新的点的集合画出新的线
Line line = new Line(pointValueList);
line.setColor(Color.parseColor("#ff33b5e5"));//设置折线颜色
line.setShape(ValueShape.CIRCLE);//设置折线图上数据点形状为 圆形 (共有三种 :ValueShape.SQUARE ValueShape.CIRCLE ValueShape.DIAMOND)
line.setCubic(isCubic);//曲线是否平滑,true是平滑曲线,false是折线
line.setHasLabels(hasLabels);//数据是否有标注
line.setHasLines(hasLines);//是否用线显示,如果为false则没有曲线只有点显示
line.setHasPoints(hasPoints);//是否显示圆点 ,如果为false则没有原点只有点显示(每个数据点都是个大圆点)
line.setFilled(true);
linesList.add(line);
/**
* 只能说这一步是神来之笔,大家可以试一下有无下面的移除内存的变化程度0.0
* 因为line存储的数据太多了,如果让linesList不断增大的话,不一会儿就崩了
*/
if (numberOfPoints > xRegionMax) {
linesList.remove(0);
}
data.setLines(linesList);//设置变化后的线集合
data.setAxisYLeft(axisY);//设置Y轴在左
data.setAxisXBottom(axisX);//X轴在底部
chart.setLineChartData(data);
//下面的代码用来滚动横坐标
Viewport port;
if (numberOfPoints > xRegionMax) {
port = initViewPort(numberOfPoints - xRegionMax, numberOfPoints);
} else {
port = initViewPort(0, xRegionMax);
}
chart.setMaximumViewport(port);
chart.setCurrentViewport(port);
}
public void stop(){
isRun = false;
}
public void restart(){
if (timerTask != null){
timerTask.cancel();
timerTask = null;
}
if (timer != null){
timer.cancel();
timer = null;
}
isRun = true;
reset();
startAClock();
}
@Override
public void onDestroy() {
if (chart!=null) {
chart.clearFocus();
chart.clearAnimation();
chart = null;
}
if (linesList != null){
linesList.clear();
linesList = null;
}
if (timeList != null){
timeList.clear();
timeList = null;
}
getActivity().unregisterReceiver(receiver);
getActivity().unregisterReceiver(receiver1);
getActivity().unregisterReceiver(receiver2);
super.onDestroy();
}
private void createBroadcast() {
// //用广播获取传递过来的数据
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.trafficdetection.currentValue");
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.example.trafficdetection.currentValue")) {
setValue(intent.getIntExtra("value",0));
showMovingLineChart();
}
}
};
getActivity().registerReceiver(receiver, filter);
// //用广播接收暂停的动作
IntentFilter filter1 = new IntentFilter();
filter1.addAction("com.example.trafficdetection.stop");
receiver1 = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// if (intent.getAction().equals("com.example.trafficdetection.stop")) {
stop();
// }
}
};
getActivity().registerReceiver(receiver1, filter1);
// 用广播接收开始的动作
IntentFilter filter2 = new IntentFilter();
filter2.addAction("com.example.trafficdetection.start");
receiver2 = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//if (intent.getAction().equals("com.example.trafficdetection.start")) {
restart();
// }
}
};
getActivity().registerReceiver(receiver2, filter2);
}
}
借鉴文章: