1. 程式人生 > >Android——利用Achartengine製作趨勢圖(含雙Y軸,浮窗)

Android——利用Achartengine製作趨勢圖(含雙Y軸,浮窗)

  Achartengine是Android平臺上一款很強大的繪圖工具,能夠實現很多複雜的效果圖,但由於大部分入門者沒有耐心仔細檢視它的庫函式,所以在使用時常常捉襟見肘。本文試圖以應用中時常出現的歷史趨勢圖為例,來幫助讀者加深對Achartengine的認識與理解。首先看一下效果圖:


 歷史趨勢圖難點分析:

    1、時間軸(X軸)的表示:

    Achartengine為我們提供了ChartFactory.getTimeChartView(context, dataset, renderer, format)方法來返回一個時間圖,這是製作歷史趨勢圖時最好的選擇,大部分人也會選擇這一方法,因為它實現起來也相對容易。但是,我們

能不能使用最普通的getLineChartView(context, mDataset,mRenderer)去製作呢?如果能的話,X軸將會使用自定義label,但又因為我們的自定義label是時間格式的字串,很多人可能覺得太棘手了,放棄了此種嘗試。事實上,這種方式使用起來更加靈活,而且可以實現雙Y軸的歷史趨勢圖效果,如果使用前者,雙Y軸的效果無法實現,因為TimeSeries沒有提供類似XYSeries的(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,程式碼中已包含基準線,基準線的圖例可以自行去掉