2013年9月2日 星期一

如何在Android上繪出類似心跳圖的動態線

先前寫了 "如何在Android上繪出折線圖、條狀圖、圓餅圖、..." 這篇主要是以靜態顯示應用為主,主要是將數據資料產生報表用。本篇則是採用類似心跳圖的動態顯示,主要應用在接收即時資訊呈現。

本篇主要是以Android的graphics.drawable來繪製動態曲線,程式碼如下:
Layout部分:
main.xml 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <edu.nkut.sensorgraph.GraphView        <--這裡要注意在GraphView 前面要加入程式的Package名稱,否則程式會出錯。
        android:id="@+id/graph"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="4" />

    <TextView
        android:id="@+id/value"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="6"
        android:background="#fff"
        android:gravity="center"
        android:text="@string/app_name"
        android:textSize="40sp" />

</LinearLayout>

程式1:(輸入資訊用)
MainActivity.java

package edu.nkut.sensorgraph;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;
import android.widget.TextView;

public class MainActivity extends Activity {
private GraphView mGraphView;
TextView mTextView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);

mGraphView = (GraphView) findViewById(R.id.graph);
mGraphView.setMaxValue(10); //設定圖形數字最大值
mTextView = (TextView) findViewById(R.id.value);

Thread t = new MyThread(); // 產生Thread物件
t.start();
}

class MyThread extends Thread {

public void run() {
super.run();
try {
do {
java.util.Random rnd = new java.util.Random(
System.currentTimeMillis()); // 取亂數
float mInput = new Float(rnd.nextInt(1000) + 0) / 100; // 取0~10小數兩位亂數值
final float reading = mInput; // mInput偵測到的接收值,本程式使用亂數模擬。
addPoint(reading); // 顯示心跳線
setText(Float.toString((reading))); // 顯示TextView值
sleep(100); // 暫停100ms
} while (MainActivity.MyThread.interrupted() == false);
} catch (Exception e) {
e.printStackTrace();
}
}
}

// 顯示心跳線
private void addPoint(final float point) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mGraphView.addDataPoint(point);
}
});
}

// 顯示TextView值
private void setText(final String str) {
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(str);
}
});
}
}


程式2: (繪圖用)
GraphView.java

package edu.nkut.sensorgraph;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class GraphView extends View {

private Bitmap mBitmap;
private Paint mPaint = new Paint();
private Canvas mCanvas = new Canvas();

private float mSpeed = 10.0f;  //更改顯示速度(寬窄),數字越小顯示越密;最小設1.0f。
private float mLastX;
private float mScale;
private float mLastValue;
private float mYOffset;
private int mColor;
private float mWidth;
private float maxValue = 1024f;

public GraphView(Context context) {
super(context);
init();
}

public GraphView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
mColor = Color.argb(192, 64, 128, 64); //定義顏色ARGB
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
}

public void addDataPoint(float value) {
final Paint paint = mPaint;
float newX = mLastX + mSpeed;
final float v = mYOffset + value * mScale;

paint.setColor(mColor);
mCanvas.drawLine(mLastX, mLastValue, newX, v, paint);
mLastValue = v;
mLastX += mSpeed;

invalidate();
}

public void setMaxValue(int max) {
maxValue = max;
mScale = -(mYOffset * (1.0f / maxValue));
}

public void setSpeed(float speed) {
mSpeed = speed;
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
mCanvas.setBitmap(mBitmap);
mCanvas.drawColor(0xFFFFFFFF);
mYOffset = h;
mScale = -(mYOffset * (1.0f / maxValue));
mWidth = w;
mLastX = mWidth;
super.onSizeChanged(w, h, oldw, oldh);
}

@Override
protected void onDraw(Canvas canvas) {
synchronized (this) {
if (mBitmap != null) {
if (mLastX >= mWidth) {
mLastX = 0;
final Canvas cavas = mCanvas;
cavas.drawColor(0xFFFFFFFF);
mPaint.setColor(0xFF777777);
cavas.drawLine(0, mYOffset, mWidth, mYOffset, mPaint);
}
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
}
}

執行結果:


說明:
1.程式在 MainActivity用亂數產生0~10小數兩位值,直接丟給addPoint()及setText()來顯示圖及數據。
2.如要修改接收值的部分只要更改mInput這個參數即可使用。
3.繪圖部分請參考Android Developers的android.graphics.drawable 說明。

參考:
1.android.graphics.drawable | Android Developers
http://developer.android.com/reference/android/graphics/drawable/package-summary.html


==============延伸閱讀=====================
1.如何在Android上繪出折線圖、條狀圖、圓餅圖、...
http://www.cheng-min-i-taiwan.blogspot.tw/2012/09/android.html

8 則留言:

  1. 你好
    請問要如何畫出負數?
    我的需求是-10~10
    程式中有設定最大值
    不過我找不到設定最小值的方法
    還是說只能從0開始?

    回覆刪除
    回覆
    1. 目前只能提供最大值,你可以自行修改GraphView類別,增加private float minValue 即可。

      刪除
    2. 請教別人後 已解決
      以下為方法:
      把mScale = -(mYOffset * (1.0f / maxValue));
      搬到addDataPoint內
      把onSizeChanged內mYOffset 設成h/2 0就會在中間了
      丟正的就會在0之上
      丟負的就在0之下

      刪除
  2. 請問假如我想改成不要亂數,而用sensor的數據來繪出動態線(我是用經過藍芽讀去數據),要如何去更改

    回覆刪除
    回覆
    1. 將 mInput由原來的亂數改成藍牙所接收的數據。

      刪除
  3. 請問 我已經將 mInput改成 MIT BIH 數據庫裡的數據
    經由藍芽傳輸,但發現畫出來的圖跟預期有很大的落差
    請問是數據過大造成的嗎?
    但如果不經由藍芽傳輸直接讀入TXT檔來話又是很正常的
    官方有說取樣頻率360HZ

    回覆刪除
  4. 您好
    我現在已經可以將wifi收到的數據即時的顯示在我的GraphView上
    請問有什麼方法可以同時顯示兩筆不同的資料?(兩條動態折線)

    回覆刪除