Advertisement
  1. Code
  2. Android SDK

Erstellen der Zusammengesetzten Ansichten unter Android

Scroll to top
Read Time: 13 min

German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)

Wenn Sie komplexe Anwendungen erstellen, möchten Sie häufig dieselbe Gruppe von Ansichten an verschiedenen Stellen der Anwendung wiederverwenden. Eine Möglichkeit, dieses Problem zu lösen, besteht darin, eine Ansicht zu erstellen, die die Logik und das Layout einer Gruppe von Ansichten kapselt, sodass Sie sie wiederverwenden können, ohne Code an verschiedenen Stellen des Projekts zu duplizieren. In diesem Tutorial erfahren Sie, wie Sie zusammengesetzte Ansichten verwenden, um benutzerdefinierte Ansichten zu erstellen, die leicht wiederverwendbar sind.

1. Einleitung

Unter Android wird eine Ansicht, die aus einer Gruppe von Ansichten besteht, als zusammengesetzte Ansicht oder zusammengesetzte Komponente bezeichnet. In diesem Tutorial erstellen Sie ein Steuerelement, mit dem Sie einen Wert aus einer Liste auswählen können, die von einer Seite zur anderen verschoben wird. Wir werden die Verbindung als Side-Spinner bezeichnen, da die Standardansicht des Android SDK zum Auswählen eines Werts aus einer Liste als Spinner bezeichnet wird. Der folgende Screenshot zeigt, was wir in diesem Tutorial erstellen werden.

2. Projekt-Einrichtung

Um zu beginnen, müssen Sie ein neues Android-Projekt mit Android 4.0 als minimalem SDK-Stufe erstellen. Dieses Projekt sollte nur eine leere Aktivität namens MainActivity enthalten. Die Activity initialisiert lediglich das Layout, wie Sie im folgenden Codeausschnitt sehen können.

1
public class MainActivity extends Activity {
2
   @Override
3
   protected void onCreate(Bundle savedInstanceState) {
4
      super.onCreate(savedInstanceState);
5
      setContentView(R.layout.activity_main);
6
   }
7
}

Das Layout für MainActivity befindet sich in der Datei /res/layout/activity_main.xml und sollte nur ein leeres RelativeLayout enthalten, in dem die zusammengesetzte Ansicht später angezeigt wird.

1
<RelativeLayout 
2
    xmlns:android="https://schemas.android.com/apk/res/android"
3
    xmlns:tools="http://schemas.android.com/tools"
4
    android:layout_width="match_parent"
5
    android:layout_height="match_parent"
6
    tools:context=".MainActivity">
7
</RelativeLayout>

3. Erstellen einer zusammengesetzten Ansicht

Um eine zusammengesetzte Ansicht zu erstellen, müssen Sie eine neue Klasse erstellen, die die Ansichten in der zusammengesetzten Ansicht verwaltet. Für den Side Spinner benötigen Sie zwei Schaltflächenansichten für die Button und eine TextView ansicht, um den ausgewählten Wert anzuzeigen.

Erstellen Sie zunächst die Layoutdatei /res/layout/sidespinner_view.xml, die wir für die Side-Spinner-Klasse verwenden, und verpacken Sie die drei Ansichten in ein <merge>-Tag.

1
<merge xmlns:android="http://schemas.android.com/apk/res/android">
2
    <Button
3
        android:id="@+id/sidespinner_view_previous"
4
        android:layout_width="wrap_content"
5
        android:layout_height="wrap_content"
6
        android:layout_toLeftOf="@+id/sidespinner_view_value"/> 
7
   <TextView
8
        android:id="@+id/sidespinner_view_current_value"
9
        android:layout_width="wrap_content"
10
        android:layout_height="wrap_content"
11
        android:textSize="24sp" />
12
   <Button
13
        android:id="@+id/sidespinner_view_next"
14
        android:layout_width="wrap_content"
15
        android:layout_height="wrap_content" />
16
</merge>

