Smarthome App #11: Übersicht der Sensoren

Smarthome App #11: Übersicht der Sensoren


05.05.2017  |  Tutorial, Smarthome App

Im heutigen Tutorial wird die Sensorübersicht implementiert, damit du auf einen Blick über die wichtigsten Werte in deinem System Bescheid weißt. Die Sensoren sind nebeneinander angeordnet und nach Räumen getrennt, wobei die Räume untereinander aufgelistet sind.

OverviewFragment implementieren

Als erstes öffnest du die Datei "OverviewFragment.java" im Hauptpaket der App. Dort legst du über dem Konstruktor des Fragments ein paar Variablen an:
private View overview;

//RecyclerView
private RecyclerView.Adapter overviewAdapter;
private LinearLayoutManager llm;
private ArrayList<OverviewItem> overviewItems;
private RecyclerView overviewArray;
Da die Klasse OverviewItem noch nicht existiert, muss sie zuerst erstellt werden. Dazu klickst du mit rechts auf das Paket "items" und wählst "New -> Java Class".
Die neue Klasse
Gib der Klasse den Namen "OverviewItem" und klicke auf "Ok". In der Klasse werden nun als erste ein paar Attribute erstellt:
private String name, location;
private ArrayList<RoomItem> valueList;
Das Attribut "name" ist der Name des Raumes, "location" ist die ID des Raumes und "valueList" ist eine Liste von RoomItems, die die einzelnen Sensoren in einem Raum darstellen. Als nächstes werden der Konstruktor und die Getter-Methoden für die Attribute implementiert. Dazu schreibst du den folgenden Code in die Klasse:
public OverviewItem(String name, String location, ArrayList<RoomItem> valueList){
    this.name = name;
    this.location = location;
    this.valueList = valueList;
}

/**
 * Gibt den Namen des Items zur&uuml;ck
 * @return
 */
public String getName(){
    return name;
}

/**
 * Gibt die Location des Items zur&uuml;ck
 * @return
 */
public String getLocation(){
    return location;
}

/**
 * Gibt die RoomItems zur&uuml;ck
 * @return
 */
public ArrayList<RoomItem> getValueList(){
    return valueList;
}

OverviewFragment designen

Bevor das OverviewFragment weiter implementiert wird, wird zuerst die Layout-Datei angepasst. Dazu öffnest du die Datei "fragment_overview.xml" im Verzeichnis "/mobile/res/layout". Dort fügst du dem äußersten Tag die beiden Parameter "id" und "background" hinzu:
android:id="@+id/frame"
android:background="@color/layoutBackground"
Innerhalb dieses Tags entfernst du den TextView-Tag und fügst dafür den folgenden Code ein:
<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/overview_list"></android.support.v7.widget.RecyclerView>

<include
    layout="@layout/loading_animation"
    android:visibility="gone"
    android:id="@+id/loading_animation" />
Die RecyclerView ist eine Liste, in der die Sensoren angezeigt werden und mit dem "include"-Tag wird das Layout "loading_animation" eingebunden, das eine Lade-Animation anzeigt. Anschließend erstellst du eine neue Layout-Datei, indem du mit rechts auf eine vorhandene Layout-Datei klickst und "New -> Layout resource file" wählst.
Die Datei
Als Namen trägst du "value_item_horizontal" und bei "Root element" gibst du "RelativeLayout" ein und klickst auf "Ok".
So wird die Layout-Datei definiert.
In der Ansicht, die sich nun öffnet, klickst du unten auf den Tab "Text", um den Code des Layouts zu bearbeiten. Als erstes änderst du die Attribute "layout_width" und "layout_height" des RelativeLayouts von "match_parent" auf "wrap_content". In den RelativeLayout-Tag fügst du nun mit folgendem Code eine CardView ein:
<android.support.v7.widget.CardView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:cardElevation="@dimen/cardViewElevation"
    app:cardCornerRadius="@dimen/cardViewCornerRadius"
    app:cardUseCompatPadding="true"
    android:id="@+id/container">

</android.support.v7.widget.CardView>
Dann öffnest du die Layout-Datei "value_item.xml" und kopierst den folgenden Code, der sich innerhalb des CardView-Tags befindet:
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="@dimen/recyclerItemHeight">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:layout_centerInParent="true"
        android:layout_margin="3dp">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/icon"
            android:layout_gravity="center"
            android:layout_weight="2"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:id="@+id/name"
            android:layout_gravity="center"
            android:textColor="@color/textColorDark"
            android:gravity="center_vertical|center_horizontal"
            android:layout_weight="3"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:id="@+id/value"
            android:layout_gravity="center"
            android:textColor="@color/textColorDark"
            android:gravity="center_vertical|center_horizontal"
            android:layout_weight="3"/>

    </LinearLayout>

</RelativeLayout>
Diesen Code fügst du nun in den CardView-Tag des Layouts "value_item_horizontal.xml" ein. In dem eingefügten Code-Block passt du nun das Attribut "layout_width" des äußersten LinearLayouts an und setzt den Wert auf "@dimen/recyclerItemWidth". Du kannst die beiden Dateien "value_item_horizontal.xml" und "value_item.xml" jetzt schließen. Nun wechselst du wieder zum OverviewFragment und erweiterst den Konstruktor. Dazu ersetzt du die Codezeile, die im Konstruktor vorhanden ist, mit dem folgenden Code:
// Inflate the layout for this fragment
overview = inflater.inflate(R.layout.fragment_overview, container, false);

