飴屋

Androidアプリ/日記11

グラフを描画してみよう

前回、体重の入力アクティビティからグラフ表示アクティビティへの遷移ができるようになったので、やっとグラフの描画処理を書いてみたいと思います。グラフの概要はというと、入力された体重や体脂肪率を縦軸に、同様に入力された計測日時を横軸にとる折れ線グラフです。とりあえず、最初に描画するのは、最終計測日時から遡って30日程度の期間とします。該当期間中の体重値の最大値と最小値が余裕をもって入るぐらいのグラフにしましょう。

Viewクラスを使う

public class GraphWeight extends Activity {
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(new GraphView(this));
}

private static class GraphView extends View
{
// Viewクラスを継承して、表示内容をここに追加していきます。
}
}

上記がグラフ表示用のActivityの概要です。体重入力アクティビティのときはXML(res/layout/input.xml)に画面の構成を定義して、それを読み込むことで画面のレイアウトを行っていましたが、グラフ表示用のアクティビティでは、画面の表示に関するViewというクラスを継承したクラスを作成して、そこにグラフの描画処理を書きこんでいくことにします。GraphWeightアクティビティの内部クラス「GraphView」が描画の肝となっています。アクティビティのonCreateの段階で、setContentViewメソッドでアクティビティの画面を構成するViewを指定するところはInputWeightアクティビティと一緒ですが、渡す引数がGraphViewクラスになっています。

続いてGraphViewの中身をみていきます。

private static class GraphView extends View
{
private DB db = null;
private List<Weight> weights = null;
private float rangeWeight;
private float rangeTime;
private float originWeight;
private float originTime;

public GraphView(Context context)
{
super(context);
setFocusable(true);
db = new DB(context);
getWeights();
}
private void getWeights()
{
// データを取得する部分
}
@Override
public void onDraw(Canvas canvas)
{
// 描画する部分
}
}

SQLiteからデータを取得して、グラフを描画するのがこのViewの全容です。コンストラクタでgetWeightsというメソッドを呼んで、データを取得することにします。描画処理のonDrawは明示的に呼び出す必要はなく、必要に応じて勝手に呼び出されます。

プロパティの説明は以下の通りです。

DB db
データベース処理オブジェクト
List weights
取得した体重測定データのリスト
float rangeWeight
体重の描画範囲幅
float rangeTime
測定日時の描画範囲幅
float originWeight
グラフの原点(左下)の体重値
float originTime
グラフの原点(左下)の測定日時

データの取得

private void getWeights()
{
weights = db.getAll();
int maxWeight = -1;
int minWeight = -1;
long maxTime = -1;
long minTime = -1;
Iterator<Weight> i = weights.iterator();
while (i.hasNext())
{
Weight w = i.next();
if (maxTime<w.dt.getTime() || maxTime==-1) maxTime = w.dt.getTime();
if (minTime>w.dt.getTime() || minTime==-1) minTime = w.dt.getTime();
}
rangeTime = 2592000000f;//30Days
originTime = maxTime - rangeTime;
i = weights.iterator();
while (i.hasNext())
{
Weight w = i.next();
if (w.dt.getTime() > (long)originTime && w.dt.getTime() < (long)(originTime+rangeTime)) {
if (maxWeight<w.weight || maxWeight==-1) maxWeight = w.weight;
if (minWeight>w.weight || minWeight==-1) minWeight = w.weight;
}
}
rangeWeight = (float) ((maxWeight - minWeight) * 1.25);
if (rangeWeight<1) rangeWeight = 1;
if (rangeWeight>1000) rangeWeight = 1000;
originWeight = minWeight - rangeWeight/10;
if (originWeight<0) originWeight = 0;
}

getWeightsメソッドでは、データを取得して、描画範囲の決定を行っています。ここで決まった描画範囲に基づいて、後述のonDrawメソッドでグラフが描画されます。

DB.getAllでは、データベース内のデータが入力順に全て取得されるので、本来なら計測日時順にソートしたり、データの取得範囲を限定したりするのが望ましいのですが、SQLの話になるのでここでは割愛します。

取得した計測データからまずは測定日時の最大値と最小値を割り出しています。そして、今回はグラフの右端に最終測定日時がくるように、測定日時の描画範囲幅を「30日」に、原点の測定日時を「最終測定日時 - 30日」としています。Date.getTimeの返す値がミリ秒であることに注意です。秒だと思って書いたらX軸の描画範囲が30/1000日になっててビックリしました。

続いて、Y軸の描画範囲を決定します。先ほど決定した描画期間内の計測データの最大値と最小値を求めてその差がグラフの80%となるように体重の描画範囲を決定しています。あとは折れ線グラフの上下に10%のマージンができるように原点の体重を決定しました。

いざonDrawで描画

@Override
public void onDraw(Canvas canvas)
{
int ox=10;
int oy=10;
float width = this.getWidth() - ox;
float height = this.getHeight() - oy;
Paint paint = new Paint();
paint.setColor(0xFFFFFFFF);
canvas.drawRect(new Rect(0,0,canvas.getWidth(),canvas.getHeight()), paint);
Iterator<Weight> i = weights.iterator();
Weight pw = null;
while (i.hasNext())
{
Weight w = i.next();
if (pw != null && w.dt.getTime()>originTime) {
paint.setColor(0xFFFF0000);
float startX = (pw.dt.getTime()-originTime) / rangeTime * width;
float stopX = (w.dt.getTime()-originTime) / rangeTime * width;
float startY = height - (pw.weight-originWeight) / rangeWeight * height;
float stopY = height - (w.weight-originWeight) / rangeWeight * height;
canvas.drawLine(startX, startY, stopX, stopY, paint);
}
if (w.dt.getTime()>originTime + rangeTime) break;
pw = w;
}
paint.setColor(0xFF000000);
canvas.drawLine(ox, 0, ox, height, paint);
canvas.drawLine(ox, height, ox+width, height, paint);
}

画面の描画、再描画が必要になるとonDrawメソッドが呼ばれます。引数のCanvasというのが具体的に線をひいたり、四角を描いたりする対象になります。とりあえずシンプルな折れ線グラフを描くために今回は以下のメソッドだけ使っています。

Canvas.drawRect
四角を描く
Canvas.drawLine
線を引く

両メソッドとも最後の引数にPaintというオブジェクトを渡すと描画時の細かい設定ができるようですので、とりあえずPaint.setColorメソッドで四角の塗りつぶし色や線の色を指定してみました。具体的な描画内容は以下の通りです。

  1. 白色で画面全体を塗りつぶす
  2. 赤色で順番に折れ線グラフを描画
  3. 黒色でX軸とY軸を描画

グラフとしては本当に最低限のことしかしていないので、後で補助線をひいたり、参考数値を適当な区切りで表示したり、グラフとしての機能を高めていかなければなりませんが、とりあえず解説するのはここまでとしたいと思います。

次回は画面の描画内容をタッチ操作に応じて動的に変更してみたいと思います。

日記一覧