Als Nächstes müssen wir die SideSpinner-Klasse erstellen, die dieses Layout aufbläst und die Pfeile als Hintergrundbilder für die Schaltflächen festlegt. Zu diesem Zeitpunkt führt die zusammengesetzte Ansicht nichts aus, da noch nichts angezeigt werden kann.

1
public class SideSpinner extends LinearLayout {
2
3
   private Button mPreviousButton;
4
   private Button mNextButton;
5
6
   public SideSpinner(Context context) {
7
      super(context);
8
      initializeViews(context);
9
   }
10
11
   public SideSpinner(Context context, AttributeSet attrs) {
12
      super(context, attrs);
13
      initializeViews(context);
14
   }
15
16
   public SideSpinner(Context context, 
17
                      AttributeSet attrs, 
18
                      int defStyle) {
19
      super(context, attrs, defStyle);
20
      initializeViews(context);
21
   }
22
23
   /**

24
    * Inflates the views in the layout.

25
    * 

26
    * @param context

27
    *           the current context for the view.

28
    */
29
   private void initializeViews(Context context) {
30
      LayoutInflater inflater = (LayoutInflater) context
31
            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
32
      inflater.inflate(R.layout.sidespinner_view, this);
33
   }
34
35
   @Override
36
   protected void onFinishInflate() {
37
      super.onFinishInflate();
38
39
      // Sets the images for the previous and next buttons. Uses 

40
      // built-in images so you don't need to add images, but in 

41
      // a real application your images should be in the 

42
      // application package so they are always available.

43
      mPreviousButton = (Button) this
44
        .findViewById(R.id.sidespinner_view_previous);
45
      mPreviousButton
46
        .setBackgroundResource(android.R.drawable.ic_media_previous);
47
48
      mNextButton = (Button)this
49
                       .findViewById(R.id.sidespinner_view_next);
50
      mNextButton
51
        .setBackgroundResource(android.R.drawable.ic_media_next);
52
   }
53
}

Sie werden feststellen, dass die zusammengesetzte Ansicht die LinearLayout-Ansichtsgruppe erweitert. Dies bedeutet, dass jedes Layout, das die zusammengesetzte Ansicht verwendet, Zugriff auf die Attribute des linearen Layouts hat. Infolgedessen unterscheidet sich das Layout für die zusammengesetzte Ansicht etwas von der üblichen. Das Root-Tag ist ein <merge> -Tag anstelle des Tags für eine Ansichtsgruppe wie <LinearLayout> oder <RelativeLayout>.

Wenn Sie die zusammengesetzte Ansicht zum Layout von MainActivity hinzufügen, fungiert das Tag für die zusammengesetzte Ansicht als <LinearLayout>-Tag. Eine zusammengesetzte Ansichtsklasse kann von jeder Klasse abgeleitet werden, die von ViewGroup abgeleitet ist. In diesem Fall ist das lineare Layout jedoch am besten geeignet, da die Ansichten horizontal angeordnet sind.

4. Fügen Sie die zusammengesetzte Ansicht einem Layout hinzu

Zu diesem Zeitpunkt wird das Projekt kompiliert, es ist jedoch nichts sichtbar, da sich die zusammengesetzte Ansicht nicht im Layout von MainActivity befindet. Die  Side Spinner-Ansicht muss wie jede andere Ansicht zum Layout der Aktivität hinzugefügt werden. Der Name des Tags ist der vollständige Name der SideSpinner-Klasse, einschließlich des Namespace.

Fügen Sie dem relativen Layout in der Datei /res/layout/activity_main.xml Folgendes hinzu, um den Side-Spinner zu MainActivity hinzuzufügen.

1
<com.cindypotvin.sidespinnerexample.SideSpinner
2
   android:id="@+id/sidespinner_fruits"
3
   android:layout_width="match_parent"
4
   android:layout_height="wrap_content"
5
   android:orientation="horizontal"
6
   android:gravity="center"/>

