前言
今天來給大家介紹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:[email protected]
* 當前人流量的折線圖
*/
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);
}
}
借鑑文章: