Freitag, 12. September 2014

Mein erstes Mal: Eine ListView mit Daten füttern (inkl. rechts-positioniertem Sterne-Rating!)

Fast witzig, wenn ich daran denke, was ich mir 2010 einen abgebrochen habe, meine Daten in eine ListView zu bekommen! Soooo viele Zeilen Code habe ich geschrieben, kopiert, abgeschaut ... und eigentlich ist alles ganz einfach (und war es "damals" sicher auch schon)!

Ausgangssituation: Ich habe mehrere Datensätze mit immer der gleichen Anzahl Infos, die ich in eine Liste verpacken möchte. Jeder Datensatz soll anklickbar sein und zu seinen kompletten Details führen. Jeden Datensatz in der Liste möchte ich gleich formatieren:


Das Ganze sieht in der manufacturer_list_view.xml dann so aus:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:orientation="vertical" >

  <TableRow
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="5dp"
    android:paddingTop="10dp" >

    <LinearLayout
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:layout_weight="1" >

      <TextView
        android:id="@+id/manufacturer_name"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:maxWidth="280dp"
        android:paddingRight="10dp"
        android:singleLine="false"
        android:text="manufacturer"
        android:textSize="18sp"
        android:textStyle="bold" />

      <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >

        <LinearLayout
          android:id="@+id/LinearLayout1"
          android:layout_width="wrap_content"
          android:layout_height="fill_parent"
          android:layout_gravity="right"
          android:orientation="vertical" >

          <RatingBar
            android:id="@+id/manufacturer_rating"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:layout_marginRight="5dp"
            android:max="5"
            android:numStars="5"
            android:stepSize="0.1" />
        </LinearLayout>
      </LinearLayout>
    </LinearLayout>
  </TableRow>

  <TableRow
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="5dp"
    android:layout_marginRight="5dp" >

    <TextView
      android:id="@+id/manufacturer_details"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:layout_weight="1"
      android:maxLines="2"
      android:paddingBottom="10px"
      android:singleLine="false"
      android:text="manufacturerDetails"
      android:textSize="15sp" />
  </TableRow>
</TableLayout>

Wer jetzt fragt, was da ein LinearLayout im LinearLayout im LinearLayout verloren hat, nur um eine RatingBar zu beherbergen, der kann gerne probieren, wie er genau 5 (fünf!) Sterne ganz nach rechts rückt. Alle meine Versuche, das ohne diese Schachtelung hin zu bekommen, waren nicht von Erfolg gekrönt. Entweder nicht rechts oder nicht fünf. Die RatingBar braucht unbedingt das Attribut  android:layout_width="wrap_content" - sonst werden es mehr als 5 Sterne, völlig egal, was man in max oder numStars rein schreibt. Und damit geht das verflixte Ding nicht nach rechts ...

Für die Darstellung der Liste brauche ich eine ListView. Die manufacturer_list.xml dazu ist eher unspektakulär:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical" >

  <TextView
    android:text="@string/manufacturer_manufacturer_list" />

  <ListView
    android:id="@+id/android:list"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:cacheColorHint="#00000000"
    android:divider="@drawable/gradient"
    android:dividerHeight="3px" >
  </ListView>
</LinearLayout>

Für die Activity ist es am einfachsten, wenn ich eine ListActivity erweitere. Zu beachten ist dann nur, dass die id nicht willkürlich vergeben werden darf sondern wie oben aussehen muss. Das Erweitern der ListActivity hat - zumindest in meinen Augen - den Vorteil, dass man die eine oder andere Zeile Code sparen kann. Ich mag Übersichtlichkeit und finde es toll, dass man mit getListView() ohne findViewById() gleich die ListView an der Hand hat, oder dass sogar ohne Zugriff auf die ListView der Adapter gesetzt werden kann:
ManufacturerListAdapter adapter 
  = new ManufacturerListAdapter(this, 0, manufacturers);
setListAdapter(adapter);

Den Adapter habe ich passend zu meinen Daten selber geschrieben. Dieser erweitert einen ArrayAdapter. So kann ich meine Daten als ArrayList übergeben und die Funktion public View getView() überschreiben, um mein XML pro Datensatz zu füllen.
public ManufacturerListAdapter(Context context,
    int textViewResourceId,
    List manufacturers) {
  super(context, textViewResourceId, manufacturers);
}

@Override
public View getView(final int position, View convertView, 
    ViewGroup parent) {
  final Manufacturer manufacturer = getItem(position);

  if (convertView == null) {
    convertView = LayoutInflater.from(getContext()).inflate(
        R.layout.manufacturer_list_view, parent, false);
  }

  TextView tvManufacturerName = (TextView) convertView
      .findViewById(R.id.manufacturer_name);
  RatingBar rbManufacturer = (RatingBar) convertView
      .findViewById(R.id.manufacturer_rating);
  TextView tvManufacturerDetails = (TextView) convertView
      .findViewById(R.id.manufacturer_details);

  tvManufacturerName.setText(manufacturer.getName());
  rbManufacturer
    .setRating((float) manufacturer.getAverageRating());
  tvManufacturerDetails.setText(manufacturer.getAddress());

  return convertView;
}

Für die Implementierung des ClickListeners gibt es mehrere Möglichkeiten. Mir hat der Aufruf convertView.setOnClickListener() in getView() am besten gefallen. So habe ich alles zusammen an einer Stelle.
convertView.setOnClickListener(new OnClickListener() {

  @Override
  public void onClick(View v) {
    final Intent intent = new Intent(getContext(),
        ManufacturerDetailsActivity.class);

    intent
      .putExtra(Constants.MANUFACTURER_ID, manufacturer.getId());
    intent.putExtra(Constants.ILLUSTRATION_MODE, "view");
    intent.putExtra(Constants.POSITION, position);

    getContext().startActivity(intent);
  }
});

Wichtig: Im XML mit liebevoll verpackter RatingBar KEIN Attribut "android:inputType="textMultiLine" unterbringen - das kann einen stundenlang aufhalten, wenn man den Fehler sucht, warum kein einzige ClickListener auf irgendwas reagiert ... und mir ist es noch immer ein Rätsel, wie das in meine TextView rein gekommen ist ... :)


Mit diesen paar Zeilen Code habe ich nun eine scrollbare ListView mit ClickListener, der mich auf eine Detailseite bringt. In meiner alten Version von 2010 habe ich auch einen eigenen Adapter geschrieben. Dieser Adapter verwendete ein RowModel für jeden Datensatz, was meiner jetzigen Manufacturer-Klasse entspricht:
public class Manufacturer {
  private int id;
  private String name;
  private String address;
  private double averageRating;

  public Manufacturer(int id, String name, String address,
      double averageRating) {
    super();
    this.id = id;
    this.name = name;
    this.address = address;
    this.averageRating = averageRating;
  }

  public int getId() {
    return id;
  }

  public String getName() {
    return name;
  }

  public String getAddress() {
    return address;
  }

  public double getAverageRating() {
    return averageRating;
  }
}

Darüber hinaus gab es einen ListWrapper, der eigentlich nur alle Werte nochmal verwirbelt und den Zugriff auf die einzelnen Elemente gesteuert hat. Natürlich, Wrapper hatte ich auch mal im Studium, aber in diesem Fall halte ich eine Verpackung für total überqualifiziert. Weg damit. Wer Gegenteiliges zu berichteten hat, kann mich gerne informieren. Ansonsten gab es noch viele Zeilen umständlichen Code, die ich entfernt habe ...

Keine Kommentare:

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.