Die im <SideSpinner>-Tag verfügbaren Attribute sind Attribute des linearen Layouts, da die von uns erstellte SideSpinner-Klasse die LinearLayout-Klasse erweitert. Wenn Sie das Projekt starten, sollte der Side Spinner sichtbar sein, enthält jedoch noch keine Werte.

5. Fügen der zusammengesetzten Ansicht Methoden hinzu

Es fehlen noch ein paar Dinge, wenn wir den Side Spinner tatsächlich verwenden wollen. Wir sollten in der Lage sein, dem Spinner neue Werte hinzuzufügen, einen Wert auszuwählen und den ausgewählten Wert zu erhalten.

Der einfachste Weg, einer zusammengesetzten Ansicht neue Verhaltensweisen hinzuzufügen, besteht darin, der SideSpinner-Klasse neue öffentliche Methoden hinzuzufügen. Diese Methoden können von jeder Activity verwendet werden, die auf die Ansicht verweist.

1
private CharSequence[] mSpinnerValues = null;
2
private int mSelectedIndex = -1;
3
4
/**

5
 * Sets the list of value in the spinner, selecting the first value 

6
 * by default.

7
 * 

8
 * @param values

9
 *           the values to set in the spinner.

10
 */
11
public void setValues(CharSequence[] values) {
12
   mSpinnerValues = values;
13
14
   // Select the first item of the string array by default since 

15
   // the list of value has changed.

16
   setSelectedIndex(0);
17
}
18
19
/**

20
 * Sets the selected index of the spinner.

21
 * 

22
 * @param index

23
 *           the index of the value to select.

24
 */
25
public void setSelectedIndex(int index) {
26
   // If no values are set for the spinner, do nothing.

27
   if (mSpinnerValues == null || mSpinnerValues.length == 0)
28
      return;
29
30
   // If the index value is invalid, do nothing.

31
   if (index < 0 || index >= mSpinnerValues.length)
32
      return;
33
34
   // Set the current index and display the value.

35
   mSelectedIndex = index;
36
   TextView currentValue;
37
   currentValue = (TextView)this
38
                  .findViewById(R.id.sidespinner_view_current_value);
39
   currentValue.setText(mSpinnerValues[index]);
40
41
   // If the first value is shown, hide the previous button.

42
   if (mSelectedIndex == 0)
43
      mPreviousButton.setVisibility(INVISIBLE);
44
   else
45
      mPreviousButton.setVisibility(VISIBLE);
46
47
   // If the last value is shown, hide the next button.

48
   if (mSelectedIndex == mSpinnerValues.length - 1)
49
      mNextButton.setVisibility(INVISIBLE);
50
   else
51
      mNextButton.setVisibility(VISIBLE);
52
}
53
54
/**

55
 * Gets the selected value of the spinner, or null if no valid 

56
 * selected index is set yet.

57
 * 

58
 * @return the selected value of the spinner.

59
 */
60
public CharSequence getSelectedValue() {
61
   // If no values are set for the spinner, return an empty string.

62
   if (mSpinnerValues == null || mSpinnerValues.length == 0)
63
      return "";
64
65
   // If the current index is invalid, return an empty string.

66
   if (mSelectedIndex < 0 || mSelectedIndex >= mSpinnerValues.length)
67
      return "";
68
69
   return mSpinnerValues[mSelectedIndex];
70
}
71
72
/**

73
 * Gets the selected index of the spinner.

74
 * 

75
 * @return the selected index of the spinner.

76
 */
77
public int getSelectedIndex() {
78
   return mSelectedIndex;
79
}

Die onFinishInflate-Methode der zusammengesetzten Ansicht wird aufgerufen, wenn alle Ansichten im Layout aufgeblasen und einsatzbereit sind. Hier können Sie Ihren Code hinzufügen, wenn Sie Ansichten in der zusammengesetzten Ansicht ändern müssen.

Mit den Methoden, die Sie gerade zur SideSpinner-Klasse hinzugefügt haben, kann das Verhalten der Schaltflächen, die den vorherigen und nächsten Wert auswählen, jetzt hinzugefügt werden. Ersetzen Sie den vorhandenen Code in der onFinishInflate-Methode durch Folgendes:

