Im heutigen Tutorial wird eine Activity angelegt, mit der der Verlauf der Sensorwerte in einem Diagramm dargestellt werden. Dabei wird für jeden Tag der jeweilge Höchst- & Tieftwert angezeigt. Der angezeigte Zeitintervall kann frei gewählt werden.
Bibliothek einbinden
Als erstes muss die benötigte Bibliothek eingebunden werden. Um Bibliotheken einbinden zu können, müssen Repositories hinzugefügt werden. Dazu öffnest du die Datei "build.gradle [Project: Smarthome]" und fügst dem Block "repositories" diese drei Zeilen hinzu:mavenCentral()
maven{ url 'https://jitpack.io' }
jcenter{ url 'http://jcenter.bintray.com/' }
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
GraphActivity designen
Nun erstellst du eine neue Activity, indem du mit rechts auf eine Java-Datei klickst und New -> Activity -> Empty Activity" wählst. Gib ihr den Namen "GraphActivity" und klicke auf "Finish". Bevor die GraphActivity implementiert wird, wird die Layout-Datei bearbeitet. Öffne die Datei "activity_graph.xml" und ersetze den vohandenen Code mit dem folgenden:<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp"
android:background="@color/layoutBackground"
android:id="@+id/frame"
tools:context="de.smarthome_blogger.smarthome.GraphActivity">
<com.github.mikephil.charting.charts.LineChart
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linechart"
android:visibility="gone"></com.github.mikephil.charting.charts.LineChart>
<include
layout="@layout/loading_animation"
android:visibility="gone"
android:id="@+id/loading_animation"></include>
<include
layout="@layout/empty_item"
android:visibility="gone"
android:id="@+id/empty_item"></include>
</RelativeLayout>
<item android:id="@+id/change_period"
android:title="Zeitraum ändern"
android:orderInCategory="100"
app:showAsAction="always"></item>
GraphActivity implementieren
Jetzt wechselst du zur Datei "GraphActivity.java" und fügst der Klasse über der Methode onCreate() ein paar neue Attribute hinzu:private String location, devicetype, post_id, einheit;
private LineChart lineChart;
private ArrayList<GraphDayItem> dayList;
//Für benutzerdefinierten Zeitraum
private final long dayMilliseconds = 60 * 60 * 24 * 1000;
private DateFormat formatter = new SimpleDateFormat("dd.MM.yyyy");
private String date;
private float minVal, maxVal;
public GraphDayItem(String date, float minVal, float maxVal){
this.date = date;
this.minVal = minVal;
this.maxVal = maxVal;
}
/**
* Gibt das Datum zurück
* @return
*/
public String getDate(){
return date;
}
/**
* Gibt den minimalen Wert zurück
* @return
*/
public float getMinVal(){
return minVal;
}
/**
* Gibt den maximalen Wert zurück
* @return
*/
public float getMaxVal(){
return maxVal;
}
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Bundle bundle = getIntent().getExtras();
setTitle(bundle.getString(MainActivity.EXTRA_TITLE));
location = bundle.getString(MainActivity.EXTRA_LOCATION);
devicetype = bundle.getString(MainActivity.EXTRA_DEVICETYPE);
id = bundle.getString(MainActivity.EXTRA_DEVICE);
//Graph definieren
lineChart = (LineChart) findViewById(R.id.linechart);
lineChart.setDescription(null);
lineChart.setHighlightPerDragEnabled(false);
lineChart.setScaleYEnabled(false);
dayList = new ArrayList<>();
Long now = System.currentTimeMillis();
Long then = now - ((long) (30 * dayMilliseconds)); //30 Tage zuvor
loadGraphData(formatter.format(then), formatter.format(now));
final static String EXTRA_DEVICETYPE = "EXTRA_DEVICETYPE";
final static String EXTRA_START_DATE = "EXTRA_START_DATE";
final static String EXTRA_END_DATE = "EXTRA_END_DATE";
final static String EXTRA_UNIT = "EXTRA_UNIT";
final static String EXTRA_DEVICE = "EXTRA_DEVICE";
final static String EXTRA_ID = "EXTRA_ID";
@Override
public boolean onSupportNavigateUp() {
super.onSupportNavigateUp();
onBackPressed();
getSupportFragmentManager().popBackStack();
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu){
getMenuInflater().inflate(R.menu.activity_graph, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item){
int post_id = item.getItemId();
if(post_id == R.id.change_period){
Dialogs.chooseTimePeriodDialog(this, findViewById(R.id.frame), new Dialogs.OnPeriodChosenListener(){
@Override
public void onPeriodChosen(String start, String end){
loadGraphData(start, end);
}
});
return true;
}
return super.onOptionsItemSelected(item);
}
Dialoge erstellen
Als nächstes müssen die neuen Dialog-Methoden in der Klasse Dialogs implementiert werden. Öffne dazu die Klasse "Dialogs.java" im Package "system" und füge diese beiden Methoden und das Interface hinzu:/**
* Erstellt einen Dialog, um einen Zeitraum zu bestimmen
* @param activity
* @param view
* @param periodChosenListener
*/
public static void chooseTimePeriodDialog(final Activity activity, final View view, final OnPeriodChosenListener periodChosenListener){
final long dayMilliseconds = 60 * 60 * 24 * 1000;
final DateFormat formatter = new SimpleDateFormat("dd.MM.yyyy");
String[] items = {"Letzte 30 Tage", "Letzte 60 Tage", "Letzte 90 Tage",
"Dieser Monat", "Letzter Monat", "Dieses Jahr", "Letztes Jahr", "Benutzerdefiniert"};
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Zeitraum wählen");
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Long now = System.currentTimeMillis();
Long then;
String vonDatum, bisDatum;
int tag, monat, jahr;
Calendar cal = Calendar.getInstance();
switch(which){
case 0:
then = now - ((long) 30 * dayMilliseconds);
periodChosenListener.onPeriodChosen(formatter.format((then)), formatter.format(now));
break;
case 1:
then = now - ((long) 60 * dayMilliseconds);
periodChosenListener.onPeriodChosen(formatter.format((then)), formatter.format(now));
break;
case 2:
then = now - ((long) 90 * dayMilliseconds);
periodChosenListener.onPeriodChosen(formatter.format((then)), formatter.format(now));
break;
case 3:
tag = 31;
monat = cal.get(Calendar.MONTH);
jahr = cal.get(Calendar.YEAR);
switch (monat){
case Calendar.FEBRUARY:
if((jahr%4)==0){
tag = 29;
}
else tag = 28;
break;
case Calendar.APRIL:
case Calendar.JUNE:
case Calendar.SEPTEMBER:
case Calendar.NOVEMBER:
tag = 30;
break;
}
if(monat == Calendar.DECEMBER){
monat = Calendar.JANUARY;
}
else{
monat++;
}
vonDatum = "01." + monat + "." + jahr;
bisDatum = tag + "." + monat + "." + jahr;
periodChosenListener.onPeriodChosen(vonDatum, bisDatum);
break;
case 4:
tag = 31;
monat = cal.get(Calendar.MONTH);
jahr = cal.get(Calendar.YEAR);
if(monat == Calendar.JANUARY){
monat = Calendar.DECEMBER;
}
else{
monat--;
}
switch (monat){
case Calendar.FEBRUARY:
if((jahr%4)==0){
tag = 29;
}
else tag = 28;
break;
case Calendar.APRIL:
case Calendar.JUNE:
case Calendar.SEPTEMBER:
case Calendar.NOVEMBER:
tag = 30;
break;
}
if(monat == Calendar.DECEMBER){
monat = Calendar.JANUARY;
}
else{
monat++;
}
vonDatum = "01." + monat + "." + jahr;
bisDatum = tag + "." + monat + "." + jahr;
periodChosenListener.onPeriodChosen(vonDatum, bisDatum);
break;
case 5:
vonDatum = "01.01." + cal.get(Calendar.YEAR);
bisDatum = "31.12." + cal.get(Calendar.YEAR);
periodChosenListener.onPeriodChosen(vonDatum, bisDatum);
break;
case 6:
jahr = cal.get(Calendar.YEAR)-1;
vonDatum = "01.01." + jahr;
bisDatum = "31.12." + jahr;
periodChosenListener.onPeriodChosen(vonDatum, bisDatum);
break;
case 7:
Dialogs.chooseCustomTimePeriodDialog(activity, view, periodChosenListener);
break;
}
}
});
builder.setNegativeButton("Abbrechen", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
/**
* Erstellt einen Dialog, um einen eigenen Zeitraum zu wählen
* @param activity
* @param v
* @param periodChosenListener
*/
public static void chooseCustomTimePeriodDialog(Activity activity, final View v,
final OnPeriodChosenListener periodChosenListener){
final Map<String, String> periodData = new HashMap<>();
periodData.put("start", null);
periodData.put("end", null);
final Map<String, Long> periodTimestamps = new HashMap<>();
periodTimestamps.put("start", null);
periodTimestamps.put("end", null);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Zeitraum wählen");
final View periodChooserView = activity.getLayoutInflater().inflate(R.layout.period_chooser, null);
final DatePicker datePicker = (DatePicker) periodChooserView.findViewById(R.id.datepicker);
final Button setStartDate = (Button) periodChooserView.findViewById(R.id.startDate);
final Button setEndDate = (Button) periodChooserView.findViewById(R.id.endDate);
builder.setView(periodChooserView);
setStartDate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int day = datePicker.getDayOfMonth();
int month = datePicker.getMonth()+1;
int year = datePicker.getYear();
periodData.put("start", ((day<10)?("0"+day):day)+"."+((month<10)?("0"+month):month)+"."+year);
Calendar calendar = new GregorianCalendar(year, month, day);
periodTimestamps.put("start", calendar.getTimeInMillis());
setStartDate.setText(periodData.get("start"));
}
});
setEndDate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int day = datePicker.getDayOfMonth();
int month = datePicker.getMonth()+1;
int year = datePicker.getYear();
periodData.put("end", ((day<10)?("0"+day):day)+"."+((month<10)?("0"+month):month)+"."+year);
Calendar calendar = new GregorianCalendar(year, month, day);
periodTimestamps.put("end", calendar.getTimeInMillis());
setStartDate.setText(periodData.get("end"));
}
});
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if(periodData.get("start") == null || periodData.get("end") == null){
Dialogs.fehlermeldung("Bitte lege ein Start- und ein Enddatum fest.", v);
}
else{
if(periodTimestamps.get("start") < periodTimestamps.get("end")){
periodChosenListener.onPeriodChosen(periodData.get("start"), periodData.get("end"));
}
else{
Dialogs.fehlermeldung("Das Startdatum muss vor dem Enddatum liegen.", v);
}
}
}
});
builder.setNegativeButton("Abbrechen", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
public interface OnPeriodChosenListener{
/**
* Wird aufgerufen, wenn ein Zeitraum ausgewählt wurde
* @param start
* @param end
*/
void onPeriodChosen(String start, String end);
}
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<DatePicker
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/datepicker"></DatePicker>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="Startdatum"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:background="@color/colorPrimary"
android:textColor="@color/textColorLight"
android:textAllCaps="false"
android:id="@+id/startDate"
android:layout_weight="1"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:text="Enddatum"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:background="@color/colorPrimary"
android:textColor="@color/textColorLight"
android:textAllCaps="false"
android:id="@+id/endDate"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
GraphActivity fertig implementieren
Wechsle nun wieder zur Datei "GraphActivity.java" und füge die Methode loadGraphData() hinzu:/**
* Lädt die Archivdaten des Sensors im angegebenen Bereich vom Server
* @param vonDatum
* @param bisDatum
*/
public void loadGraphData(String vonDatum, String bisDatum){
lineChart.setVisibility(View.GONE);
findViewById(R.id.empty_item).setVisibility(View.GONE);
findViewById(R.id.loading_animation).setVisibility(View.VISIBLE);
final Map<String, String> requestData = new HashMap<>();
requestData.put("action", "getgraphdata");
requestData.put("room", "location");
requestData.put("type", devicetype);
requestData.put("id", post_id);
requestData.put("von", vonDatum);
requestData.put("bis", bisDatum);
requestData.put("username", SaveData.getUsername(getApplicationContext()));
requestData.put("password", SaveData.getPassword(getApplicationContext()));
HTTPRequest.sendRequest(getApplicationContext(), requestData, SaveData.getServerIp(getApplicationContext()), new HTTPRequest.HTTPRequestCallback() {
@Override
public void onRequestResult(String result) {
findViewById(R.id.loading_animation).setVisibility(View.GONE);
switch(result){
default:
try{
JSONObject jsonObject = new JSONObject(result);
JSONArray values = jsonObject.getJSONArray("values");
einheit = jsonObject.getString("einheit");
dayList.clear();
for(int i = 0; i < values.length(); i++){
JSONObject o = values.getJSONObject(i);
dayList.add(new GraphDayItem(o.getString("date"), BigDecimal.valueOf(o.getDouble("min")).floatValue(),
BigDecimal.valueOf(o.getDouble("max")).floatValue()));
}
fillLineChart();
if(dayList.isEmpty()){
lineChart.setVisibility(View.GONE);
findViewById(R.id.empty_item).setVisibility(View.VISIBLE);
((ImageView) findViewById(R.id.empty_icon)).setImageResource(Icons.getDrawerIcon("overview"));
((TextView) findViewById(R.id.empty_title)).setText("Keine Daten");
((TextView) findViewById(R.id.empty_info)).setText("Für den ausgewählten Zeitraum sind keine Daten vorhanden.");
}
}
catch(JSONException e){
e.printStackTrace();
onError("");
}
break;
}
}
@Override
public void onError(String msg) {
lineChart.setVisibility(View.GONE);
findViewById(R.id.empty_item).setVisibility(View.VISIBLE);
((ImageView) findViewById(R.id.empty_icon)).setImageResource(Icons.getDrawerIcon("overview"));
((TextView) findViewById(R.id.empty_title)).setText("Keine Daten");
((TextView) findViewById(R.id.empty_info)).setText("Die Daten konnten nicht geladen werden");
}
});
}
/**
* Füllt den LineChart mit Werten
*/
public void fillLineChart(){
if(dayList.isEmpty()){
return;
}
ArrayList<Entry> minVals = new ArrayList<>();
ArrayList<Entry> maxVals = new ArrayList<>();
final ArrayList<String> dates = new ArrayList<>();
for(int i = 0; i < dayList.size(); i++){
GraphDayItem item = dayList.get(i);
minVals.add(new Entry(i, (float) item.getMinVal()));
maxVals.add(new Entry(i, (float) item.getMaxVal()));
dates.add(item.getDate());
}
//Einstellungen des Graphen
final LineDataSet minLine = new LineDataSet(minVals, "Minumum");
minLine.setMode(LineDataSet.Mode.CUBIC_BEZIER);
minLine.setCubicIntensity(0.2f);
minLine.setAxisDependency(YAxis.AxisDependency.LEFT);
minLine.setColor(getResources().getColor(R.color.colorPrimary));
minLine.setCircleSize(4.5f);
minLine.setCircleColor(getResources().getColor(R.color.colorPrimary));
minLine.setDrawCircles(true);
minLine.setDrawValues(false);
final LineDataSet maxLine = new LineDataSet(maxVals, "Maximum");
maxLine.setMode(LineDataSet.Mode.CUBIC_BEZIER);
maxLine.setCubicIntensity(0.2f);
maxLine.setAxisDependency(YAxis.AxisDependency.LEFT);
maxLine.setColor(getResources().getColor(R.color.red));
maxLine.setCircleSize(4.5f);
maxLine.setCircleColor(getResources().getColor(R.color.red));
maxLine.setDrawCircles(true);
maxLine.setDrawValues(false);
ArrayList<ILineDataSet> dataSets = new ArrayList<>();
dataSets.add(minLine);
dataSets.add(maxLine);
Legend legend = lineChart.getLegend();
legend.setTextSize(20);
LineData lineData = new LineData(dataSets);
lineChart.animateY(500);
lineChart.setData(lineData);
XAxis xAxis = lineChart.getXAxis();
xAxis.setGranularity(1f);
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
try{
return dates.get((int) value);
}
catch(Exception e){
e.printStackTrace();
return String.valueOf(value);
}
}
});
lineChart.invalidate();
lineChart.setVisibility(View.VISIBLE);
lineChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry e, Highlight h) {
int index = minLine.getEntryIndex(e);
if(index == -1){
index = maxLine.getEntryIndex(e);
}
if(index == -1){
return;
}
//ValueCourseActivity starten
/*Intent intent = new Intent(GraphActivity.this, ValueCourseActivity.class);
intent.putExtra(MainActivity.EXTRA_START_DATE, dayList.get(index).getDate());
intent.putExtra(MainActivity.EXTRA_DEVICETYPE, devicetype);
intent.putExtra(MainActivity.EXTRA_ID, post_id);
intent.putExtra(MainActivity.EXTRA_LOCATION, location);
intent.putExtra(MainActivity.EXTRA_UNIT, einheit);
startActivity(intent);*/
}
@Override
public void onNothingSelected() {
}
});
}
GraphActivity aufrufen
Damit die GraphActivity nun auch gestartet wird, wenn ein Sensor angeklickt wird, muss die Methode showOverview() in der Klasse "RoomControl" implementiert werden. Öffne dazu die Datei "RoomControl.java" im Package "system". Füge dort den folgenden Code in den Rumpf der Methode showOverview() ein://GraphActivity aufrufen
Intent intent = new Intent(activity, GraphActivity.class);
intent.putExtra(MainActivity.EXTRA_TITLE, item.getName());
intent.putExtra(MainActivity.EXTRA_LOCATION, location);
intent.putExtra(MainActivity.EXTRA_DEVICETYPE, item.getDeviceType());
intent.putExtra(MainActivity.EXTRA_DEVICE, item.getId());
activity.startActivity(intent);
Dieser Beitrag hat dir gefallen?
Dann abonniere doch unseren Newsletter!