overviewArray = (RecyclerView) overview.findViewById(R.id.overview_list);
overviewArray.setHasFixedSize(true);
llm = new LinearLayoutManager(getContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
overviewArray.setLayoutManager(llm);

loadRoomData();

return overview;
Der Code weißt das erstellte Layout der Variable "overview" zu, initialisiert die Klassen-Attribute, ruft die (noch) nicht vorhandene Methode loadRoomData() zum Laden der Übersichtsdaten der Raume auf und gibt als letztes die Variable "overview" zurück. Als nächstes wird die Funktion loadRoomData() implementiert. Dazu fügst du den folgenden Code unter dem Konstruktor ein:
/**
 * L&auml;dt die Sensoren des Raumes
 */
public void loadRoomData(){
    overview.findViewById(R.id.loading_animation).setVisibility(View.VISIBLE);

    final Map<String, String> requestData = new HashMap<>();
    requestData.put("action", "getsensordata");
    requestData.put("username", SaveData.getUsername(getContext()));
    requestData.put("password", SaveData.getPassword(getContext()));
    requestData.put("room", "all");

    HTTPRequest.sendRequest(getContext(), requestData, SaveData.getServerIp(getContext()), new HTTPRequest.HTTPRequestCallback() {
        @Override
        public void onRequestResult(String result) {
            overview.findViewById(R.id.loading_animation).setVisibility(View.GONE);

            if(result.equals("wrongdata")){
                Dialogs.fehlermeldung("Anmeldung nicht m&ouml;glich!\nBitte logge dich erneut ein.", overview.findViewById(R.id.frame));
            }
            else if(result.equals("unknownuser")){
                Dialogs.fehlermeldung("Dieser Benutzer existiert nicht.\nBitte logge dich erneut ein.", overview.findViewById(R.id.frame));
            }
            else{
                try{
                    JSONObject jsonObject = null;
                    try{
                        jsonObject = new JSONObject(result);
                    }catch (JSONException e){
                        e.printStackTrace();
                    }

                    overviewItems = new ArrayList<>();

                    JSONArray overviewItemArray = jsonObject.getJSONArray("values");

                    for(int i = 0; i < overviewItemArray.length(); i++){
                        JSONObject o = overviewItemArray.getJSONObject(i);

                        ArrayList<RoomItem> valueList = new ArrayList<>();

                        JSONArray valueArray = o.getJSONArray("value_array");

                        for(int j = 0; j < valueArray.length(); j++){
                            JSONObject v = valueArray.getJSONObject(j);

                            valueList.add(new RoomItem(v.getString("shortform"), v.getString("id"),
                                    v.getString("icon"), v.getString("device_type"), "sensor",
                                    v.getString("wert"),
                                    o.getString("location")));
                        }

                        overviewItems.add(new OverviewItem(o.getString("name"), o.getString("location"), valueList));
                    }

                    //Adapter setzen
                    overviewAdapter = new OverviewAdapter(overviewItems, getActivity(), getContext(),
                            overview.findViewById(R.id.frame));
                    overviewArray.setAdapter(overviewAdapter);
                    overviewAdapter.notifyDataSetChanged();

                }catch (JSONException e){
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onError(String msg) {
            overview.findViewById(R.id.loading_animation).setVisibility(View.GONE);
            Dialogs.fehlermeldung(msg, overview.findViewById(R.id.frame));
        }
    });
}
Die Methode loadRoomData() sendet eine HTTP-POST-Anfrage an den Server. Der Server gibt die Räume mit den vorhandenen Sensoren im JSON-Format zurück. Das JSON-Objekt wird zerlegt und daraus eine Liste von Sensoren erstellt, mit denen anschließend der Adapter gefüllt wird.

Adapter erstellen & implementieren

Da die Klasse "OverviewAdapter" noch nicht existiert, wird sie nun erstellt. Dazu klickst du mit rechts auf das Paket "adapters" und wählst "New -> Java Class".
Die neue Klasse
Gibt der Klasse den Namen "OverviewAdapter" und klicke auf "Ok". Als erstes fügst du dem Klassen-Kopf ("public class OverviewAdapter") den Code "extends RecyclerView.Adapter" hinzu und erstellst die Klassenattribute:
private int lastPosition = -1;

private ArrayList<OverviewItem> overviewItems;
private Activity activity;
private Context context;
private View view;
Danach erstellst du den Konstruktor der Klasse, der die Attribute initialisiert:
public OverviewAdapter(ArrayList<OverviewItem> overviewItems, Activity activity, Context context, View view){
    this.overviewItems = overviewItems;
    this.activity = activity;
    this.context = context;
    this.view = view;
}
Bevor der Adapter komplett implementiert werden kann, wird noch eine Layout-Datei für ein OverviewItem benötigt. Erstelle es, indem du auf eine Layout-Datei klickst und "New -> Layout resource file" wählst.
Das Layout für das OverviewItem wird erstellt.
Als Namen trägst du "overview_item" ein und klickst auf "Ok". Ersetze den Code der Layout-Datei mit dem folgenden:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="5dp"
    android:layout_marginTop="5dp"
    android:id="@+id/container">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:id="@+id/title"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="5dp"/>

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/value_list"></android.support.v7.widget.RecyclerView>

    <include
        layout="@layout/empty_item"
        android:visibility="gone"
        android:id="@+id/empty_item"/>

</LinearLayout>
Für die Übersicht wird außerem ein horizontaler Adapter benötigt, der die Sensoren der einzelnen Räume anzeigt. Dazu erstellst du im Paket "adapters" eine neue Klasse namens "HorizontalRoomAdapter":
Die Klasse für den
Der "HorizontalRoomAdapter" erbt von der Klasse "RoomAdapter" und überschreibt die Methoden onBindViewHolder(), onCreateViewHolder() und getItemViewType(). Ersetze den Code "public class HorizontalRoomAdapter{}" mit dem folgenden:
public class HorizontalRoomAdapter extends RoomAdapter {

    public HorizontalRoomAdapter(ArrayList<RoomItem> roomItems, Activity activity, Context context, View view){
        super(roomItems, activity, context, view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int i){
        final RoomItem ri = getRoomItems().get(i);
        final SensorViewHolder sensorViewHolder = (SensorViewHolder) holder;
        sensorViewHolder.icon.setImageResource(Icons.getDeviceIcon(ri.getIcon()));
        sensorViewHolder.value.setText(ri.getValue());
        sensorViewHolder.name.setText(ri.getName());

        sensorViewHolder.container.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                RoomControl.showOverview(getActivity(), ri, ri.getRoom());
            }
        });
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType){
        View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.value_item_horizontal, viewGroup, false);
        return new SensorViewHolder(itemView);
    }

    @Override
    public int getItemViewType(int position){
        return VIEW_TYPE_SENSOR;
    }
}
Der HorizontalRoomAdapter überschreibt die Methoden onCreateViewHolder(), onBindViewHolder() und getItemViewType() von seiner Oberklasse. Die neue Implementierung von OnBindViewHolder() füllt für den angegebenen Index das ViewElement mit den Daten des Sensors an der Stelle mit dem entsprechenden Index. Die Methode onCreateViewHolder() gibt lediglich die View für das Sensorelement zurück und getItemViewType() liefert den View-Type des Sensors zurück. Öffne die Datei "RoomAdapter.java" im Paket "adapters" und füge der Klasse die folgenden Methode hinzu:
/**
 * Gibt die RoomItems zur&uuml;ck
 * @return
 */
public ArrayList<RoomItem> getRoomItems(){
    return roomItems;
}

/**
 * Gibt die Activity zur&uuml;ck
 * @return
 */
public Activity getActivity(){
    return activity;
}
Wechsle wieder zum OverviewAdapter und füge die folgenden Methoden in die Klasse ein:
@Override
public int getItemCount(){
    return overviewItems.size();
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i){
    if(holder instanceof OverviewHolder){
        final OverviewItem oi = overviewItems.get(i);
        final OverviewHolder overviewHolder = (OverviewHolder) holder;
        overviewHolder.name.setText(oi.getName());

        //Adapter setzen
        RoomAdapter valueAdapter = new HorizontalRoomAdapter(oi.getValueList(), activity, context, view);
        overviewHolder.valueArray.setAdapter(valueAdapter);
        valueAdapter.notifyDataSetChanged();

        if(oi.getValueList().isEmpty()){
            overviewHolder.valueArray.setVisibility(View.GONE);

            overviewHolder.container.findViewById(R.id.empty_item).setVisibility(View.VISIBLE);
            ((ImageView) overviewHolder.container.findViewById(R.id.empty_icon)).setImageResource(Icons.getRoomIcon(oi.getLocation()));
            ((TextView) overviewHolder.container.findViewById(R.id.empty_title)).setText("Raum leer");
            ((TextView) overviewHolder.container.findViewById(R.id.empty_info)).setText("Diesem Raum wurden noch keine Sensoren hinzugef&uuml;gt.");
        }

        setAnimation(overviewHolder.container, i);
    }
}

private void setAnimation(View viewToAnimate, int position){
    if(position > lastPosition){
        Animation animation = AnimationUtils.loadAnimation(context, R.anim.recycler_animation);
        viewToAnimate.startAnimation(animation);
        lastPosition = position;
    }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType){
    View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.overview_item, viewGroup,false);
    return new OverviewHolder(itemView);
}
Die Methode getItemCount() liefert lediglich die Länge der Liste "overviewItems" zurück. Mit onBindViewHolder() werden Views für die einzelnen Elemente von "overviewItems" erstellt. Dabei wird der Titel gesetzt und der Adapter für die einzelnen Sensoren mit Werten gefüllt. Falls ein Raum keine Sensoren enthält, wird stattdessen eine Info-Karte angezeigt. Anschließend wird die Funktion setAnimation() aufgerufen, die jedes Listenelement erscheinen lässt. Anschließend fügst du auch die folgende neue Klasse in den OverviewAdapter ein:
public class OverviewHolder extends RecyclerView.ViewHolder{
    protected TextView name;
    protected RecyclerView valueArray;
    protected View container;