1
@Override
2
protected void onFinishInflate() {
3
4
   // When the controls in the layout are doing being inflated, set 

5
   // the callbacks for the side arrows.

6
   super.onFinishInflate();
7
8
   // When the previous button is pressed, select the previous value 

9
   // in the list.

10
   mPreviousButton = (Button) this
11
      .findViewById(R.id.sidespinner_view_previous);
12
   mPreviousButton
13
      .setBackgroundResource(android.R.drawable.ic_media_previous);
14
15
   mPreviousButton.setOnClickListener(new OnClickListener() {
16
      public void onClick(View view) {
17
         if (mSelectedIndex > 0) {
18
            int newSelectedIndex = mSelectedIndex - 1;
19
            setSelectedIndex(newSelectedIndex);
20
         }
21
      }
22
   });
23
24
   // When the next button is pressed, select the next item in the 

25
   // list.

26
   mNextButton = (Button)this
27
                    .findViewById(R.id.sidespinner_view_next);
28
   mNextButton
29
      .setBackgroundResource(android.R.drawable.ic_media_next);
30
   mNextButton.setOnClickListener(new OnClickListener() {
31
      public void onClick(View view) {
32
         if (mSpinnerValues != null
33
               && mSelectedIndex < mSpinnerValues.length - 1) {
34
            int newSelectedIndex = mSelectedIndex + 1;
35
            setSelectedIndex(newSelectedIndex);
36
         }
37
      }
38
   });
39
40
   // Select the first value by default.

41
   setSelectedIndex(0);
42
}

Mit den neu erstellten Methoden setValues und setSelectedIndex können wir jetzt den Side Spinner aus unserem Code initialisieren. Wie bei jeder anderen Ansicht müssen Sie die Side Spinner-Ansicht im Layout mit der findViewById-Methode finden. Wir können dann jede öffentliche Methode für die Ansicht des zurückgegebenen Objekts aufrufen, einschließlich der gerade erstellten.

Das folgende Codefragment zeigt, wie Sie die onCreate-Methode der MainActivity-Klasse aktualisieren, um mithilfe der setValues-Methode eine Liste der Werte im Side-Spinner anzuzeigen. Sie können auch standardmäßig den zweiten Wert in der Liste auswählen, indem Sie die setSelectedIndex-Methode aufrufen.

1
public class MainActivity extends Activity {
2
3
   @Override
4
   protected void onCreate(Bundle savedInstanceState) {
5
      super.onCreate(savedInstanceState);
6
      setContentView(R.layout.activity_main);
7
8
      // Initializes the side spinner from code.

9
      SideSpinner fruitsSpinner;
10
      fruitsSpinner = (SideSpinner)this
11
                         .findViewById(R.id.sidespinner_fruits);
12
13
      CharSequence fruitList[] = { "Apple", 
14
                                   "Orange", 
15
                                   "Pear", 
16
                                   "Grapes" };
17
      fruitsSpinner.setValues(fruitList);
18
      fruitsSpinner.setSelectedIndex(1);
19
   }
20
}

Wenn Sie die Anwendung starten, sollte der Side Spinner wie erwartet funktionieren. Die Liste der Werte wird angezeigt und der Wert Orange ist standardmäßig ausgewählt.

6. Fügen Sie der zusammengesetzten Ansicht Layoutattribute hinzu

Die im Android SDK verfügbaren Ansichten können über Code geändert werden. Einige Attribute können jedoch auch direkt im entsprechenden Layout festgelegt werden. Fügen wir dem Side Spinner ein Attribut hinzu, das die Werte festlegt, die der Side Spinner anzeigen muss.

Um ein benutzerdefiniertes Attribut für die zusammengesetzte Ansicht zu erstellen, müssen Sie zuerst das Attribut in der Datei /res/values/attr.xml definieren. Jedes Attribut der zusammengesetzten Ansicht sollte in einem Stil mit einem <declare-styleable>-Tag gruppiert werden. Für den Side-Spinner wird der Name der Klasse wie unten gezeigt verwendet.

