Wer sich mit Java schon etwas beschäftigt hat, sollte das Framework JFreeChart kennen. Für Android wurde dieses durchaus sehr gute Framework portiert und kann aus dem Repository von Google „https://code.google.com/p/afreechart/“ heruntergeladen werden.
Für mein Projekt habe ich die Version 0.0.4 gewählte welche unter der Adresse AFreeChart Downloads bezogen werden kann (dort befindet sich unter anderem die JavaDoc und eine *.apk Datei als lauffähige Android App).
Einbinden der Sourcen
Zuerst muss die Datei afreechart-0.0.4.jar in das Projekt eingebunden werden. Dieses ist in jeder IDE unterschiedlich. Da ich InteliJ von JetBrains verwende und mein Projekt mit Gradle erstellen muss ich einen Ordner „libs“ neben dem „src“ Ordner erstellen.
Nun noch die Datei in diesem Ordner ablegen und im Android Projekt bekannt machen.
Als Nächstes muss dieser Ordner in der Konfigurationsdatei „build.gradle(Module: app)“ eingetragen werden.
Es wird lediglich folgender Eintrag hinzugefügt bzw ergänzt.
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) }
Erstellen einer Activity mit einem Liniendiagramm
Um eine Activity mit zbsp. einem Liniendiagramm zu erstellen muss man ähnlich wie bei JFreeChart das Diagramm Objekt „AFreeChart“ erstellen.
AFreeChart chart = ChartFactory.createTimeSeriesChart( "Titel",//Der Titel des Diagramms "Datum", //Die Achsenbeschriftung in X Richtung "Werte", //Die Achsenbeschriftung in Y Richtung dataset, //Das DataSet Objekt mit den Werten true, //Legende Anzeigen false, //Tooltips Anzeigen false //URL's Anzeigen );
Diagramm einer View hinzufügen
Dieses Diagramm kann nun zu einer View hinzugefügt werden. Ich wende aus dem Beispiel die DemoView und leite davon meine neue View ab, somit muss ich mich nicht mit dem Überschreiben von Methoden kümmern.
public class EvaluationChartView extends DemoView {}
Als Erstes müssen wir unseren Konstruktor erstellen welcher als Parameter den Context und eine java.util.List (oder eine andere Collection) mit den Werten enthält. In meinem Fall sind dies Messergebnisse.
public EvaluationChartView(Context context, List<Measurement> measurements) { super(context); }
Diese Messergebnisse müssen nun einem Dataset zugeordnet werden.
String text = ctx.getResources().getString(R.string.seriesTxt); TimeSeries myTimeSerie = new TimeSeries(text); for (Measurement measurement : measurements) { Minute minute = formatTimestamp(measurement.getTimestamp()); myTimeSerie.add(minute, measurement.getValue()); } TimeSeriesCollection dataset = new TimeSeriesCollection(); dataset.addSeries(myTimeSerie);
In diesem Beispiel habe ich die Zeitachse in Minuten Abstände eingestellt. Andere Werte können zbsp. sein:
Der Konstruktor der Klasse „Minute“ erwartet ein java.util.Date Objekt daher muss ich erst mein Timestamp (long) in ein Date Objekt umwandeln. Am einfachsten geht dieses mit der Klasse java.util.Calendar.
private Minute formatTimestamp(long timestamp) { Calendar cal = Calendar.getInstance(); cal.setTimeZone(TimeZone.getTimeZone("Europe/Berlin")); cal.setTimeInMillis(timestamp); return new Minute(cal.getTime()); }
Als Zeitzone setzte ich hier die deutsche Zeitzone, eleganter ist es jedoch die Zeitzone vom Gerät zu verwenden.
Wenn Sie die Zeitzone vom Gerät verwenden möchten, kann die Zeile
cal.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
entfallen, da die Klasse java.util.Calendar schon die Zeitzone vom Gerät hat.
Das Diagramm auf einer Activity darstellen
Nachdem wir das Diagramm erstellt und mit Daten befüllt haben, werden wir nun dieses auf einer Activity darstellen.
Das Layout zur Activity
Das Layout für diese Activity wird einfach ausfallen, da wir unser View Objekt selbst erstellen benötigen wir zuerst nur einen Container für dieses.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/evaluationCreateLayout"></LinearLayout> </RelativeLayout>
Das „LinearLayout“ wird unser Container.
Einbetten des Diagramms in den Container
Zuerst erstellen wir unser DiagrammView Objekt und holen eine Referenz zum Linearlayout (unser Container). In dieses Layout wird nun unser DiagrammView Objekt mit der Methode „addView(View view)“ hinzugefügt.
Context ctx = getApplicationContext(); EvaluationChartView chartView = new EvaluationChartView(ctx, measurements, evaluation.getEvaluationChartValues()); Point p = LayoutHelper.getScreenSize(ctx); chartView.setMinimumWidth(p.x); LinearLayout evaluationCreateLayout = (LinearLayout) findViewById(R.id.evaluationCreateLayout); evaluationCreateLayout.addView(chartView);
Damit das Diagramm die volle Breite des Bildschirms einnimmt, nutze ich eine Hilfsklasse welche mir die Bildschirmgröße als android.graphics.Point liefert (ähnlich wie java.awt.Point).
public static Point getScreenSize(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); Point size = new Point(); display.getSize(size); return size; }
Speichern des Diagramms als Bitmap
Wer sich schonmal mit JFreeChart beschäftig hat, wird nun eine UtilKlasse vermissen
ChartUtilities.writeChartAsPNG(outputStream,getDataset(), width,height);
Hier kann mit einem einfachen Trick Abhilfe geschaffen werden. Es ist in Android möglich von jedem View Objekt einen „Screenshot“ zu erstellen und somit erstellen wir von unserem Container welches unser Diagramm enthält, einen Screenshot und speichern dieses als Bitmap auf dem Gerät.
private File storeChart(){ File file = new File(CardioLoggerStatics.getExternalDirectory(getApplicationContext())+"/chart.png"); FileOutputStream out = null; try { out = new FileOutputStream(file); LinearLayout view = (LinearLayout) findViewById(R.id.evaluationCreateLayout); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bmp = view.getDrawingCache(); bmp.compress(Bitmap.CompressFormat.PNG, 100, out); } catch (Exception e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } return file; }
Da wir das Bitmap auf dem Gerät ablegen wollen benötigen wir zusätzliche Rechte (Permissions) welche in der Android.Manifest.xml festgehalten werden.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Teilen des Diagramms
Dieses erstellte Bild kann nun mit anderen Geräte oder Systemen geteilt / verschickt werden. Das Objekt „chartFile“ ist vom Typ java.io.File und enthält unseren Screenshot vom Container.
private void shareEvaluation() { Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); intent.setType("text/plain"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); File chartFile = storeChart(); intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(chartFile)); getApplicationContext().startActivity(intent); }
Das fertige Diagramm
Folgendes Diagramm habe ich mit dem oben dargestellten Code generiert.
Die Werte sind nur Beispielhaft.