    public OverviewHolder(View v){
        super(v);
        container = v.findViewById(R.id.container);
        name = (TextView) v.findViewById(R.id.title);
        valueArray = (RecyclerView) v.findViewById(R.id.value_list);

        valueArray.setHasFixedSize(true);
        LinearLayoutManager llm = new LinearLayoutManager(context);
        llm.setOrientation(LinearLayoutManager.HORIZONTAL);
        valueArray.setLayoutManager(llm);
    }
}
Das war's schon wieder mit diesem Tutorial. Bei Fragen oder Problemen kannst du mir gerne einen Kommentar hinterlassen.

Über den Autor


Sascha

Hallo, ich bin Sascha, der Gründer von Smarthome Blogger.

Mit einer Leidenschaft für Technologie und einem Hintergrund als Software Engineer habe ich 2016 Smarthome Blogger gegründet. Mein Ziel war es schon immer, innovative Lösungen zu entdecken, die unser Leben einfacher und intelligenter gestalten können. In meinem beruflichen Leben arbeite ich täglich mit Software und Technik, aber auch in meiner Freizeit bin ich stets auf der Suche nach neuen technischen Spielereien und Möglichkeiten, mein Zuhause zu automatisieren und zu verbessern.

Auf Smarthome Blogger teile ich mein Wissen, meine Erfahrungen und meine Begeisterung für alles rund um das Thema Smarthome.



Dieser Beitrag hat dir gefallen?

Dann abonniere doch unseren Newsletter!