1
<resources>
2
  <declare-styleable name="SideSpinner">
3
    <attr name="values" format="reference" />
4
  </declare-styleable>
5
</resources>

Im <attr>-Tag enthält das Attribut name den Bezeichner, mit dem auf das neue Attribut im Layout verwiesen wird, und das format attribut enthält den Typ des neuen Attributs.

Für die Werteliste wird der reference typ verwendet, da sich das Attribut auf eine Liste von Zeichenfolgen bezieht, die als Ressource definiert sind. Die Werttypen, die normalerweise in Layouts verwendet werden, können für Ihre benutzerdefinierten Attribute verwendet werden, einschließlich booleancolordimensionenumintegerfloat und string.

Hier erfahren Sie, wie Sie die Ressource für eine Liste von values definieren, auf die sich das Wertattribut des Side Spinners bezieht. Es muss wie unten gezeigt zur Datei /res/values/strings.xml hinzugefügt werden.

1
<resources>
2
    <string-array name="vegetable_array">
3
        <item>Cucumber</item>
4
        <item>Potato</item>
5
        <item>Tomato</item>
6
        <item>Onion</item>
7
        <item>Squash</item>
8
    </string-array>  
9
</resources>

Um das neue values attribut zu testen, erstellen Sie eine Side-Spinner-Ansicht im MainActivity-Layout unter dem vorhandenen Side-Spinner. Dem Attribut muss ein dem RelativeLayout hinzugefügter Namespace vorangestellt werden, z. B. xmlns:sidespinner="http://schemas.android.com/apk/res-auto". So sollte das endgültige Layout in /res/layout/activity_main.xml aussehen.

1
<RelativeLayout 
2
    xmlns:android="http://schemas.android.com/apk/res/android"
3
    xmlns:tools="http://schemas.android.com/tools"
4
    xmlns:sidespinner="http://schemas.android.com/apk/res-auto"
5
    android:layout_width="match_parent"
6
    android:layout_height="match_parent"
7
    tools:context=".MainActivity">
8
    <com.cindypotvin.sidespinnerexample.SideSpinner
9
      android:id="@+id/sidespinner_fruits"
10
      android:layout_width="match_parent"
11
      android:layout_height="wrap_content"
12
      android:orientation="horizontal"
13
      android:gravity="center"/>
14
    
15
     <com.cindypotvin.sidespinnerexample.SideSpinner
16
      android:id="@+id/sidespinner_vegetables"
17
      android:layout_width="match_parent"
18
      android:layout_height="wrap_content"
19
      android:orientation="horizontal"
20
      android:gravity="center"
21
      android:layout_below="@id/sidespinner_fruits" 
22
      sidespinner:values="@array/vegetable_array" />
23
</RelativeLayout>

Schließlich muss die SideSpinner-Klasse geändert werden, um das Attribut values zu lesen. Der Wert jedes Attributs der Ansicht ist im AttributeSet-Objekt verfügbar, das als Parameter des Konstruktors der Ansicht übergeben wird.

Um den values Ihres Attributs für benutzerdefinierte Werte abzurufen, rufen Sie zuerst die Methode obtainStyledAttributes des AttributeSet-Objekts mit dem Namen des Stils auf, der das Attribut enthält. Dies gibt die Liste der Attribute zurück, die als TypedArray-Objekt formatiert werden können.

Anschließend rufen wir die Getter-Methode des TypedArray-Objekts auf, die den richtigen Typ für das gewünschte Attribut hat, und übergeben den Bezeichner des Attributs als Parameter. Der folgende Codeblock zeigt, wie Sie den Konstruktor des Seitenspinners ändern, um die Liste der Werte abzurufen und sie im Side Spinner festzulegen.

