Android——利用Achartengine製作趨勢圖(含雙Y軸,浮窗)
Achartengine是Android平臺上一款很強大的繪圖工具,能夠實現很多複雜的效果圖,但由於大部分入門者沒有耐心仔細檢視它的庫函式,所以在使用時常常捉襟見肘。本文試圖以應用中時常出現的歷史趨勢圖為例,來幫助讀者加深對Achartengine的認識與理解。首先看一下效果圖:
歷史趨勢圖難點分析:
1、時間軸(X軸)的表示:
Achartengine為我們提供了ChartFactory.getTimeChartView(context, dataset, renderer, format)方法來返回一個時間圖,這是製作歷史趨勢圖時最好的選擇,大部分人也會選擇這一方法,因為它實現起來也相對容易。但是,我們(String title,
int scaleNumber)構造方法
。
2.拖動重新整理趨勢圖:
既然是趨勢圖,自然要能夠實現拖動重新整理趨勢圖,這會使用到PanListener介面。通過addPanListener方法可以為返回的圖表新增面板事件監聽器,一旦拖動趨勢圖,就會觸發 public void panApplied()方法,在該方法裡,我們可以去處理資料的更新。而另外一點需要注意的是,我們需要確定從X軸哪一個位置處繼續往後增加X label,而X
label如何處理、如何獲取也是需要我們考慮。在本例的實現過程中,以全域性變數i為X軸的索引,這樣,每次拖動時,只需要將Y座標新增到指定的X索引座標處即可,同時,也要進行X座標軸自定義Label的更新,處理思路也是如此。
3.浮窗顯示某點數值:
由於螢幕大小有限,所以有時候趨勢圖會很小,所以,點選某點時,彈出浮窗以顯示該點的數值就很有必要了。實現該功能最簡單的方法就是,使用Framelayout佈局,趨勢圖和浮窗分屬不同的Framelayout。首先獲取被點選的點的真實位置,其次獲取被點選點附近最近的趨勢線上的點所表示的值。前者需要為我們的圖表新增OnTouchListener監聽器,後者需要使用到SeriesSelection
seriesSelection = mChartView.getCurrentSeriesAndPoint()以獲取當前點選處附近趨勢線上點的相關資訊。獲取相關資訊後,就可以在Framelayout裡使用自定義繪圖的方式,繪製一個浮窗,並設定浮窗的位置,以及需要顯示的資訊。
以上是趨勢圖繪製過程中不得不考慮的幾點,好了閒話不說了。關鍵程式碼如下:
主Activity程式碼:
package com.lamelias.achartengine;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CustomFragment cf=new CustomFragment();
getFragmentManager().beginTransaction().replace(R.id.trend_container, cf).commit();
}
}
放置趨勢圖的Fragment:
/**
* @Title: CustomFragment.java
* @Package com.mbelec.flowmeter.fragment
* @Description: TODO
* @author Lamelias
* @date 2014年10月20日 上午11:23:23
* @version V1.0
*/
package com.lamelias.achartengine;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Random;
import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView;
import org.achartengine.chart.PointStyle;
import org.achartengine.model.SeriesSelection;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
import org.achartengine.tools.PanListener;
import com.llamelias.achartengine.R;
import android.app.Fragment;
import android.graphics.Color;
import android.graphics.Paint.Align;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnTouchListener;
import android.widget.FrameLayout;
/**
* @ClassName: CustomFragment
* @Description: 自定義趨勢圖,含自定義x軸label的使用
* @author Lamelias
* @date 2014年10月20日 上午11:23:23
*/
public class CustomFragment extends Fragment {
private static final String TAG = CustomFragment.class.getSimpleName();
private FrameLayout layoutTendency, layoutCursor, layoutBg; // layoutBg 是第三個Framelayout,可用來設定座標系不同區域的底色,暫未使用
private XYMultipleSeriesDataset mDataset = new XYMultipleSeriesDataset();
private XYMultipleSeriesRenderer mRenderer = new XYMultipleSeriesRenderer(2);
private GraphicalView mChartView;
private XYSeries pefSeries = new XYSeries("PEF");// 定義XYSeries
private XYSeries fevSeries = new XYSeries("FEV1",1);// 定義XYSeries
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
private Random rand = new Random();
private Calendar c = Calendar.getInstance();
private int i ; //自定義label時,X軸刻度索引
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.trend_view_fragment, container,false);
layoutTendency = (FrameLayout) view.findViewById(R.id.layout_tendency_chart);
layoutCursor = (FrameLayout) view.findViewById(R.id.layout_tendency_cursor);
layoutBg = (FrameLayout) view.findViewById(R.id.layout_tendency_bg);
return view;
}
@Override
public void onResume() {
super.onResume();
renderSettings();
if (mChartView == null) {
mChartView = ChartFactory.getLineChartView(getActivity(), mDataset,mRenderer);
layoutTendency.addView(mChartView);
getTendencyView(); //初始化趨勢圖;
//新增PanListener監聽器,監測拖動趨勢圖的事件.
mChartView.addPanListener(new PanListener() {
@Override
public void panApplied() {
update(); //拖動圖表時更新趨勢圖
}
});
mChartView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
layoutCursor.removeAllViews();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
SeriesSelection seriesSelection = mChartView.getCurrentSeriesAndPoint();//獲取當前點選的點的相關資訊
if(seriesSelection!=null){
PopupView pv=new PopupView(getActivity()); // 浮窗
pv.setStartX(event.getX()); //設定浮窗的位置
pv.setStartY(event.getY());
pv.setValue(String.valueOf(seriesSelection.getValue())); //設定浮窗上顯示的數值
layoutCursor.addView(pv); //將浮窗新增到Framelayout
}
break;
}
Log.w(TAG, "X座標:"+event.getX()+" Y座標:"+event.getY());
return false;
}
});
}
}
/**
* @Title renderSettings
* @Description 渲染整個座標系
* @return void
*/
public void renderSettings() {
mRenderer.setApplyBackgroundColor(true);// 設定是否顯示背景色
mRenderer.setBackgroundColor(Color.WHITE);// 設定背景色
mRenderer.setMarginsColor(Color.parseColor("#f0f3f6"));
mRenderer.setAxesColor(Color.parseColor("#000000"));
mRenderer.setXLabelsColor(Color.BLACK);
mRenderer.setYLabelsColor(0, Color.BLACK);
mRenderer.setYTitle("PEF");
mRenderer.setYLabelsAlign(Align.LEFT);
mRenderer.setLabelsColor(Color.BLACK);
mRenderer.setChartTitleTextSize(20);
mRenderer.setAxisTitleTextSize(16); // 設定軸標題文字的大小
mRenderer.setChartTitleTextSize(20);// 設定整個圖表標題文字大小
mRenderer.setLabelsTextSize(15);// 設定刻度顯示文字的大小(XY軸都會被設定)
mRenderer.setLegendTextSize(15);// 圖例文字大小
mRenderer.setMargins(new int[] { 20, 20, 0, 20 });// 設定圖表的外邊框(上/左/下/右)
mRenderer.setPointSize(8);// 設定點的大小(圖上顯示的點的大小和圖例中點的大小都會被設定)
mRenderer.setShowCustomTextGrid(true);//設定是否顯示自定義的刻度的網格豎線
mRenderer.setPanEnabled(true, false);// 不可上下拖動,可水平拖動
mRenderer.setAntialiasing(true);
mRenderer.setFitLegend(true);// 調節圖例至適當位置
//mRenderer.setClickEnabled(true);// 設定圖表是否允許點選
mRenderer.setSelectableBuffer(40);// 設定點的緩衝半徑值(在某點附近點選時,多大範圍內算點選這個點)
// 自定義標籤
mRenderer.setXLabels(0);
mRenderer.setYLabels(10);
mRenderer.setYAxisMax(600);
mRenderer.setYAxisMin(200);
/*設定第二條Y軸*/
mRenderer.setYLabelsColor(1, Color.BLACK);
mRenderer.setYTitle("Fev1", 1);
mRenderer.setYAxisMin(1, 1);
mRenderer.setYAxisMax(10, 1);
mRenderer.setYAxisAlign(Align.RIGHT, 1);
mRenderer.setYLabelsPadding(-5);//設定label距離Y軸的水平距離
mRenderer.setYLabelsAlign(Align.RIGHT, 1);
}
/**
* @Title update
* @Description 拖動時更新趨勢圖
* @return void
*/
public void update(){
mDataset.clear();
for (int j=0; j <= 5; j++,i++) {
pefSeries.add(i, 400+rand.nextInt(200)); //構造pef趨勢線上新的資料點集
fevSeries.add(i, 1+rand.nextInt(7)); //構造pef趨勢線上新的資料點集
mRenderer.addXTextLabel(i, sdf.format(c.getTime())); //構造X軸新的自定義Label,自定義label只是改變了顯示的名稱,X軸本質上依然是0,1,2·······
c.add(Calendar.DAY_OF_YEAR, 1); //獲得下一天
}
mDataset.addSeries(pefSeries); // 在XYMultipleSeriesDataset中新增pefSeries
mDataset.addSeries(fevSeries); // 在XYMultipleSeriesDataset中新增fevSeries
mChartView.repaint(); //重繪 等同於invalidate(),只不過是把它封裝了
}
public void getTendencyView() {
mDataset.clear();
mRenderer.removeAllRenderers();
pefSeries.clear();
fevSeries.clear();
for (int j=0; j <= 5; j++,i++){
pefSeries.add(i, 400+rand.nextInt(200));
fevSeries.add(i, 1+rand.nextInt(7));
mRenderer.addXTextLabel(i, sdf.format(c.getTime()));
c.add(Calendar.DAY_OF_YEAR, 1);
}
mDataset.addSeries(pefSeries);// 在XYMultipleSeriesDataset中新增XYSeries
mDataset.addSeries(fevSeries);// 在XYMultipleSeriesDataset中新增XYSeries
XYSeriesRenderer lowRenderer = new XYSeriesRenderer();// 定義XYSeriesRenderer
mRenderer.addSeriesRenderer(lowRenderer);// 將單個XYSeriesRenderer增加到XYMultipleSeriesRenderer
lowRenderer.setPointStyle(PointStyle.CIRCLE);// 點的型別是圓形
lowRenderer.setFillPoints(true);// 設定點是否實心
lowRenderer.setColor(Color.GREEN);
XYSeriesRenderer highRenderer = new XYSeriesRenderer();// 定義XYSeriesRenderer
mRenderer.addSeriesRenderer(highRenderer);// 將單個XYSeriesRenderer增加到XYMultipleSeriesRenderer
highRenderer.setPointStyle(PointStyle.CIRCLE);// 點的型別是圓形
highRenderer.setFillPoints(true);// 設定點是否實心
mChartView.repaint();
}
}
自定義浮窗檢視類:
/**
* @Title: PopupView.java
* @Package com.mbelec.flowmeter.widget
* @Description: 浮窗
* @author Lamelias
* @date 2014年10月17日 下午1:53:08
* @version V1.0
*/
package com.lamelias.achartengine;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
/**
* @ClassName: PopupView
* @Description: 彈窗
* @author Lamelias
* @date 2014年10月17日 下午1:53:08
*/
public class PopupView extends View {
private static final String TAG = PopupView.class.getSimpleName();
private float startX; // 浮窗起始繪製位置X座標
private float startY; // 浮窗起始繪製位置Y座標
private String value; // 浮窗顯示的點的值
private Paint textPaint=new Paint();// 字型畫筆
private Paint paint=new Paint();
public PopupView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
renderPaint();
Resources rec = getResources();
BitmapDrawable bitmapDrawable = (BitmapDrawable) rec.getDrawable(R.drawable.popup);
Bitmap bitmap = bitmapDrawable.getBitmap();
canvas.drawBitmap(bitmap, startX-bitmap.getWidth()/2, startY, null);
canvas.drawText(value, startX-bitmap.getWidth()/4, startY+bitmap.getHeight()*2/3, textPaint);
super.onDraw(canvas);
}
/**
* @Title renderPaint
* @Description 渲染畫筆
* @return void
*/
public void renderPaint(){
paint.setColor(Color.parseColor("#97aca5"));
paint.setStyle(Style.FILL);
textPaint.setColor(Color.parseColor("#40a9b7"));
textPaint.setTextSize(25);
textPaint.setStyle(Style.FILL);
}
public float getStartX() {
return startX;
}
public void setStartX(float startX) {
this.startX = startX;
}
public float getStartY() {
return startY;
}
public void setStartY(float startY) {
this.startY = startY;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
完整工程程式碼下載連結:http://download.csdn.net/detail/lamelias/8062767,程式碼中已包含基準線,基準線的圖例可以自行去掉