1
public SideSpinner(Context context) {
2
   super(context);
3
4
   initializeViews(context);
5
}
6
7
public SideSpinner(Context context, AttributeSet attrs) {
8
   super(context, attrs);
9
10
   TypedArray typedArray;
11
   typedArray = context
12
             .obtainStyledAttributes(attrs, R.styleable.SideSpinner);
13
   mSpinnerValues = typedArray
14
                       .getTextArray(R.styleable.SideSpinner_values);
15
   typedArray.recycle();
16
17
   initializeViews(context);
18
}
19
20
public SideSpinner(Context context, 
21
                   AttributeSet attrs,
22
                   int defStyle) {
23
   super(context, attrs, defStyle);
24
25
   TypedArray typedArray;
26
   typedArray = context
27
             .obtainStyledAttributes(attrs, R.styleable.SideSpinner);
28
   mSpinnerValues = typedArray
29
                       .getTextArray(R.styleable.SideSpinner_values);
30
   typedArray.recycle();
31
32
   initializeViews(context);
33
}

Wenn Sie die Anwendung starten, sollten Sie zwei Side-Spinner sehen, die unabhängig voneinander arbeiten.

7. Status speichern und wiederherstellen

Der letzte Schritt, den wir ausführen müssen, ist das Speichern und Wiederherstellen des Status der zusammengesetzten Ansicht. Wenn eine Aktivität zerstört und neu erstellt wird, z. B. wenn das Gerät gedreht wird, werden die Werte nativer Ansichten mit einer eindeutigen Kennung automatisch gespeichert und wiederhergestellt. Dies gilt derzeit nicht für den Side Spinner.

Der Status der Ansichten wird nicht gespeichert. Die Bezeichner der Ansichten in der SideSpinner-Klasse sind nicht eindeutig, da sie viele Male wiederverwendet werden können. Dies bedeutet, dass wir für das Speichern und Wiederherstellen der Werte der Ansichten in der zusammengesetzten Ansicht verantwortlich sind. Dazu implementieren wir die Methoden onSaveInstanceState, onRestoreInstanceState und dispatchSaveInstanceState. Der folgende Codeblock zeigt, wie dies für den Side Spinner gemacht wird.

1
/**

2
 * Identifier for the state to save the selected index of 

3
 * the side spinner.

4
 */
5
private static String STATE_SELECTED_INDEX = "SelectedIndex";
6
7
/**

8
 * Identifier for the state of the super class.

9
 */
10
private static String STATE_SUPER_CLASS = "SuperClass";
11
12
@Override
13
protected Parcelable onSaveInstanceState() { 
14
   Bundle bundle = new Bundle();
15
16
    bundle.putParcelable(STATE_SUPER_CLASS,
17
                         super.onSaveInstanceState());
18
    bundle.putInt(STATE_SELECTED_INDEX, mSelectedIndex);
19
20
    return bundle;
21
}
22
23
@Override
24
protected void onRestoreInstanceState(Parcelable state) {
25
    if (state instanceof Bundle) {
26
        Bundle bundle = (Bundle)state;
27
28
        super.onRestoreInstanceState(bundle
29
                                 .getParcelable(STATE_SUPER_CLASS));
30
        setSelectedIndex(bundle.getInt(STATE_SELECTED_INDEX));
31
    } 
32
    else
33
       super.onRestoreInstanceState(state);   
34
}
35
36
@Override
37
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
38
    // Makes sure that the state of the child views in the side 

39
    // spinner are not saved since we handle the state in the

40
    // onSaveInstanceState.

41
    super.dispatchFreezeSelfOnly(container);
42
}
43
44
@Override
45
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
46
    // Makes sure that the state of the child views in the side 

47
    // spinner are not restored since we handle the state in the

48
    // onSaveInstanceState.

49
    super.dispatchThawSelfOnly(container);
50
}

Abschluss

Der Seitenspinner ist jetzt fertig. Beide Side Spinners arbeiten wie erwartet und ihre Werte werden wiederhergestellt, wenn die Aktivität zerstört und neu erstellt wird. Sie können jetzt das Gelernte anwenden, um eine beliebige Gruppe von Ansichten in einer Android-Anwendung mithilfe zusammengesetzter Ansichten wiederzuverwenden.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.